diff --git a/lib/daru/view.rb b/lib/daru/view.rb index 2b17056..1de6dc9 100644 --- a/lib/daru/view.rb +++ b/lib/daru/view.rb @@ -4,6 +4,7 @@ require 'daru/view/adapters/highcharts/display' require 'daru/view/adapters/nyaplot/display' require 'daru/view/adapters/googlecharts/display' +require 'daru/view/adapters/googlecharts/google_visualr' require 'daru/view/table' # needed in load_lib_in_iruby method diff --git a/lib/daru/view/adapters/googlecharts.rb b/lib/daru/view/adapters/googlecharts.rb index a9d8282..851c5dd 100644 --- a/lib/daru/view/adapters/googlecharts.rb +++ b/lib/daru/view/adapters/googlecharts.rb @@ -187,14 +187,24 @@ def export_html_file(plot, path='./plot.html') File.write(path, str) end - def export(_plot, _export_type='png', _file_name='chart') - raise 'Not implemented yet' - end - def show_in_iruby(plot) plot.show_in_iruby end + # Exporting a googlchart to pdf is not working in IRuby notebook because + # the dependency of jspdf is not properly loaded in IRuby notebook. + # TODO: Need to find some other way to export the chart to pdf in + # IRuby notebook. + # + # @see #Daru::View::Plot.export + def export(plot, export_type='png', file_name='chart') + raise NotImplementedError, 'Not yet implemented!' unless + export_type == 'png' + plot.export_iruby(export_type, file_name) if defined? IRuby + rescue NameError + plot.export(export_type, file_name) + end + def generate_html(plot) path = File.expand_path( '../templates/googlecharts/static_html.erb', __dir__ diff --git a/lib/daru/view/adapters/googlecharts/display.rb b/lib/daru/view/adapters/googlecharts/display.rb index 39209af..7594ccd 100644 --- a/lib/daru/view/adapters/googlecharts/display.rb +++ b/lib/daru/view/adapters/googlecharts/display.rb @@ -119,6 +119,34 @@ def get_html_spreadsheet(data, dom) html end + # @see #Daru::View::Plot.export + def export(export_type='png', file_name='chart') + add_listener('ready', extract_export_code(export_type, file_name)) + to_html + end + + # Exports chart to different formats in IRuby notebook + # + # @param type [String] format to which chart has to be exported + # @param file_name [String] The name of the file after exporting the chart + # @return [void] loads the js code of chart along with the code to export + # in IRuby notebook + def export_iruby(export_type='png', file_name='chart') + IRuby.html(export(export_type, file_name)) + end + + # Returns the script to export the chart in different formats + # + # @param type [String] format to which chart has to be exported + # @param file_name [String] The name of the file after exporting the chart + # @return [String] the script to export the chart + def extract_export_code(export_type='png', file_name='chart') + case export_type + when 'png' + extract_export_png_code(file_name) + end + end + def to_html(id=nil, options={}) path = File.expand_path( '../../templates/googlecharts/chart_div.erb', __dir__ diff --git a/lib/daru/view/adapters/googlecharts/generate_javascript.rb b/lib/daru/view/adapters/googlecharts/generate_javascript.rb index 07dbcd9..d54882c 100644 --- a/lib/daru/view/adapters/googlecharts/generate_javascript.rb +++ b/lib/daru/view/adapters/googlecharts/generate_javascript.rb @@ -66,6 +66,19 @@ def draw_wrapper(element_id) js end + # @param file_name [String] The name of the file after exporting the chart + # @return [String] the script to export the chart in png format + def extract_export_png_code(file_name) + js = '' + js << "\n \tvar a = document.createElement('a');" + js << "\n \ta.href = chart.getImageURI();" + js << "\n \ta.download = '#{file_name}.png';" + js << "\n \tdocument.body.appendChild(a);" + js << "\n \ta.click();" + js << "\n \tdocument.body.removeChild(a);" + js + end + # Generates JavaScript and renders the Google Chartwrapper in the # final HTML output. # diff --git a/lib/daru/view/plot.rb b/lib/daru/view/plot.rb index e24ae1c..5b91120 100644 --- a/lib/daru/view/plot.rb +++ b/lib/daru/view/plot.rb @@ -82,10 +82,10 @@ def export_html_file(path='./plot.html') # @param file_name [String] The name of the file after exporting the chart # @return [String, void] js code of chart along with the code to export it # and loads the js code to export it in IRuby. - # @example Export a HighChart + # @example # data = Daru::Vector.new([5 ,3, 4]) - # hchart = Daru::View::Plot.new(data) - # hchart.export('png', 'daru') + # chart = Daru::View::Plot.new(data) + # chart.export('png', 'daru') def export(export_type='png', file_name='chart') @adapter.export(@chart, export_type, file_name) end diff --git a/spec/adapters/googlecharts/base_chart_spec.rb b/spec/adapters/googlecharts/base_chart_spec.rb index 81671e6..c4e04b6 100644 --- a/spec/adapters/googlecharts/base_chart_spec.rb +++ b/spec/adapters/googlecharts/base_chart_spec.rb @@ -12,13 +12,20 @@ {type: :column, width: 800} ) } + + let(:data_array) do + [ + ['Year'], + ['2013'], + ] + end let(:user_options) {{ listeners: { select: "alert('A table row was selected');" } }} let(:column_chart) { Daru::View::Plot.new( - data_spreadsheet, + data_array, { type: :column }, user_options) } @@ -83,4 +90,26 @@ expect(js).to match(/chart.draw\(data_table, \{width: 800\}/i) end end -end \ No newline at end of file + + describe "#draw_chart_js" do + subject(:js) { column_chart.chart.draw_chart_js('id') } + it "adds correct data" do + expect(js).to match( + /data_table.addColumn\({"type":"string","label":"Year"}\)/ + ) + expect(js).to match(/data_table.addRow\(\[{v: "2013"}\]\)/) + end + it "adds correct listener" do + column_chart.chart.add_listener('ready', "alert('hi');") + expect(js).to match( + /google.visualization.events.addListener\(chart, 'ready', function \(e\) {/ + ) + expect(js).to match(/alert\('hi'\);/) + end + it "generates the valid chart script" do + expect(js).to match(/new google.visualization.DataTable/) + expect(js).to match(/new google.visualization.ColumnChart/) + expect(js).to match(/chart.draw\(data_table, {}\)/) + end + end +end diff --git a/spec/adapters/googlecharts/data_table_iruby_spec.rb b/spec/adapters/googlecharts/data_table_iruby_spec.rb index 0484d9b..1ac56df 100644 --- a/spec/adapters/googlecharts/data_table_iruby_spec.rb +++ b/spec/adapters/googlecharts/data_table_iruby_spec.rb @@ -59,6 +59,14 @@ end end + describe "#add_listener" do + it "adds ready listener for Google datatables" do + data_table.table.add_listener('ready', 'callback') + expect(data_table.table.listeners[0][:event]).to eq('ready') + expect(data_table.table.listeners[0][:callback]).to eq('callback') + end + end + describe "#load_js" do it "loads valid packages" do js = data_table.table.load_js('id') @@ -85,6 +93,14 @@ /data_table.addRow\(\[\{v: \"2013\"\}\]\);/i) expect(js).to match(/google.visualization.Table/i) expect(js).to match(/table.draw\(data_table, \{\}/i) + end + it "adds correct listener" do + data_table.table.add_listener('ready', "alert('hi');") + js = data_table.table.draw_js('id') + expect(js).to match( + /google.visualization.events.addListener\(table, 'ready', function \(e\) {/ + ) + expect(js).to match(/alert\('hi'\);/) end end diff --git a/spec/adapters/googlecharts/display_spec.rb b/spec/adapters/googlecharts/display_spec.rb index 6b61e13..d629b36 100644 --- a/spec/adapters/googlecharts/display_spec.rb +++ b/spec/adapters/googlecharts/display_spec.rb @@ -512,6 +512,44 @@ end end + describe "#export" do + it "adds ready listener" do + area_chart_chart.chart.export + expect(area_chart_chart.chart.listeners[0][:event]).to eq('ready') + end + it "generates correct code of the chart" do + js = area_chart_chart.chart.export + expect(js).to match(/google.visualization.DataTable\(\);/i) + expect(js).to match( + /data_table.addColumn\(\{\"type\":\"string\",\"label\":\"Year\"\}\);/) + expect(js).to match( + /data_table.addRow\(\[\{v: \"2013\"\}\]\);/) + expect(js).to match(/google.visualization.AreaChart/i) + expect(js).to match( + /google.visualization.events.addListener\(chart, 'ready'/) + expect(js).to match(/chart.draw\(data_table, \{\}/i) + end + it "generates correct png code" do + js = area_chart_chart.chart.export + expect(js).to match(/document.createElement\('a'\);/) + expect(js).to match(/a.href = chart.getImageURI()/) + expect(js).to match(/a.download = 'chart.png'/) + end + end + + describe "#extract_export_code" do + it "extracts correct png code" do + area_chart_chart.chart.html_id = 'id' + js = area_chart_chart.chart.extract_export_code('png', 'daru') + expect(js).to match(/document.createElement\('a'\);/) + expect(js).to match(/a.href = chart.getImageURI()/) + expect(js).to match(/a.download = 'daru.png'/) + expect(js).to match(/document.body.appendChild\(a\)/) + expect(js).to match(/a.click\(\)/) + expect(js).to match(/document.body.removeChild\(a\)/) + end + end + describe "#add_listener_to_chart" do it "adds the listener mentioned in user_options to the chart" do column_chart_chart.chart.add_listener_to_chart diff --git a/spec/adapters/googlecharts_spec.rb b/spec/adapters/googlecharts_spec.rb index dc8b2e1..04696d7 100644 --- a/spec/adapters/googlecharts_spec.rb +++ b/spec/adapters/googlecharts_spec.rb @@ -350,6 +350,20 @@ end end + describe "#export" do + it "generates valid script to export a chart to png" do + js = area_chart_chart.adapter.export(area_chart_chart.chart) + expect(js).to match(/google.visualization.DataTable\(\);/) + expect(js).to match(/google.visualization.AreaChart/) + expect(js).to match(/a.href = chart.getImageURI()/) + expect(js).to match(/a.download = 'chart.png'/) + end + it 'raises error for not implemented export types' do + expect{area_chart_chart.adapter.export(area_chart_chart.chart, 'jpeg')} + .to raise_error(NotImplementedError, 'Not yet implemented!') + end + end + describe "#export_html_file" do it "writes valid html code of the Area Chart to the file" do area_chart_chart.export_html_file('./plot.html') diff --git a/spec/dummy_iruby/Google Chart | Area Chart (export examples also).ipynb b/spec/dummy_iruby/Google Chart | Area Chart (export examples also).ipynb new file mode 100644 index 0000000..e911460 --- /dev/null +++ b/spec/dummy_iruby/Google Chart | Area Chart (export examples also).ipynb @@ -0,0 +1,600 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "Install the spreadsheet gem version ~>1.1.1 for using spreadsheet functions.\n", + "\n", + "Install the mechanize gem version ~>2.7.5 for using mechanize functions.\n" + ] + }, + { + "data": { + "text/plain": [ + "true" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "require 'daru/view'" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "\n", + " /* BEGIN google_visualr.js */\n", + "\n", + "if(!window['googleLT_']){window['googleLT_']=(new Date()).getTime();}if (!window['google']) {\n", + "window['google'] = {};\n", + "}\n", + "if (!window['google']['loader']) {\n", + "window['google']['loader'] = {};\n", + "google.loader.ServiceBase = 'https://www.google.com/uds';\n", + "google.loader.GoogleApisBase = 'https://ajax.googleapis.com/ajax';\n", + "google.loader.ApiKey = 'notsupplied';\n", + "google.loader.KeyVerified = true;\n", + "google.loader.LoadFailure = false;\n", + "google.loader.Secure = true;\n", + "google.loader.GoogleLocale = 'www.google.com';\n", + "google.loader.ClientLocation = null;\n", + "google.loader.AdditionalParams = '';\n", + "(function() {function g(a){return a in l?l[a]:l[a]=-1!=navigator.userAgent.toLowerCase().indexOf(a)}var l={};function m(a,b){var c=function(){};c.prototype=b.prototype;a.ca=b.prototype;a.prototype=new c}function n(a,b,c){var d=Array.prototype.slice.call(arguments,2)||[];return function(){return a.apply(b,d.concat(Array.prototype.slice.call(arguments)))}}function p(a){a=Error(a);a.toString=function(){return this.message};return a}\n", + "function q(a,b){a=a.split(/\\./);for(var c=window,d=0;d\\x3c/script>\"):(g(\"safari\")||g(\"konqueror\"))&&window.setTimeout(B,10)),x.push(a)):y(window,\"load\",a)};t(\"google.setOnLoadCallback\",google.ba);\n", + "function y(a,b,c){if(a.addEventListener)a.addEventListener(b,c,!1);else if(a.attachEvent)a.attachEvent(\"on\"+b,c);else{var d=a[\"on\"+b];a[\"on\"+b]=null!=d?C([c,d]):c}}function C(a){return function(){for(var b=0;b\\x3c/script>'):\"css\"==a&&document.write('')};\n", + "t(\"google.loader.writeLoadTag\",google.loader.f);google.loader.Z=function(a){w=a};t(\"google.loader.rfm\",google.loader.Z);google.loader.aa=function(a){for(var b in a)\"string\"==typeof b&&b&&\":\"==b.charAt(0)&&!v[b]&&(v[b]=new E(b.substring(1),a[b]))};t(\"google.loader.rpl\",google.loader.aa);google.loader.$=function(a){if((a=a.specs)&&a.length)for(var b=0;b\\x3c/script>')},K.Ki=function(b){var c=K.global.document,d=c.createElement(\"script\");d.type=C;d.src=b;d.defer=!1;d.async=!1;c.head.appendChild(d)},\n", + "K.Tl=function(b,c){if(K.qg()){var d=K.global.document;if(!K.De&&d.readyState==t){if(/\\bdeps.js$/.test(b))return!1;throw Error('Cannot write \"'+b+'\" after document load');}void 0===c?K.di?(K.$g=!0,c=\" onreadystatechange='goog.onScriptLoad_(this, \"+ ++K.Pg+\")' \",d.write(n+b+'\"'+c+\">\\x3c/script>\")):K.De?K.Ki(b):K.Sl(b):d.write('\n" + ], + "text/plain": [ + "\"
\\n\\n\"" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data = [\n", + " ['Year', 'Sales', 'Expenses'],\n", + " ['2013', 1000, 400],\n", + " ['2014', 1170, 460],\n", + " ['2015', 660, 1120],\n", + " ['2016', 1030, 540]\n", + " ]\n", + "area_chart_table = Daru::View::Table.new(data)\n", + "area_chart_table.show_in_iruby" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n" + ], + "text/plain": [ + "\"
\\n\\n\"" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "area_chart_options = {\n", + " type: :area\n", + "}\n", + "area_chart_chart = Daru::View::Plot.new(area_chart_table.table, area_chart_options)\n", + "area_chart_chart.show_in_iruby\n", + "# Try to run this cell after uncommenting the below line to export this chart in png format\n", + "# area_chart_chart.export" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n" + ], + "text/plain": [ + "\"
\\n\\n\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "area_chart_options = {\n", + " title: 'Company Performance',\n", + " hAxis: {title: 'Year', titleTextStyle: {color: '#333'}},\n", + " vAxis: {minValue: 0},\n", + " type: :area\n", + "}\n", + "area_chart_chart = Daru::View::Plot.new(area_chart_table.table, area_chart_options)\n", + "area_chart_chart.show_in_iruby\n", + "# Try to run this cell after uncommenting the below line to export this chart in png format\n", + "# area_chart_chart.export('png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Stacked area chart" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n" + ], + "text/plain": [ + "\"
\\n\\n\"" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "area_chart_options = {\n", + "isStacked: true,\n", + " height: 300,\n", + " legend: {position: 'top', maxLines: 3},\n", + " vAxis: {minValue: 0},\n", + " type: :area\n", + "}\n", + "area_chart_chart = Daru::View::Plot.new(area_chart_table.table, area_chart_options)\n", + "area_chart_chart.show_in_iruby\n", + "# Try to run this cell after uncommenting the below line to export this chart in png format with file name as 'daru'\n", + "# area_chart_chart.export('png', 'daru')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Stacked relative" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n" + ], + "text/plain": [ + "\"
\\n\\n\"" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "area_chart_options = {\n", + " isStacked: 'relative',\n", + " height: 300,\n", + " legend: {position: 'top', maxLines: 3},\n", + " vAxis: {\n", + " minValue: 0,\n", + " ticks: [0, 0.3, 0.6, 0.9, 1]\n", + " },\n", + " type: :area\n", + "}\n", + "area_chart_chart = Daru::View::Plot.new(area_chart_table.table, area_chart_options)\n", + "area_chart_chart.show_in_iruby" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Ruby 2.4.1", + "language": "ruby", + "name": "ruby" + }, + "language_info": { + "file_extension": ".rb", + "mimetype": "application/x-ruby", + "name": "ruby", + "version": "2.4.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/spec/plot_spec.rb b/spec/plot_spec.rb index 387b5c3..4bd72f1 100644 --- a/spec/plot_spec.rb +++ b/spec/plot_spec.rb @@ -138,49 +138,56 @@ end describe Daru::View::Plot, 'Chart plotting with Googlecharts library' do + before { Daru::View.plotting_library = :googlecharts } + let(:lib) { Daru::View.plotting_library } + let(:options) {{width: 800, height: 720}} + let(:df) do + Daru::DataFrame.new( + { + a: [1, 2, 3, 4, 5, 6], + b: [1, 5, 2, 5, 1, 0], + c: [1, 6, 7, 2, 6, 0] + }, index: 'a'..'f' + ) + end + let(:dv) { Daru::Vector.new [1, 2, 3] } + let(:plot_df) { Daru::View::Plot.new(df, options) } + let(:plot_dv) { Daru::View::Plot.new(dv, options) } + let(:plot_array) { Daru::View::Plot.new( + [['col1', 'col2', 'col3'],[1, 2, 3]], options) } context 'initialize with Googlecharts library' do - context 'Googlecharts library' do - before { Daru::View.plotting_library = :googlecharts } - let(:lib) { Daru::View.plotting_library } - let(:options) {{width: 800, height: 720}} - let(:df) do - Daru::DataFrame.new( - { - a: [1, 2, 3, 4, 5, 6], - b: [1, 5, 2, 5, 1, 0], - c: [1, 6, 7, 2, 6, 0] - }, index: 'a'..'f' - ) - end - let(:dv) { Daru::Vector.new [1, 2, 3] } - let(:plot_df) { Daru::View::Plot.new(df, options) } - let(:plot_dv) { Daru::View::Plot.new(dv, options) } - let(:plot_array) { Daru::View::Plot.new( - [['col1', 'col2', 'col3'],[1, 2, 3]], options) } - it 'check plotting library' do - expect(lib).to eq(:googlecharts) - end + it 'check plotting library' do + expect(lib).to eq(:googlecharts) + end - it 'Googlecharts chart using Array' do - expect(plot_array).to be_a Daru::View::Plot - expect(plot_array.chart).to be_a GoogleVisualr::Interactive::LineChart - expect(plot_array.data).to eq [[1, 2, 3]] - expect(plot_array.options).to eq options - end + it 'Googlecharts chart using Array' do + expect(plot_array).to be_a Daru::View::Plot + expect(plot_array.chart).to be_a GoogleVisualr::Interactive::LineChart + expect(plot_array.data).to eq [[1, 2, 3]] + expect(plot_array.options).to eq options + end - it 'Googlecharts chart using DataFrame' do - expect(plot_df).to be_a Daru::View::Plot - expect(plot_df.chart).to be_a GoogleVisualr::Interactive::LineChart - expect(plot_df.data).to eq df - expect(plot_df.options).to eq options - end + it 'Googlecharts chart using DataFrame' do + expect(plot_df).to be_a Daru::View::Plot + expect(plot_df.chart).to be_a GoogleVisualr::Interactive::LineChart + expect(plot_df.data).to eq df + expect(plot_df.options).to eq options + end - it 'Googlecharts chart using Vector' do - expect(plot_dv).to be_a Daru::View::Plot - expect(plot_dv.chart).to be_a GoogleVisualr::Interactive::LineChart - expect(plot_dv.data).to eq dv - expect(plot_dv.options).to eq options - end + it 'Googlecharts chart using Vector' do + expect(plot_dv).to be_a Daru::View::Plot + expect(plot_dv.chart).to be_a GoogleVisualr::Interactive::LineChart + expect(plot_dv.data).to eq dv + expect(plot_dv.options).to eq options end end # initialize context end + context '#export' do + it "generates valid script to export a chart to png" do + js = plot_df.export('png', 'daru') + expect(js).to match(/google.visualization.DataTable\(\);/) + expect(js).to match(/google.visualization.LineChart/) + expect(js).to match(/a.href = chart.getImageURI()/) + expect(js).to match(/a.download = 'daru.png'/) + end + end end