ghazel-googlecharts 1.4.0.1 → 1.4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/gchart.rb +107 -70
- metadata +1 -1
data/lib/gchart.rb
CHANGED
@@ -6,10 +6,11 @@ require "uri"
|
|
6
6
|
require "cgi"
|
7
7
|
require 'enumerator'
|
8
8
|
|
9
|
+
|
9
10
|
class Gchart
|
10
11
|
|
11
12
|
include GchartInfo
|
12
|
-
|
13
|
+
|
13
14
|
@@url = "http://chart.apis.google.com/chart?"
|
14
15
|
@@types = ['line', 'line_xy', 'scatter', 'bar', 'venn', 'pie', 'pie_3d', 'jstize', 'sparkline', 'meter', 'map']
|
15
16
|
@@simple_chars = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
|
@@ -17,9 +18,11 @@ class Gchart
|
|
17
18
|
@@ext_pairs = @@chars.map { |char_1| @@chars.map { |char_2| char_1 + char_2 } }.flatten
|
18
19
|
@@file_name = 'chart.png'
|
19
20
|
|
20
|
-
attr_accessor :title, :type, :width, :height, :horizontal, :grouped, :legend,
|
21
|
-
:
|
22
|
-
:
|
21
|
+
attr_accessor :title, :type, :width, :height, :horizontal, :grouped, :legend,
|
22
|
+
:data, :encoding, :bar_colors, :title_color,
|
23
|
+
:title_size, :custom, :axis_with_labels, :axis_labels,
|
24
|
+
:bar_width_and_spacing, :id, :alt, :class, :range_markers,
|
25
|
+
:geographical_area, :map_colors, :country_codes, :axis_range
|
23
26
|
|
24
27
|
# Support for Gchart.line(:title => 'my title', :size => '400x600')
|
25
28
|
def self.method_missing(m, options={})
|
@@ -46,14 +49,11 @@ class Gchart
|
|
46
49
|
|
47
50
|
def initialize(options={})
|
48
51
|
@type = :line
|
49
|
-
@data = []
|
50
52
|
@width = 300
|
51
53
|
@height = 200
|
52
54
|
@horizontal = false
|
53
55
|
@grouped = false
|
54
56
|
@encoding = 'simple'
|
55
|
-
self.min_value = 'auto'
|
56
|
-
self.max_value = 'auto'
|
57
57
|
# Sets the alt tag when chart is exported as image tag
|
58
58
|
@alt = 'Google Chart'
|
59
59
|
# Sets the CSS id selector when chart is exported as image tag
|
@@ -137,57 +137,94 @@ class Gchart
|
|
137
137
|
def full_data_range(ds)
|
138
138
|
return if @max_value == false
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
else
|
155
|
-
if @type == :bar and not grouped
|
156
|
-
@min_value = ds.compact.first.compact.min if @min_value.nil?
|
157
|
-
if @max_value.nil?
|
158
|
-
totals = []
|
159
|
-
ds.compact.each do |mds|
|
160
|
-
mds.each_with_index do |v, index|
|
161
|
-
next if v.nil?
|
162
|
-
totals[index] ||= 0
|
163
|
-
totals[index] += v
|
164
|
-
end
|
140
|
+
ds.each do |mds|
|
141
|
+
# global limits override individuals. is this preferred?
|
142
|
+
mds[:min_value] = @min_value if not @min_value.nil?
|
143
|
+
mds[:max_value] = @max_value if not @max_value.nil?
|
144
|
+
|
145
|
+
# TODO: can you have grouped stacked bars?
|
146
|
+
if @type == :bar and not grouped and mds[:data].first.is_a?(Array)
|
147
|
+
mds[:min_value] ||= mds[:data].first.to_a.compact.min
|
148
|
+
totals = []
|
149
|
+
mds[:data].each do |l|
|
150
|
+
l.each_with_index do |v, index|
|
151
|
+
next if v.nil?
|
152
|
+
totals[index] ||= 0
|
153
|
+
totals[index] += v
|
165
154
|
end
|
166
|
-
@max_value = totals.compact.max
|
167
155
|
end
|
156
|
+
mds[:max_value] ||= totals.compact.max
|
168
157
|
else
|
169
|
-
|
170
|
-
|
158
|
+
all = mds[:data].flatten.compact
|
159
|
+
mds[:min_value] ||= all.min
|
160
|
+
mds[:max_value] ||= all.max
|
171
161
|
end
|
172
|
-
@axis << [@min_value, @max_value]
|
173
162
|
end
|
174
163
|
|
175
164
|
if not @axis_range
|
176
|
-
@axis_range =
|
177
|
-
if
|
165
|
+
@axis_range = ds.map{|mds| [mds[:min_value], mds[:max_value]]}
|
166
|
+
if dimensions == 1 and (@type != :bar or not @horizontal)
|
178
167
|
tmp = @axis_range.fetch(0, [])
|
179
168
|
@axis_range[0] = @axis_range.fetch(1, [])
|
180
169
|
@axis_range[1] = tmp
|
181
170
|
end
|
182
171
|
end
|
183
172
|
end
|
184
|
-
|
185
|
-
def
|
186
|
-
|
173
|
+
|
174
|
+
def number_visible
|
175
|
+
n = 0
|
176
|
+
axis_set.each do |mds|
|
177
|
+
return n.to_s if mds[:invisible] == true
|
178
|
+
if mds[:data].first.is_a?(Array)
|
179
|
+
n += mds[:data].length
|
180
|
+
else
|
181
|
+
n += 1
|
182
|
+
end
|
183
|
+
end
|
184
|
+
""
|
185
|
+
end
|
186
|
+
|
187
|
+
# Turns input into an array of axis hashes, dependent on the chart type
|
188
|
+
def convert_dataset(ds)
|
189
|
+
if dimensions == 2
|
190
|
+
# valid inputs include:
|
191
|
+
# an array of >=2 arrays, or an array of >=2 hashes
|
192
|
+
ds = ds.map do |d|
|
193
|
+
d.is_a?(Hash) ? d : {:data => d}
|
194
|
+
end
|
195
|
+
elsif dimensions == 1
|
196
|
+
# valid inputs include:
|
197
|
+
# a hash, an array of data, an array of >=1 array, or an array of >=1 hash
|
198
|
+
if ds.is_a?(Hash)
|
199
|
+
ds = [ds]
|
200
|
+
elsif not ds.first.is_a?(Hash)
|
201
|
+
ds = [{:data => ds}]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
ds
|
205
|
+
end
|
206
|
+
|
207
|
+
def prepare_dataset
|
208
|
+
@dataset = convert_dataset(data || [])
|
187
209
|
full_data_range(@dataset)
|
210
|
+
end
|
211
|
+
|
212
|
+
def axis_set
|
188
213
|
@dataset
|
189
214
|
end
|
190
|
-
|
215
|
+
|
216
|
+
def dataset
|
217
|
+
datasets = []
|
218
|
+
@dataset.each do |d|
|
219
|
+
if d[:data].first.is_a?(Array)
|
220
|
+
datasets += d[:data]
|
221
|
+
else
|
222
|
+
datasets << d[:data]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
datasets
|
226
|
+
end
|
227
|
+
|
191
228
|
def self.jstize(string)
|
192
229
|
string.gsub(' ', '+').gsub(/\[|\{|\}|\||\\|\^|\[|\]|\`|\]/) {|c| "%#{c[0].to_s(16).upcase}"}
|
193
230
|
end
|
@@ -379,7 +416,6 @@ class Gchart
|
|
379
416
|
# a passed axis_range should look like:
|
380
417
|
# [[10,100]] or [[10,100,4]] or [[10,100], [20,300]]
|
381
418
|
# in the second example, 4 is the interval
|
382
|
-
dataset # just making sure we processed the data before
|
383
419
|
if axis_range && axis_range.respond_to?(:each) && axis_range.first.respond_to?(:each)
|
384
420
|
'chxr=' + axis_range.enum_for(:each_with_index).map{|range, index| [index, range[0], range[1], range[2]].compact.join(',')}.join("|")
|
385
421
|
else
|
@@ -426,33 +462,34 @@ class Gchart
|
|
426
462
|
'ls'
|
427
463
|
end
|
428
464
|
end
|
429
|
-
|
430
|
-
# Wraps a single dataset inside another array to support more datasets
|
431
|
-
def prepare_dataset(ds)
|
432
|
-
ds = [ds] unless ds.first.is_a?(Array)
|
433
|
-
ds
|
434
|
-
end
|
435
465
|
|
436
466
|
def encode_scaled_dataset chars, nil_char
|
437
|
-
|
467
|
+
dsets = []
|
468
|
+
axis_set.each do |ds|
|
438
469
|
if @max_value != false
|
439
|
-
|
440
|
-
min = ax[0]
|
441
|
-
range = ax[1] - min
|
470
|
+
range = ds[:max_value] - ds[:min_value]
|
442
471
|
range = 1 if range == 0
|
443
472
|
end
|
444
|
-
ds.
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
473
|
+
if not ds[:data].first.is_a?(Array)
|
474
|
+
datasets = [ds[:data]]
|
475
|
+
else
|
476
|
+
datasets = ds[:data]
|
477
|
+
end
|
478
|
+
datasets.each do |l|
|
479
|
+
dsets << l.map do |number|
|
480
|
+
if number.nil?
|
481
|
+
nil_char
|
482
|
+
else
|
483
|
+
if not range.nil?
|
484
|
+
number = chars.size * (number - ds[:min_value]) / range
|
485
|
+
number = [number, chars.size - 1].min
|
486
|
+
end
|
487
|
+
chars[number.to_i]
|
488
|
+
end
|
489
|
+
end.join
|
490
|
+
end
|
491
|
+
end
|
492
|
+
dsets.join(',')
|
456
493
|
end
|
457
494
|
|
458
495
|
# http://code.google.com/apis/chart/#simple
|
@@ -460,7 +497,7 @@ class Gchart
|
|
460
497
|
# Allowing five pixels per data point, this is sufficient for line and bar charts up
|
461
498
|
# to about 300 pixels. Simple encoding is suitable for all other types of chart regardless of size.
|
462
499
|
def simple_encoding
|
463
|
-
"s:" + encode_scaled_dataset(@@simple_chars, '_')
|
500
|
+
"s" + number_visible + ":" + encode_scaled_dataset(@@simple_chars, '_')
|
464
501
|
end
|
465
502
|
|
466
503
|
# http://code.google.com/apis/chart/#text
|
@@ -476,19 +513,19 @@ class Gchart
|
|
476
513
|
# This encoding is not available for maps.
|
477
514
|
#
|
478
515
|
def text_encoding
|
479
|
-
|
480
|
-
"t:" + dataset.map{ |ds| ds.join(',') }.join('|') + "&chds
|
516
|
+
chds = axis_set.map{ |ds| "#{ds[:min_value]},#{ds[:max_value]}" }.join(",")
|
517
|
+
"t" + number_visible + ":" + dataset.map{ |ds| ds.join(',') }.join('|') + "&chds=" + chds
|
481
518
|
end
|
482
519
|
|
483
520
|
# http://code.google.com/apis/chart/#extended
|
484
521
|
# Extended encoding has a resolution of 4,096 different values
|
485
522
|
# and is best used for large charts where a large data range is required.
|
486
523
|
def extended_encoding
|
487
|
-
"e:" + encode_scaled_dataset(@@ext_pairs, '__')
|
524
|
+
"e" + number_visible + ":" + encode_scaled_dataset(@@ext_pairs, '__')
|
488
525
|
end
|
489
526
|
|
490
527
|
def query_builder(options="")
|
491
|
-
|
528
|
+
prepare_dataset
|
492
529
|
query_params = instance_variables.map do |var|
|
493
530
|
case var
|
494
531
|
when '@data'
|