mattetti-googlecharts 1.3.3
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/History.txt +33 -0
- data/License.txt +20 -0
- data/Manifest.txt +16 -0
- data/README.txt +180 -0
- data/Rakefile +32 -0
- data/config/hoe.rb +71 -0
- data/config/requirements.rb +16 -0
- data/lib/gchart/aliases.rb +14 -0
- data/lib/gchart/version.rb +9 -0
- data/lib/gchart.rb +392 -0
- data/setup.rb +1585 -0
- data/spec/gchart_spec.rb +411 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/website/index.txt +476 -0
- metadata +74 -0
data/lib/gchart.rb
ADDED
@@ -0,0 +1,392 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'gchart/version'
|
3
|
+
require "open-uri"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
class Gchart
|
7
|
+
|
8
|
+
include GchartInfo
|
9
|
+
|
10
|
+
@@url = "http://chart.apis.google.com/chart?"
|
11
|
+
@@types = ['line', 'line_xy', 'scatter', 'bar', 'venn', 'pie', 'pie_3d', 'jstize', 'sparkline', 'meter']
|
12
|
+
@@simple_chars = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
|
13
|
+
@@chars = @@simple_chars + ['-', '.']
|
14
|
+
@@ext_pairs = @@chars.map { |char_1| @@chars.map { |char_2| char_1 + char_2 } }.flatten
|
15
|
+
@@file_name = 'chart.png'
|
16
|
+
|
17
|
+
attr_accessor :title, :type, :width, :height, :horizontal, :grouped, :legend, :data, :encoding, :max_value, :bar_colors,
|
18
|
+
:title_color, :title_size, :custom, :axis_with_labels, :axis_labels, :bar_width_and_spacing
|
19
|
+
|
20
|
+
class << self
|
21
|
+
# Support for Gchart.line(:title => 'my title', :size => '400x600')
|
22
|
+
def method_missing(m, options={})
|
23
|
+
# Extract the format and optional filename, then clean the hash
|
24
|
+
format = options[:format] || 'url'
|
25
|
+
@@file_name = options[:filename] unless options[:filename].nil?
|
26
|
+
options.delete(:format)
|
27
|
+
options.delete(:filename)
|
28
|
+
# create the chart and return it in the format asked for
|
29
|
+
if @@types.include?(m.to_s)
|
30
|
+
chart = new(options.merge!({:type => m}))
|
31
|
+
chart.send(format)
|
32
|
+
elsif m.to_s == 'version'
|
33
|
+
Gchart::VERSION::STRING
|
34
|
+
else
|
35
|
+
"#{m} is not a supported chart format, please use one of the following: #{supported_types}."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(options={})
|
42
|
+
@type = :line
|
43
|
+
@data = []
|
44
|
+
@width = 300
|
45
|
+
@height = 200
|
46
|
+
@horizontal = false
|
47
|
+
@grouped = false
|
48
|
+
@encoding = 'simple'
|
49
|
+
@max_value = 'auto'
|
50
|
+
|
51
|
+
# set the options value if definable
|
52
|
+
options.each do |attribute, value|
|
53
|
+
send("#{attribute.to_s}=", value) if self.respond_to?("#{attribute}=")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.supported_types
|
58
|
+
@@types.join(' ')
|
59
|
+
end
|
60
|
+
|
61
|
+
# Defines the Graph size using the following format:
|
62
|
+
# width X height
|
63
|
+
def size=(size='300x200')
|
64
|
+
@width, @height = size.split("x").map { |dimension| dimension.to_i }
|
65
|
+
end
|
66
|
+
|
67
|
+
def size
|
68
|
+
"#{@width}x#{@height}"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets the orientation of a bar graph
|
72
|
+
def orientation=(orientation='h')
|
73
|
+
if orientation == 'h' || orientation == 'horizontal'
|
74
|
+
self.horizontal = true
|
75
|
+
elsif orientation == 'v' || orientation == 'vertical'
|
76
|
+
self.horizontal = false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sets the bar graph presentation (stacked or grouped)
|
81
|
+
def stacked=(option=true)
|
82
|
+
@grouped = option ? false : true
|
83
|
+
end
|
84
|
+
|
85
|
+
def bg=(options)
|
86
|
+
if options.is_a?(String)
|
87
|
+
@bg_color = options
|
88
|
+
elsif options.is_a?(Hash)
|
89
|
+
@bg_color = options[:color]
|
90
|
+
@bg_type = options[:type]
|
91
|
+
@bg_angle = options[:angle]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def graph_bg=(options)
|
96
|
+
if options.is_a?(String)
|
97
|
+
@chart_color = options
|
98
|
+
elsif options.is_a?(Hash)
|
99
|
+
@chart_color = options[:color]
|
100
|
+
@chart_type = options[:type]
|
101
|
+
@chart_angle = options[:angle]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.jstize(string)
|
106
|
+
string.gsub(' ', '+').gsub(/\[|\{|\}|\||\\|\^|\[|\]|\`|\]/) {|c| "%#{c[0].to_s(16).upcase}"}
|
107
|
+
end
|
108
|
+
# load all the custom aliases
|
109
|
+
require 'gchart/aliases'
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
# Returns the chart's generated PNG as a blob. (borrowed from John's gchart.rubyforge.org)
|
114
|
+
def fetch
|
115
|
+
open(query_builder) { |io| io.read }
|
116
|
+
end
|
117
|
+
|
118
|
+
# Writes the chart's generated PNG to a file. (borrowed from John's gchart.rubyforge.org)
|
119
|
+
def write(io_or_file=@@file_name)
|
120
|
+
return io_or_file.write(fetch) if io_or_file.respond_to?(:write)
|
121
|
+
open(io_or_file, "w+") { |io| io.write(fetch) }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Format
|
125
|
+
|
126
|
+
def img_tag
|
127
|
+
"<img src='#{query_builder}'/>"
|
128
|
+
end
|
129
|
+
|
130
|
+
def url
|
131
|
+
query_builder
|
132
|
+
end
|
133
|
+
|
134
|
+
def file
|
135
|
+
write
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
def jstize(string)
|
140
|
+
Gchart.jstize(string)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# The title size cannot be set without specifying a color.
|
146
|
+
# A dark key will be used for the title color if no color is specified
|
147
|
+
def set_title
|
148
|
+
title_params = "chtt=#{title}"
|
149
|
+
unless (title_color.nil? && title_size.nil? )
|
150
|
+
title_params << "&chts=" + (color, size = (@title_color || '454545'), @title_size).compact.join(',')
|
151
|
+
end
|
152
|
+
title_params
|
153
|
+
end
|
154
|
+
|
155
|
+
def set_size
|
156
|
+
"chs=#{size}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def set_data
|
160
|
+
data = send("#{@encoding}_encoding", @data)
|
161
|
+
"chd=#{data}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_colors
|
165
|
+
bg_type = fill_type(@bg_type) || 's' if @bg_color
|
166
|
+
chart_type = fill_type(@chart_type) || 's' if @chart_color
|
167
|
+
|
168
|
+
"chf=" + {'bg' => fill_for(bg_type, @bg_color, @bg_angle), 'c' => fill_for(chart_type, @chart_color, @chart_angle)}.map{|k,v| "#{k},#{v}" unless v.nil?}.compact.join('|')
|
169
|
+
end
|
170
|
+
|
171
|
+
# set bar, line colors
|
172
|
+
def set_bar_colors
|
173
|
+
@bar_colors = @bar_colors.join(',') if @bar_colors.is_a?(Array)
|
174
|
+
"chco=#{@bar_colors}"
|
175
|
+
end
|
176
|
+
|
177
|
+
# set bar spacing
|
178
|
+
# chbh=
|
179
|
+
# <bar width in pixels>,
|
180
|
+
# <optional space between bars in a group>,
|
181
|
+
# <optional space between groups>
|
182
|
+
def set_bar_width_and_spacing
|
183
|
+
width_and_spacing_values = case @bar_width_and_spacing
|
184
|
+
when String
|
185
|
+
@bar_width_and_spacing
|
186
|
+
when Array
|
187
|
+
@bar_width_and_spacing.join(',')
|
188
|
+
when Hash
|
189
|
+
width = @bar_width_and_spacing[:width] || 23
|
190
|
+
spacing = @bar_width_and_spacing[:spacing] || 4
|
191
|
+
group_spacing = @bar_width_and_spacing[:group_spacing] || 8
|
192
|
+
[width,spacing,group_spacing].join(',')
|
193
|
+
else
|
194
|
+
@bar_width_and_spacing.to_s
|
195
|
+
end
|
196
|
+
"chbh=#{width_and_spacing_values}"
|
197
|
+
end
|
198
|
+
|
199
|
+
def fill_for(type=nil, color='', angle=nil)
|
200
|
+
unless type.nil?
|
201
|
+
case type
|
202
|
+
when 'lg'
|
203
|
+
angle ||= 0
|
204
|
+
color = "#{color},0,ffffff,1" if color.split(',').size == 1
|
205
|
+
"#{type},#{angle},#{color}"
|
206
|
+
when 'ls'
|
207
|
+
angle ||= 90
|
208
|
+
color = "#{color},0.2,ffffff,0.2" if color.split(',').size == 1
|
209
|
+
"#{type},#{angle},#{color}"
|
210
|
+
else
|
211
|
+
"#{type},#{color}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# A chart can have one or many legends.
|
217
|
+
# Gchart.line(:legend => 'label')
|
218
|
+
# or
|
219
|
+
# Gchart.line(:legend => ['first label', 'last label'])
|
220
|
+
def set_legend
|
221
|
+
return set_labels if @type == :pie || @type == :pie_3d
|
222
|
+
|
223
|
+
if @legend.is_a?(Array)
|
224
|
+
"chdl=#{@legend.map{|label| "#{label}"}.join('|')}"
|
225
|
+
else
|
226
|
+
"chdl=#{@legend}"
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
def set_labels
|
232
|
+
if @legend.is_a?(Array)
|
233
|
+
"chl=#{@legend.map{|label| "#{label}"}.join('|')}"
|
234
|
+
else
|
235
|
+
"chl=#{@legend}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def set_axis_with_labels
|
240
|
+
@axis_with_labels = @axis_with_labels.join(',') if @axis_with_labels.is_a?(Array)
|
241
|
+
"chxt=#{@axis_with_labels}"
|
242
|
+
end
|
243
|
+
|
244
|
+
def set_axis_labels
|
245
|
+
labels_arr = []
|
246
|
+
axis_labels.each_with_index do |labels,index|
|
247
|
+
if labels.is_a?(Array)
|
248
|
+
labels_arr << "#{index}:|#{labels.join('|')}"
|
249
|
+
else
|
250
|
+
labels_arr << "#{index}:|#{labels}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
"chxl=#{labels_arr.join('|')}"
|
254
|
+
end
|
255
|
+
|
256
|
+
def set_type
|
257
|
+
case @type
|
258
|
+
when :line
|
259
|
+
"cht=lc"
|
260
|
+
when :line_xy
|
261
|
+
"cht=lxy"
|
262
|
+
when :bar
|
263
|
+
"cht=b" + (horizontal? ? "h" : "v") + (grouped? ? "g" : "s")
|
264
|
+
when :pie_3d
|
265
|
+
"cht=p3"
|
266
|
+
when :pie
|
267
|
+
"cht=p"
|
268
|
+
when :venn
|
269
|
+
"cht=v"
|
270
|
+
when :scatter
|
271
|
+
"cht=s"
|
272
|
+
when :sparkline
|
273
|
+
"cht=ls"
|
274
|
+
when :meter
|
275
|
+
"cht=gom"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def fill_type(type)
|
280
|
+
case type
|
281
|
+
when 'solid'
|
282
|
+
's'
|
283
|
+
when 'gradient'
|
284
|
+
'lg'
|
285
|
+
when 'stripes'
|
286
|
+
'ls'
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Wraps a single dataset inside another array to support more datasets
|
291
|
+
def prepare_dataset(ds)
|
292
|
+
ds = [ds] unless ds.first.is_a?(Array)
|
293
|
+
ds
|
294
|
+
end
|
295
|
+
|
296
|
+
def convert_to_simple_value(number)
|
297
|
+
if number.nil?
|
298
|
+
"_"
|
299
|
+
else
|
300
|
+
value = @@simple_chars[number.to_i]
|
301
|
+
value.nil? ? "_" : value
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# http://code.google.com/apis/chart/#simple
|
306
|
+
# Simple encoding has a resolution of 62 different values.
|
307
|
+
# Allowing five pixels per data point, this is sufficient for line and bar charts up
|
308
|
+
# to about 300 pixels. Simple encoding is suitable for all other types of chart regardless of size.
|
309
|
+
def simple_encoding(dataset=[])
|
310
|
+
dataset = prepare_dataset(dataset)
|
311
|
+
@max_value = dataset.map{|ds| ds.max}.max if @max_value == 'auto'
|
312
|
+
|
313
|
+
if @max_value == false || @max_value == 'false' || @max_value == :false || @max_value == 0
|
314
|
+
"s:" + dataset.map { |ds| ds.map { |number| convert_to_simple_value(number) }.join }.join(',')
|
315
|
+
else
|
316
|
+
"s:" + dataset.map { |ds| ds.map { |number| convert_to_simple_value( (@@simple_chars.size - 1) * number / @max_value) }.join }.join(',')
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
# http://code.google.com/apis/chart/#text
|
322
|
+
# Text encoding has a resolution of 1,000 different values,
|
323
|
+
# using floating point numbers between 0.0 and 100.0. Allowing five pixels per data point,
|
324
|
+
# integers (1.0, 2.0, and so on) are sufficient for line and bar charts up to about 500 pixels.
|
325
|
+
# Include a single decimal place (35.7 for example) if you require higher resolution.
|
326
|
+
# Text encoding is suitable for all other types of chart regardless of size.
|
327
|
+
def text_encoding(dataset=[])
|
328
|
+
dataset = prepare_dataset(dataset)
|
329
|
+
"t:" + dataset.map{ |ds| ds.join(',') }.join('|')
|
330
|
+
end
|
331
|
+
|
332
|
+
def convert_to_extended_value(number)
|
333
|
+
if number.nil?
|
334
|
+
'__'
|
335
|
+
else
|
336
|
+
value = @@ext_pairs[number.to_i]
|
337
|
+
value.nil? ? "__" : value
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# http://code.google.com/apis/chart/#extended
|
342
|
+
# Extended encoding has a resolution of 4,096 different values
|
343
|
+
# and is best used for large charts where a large data range is required.
|
344
|
+
def extended_encoding(dataset=[])
|
345
|
+
|
346
|
+
dataset = prepare_dataset(dataset)
|
347
|
+
@max_value = dataset.map{|ds| ds.max}.max if @max_value == 'auto'
|
348
|
+
|
349
|
+
if @max_value == false || @max_value == 'false' || @max_value == :false
|
350
|
+
"e:" + dataset.map { |ds| ds.map { |number| convert_to_extended_value(number)}.join }.join(',')
|
351
|
+
else
|
352
|
+
"e:" + dataset.map { |ds| ds.map { |number| convert_to_extended_value( (@@ext_pairs.size - 1) * number / @max_value) }.join }.join(',')
|
353
|
+
end
|
354
|
+
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
def query_builder
|
359
|
+
query_params = instance_variables.map do |var|
|
360
|
+
case var
|
361
|
+
# Set the graph size
|
362
|
+
when '@width'
|
363
|
+
set_size unless @width.nil? || @height.nil?
|
364
|
+
when '@type'
|
365
|
+
set_type
|
366
|
+
when '@title'
|
367
|
+
set_title unless @title.nil?
|
368
|
+
when '@legend'
|
369
|
+
set_legend unless @legend.nil?
|
370
|
+
when '@bg_color'
|
371
|
+
set_colors
|
372
|
+
when '@chart_color'
|
373
|
+
set_colors if @bg_color.nil?
|
374
|
+
when '@data'
|
375
|
+
set_data unless @data == []
|
376
|
+
when '@bar_colors'
|
377
|
+
set_bar_colors
|
378
|
+
when '@bar_width_and_spacing'
|
379
|
+
set_bar_width_and_spacing
|
380
|
+
when '@axis_with_labels'
|
381
|
+
set_axis_with_labels
|
382
|
+
when '@axis_labels'
|
383
|
+
set_axis_labels
|
384
|
+
when '@custom'
|
385
|
+
@custom
|
386
|
+
end
|
387
|
+
end.compact
|
388
|
+
|
389
|
+
jstize(@@url + query_params.join('&'))
|
390
|
+
end
|
391
|
+
|
392
|
+
end
|