gvis 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|