gvis 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.md +71 -0
- data/Rakefile +15 -19
- data/gvis.gemspec +27 -0
- data/lib/google_visualization.rb +78 -61
- data/lib/gvis.rb +1 -1
- data/lib/gvis/data_table.rb +111 -0
- data/lib/gvis/version.rb +3 -0
- data/test/helper.rb +19 -0
- data/test/test_data_table.rb +62 -0
- data/test/test_google_visualization.rb +61 -0
- data/test/views/_corechart.html.erb +11 -0
- data/test/views/_motionchart.html.erb +18 -0
- data/test/views/layout.html.erb +13 -0
- metadata +98 -12
- data/README +0 -64
- data/lib/data_table.rb +0 -76
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
Gvis
|
2
|
+
====
|
3
|
+
|
4
|
+
Rails plugin that provides a Ruby wrapper for easily loading the Google Visualization API, and simple generation of the javascript required to plot the graphs
|
5
|
+
For a full list of the graphs provided by google's visualization api see the [gallery](http://code.google.com/apis/visualization/documentation/gallery.html)
|
6
|
+
For documentation on how to use each graph type see google's [API documentation](http://code.google.com/apis/visualization/documentation/)
|
7
|
+
|
8
|
+
Compatiblity
|
9
|
+
------------
|
10
|
+
|
11
|
+
gvis version 2.x - Rails 3.x compatible (And rails 2.x using rails_xss)
|
12
|
+
|
13
|
+
gvis version 1.x - Rails 2.x compatible
|
14
|
+
|
15
|
+
|
16
|
+
Installation
|
17
|
+
============
|
18
|
+
|
19
|
+
Rails 3:
|
20
|
+
|
21
|
+
# Gemfile
|
22
|
+
gem 'gvis', '>= 2.0.0'
|
23
|
+
|
24
|
+
Rails 2.X:
|
25
|
+
|
26
|
+
# config/environment.rb
|
27
|
+
config.gem 'gvis', :version => '< 2.0.0'
|
28
|
+
|
29
|
+
Then include the GoogleVisualization module in app/helpers/application_helper.rb
|
30
|
+
|
31
|
+
module ApplicationHelper
|
32
|
+
include GoogleVisualization
|
33
|
+
end
|
34
|
+
|
35
|
+
Load the API, and render any graphs by placing these methods inside your layout
|
36
|
+
|
37
|
+
# app/views/layouts/application.html.erb
|
38
|
+
<head>
|
39
|
+
<%= include_visualization_api %>
|
40
|
+
<%= render_visualizations %>
|
41
|
+
...
|
42
|
+
</head>
|
43
|
+
|
44
|
+
|
45
|
+
Example
|
46
|
+
=======
|
47
|
+
|
48
|
+
Render desired graphs in the view like this:
|
49
|
+
|
50
|
+
# index.html.erb
|
51
|
+
<% visualization "my_chart", "MotionChart", :width => 600, :height => 400, :html => {:class => "graph_chart"} do |chart| %>
|
52
|
+
<%# Add the columns that the graph will have %>
|
53
|
+
<% chart.string "Fruit" %>
|
54
|
+
<% chart.date "Date" %>
|
55
|
+
<% chart.number "Sales" %>
|
56
|
+
<% chart.number "Expenses" %>
|
57
|
+
<% chart.string "Location" %>
|
58
|
+
|
59
|
+
<%# Add the data %>
|
60
|
+
<% chart.add_rows([
|
61
|
+
["Apples", Date.new(1998,1,1), 1000,300,'East'],
|
62
|
+
["Oranges", Date.new(1998,1,1), 950,200,'West'],
|
63
|
+
["Bananas", Date.new(1998,1,1), 300,250,'West'],
|
64
|
+
["Apples", Date.new(1998,2,1), 1200,400,'East'],
|
65
|
+
["Oranges", Date.new(1998,2,1), 950,150,'West'],
|
66
|
+
["Bananas", Date.new(1998,2,1), 788,617,'West']
|
67
|
+
]) %>
|
68
|
+
<% end %>
|
69
|
+
|
70
|
+
|
71
|
+
Copyright (c) 2009 [Jeremy Olliver], released under the MIT license
|
data/Rakefile
CHANGED
@@ -1,23 +1,19 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require 'rake/rdoctask'
|
4
|
-
|
5
|
-
desc 'Default: run unit tests.'
|
6
|
-
task :default => :test
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
7
3
|
|
8
|
-
|
9
|
-
Rake::TestTask.new(:test) do |
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
t.verbose = true
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
6
|
+
test.libs << 'lib' << 'test'
|
7
|
+
test.pattern = 'test/**/test_*.rb'
|
8
|
+
test.verbose = true
|
14
9
|
end
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
11
|
+
require 'rcov/rcovtask'
|
12
|
+
Rcov::RcovTask.new do |test|
|
13
|
+
test.rcov_opts << '--exclude /gems/,/Library/,/usr/,spec,lib/tasks' # exclude external gems/libraries
|
14
|
+
test.libs << 'test'
|
15
|
+
test.pattern = 'test/**/test_*.rb'
|
16
|
+
test.verbose = true
|
23
17
|
end
|
18
|
+
|
19
|
+
task :default => :test
|
data/gvis.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gvis/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gvis"
|
7
|
+
s.version = Gvis::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.author = "Jeremy Olliver"
|
10
|
+
s.email = "jeremy.olliver@gmail.com"
|
11
|
+
s.homepage = "http://github.com/jeremyolliver/gvis"
|
12
|
+
s.summary = "Easily embed charts with Google Visualization API"
|
13
|
+
s.description = "Easily embed charts with Google Visualization API, using ruby formatted options in your view files"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'json'
|
20
|
+
|
21
|
+
s.add_development_dependency 'bundler'
|
22
|
+
s.add_development_dependency 'minitest'
|
23
|
+
s.add_development_dependency 'rcov'
|
24
|
+
# Although this runs on rails 3, I'm only specifying this as development dependency for now,
|
25
|
+
# (instead of runtime dependency) since rails can be vendored and need not be installed/required as gems
|
26
|
+
s.add_development_dependency 'actionpack', '> 3.0.0'
|
27
|
+
end
|
data/lib/google_visualization.rb
CHANGED
@@ -1,29 +1,31 @@
|
|
1
|
-
#
|
1
|
+
# view helper, and view methods for using the Google Visualization API
|
2
2
|
#
|
3
|
-
# For use with rails, include this Module in ApplicationHelper
|
4
|
-
# See the Readme for usage details
|
3
|
+
# For use with rails, include this Module in ApplicationHelper, and call {#include_visualization_api} and {#render_visualizations} within the head tag of your html layout
|
4
|
+
# See the Readme[http://github.com/jeremyolliver/gvis#readme] for examples and usage details. For more detailed info on each visualization see the API[http://code.google.com/apis/visualization/documentation/]
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# @author Jeremy Olliver
|
7
7
|
module GoogleVisualization
|
8
|
-
|
8
|
+
|
9
9
|
attr_accessor :google_visualizations, :visualization_packages
|
10
|
-
|
11
|
-
|
12
|
-
# Place these method calls inside the
|
13
|
-
|
14
|
-
|
10
|
+
|
11
|
+
# @group Layout helper methods
|
12
|
+
# Place these method calls inside the head tag in your layout file.
|
13
|
+
|
15
14
|
# Include the Visualization API code from google.
|
16
15
|
# (Omit this call if you prefer to include the API in your own js package)
|
17
16
|
def include_visualization_api
|
18
17
|
javascript_include_tag("http://www.google.com/jsapi")
|
19
18
|
end
|
20
|
-
|
21
|
-
#
|
19
|
+
|
20
|
+
# Call this method from the within the head tag (or alternately just before the closing body tag)
|
21
|
+
# This will render the graph's generated by the rendering of your view files
|
22
|
+
# @return [String] a javascript tag that contains the generated javascript to render the graphs
|
22
23
|
def render_visualizations
|
23
24
|
if @google_visualizations
|
24
|
-
package_list =
|
25
|
-
|
26
|
-
|
25
|
+
package_list = @visualization_packages.uniq.collect do |p|
|
26
|
+
package = p.to_s.camelize.downcase
|
27
|
+
package = "corechart" if ["areachart", "barchart", "columnchart", "linechart", "piechart"].include?(package)
|
28
|
+
"\'#{package}\'"
|
27
29
|
end
|
28
30
|
output = %Q(
|
29
31
|
<script type="text/javascript">
|
@@ -32,91 +34,106 @@ module GoogleVisualization
|
|
32
34
|
var chartData = {};
|
33
35
|
var visualizationCharts = {};
|
34
36
|
function drawCharts() { )
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
@google_visualizations.each do |id, vis|
|
38
|
+
output += generate_visualization(id, vis[0], vis[1], vis[2])
|
39
|
+
end
|
38
40
|
output += "} </script>"
|
39
41
|
raw(output + "<!-- Rendered Google Visualizations /-->")
|
40
42
|
else
|
41
|
-
raw("<!-- No graphs on this page /-->")
|
43
|
+
raw("<!-- No graphs on this page /-->") if debugging?
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
|
47
|
+
# @endgroup Layout helper methods
|
48
|
+
|
49
|
+
# @group View methods
|
50
|
+
|
51
|
+
# Call this method from the view to insert the visualization graph/chart here.
|
52
|
+
# This method will output a div with the specified id, and store the chart data to be rendered later via {#render_visualizations}
|
53
|
+
# @param [String] id the id of the chart. corresponds to the id of the div
|
54
|
+
# @param [String] chart_type the kind of chart to render
|
55
|
+
# @param [Hash] options the configuration options for the graph
|
50
56
|
def visualization(id, chart_type, options = {}, &block)
|
51
57
|
init
|
52
58
|
chart_type = chart_type.camelize # Camelize the chart type, as the Google API follows Camel Case conventions (e.g. ColumnChart, MotionChart)
|
53
59
|
options.stringify_keys! # Ensure consistent hash access
|
54
60
|
@visualization_packages << chart_type # Add the chart type to the packages needed to be loaded
|
55
|
-
|
61
|
+
|
56
62
|
# Initialize the data table (with hashed options), and pass it the block for cleaner adding of attributes within the block
|
57
|
-
table = DataTable.new(options.delete("data"), options.delete("columns"), options)
|
63
|
+
table = Gvis::DataTable.new(options.delete("data"), options.delete("columns"), options)
|
58
64
|
if block_given?
|
59
65
|
yield table
|
60
66
|
end
|
61
|
-
|
62
|
-
# Extract the html options
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# Output a div with given id, that our graph will be embedded into
|
68
|
-
html = ""
|
69
|
-
html_options.each do |key, value|
|
70
|
-
html += %Q(#{key}="#{value}" )
|
71
|
-
end
|
67
|
+
|
68
|
+
html_options = options.delete("html") || {} # Extract the html options
|
69
|
+
@google_visualizations.merge!(id => [chart_type, table, options]) # Store our chart in an instance variable to be rendered in the head tag
|
70
|
+
|
71
|
+
# Output a div with given id on the page right now, that our graph will be embedded into
|
72
|
+
html = html_options.collect {|key,value| "#{key}=\"#{value}\"" }.join(" ")
|
72
73
|
concat raw(%Q(<div id="#{id}" #{html}><!-- /--></div>))
|
73
|
-
nil
|
74
|
+
nil # Return nil just incase this is called with an output erb tag, as we don't to output the html twice
|
74
75
|
end
|
75
|
-
|
76
|
+
|
77
|
+
# @endgroup View methods
|
78
|
+
|
76
79
|
protected
|
77
|
-
|
78
|
-
# Initialize instance variables
|
79
|
-
def init
|
80
|
-
@google_visualizations ||= {}
|
81
|
-
@visualization_packages ||=[]
|
82
|
-
end
|
83
|
-
|
80
|
+
|
84
81
|
###################################################
|
85
82
|
# Internal methods for building the script data #
|
86
83
|
###################################################
|
87
|
-
|
84
|
+
|
85
|
+
# Generates the javascript for initializing each graph/chart
|
86
|
+
# @param [String] id the id for the given chart, this should be unique per html page
|
87
|
+
# @param [String] chart_type the name of the chart type that we're rendering.
|
88
|
+
# @param [DataTable] table the DataTable containing the column definitions and data rows
|
89
|
+
# @param [Hash] options the view formatting options
|
90
|
+
# @return [String] javascript that creates the chart, and adds it to the window variable
|
91
|
+
def generate_visualization(id, chart_type, table, options={})
|
88
92
|
# Generate the js chart data
|
89
93
|
output = "chartData['#{id}'] = new google.visualization.DataTable();"
|
90
94
|
table.columns.each do |col|
|
91
95
|
output += "chartData['#{id}'].addColumn('#{table.column_types[col]}', '#{col}');"
|
92
96
|
end
|
93
|
-
option_str =
|
94
|
-
|
97
|
+
option_str = parse_options(options)
|
98
|
+
|
95
99
|
output += %Q(
|
96
|
-
chartData['#{id}'].addRows(#{table.
|
97
|
-
visualizationCharts['#{id}'] = new google.visualization.#{
|
100
|
+
chartData['#{id}'].addRows(#{table.format_data});
|
101
|
+
visualizationCharts['#{id}'] = new google.visualization.#{chart_type.to_s.camelize}(document.getElementById('#{id}'));
|
98
102
|
visualizationCharts['#{id}'].draw(chartData['#{id}'], {#{option_str}});
|
99
103
|
)
|
100
104
|
end
|
101
105
|
|
102
|
-
#
|
103
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
|
106
|
+
# Parse options hash into a string containing a javascript hash key-value pairs
|
107
|
+
# @param [Hash] options the hash to parse
|
108
|
+
# @return [String] a javascript representation of the input
|
109
|
+
def parse_options(options)
|
110
|
+
options.collect do |key, val|
|
107
111
|
str = "#{key}: "
|
108
112
|
if val.kind_of? Hash
|
109
|
-
str += "{" +
|
113
|
+
str += "{" + parse_options(val) + "}"
|
110
114
|
elsif val.kind_of? Array
|
111
115
|
str += "[ " + val.collect { |v| "'#{v}'" }.join(", ") + " ]"
|
112
116
|
else
|
113
117
|
str += (val.kind_of?(String) ? "'#{val}'" : val.to_s)
|
114
118
|
end
|
119
|
+
str
|
120
|
+
end.join(',')
|
121
|
+
end
|
115
122
|
|
116
|
-
|
117
|
-
|
123
|
+
# Convenience method for initializing instance variables
|
124
|
+
def init
|
125
|
+
@google_visualizations ||= {}
|
126
|
+
@visualization_packages ||= []
|
127
|
+
end
|
118
128
|
|
119
|
-
|
129
|
+
# Determines if we're in a debugging environment
|
130
|
+
# @return [boolean]
|
131
|
+
def debugging?
|
132
|
+
debugging = ENV["DEBUG"]
|
133
|
+
if defined?(Rails) && Rails.responsd_to?(:env)
|
134
|
+
debugging = true if ["development", "test"].include? Rails.env
|
135
|
+
end
|
136
|
+
debugging
|
120
137
|
end
|
121
|
-
|
138
|
+
|
122
139
|
end
|
data/lib/gvis.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require 'data_table'
|
1
|
+
require 'gvis/data_table'
|
2
2
|
require 'google_visualization'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# This is an internal class intended for use by the helper methods present in the {GoogleVisualization} class.
|
2
|
+
#
|
3
|
+
# Converts Ruby data structures into a string containing a javascript array for use with a google.visualization.DataTable[http://code.google.com/apis/visualization/documentation/reference.html#DataTable] javascript object
|
4
|
+
#
|
5
|
+
# @author Jeremy Olliver
|
6
|
+
module Gvis
|
7
|
+
class DataTable
|
8
|
+
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
COLUMN_TYPES = ["string", "number", "date", "datetime"]
|
12
|
+
|
13
|
+
attr_accessor :data, :table_columns, :column_types
|
14
|
+
|
15
|
+
# @param [Array] data optional param that may contain a 2D Array for specifying the data upon initialization
|
16
|
+
# @param [Array] columns optional param for specifying the column structure upon initialization
|
17
|
+
# @param [Hash] options optional param of configuration options for the google.visualization.DataTable javascript object
|
18
|
+
def initialize(data = nil, columns = [], options = {})
|
19
|
+
@table_columns, @column_types = [], {}
|
20
|
+
if columns && columns.any?
|
21
|
+
columns.each do |name, type|
|
22
|
+
register_column(type, name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@data = data || []
|
26
|
+
end
|
27
|
+
|
28
|
+
# A one liner to set all the columns at once
|
29
|
+
# eg: chart.columns = { :signups => "number", :day => "date" }
|
30
|
+
# @param [Hash] cols a hash where keys are column names, and values are a string of the column type
|
31
|
+
def columns=(cols)
|
32
|
+
cols.each do |name, coltype|
|
33
|
+
register_column(coltype, name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Array] The columns stored in this table
|
38
|
+
def columns
|
39
|
+
@table_columns
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds a single row to the table
|
43
|
+
# @param [Array] row An array with a single row of data for the table. Should have the same number of entries as there are columns
|
44
|
+
def add_row(row)
|
45
|
+
size = row.size
|
46
|
+
raise ArgumentError.new("Given a row of data with #{size} entries, but there are only #{@table_columns.size} columns in the table") unless size == @table_columns.size
|
47
|
+
@data << row
|
48
|
+
end
|
49
|
+
|
50
|
+
# Adds multiple rows to the table
|
51
|
+
# @param [Array] rows A 2d Array containing multiple rows of data. Each Array should have the same number of entries as the table has columns
|
52
|
+
def add_rows(rows)
|
53
|
+
sizes = rows.collect {|r| r.size }.uniq
|
54
|
+
expected_size = @table_columns.size
|
55
|
+
errors = sizes.select {|s| s != expected_size }
|
56
|
+
raise ArgumentError.new("Given a row of data with #{errors.to_sentence} entries, but there are only #{expected_size} columns in the table") if errors.any?
|
57
|
+
@data += rows
|
58
|
+
end
|
59
|
+
|
60
|
+
# Handle the Column definition methods (#string, #number, #date, #datetime)
|
61
|
+
# This allows columns to be defined one at a time, with a dsl similar to AR migrations
|
62
|
+
# e.g. table.string "name"
|
63
|
+
COLUMN_TYPES.each do |col_type|
|
64
|
+
define_method(col_type) do |args|
|
65
|
+
register_column(col_type, *args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Outputs the data within this table as a javascript array ready for use by google.visualization.DataTable
|
70
|
+
# This is where conversions of ruby date objects to javascript Date objects and escaping strings, and formatting options is done
|
71
|
+
# @return [String] a javascript array with the first row defining the table, and subsequent rows holding the table's data
|
72
|
+
def format_data
|
73
|
+
formatted_rows = []
|
74
|
+
@data.each do |row|
|
75
|
+
values = []
|
76
|
+
row.each_with_index do |entry,index|
|
77
|
+
# Format/escape individual values for javascript, checking column types, and the ruby value as a failsafe
|
78
|
+
safe_val = if @column_types[index] == "date" || entry.is_a?(Date)
|
79
|
+
# Format a date object as a javascript date
|
80
|
+
entry.is_a?(String) ? entry : "new Date (#{entry.year},#{entry.month - 1},#{entry.day})"
|
81
|
+
elsif @column_types[index] == "datetime" || entry.is_a?(Time)
|
82
|
+
# Format a Time (datetime) as a javascript date object down to seconds
|
83
|
+
entry.is_a?(String) ? entry : "new Date (#{entry.year},#{entry.month - 1},#{entry.day},#{entry.hour},#{entry.min},#{entry.sec})"
|
84
|
+
else
|
85
|
+
# Non date/time values can be JS escaped/formatted safely with # to_json
|
86
|
+
entry.to_json
|
87
|
+
end
|
88
|
+
values << safe_val
|
89
|
+
end
|
90
|
+
rowstring = "[#{values.join(", ")}]"
|
91
|
+
formatted_rows << rowstring
|
92
|
+
end
|
93
|
+
"[#{formatted_rows.join(', ')}]"
|
94
|
+
end
|
95
|
+
alias :js_format_data :format_data # For backwards compatibility, just in case
|
96
|
+
alias :to_s :format_data
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Registers each column explicitly, with data type, and a name associated
|
101
|
+
# @param [String] type the type of data column being registered, valid input here are entries from DataTable::COLUMN_TYPES
|
102
|
+
# @param [String] name the column name that will be used as a label on the graph
|
103
|
+
def register_column(type, name)
|
104
|
+
type = type.to_s.downcase
|
105
|
+
raise ArgumentError.new("invalid column type #{type}, permitted types are #{COLUMN_TYPES.join(', ')}") unless COLUMN_TYPES.include?(type)
|
106
|
+
@table_columns << name.to_s
|
107
|
+
@column_types.merge!(name.to_s => type)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
data/lib/gvis/version.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'minitest/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
require 'gvis'
|
15
|
+
|
16
|
+
class MiniTest::Unit::TestCase
|
17
|
+
end
|
18
|
+
|
19
|
+
MiniTest::Unit.autorun
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestDataTable < MiniTest::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@table = Gvis::DataTable.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_attributes_methods_and_constants
|
10
|
+
assert defined?(Gvis::DataTable::COLUMN_TYPES)
|
11
|
+
assert_equal ["string", "number", "date", "datetime"], Gvis::DataTable::COLUMN_TYPES
|
12
|
+
defined_attributes = [:data, :table_columns, :column_types]
|
13
|
+
defined_attributes.each do |a|
|
14
|
+
assert @table.respond_to?(a), "DataTable should have attribute #{a} defined"
|
15
|
+
end
|
16
|
+
methods = [:columns, :columns=, :add_row, :add_rows, :format_data] + Gvis::DataTable::COLUMN_TYPES
|
17
|
+
methods.each do |meth|
|
18
|
+
assert @table.respond_to?(meth), "DataTable should respond to instance method ##{meth}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_defining_columns
|
23
|
+
@table.string "Name"
|
24
|
+
@table.number "age"
|
25
|
+
@table.date "dob"
|
26
|
+
@table.datetime "deceased_at"
|
27
|
+
assert_equal 4, @table.columns.size
|
28
|
+
assert_equal({"Name" => "string", "age" => "number", "dob" => "date", "deceased_at" => "datetime"}, @table.column_types)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_single_assign_columns
|
32
|
+
# should be case insensitive on column types
|
33
|
+
@table.columns = [["Name", "String"], ["Surname", "string"], ["age", "number"]]
|
34
|
+
assert_equal 3, @table.columns.size
|
35
|
+
assert_equal({"Name" => "string", "Surname" => "string", "age" => "number"}, @table.column_types)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_initializing_with_columns
|
39
|
+
table = Gvis::DataTable.new(nil, [["Name", "String"], ["Surname", "string"], ["age", "number"]])
|
40
|
+
assert_equal 3, table.columns.size
|
41
|
+
assert_equal({"Name" => "string", "Surname" => "string", "age" => "number"}, table.column_types)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_adding_rows
|
45
|
+
@table.columns = [["Name", "String"], ["Surname", "string"], ["age", "number"]]
|
46
|
+
@table.add_row(["Jeremy", "Olliver", 23])
|
47
|
+
@table.add_rows([
|
48
|
+
["Optimus", "Prime", 1000],
|
49
|
+
["Mega", "Tron", 999]
|
50
|
+
])
|
51
|
+
assert_equal [["Jeremy", "Olliver", 23], ["Optimus", "Prime", 1000], ["Mega", "Tron", 999]], @table.data
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_formatting_data
|
55
|
+
@table.columns = [["Name", "String"], ["age", "number"], ["dob", "date"], ["now", "datetime"]]
|
56
|
+
now = Time.utc(2011,1,23,11,30,4)
|
57
|
+
@table.add_rows([["Jeremy Olliver", 23, Date.new(2011,1,1), now], ["Optimus Prime", 1000, Date.new(1980,2,23), now], ["'The MegaTron'", 999, Date.new(1981,1,1), now]])
|
58
|
+
|
59
|
+
assert_equal %q([["Jeremy Olliver", 23, new Date (2011,0,1), new Date (2011,0,23,11,30,4)], ["Optimus Prime", 1000, new Date (1980,1,23), new Date (2011,0,23,11,30,4)], ["'The MegaTron'", 999, new Date (1981,0,1), new Date (2011,0,23,11,30,4)]]), @table.format_data
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestGoogleVisualization < MiniTest::Unit::TestCase
|
4
|
+
|
5
|
+
require 'action_view'
|
6
|
+
|
7
|
+
module ApplicationHelper
|
8
|
+
include GoogleVisualization
|
9
|
+
end
|
10
|
+
|
11
|
+
# Mocking out ActionView
|
12
|
+
class MockView
|
13
|
+
attr_accessor :buffer
|
14
|
+
include ApplicationHelper
|
15
|
+
|
16
|
+
def concat(s)
|
17
|
+
@buffer ||= ""
|
18
|
+
@buffer << s
|
19
|
+
end
|
20
|
+
|
21
|
+
def raw(s)
|
22
|
+
s
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(template)
|
26
|
+
ERB.new(File.read(template)).result(binding)
|
27
|
+
# Render the view file first, then the overall layout
|
28
|
+
ERB.new(File.read("test/views/layout.html.erb")).result(binding)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup
|
33
|
+
@view = MockView.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_attributes_and_methods
|
37
|
+
defined_attributes = [:google_visualizations, :visualization_packages]
|
38
|
+
defined_attributes.each do |a|
|
39
|
+
assert @view.respond_to?(a), "GoogleVisualization should have defined attribute: #{a}"
|
40
|
+
end
|
41
|
+
methods = [:include_visualization_api, :render_visualizations, :visualization]
|
42
|
+
methods.each do |meth|
|
43
|
+
assert @view.respond_to?(meth), "GoogleVisualization should have defined method: ##{meth}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_support_for_corechart
|
48
|
+
output = @view.render("test/views/_corechart.html.erb")
|
49
|
+
assert_match("'packages':['corechart']", output, "only the corechart package should have been loaded")
|
50
|
+
%w(AreaChart BarChart ColumnChart LineChart PieChart).each do |chart_type|
|
51
|
+
assert_match("<div id=\"#{chart_type}_id\"", output, "The #{chart_type} graph div should exist on the page")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_motion_chart
|
56
|
+
output = @view.render("test/views/_motionchart.html.erb")
|
57
|
+
assert_match("'packages':['motionchart']", output, "the motionchart package should have been loaded")
|
58
|
+
assert_match("<div id=\"my_chart\"", output, "The graph div should exist on the page")
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%
|
2
|
+
%w(AreaChart BarChart ColumnChart LineChart PieChart).each do |chart_type|
|
3
|
+
visualization("#{chart_type}_id", "LineChart") do |chart|
|
4
|
+
chart.columns = [["Name", "String"], ["age", "number"]]
|
5
|
+
chart.add_rows([
|
6
|
+
["Jeremy", 23],
|
7
|
+
["Ben", 24]
|
8
|
+
])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
%>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% visualization "my_chart", "MotionChart", :width => 600, :height => 400, :html => {:class => "graph_chart"} do |chart| %>
|
2
|
+
<%# Add the columns that the graph will have %>
|
3
|
+
<% chart.string "Fruit" %>
|
4
|
+
<% chart.date "Date" %>
|
5
|
+
<% chart.number "Sales" %>
|
6
|
+
<% chart.number "Expenses" %>
|
7
|
+
<% chart.string "Location" %>
|
8
|
+
|
9
|
+
<%# Add the data %>
|
10
|
+
<% chart.add_rows([
|
11
|
+
["Apples", Date.new(1998,1,1), 1000,300,'East'],
|
12
|
+
["Oranges", Date.new(1998,1,1), 950,200,'West'],
|
13
|
+
["Bananas", Date.new(1998,1,1), 300,250,'West'],
|
14
|
+
["Apples", Date.new(1998,2,1), 1200,400,'East'],
|
15
|
+
["Oranges", Date.new(1998,2,1), 950,150,'West'],
|
16
|
+
["Bananas", Date.new(1998,2,1), 788,617,'West']
|
17
|
+
]) %>
|
18
|
+
<% end %>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gvis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 2.0.
|
9
|
+
- 2
|
10
|
+
version: 2.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jeremy Olliver
|
@@ -15,10 +15,81 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-04-17 00:00:00 +12:00
|
19
19
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bundler
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: minitest
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rcov
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: actionpack
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 7
|
86
|
+
segments:
|
87
|
+
- 3
|
88
|
+
- 0
|
89
|
+
- 0
|
90
|
+
version: 3.0.0
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
22
93
|
description: Easily embed charts with Google Visualization API, using ruby formatted options in your view files
|
23
94
|
email: jeremy.olliver@gmail.com
|
24
95
|
executables: []
|
@@ -28,12 +99,22 @@ extensions: []
|
|
28
99
|
extra_rdoc_files: []
|
29
100
|
|
30
101
|
files:
|
31
|
-
-
|
32
|
-
-
|
102
|
+
- .gitignore
|
103
|
+
- Gemfile
|
33
104
|
- MIT-LICENSE
|
34
|
-
-
|
35
|
-
-
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- gvis.gemspec
|
36
108
|
- lib/google_visualization.rb
|
109
|
+
- lib/gvis.rb
|
110
|
+
- lib/gvis/data_table.rb
|
111
|
+
- lib/gvis/version.rb
|
112
|
+
- test/helper.rb
|
113
|
+
- test/test_data_table.rb
|
114
|
+
- test/test_google_visualization.rb
|
115
|
+
- test/views/_corechart.html.erb
|
116
|
+
- test/views/_motionchart.html.erb
|
117
|
+
- test/views/layout.html.erb
|
37
118
|
has_rdoc: true
|
38
119
|
homepage: http://github.com/jeremyolliver/gvis
|
39
120
|
licenses: []
|
@@ -68,5 +149,10 @@ rubygems_version: 1.6.1
|
|
68
149
|
signing_key:
|
69
150
|
specification_version: 3
|
70
151
|
summary: Easily embed charts with Google Visualization API
|
71
|
-
test_files:
|
72
|
-
|
152
|
+
test_files:
|
153
|
+
- test/helper.rb
|
154
|
+
- test/test_data_table.rb
|
155
|
+
- test/test_google_visualization.rb
|
156
|
+
- test/views/_corechart.html.erb
|
157
|
+
- test/views/_motionchart.html.erb
|
158
|
+
- test/views/layout.html.erb
|
data/README
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
Gvis
|
2
|
-
====
|
3
|
-
|
4
|
-
Rails plugin that provides a Ruby wrapper for easily loading the Google Visualization API, and simple generation of the javascript required to plot the graphs
|
5
|
-
For a full list of the graphs provided by google's visualization api see: http://code.google.com/apis/visualization/documentation/gallery.html
|
6
|
-
For documentation on how to use each graph type see: http://code.google.com/apis/visualization/documentation/
|
7
|
-
|
8
|
-
version 2.X - Rails 3.X
|
9
|
-
version 1.X - Rails 2.X
|
10
|
-
|
11
|
-
|
12
|
-
Installation
|
13
|
-
============
|
14
|
-
|
15
|
-
Rails 3:
|
16
|
-
# Gemfile
|
17
|
-
gem 'gvis', '>= 2.0.0'
|
18
|
-
|
19
|
-
Rails 2.X:
|
20
|
-
# config/environment.rb
|
21
|
-
config.gem 'gvis', :version => '< 2.0.0'
|
22
|
-
|
23
|
-
# Include the GoogleVisualization module in app/helpers/application_helper.rb
|
24
|
-
module ApplicationHelper
|
25
|
-
|
26
|
-
include GoogleVisualization
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
# Load the API, and render any graphs by placing these methods inside your layout
|
31
|
-
# app/views/layouts/application.html.erb
|
32
|
-
<head>
|
33
|
-
<%= include_visualization_api %>
|
34
|
-
<%= render_visualizations %>
|
35
|
-
...
|
36
|
-
</head>
|
37
|
-
|
38
|
-
|
39
|
-
Example
|
40
|
-
=======
|
41
|
-
|
42
|
-
# Render desired graphs in the view
|
43
|
-
# index.html.erb
|
44
|
-
<% visualization "my_chart", "MotionChart", :width => 600, :height => 400, :html => {:class => "graph_chart"} do |chart| %>
|
45
|
-
<%# Add the columns that the graph will have %>
|
46
|
-
<% chart.string "Fruit" %>
|
47
|
-
<% chart.date "Date" %>
|
48
|
-
<% chart.number "Sales" %>
|
49
|
-
<% chart.number "Expenses" %>
|
50
|
-
<% chart.string "Location" %>
|
51
|
-
|
52
|
-
<%# Add the data %>
|
53
|
-
<% chart.add_rows([
|
54
|
-
["Apples", Date.new(1998,1,1), 1000,300,'East'],
|
55
|
-
["Oranges", Date.new(1998,1,1), 950,200,'West'],
|
56
|
-
["Bananas", Date.new(1998,1,1), 300,250,'West'],
|
57
|
-
["Apples", Date.new(1998,2,1), 1200,400,'East'],
|
58
|
-
["Oranges", Date.new(1998,2,1), 950,150,'West'],
|
59
|
-
["Bananas", Date.new(1998,2,1), 788,617,'West']
|
60
|
-
]) %>
|
61
|
-
<% end %>
|
62
|
-
|
63
|
-
|
64
|
-
Copyright (c) 2009 [Jeremy Olliver], released under the MIT license
|
data/lib/data_table.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
# Converts Ruby data structures into javascript strings for constructing the data for Google Visualization API clients.
|
2
|
-
#
|
3
|
-
# This library can be used to create a google.visualization.DataTable usable by
|
4
|
-
# visualizations built on the Google Visualization API. Output formats are raw
|
5
|
-
# JSON, JSON response, and JavaScript.
|
6
|
-
#
|
7
|
-
# written by Jeremy Olliver
|
8
|
-
class DataTable
|
9
|
-
|
10
|
-
attr_accessor :data, :columns, :column_types
|
11
|
-
|
12
|
-
def initialize(data = nil, columns = [], options = {})
|
13
|
-
@columns, @column_types = [], {}
|
14
|
-
unless columns.nil? || columns.empty?
|
15
|
-
columns.each do |type, name|
|
16
|
-
register_column(type, name, options)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
@data = data || []
|
20
|
-
end
|
21
|
-
|
22
|
-
# Registers each column explicitly, with data type, and a name associated
|
23
|
-
def register_column(type, name, options = {})
|
24
|
-
@columns << name.to_s
|
25
|
-
@column_types.merge!(name.to_s => type.to_s)
|
26
|
-
end
|
27
|
-
|
28
|
-
# Adds a single row to the table
|
29
|
-
def add_row(row)
|
30
|
-
@data << row
|
31
|
-
end
|
32
|
-
|
33
|
-
# Adds multiple rows (2D array) to the table
|
34
|
-
def add_rows(rows)
|
35
|
-
@data += rows
|
36
|
-
end
|
37
|
-
|
38
|
-
# Handle the Column definition methods (#string, #number, #date, #datetime)
|
39
|
-
def method_missing(method, *arguments)
|
40
|
-
if ["string", "number", "date", "datetime"].include?(method.to_s)
|
41
|
-
options = arguments.extract_options!
|
42
|
-
register_column(method, arguments, options)
|
43
|
-
else
|
44
|
-
raise NoMethodError.new(method)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
###################################################################################
|
49
|
-
# Format the ruby arrays into JS #
|
50
|
-
# Builds up a string representing a JS Array with JS escaped and formatted values #
|
51
|
-
###################################################################################
|
52
|
-
def js_format_data
|
53
|
-
formatted_rows = []
|
54
|
-
@data.each do |row|
|
55
|
-
values = []
|
56
|
-
row.each_with_index do |entry,index|
|
57
|
-
# Format/escape individual values for javascript, checking column types, and the ruby value as a failsafe
|
58
|
-
safe_val = if @column_types[index] == "date" || entry.is_a?(Date)
|
59
|
-
# Format a date object as a javascript date
|
60
|
-
entry.is_a?(String) ? entry : "new Date (#{entry.year},#{entry.month - 1},#{entry.day})"
|
61
|
-
elsif @column_types[index] == "datetime" || entry.is_a?(Time)
|
62
|
-
# Format a Time (datetime) as a javascript date object down to seconds
|
63
|
-
entry.is_a?(String) ? entry : "new Date (#{entry.year},#{entry.month - 1},#{entry.day},#{entry.hour},#{entry.min},#{entry.sec})"
|
64
|
-
else
|
65
|
-
# Non date/time values can be JS escaped/formatted safely with # to_json
|
66
|
-
entry.to_json
|
67
|
-
end
|
68
|
-
values << safe_val
|
69
|
-
end
|
70
|
-
rowstring = "[#{values.join(",")}]"
|
71
|
-
formatted_rows << rowstring
|
72
|
-
end
|
73
|
-
"[#{formatted_rows.join(',')}]"
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|