smart_chart 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +187 -0
- data/Rakefile +56 -0
- data/lib/smart_chart/base_chart.rb +428 -0
- data/lib/smart_chart/charts/bar.rb +20 -0
- data/lib/smart_chart/charts/line.rb +22 -0
- data/lib/smart_chart/charts/map.rb +163 -0
- data/lib/smart_chart/charts/meter.rb +13 -0
- data/lib/smart_chart/charts/pie.rb +32 -0
- data/lib/smart_chart/charts/qr_code.rb +85 -0
- data/lib/smart_chart/charts/radar.rb +14 -0
- data/lib/smart_chart/charts/scatter.rb +21 -0
- data/lib/smart_chart/charts/venn.rb +13 -0
- data/lib/smart_chart/data_set.rb +15 -0
- data/lib/smart_chart/encoder.rb +226 -0
- data/lib/smart_chart/exceptions.rb +43 -0
- data/lib/smart_chart/features/axis_lines.rb +29 -0
- data/lib/smart_chart/features/grid_lines.rb +47 -0
- data/lib/smart_chart/features/labels.rb +23 -0
- data/lib/smart_chart/multiple_data_set_chart.rb +128 -0
- data/lib/smart_chart/single_data_set_chart.rb +56 -0
- data/lib/smart_chart.rb +30 -0
- data/test/smart_charts_test.rb +400 -0
- data/test/test_helper.rb +42 -0
- metadata +82 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Alex Reisner
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
= SmartChart
|
2
|
+
|
3
|
+
SmartChart is an easy way to render charts on web pages. It uses the Google Charts engine so there are no server-side dependencies or performance issues--just install and go.
|
4
|
+
|
5
|
+
<b>SmartChart is still in the early stages of development. Only maps, barcodes, and line charts (partially) are working at all. However, much of the interface is described below (plus to-do list) and if you'd like to contribute, please do.</b>
|
6
|
+
|
7
|
+
|
8
|
+
== Key Benefits
|
9
|
+
|
10
|
+
<b>1. Designed as a chart-making interface, not as a Google Charts wrapper.</b> Other APIs effectively just give Google Chart parameters different names, leading you to wonder: why am I learning an API to an API? SmartChart is an intelligent chart-authoring syntax that happens to use Google Charts as a back-end. It may support other charting engines in the future.
|
11
|
+
|
12
|
+
<b>2. Place chart elements with respect to data points, not chart size.</b> If you want horizontal axis lines on your graph every 10 units (along the Y-axis) you simply specify this. If you've worked much with the native Google Charts interface you know you have to do several calculations to get this to work, and any library that simply "wraps" Google Charts suffers from this same annoyance.
|
13
|
+
|
14
|
+
<b>3. You get useful feedback when you do something wrong.</b> If you specify more data points than Google can handle, you get an error message. If you specify a bigger chart than Google will serve, you get an error message. Forget to specify a required parameter? That's an error message too. The thing is, with the raw Google Charts interface you get no useful feedback in any of these cases, which can lead to _very_ long and frustrating debugging sessions.
|
15
|
+
|
16
|
+
<b>4. The best data encoding is selected automatically.</b> SmartChart examines your data and selects the optimal way to encode your data to keep HTTP requests short while preserving granularity. There's no way a chart author should have to think about Google's data encoding methods. Forget I even mentioned it.
|
17
|
+
|
18
|
+
|
19
|
+
== Examples
|
20
|
+
|
21
|
+
SmartChart::Line.new(
|
22
|
+
|
23
|
+
# y-axis range
|
24
|
+
:y_min => -40,
|
25
|
+
:y_max => 80,
|
26
|
+
|
27
|
+
# data (specify line/bar styles with data)
|
28
|
+
:data => [
|
29
|
+
{
|
30
|
+
:values => [1,2,3,4],
|
31
|
+
:label => "Profit",
|
32
|
+
:thickness => 2,
|
33
|
+
:color => '550055',
|
34
|
+
:style => {:solid => 3, :blank => 2}
|
35
|
+
},
|
36
|
+
{
|
37
|
+
:values => [2,4,6,8],
|
38
|
+
:label => "Reputation",
|
39
|
+
:thickness => 2,
|
40
|
+
:color => 'AABBCC',
|
41
|
+
:style => :dotted
|
42
|
+
}
|
43
|
+
],
|
44
|
+
|
45
|
+
# axis lines
|
46
|
+
:axis => {
|
47
|
+
:sides => [:left, :right, :bottom], # empty array for none
|
48
|
+
:color => 'DDDDDD',
|
49
|
+
:style => :dashed
|
50
|
+
}
|
51
|
+
|
52
|
+
# grid lines
|
53
|
+
:grid => {
|
54
|
+
:x => {:every => 10, :offset => 2}, # based on number of data points
|
55
|
+
:y => {:every => 5}, # based on numeric data range
|
56
|
+
:style => :dashed # no :color or :thickness
|
57
|
+
},
|
58
|
+
|
59
|
+
# labels
|
60
|
+
:x_labels => {
|
61
|
+
1 => "Jan",
|
62
|
+
4 => "Apr",
|
63
|
+
7 => "Jul",
|
64
|
+
10 => "Oct"
|
65
|
+
}
|
66
|
+
|
67
|
+
# options for HTML tag
|
68
|
+
:html => {
|
69
|
+
:id => "stock_graph",
|
70
|
+
:class => "graph"
|
71
|
+
}
|
72
|
+
)
|
73
|
+
|
74
|
+
SmartChart::Pie.new(
|
75
|
+
:style => "3d",
|
76
|
+
:rotate => 45, # degrees from vertical (start of first slice)
|
77
|
+
...
|
78
|
+
)
|
79
|
+
|
80
|
+
# display
|
81
|
+
g = SmartChart::Line.new(...)
|
82
|
+
g.to_url
|
83
|
+
g.to_html
|
84
|
+
|
85
|
+
# QR Code
|
86
|
+
g = SmartChart::QRCode.new(:data => "some data").to_s
|
87
|
+
|
88
|
+
|
89
|
+
== Specifying Data
|
90
|
+
|
91
|
+
Data is specified in slightly different ways for different charts. In the simplest case, a QR code (<tt>SmartChart::QRCode</tt>), the data is simply a string:
|
92
|
+
|
93
|
+
chart.data = "A sentence full of data."
|
94
|
+
|
95
|
+
Another simple case is a map (<tt>SmartChart::Map</tt>), where data is specified as a hash of region-value pairs:
|
96
|
+
|
97
|
+
chart.data = {
|
98
|
+
:US => 74,
|
99
|
+
:CA => 81,
|
100
|
+
:MX => 52,
|
101
|
+
:RU => 19,
|
102
|
+
:AU => 41
|
103
|
+
}
|
104
|
+
|
105
|
+
Data can be passed to pie charts in a similar way. For more complex graphs depicting multiple series, data and other information about each series is given as a hash (in an array if there is more than one), for example for a line graph:
|
106
|
+
|
107
|
+
chart.data = [
|
108
|
+
{
|
109
|
+
:values => [23, 26, 46, 52, 51, 78],
|
110
|
+
:label => "Stock price",
|
111
|
+
:thickness => 2,
|
112
|
+
:color => '0099FF'
|
113
|
+
},
|
114
|
+
{
|
115
|
+
:values => [65, 64, 58, 52, 63, 79],
|
116
|
+
:label => "Consumer interest",
|
117
|
+
:thickness => 1,
|
118
|
+
:color => 'FF0099',
|
119
|
+
:style => :dotted
|
120
|
+
}
|
121
|
+
}
|
122
|
+
]
|
123
|
+
|
124
|
+
|
125
|
+
== Axis Lines
|
126
|
+
|
127
|
+
Actual output is limited by Google's requirements. For example, while Google provides for a line chart with no axis lines ("sparkline"), there is no analogous bar chart type. However, SmartChart will simulate this for you by cropping the chart image using a <div> when you call the chart.to_html method.
|
128
|
+
|
129
|
+
|
130
|
+
== To-do List
|
131
|
+
|
132
|
+
* query_string_params should be a class variable so feature modules can
|
133
|
+
auto-add their parameters
|
134
|
+
|
135
|
+
* validations
|
136
|
+
* margins and legend dimensions are integers
|
137
|
+
* grid line attributes
|
138
|
+
|
139
|
+
* grids
|
140
|
+
* easy placement of y-gridline at zero, if exists
|
141
|
+
* easy placement of gridlines at label positions
|
142
|
+
|
143
|
+
* axis lines
|
144
|
+
* specify which ones to print
|
145
|
+
* hide all ("sparklines")
|
146
|
+
* hide bar graph axes by hiding 1px from left and bottom of image when to_html is called
|
147
|
+
* chxr parameter?
|
148
|
+
|
149
|
+
* labels
|
150
|
+
* on line, scatter, bar graphs
|
151
|
+
* labels on other axes (top and right)
|
152
|
+
* multiple rows of labels on same axis
|
153
|
+
|
154
|
+
* legend
|
155
|
+
* size, position
|
156
|
+
* inline legends (line up with ends of lines -- see http://code.google.com/p/graphy/wiki/UserGuide)
|
157
|
+
|
158
|
+
* general
|
159
|
+
* support advanced background ("fill") options like gradients
|
160
|
+
|
161
|
+
* markers
|
162
|
+
* note: invisible data series available for marker positioning
|
163
|
+
see: http://code.google.com/apis/chart/formats.html#multiple_data_series
|
164
|
+
|
165
|
+
* SingleDataSetChart
|
166
|
+
* document attributes
|
167
|
+
|
168
|
+
* QRCode
|
169
|
+
* data length validation for given EC level and character type
|
170
|
+
* see table: http://code.google.com/apis/chart/types.html#qrcodes
|
171
|
+
* may be irrelevant because URL_MAX_LENGTH == 2074
|
172
|
+
|
173
|
+
* data and encoding
|
174
|
+
* The best encoding type should be selected automatically (whatever is shortest with enough granularity). Avoid URLs longer than 2074 characters. Default to Extended, but use Simple if (1) URL would be too long, (2) image is less than 100px tall, or (3) not enough data point to justify it.
|
175
|
+
* data granularity adjustment (curve smoothing, rolling average?)
|
176
|
+
* see bottom: http://code.google.com/apis/chart/formats.html
|
177
|
+
* at least 1 pixel per data point
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
== References
|
182
|
+
|
183
|
+
Other Google Charts APIs:
|
184
|
+
http://groups.google.com/group/google-chart-api/web/useful-links-to-api-libraries
|
185
|
+
|
186
|
+
|
187
|
+
Copyright (c) 2009 Alex Reisner. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "smart_chart"
|
8
|
+
gem.summary = %Q{Easily create charts and graphs for the web (uses Google Charts).}
|
9
|
+
gem.description = %Q{Easily create charts and graphs for the web (uses Google Charts).}
|
10
|
+
gem.email = "alex@alexreisner.com"
|
11
|
+
gem.homepage = "http://github.com/alexreisner/smart_chart"
|
12
|
+
gem.authors = ["Alex Reisner"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/*_test.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
if File.exist?('VERSION')
|
47
|
+
version = File.read('VERSION')
|
48
|
+
else
|
49
|
+
version = ""
|
50
|
+
end
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "SmartChart #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
@@ -0,0 +1,428 @@
|
|
1
|
+
module SmartChart
|
2
|
+
|
3
|
+
##
|
4
|
+
# Maximum length of URL accepted by Google.
|
5
|
+
#
|
6
|
+
URL_MAX_LENGTH = 2074
|
7
|
+
|
8
|
+
##
|
9
|
+
# Takes a decimal number and returns a string with up to +frac+
|
10
|
+
# digits to the right of the '.'.
|
11
|
+
#
|
12
|
+
def self.decimal_string(num, frac = 3)
|
13
|
+
str = "%.#{frac}f" % num
|
14
|
+
str = str[0...-1] while str[-1,1] == "0"
|
15
|
+
str = str[0...-1] if str[-1,1] == "." # leave zeros left of .
|
16
|
+
str
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Method names are called attributes, data for URL are called parameters.
|
21
|
+
# Use attr_writers for all attributes, and wrte readers so
|
22
|
+
# they instantiate the correct object type.
|
23
|
+
#
|
24
|
+
class BaseChart
|
25
|
+
|
26
|
+
# dimensions of chart image, in pixels
|
27
|
+
attr_accessor :width, :height
|
28
|
+
|
29
|
+
# chart data
|
30
|
+
attr_accessor :data
|
31
|
+
|
32
|
+
# chart range
|
33
|
+
attr_accessor :y_min, :y_max
|
34
|
+
|
35
|
+
# chart background
|
36
|
+
attr_accessor :background
|
37
|
+
|
38
|
+
# chart margins
|
39
|
+
attr_accessor :margins
|
40
|
+
|
41
|
+
# legend properties
|
42
|
+
attr_accessor :legend
|
43
|
+
|
44
|
+
# bar chart orientation -- :vertical (default) or :horizontal
|
45
|
+
# pie chart orientation -- degrees of rotation
|
46
|
+
attr_accessor :orientation
|
47
|
+
|
48
|
+
# bar -- :grouped (default) or :stacked
|
49
|
+
# pie -- nil (2D, default), "3d", or :concentric
|
50
|
+
# radar -- nil (default) or :filled
|
51
|
+
attr_accessor :style
|
52
|
+
|
53
|
+
##
|
54
|
+
# Accept attributes and attempt to assign each to an attribute.
|
55
|
+
#
|
56
|
+
def initialize(options = {})
|
57
|
+
options.each do |k,v|
|
58
|
+
begin
|
59
|
+
send("#{k}=", v)
|
60
|
+
rescue NoMethodError
|
61
|
+
raise NoAttributeError.new(self, k)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Get the chart URL query string.
|
68
|
+
#
|
69
|
+
def to_query_string(encode = true, validation = true)
|
70
|
+
validate if validation
|
71
|
+
query_string(encode)
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Get the full chart URL.
|
76
|
+
#
|
77
|
+
def to_url(encode = true, validation = true)
|
78
|
+
"http://chart.apis.google.com/chart?" +
|
79
|
+
to_query_string(encode, validation)
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Chart as an HTML tag.
|
84
|
+
#
|
85
|
+
def to_html(encode = true, validation = true)
|
86
|
+
'<img src="%s" />' % to_url(encode, validation)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Run validation (may raise exceptions).
|
91
|
+
#
|
92
|
+
def validate!
|
93
|
+
validate
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Does the chart pass all validations?
|
98
|
+
#
|
99
|
+
def valid?
|
100
|
+
begin
|
101
|
+
validate
|
102
|
+
true
|
103
|
+
rescue ValidationError
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
private # ---------------------------------------------------------------
|
110
|
+
|
111
|
+
##
|
112
|
+
# Chart type URL parameter, for example:
|
113
|
+
#
|
114
|
+
# :bvs # vertical bar
|
115
|
+
# :p # pie
|
116
|
+
# :p3 # 3D pie
|
117
|
+
#
|
118
|
+
# All subclasses *must* implement this method.
|
119
|
+
#
|
120
|
+
def type
|
121
|
+
fail
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Get an array of values to be graphed.
|
126
|
+
# Subclasses *must* implement this method.
|
127
|
+
#
|
128
|
+
def data_values
|
129
|
+
fail
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# The number of data points represented along the x-axis.
|
134
|
+
#
|
135
|
+
def data_values_count
|
136
|
+
data_values.map{ |set| set.size }.max
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Get the minimum Y-value for the chart (from data or explicitly set).
|
141
|
+
#
|
142
|
+
def y_min
|
143
|
+
@y_min || data_values.flatten.compact.min
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Get the maximum Y-value for the chart (from data or explicitly set).
|
148
|
+
#
|
149
|
+
def y_max
|
150
|
+
@y_max || data_values.flatten.compact.max
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Array of names of required attributes.
|
155
|
+
#
|
156
|
+
def required_attrs
|
157
|
+
[
|
158
|
+
:width,
|
159
|
+
:height,
|
160
|
+
:data
|
161
|
+
]
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Array of validations to be run on the chart.
|
166
|
+
#
|
167
|
+
def validations
|
168
|
+
[
|
169
|
+
:required_attrs,
|
170
|
+
:dimensions,
|
171
|
+
:data_format,
|
172
|
+
:labels,
|
173
|
+
:colors,
|
174
|
+
:url_length
|
175
|
+
]
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Make sure chart dimensions are within Google's 300,000 pixel limit.
|
180
|
+
#
|
181
|
+
def validate_dimensions
|
182
|
+
raise DimensionsError unless width * height <= 300000
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Validate data format.
|
187
|
+
# Subclasses *must* implement this method.
|
188
|
+
#
|
189
|
+
def validate_data_format
|
190
|
+
fail
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Make sure labels are specified in proper format
|
195
|
+
# Subclasses *must* implement this method.
|
196
|
+
#
|
197
|
+
def validate_labels
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Make sure colors are valid hex codes.
|
202
|
+
# Subclasses should probably implement this method.
|
203
|
+
#
|
204
|
+
def validate_colors
|
205
|
+
validate_color(background)
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
# --- subclasses should not overwrite anything below this line ----------
|
210
|
+
|
211
|
+
##
|
212
|
+
# The query string for the chart.
|
213
|
+
#
|
214
|
+
def query_string(encode = true)
|
215
|
+
values = query_string_params.map{ |p| format_param(p, encode) }
|
216
|
+
values.compact.join("&")
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# Format a query string parameter for a URL (string: name=value). Uses
|
221
|
+
# %-encoding unless second argument is false.
|
222
|
+
#
|
223
|
+
def format_param(name, encode = true)
|
224
|
+
unless (value = send(name).to_s) == ""
|
225
|
+
value = CGI.escape(value) if encode
|
226
|
+
name.to_s + '=' + value
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Is the data given as a single bare array of values?
|
232
|
+
#
|
233
|
+
def bare_data_set?
|
234
|
+
data.is_a?(Array) and ![Array, Hash].include?(data.first.class)
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# Run all validations on the chart attributes.
|
239
|
+
#
|
240
|
+
def validate
|
241
|
+
validations.each{ |v| send "validate_#{v}" }
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Make sure all required chart attributes are specified.
|
246
|
+
#
|
247
|
+
def validate_required_attrs
|
248
|
+
required_attrs.each do |param|
|
249
|
+
if send(param).nil?
|
250
|
+
raise MissingRequiredAttributeError.new(self, param)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# Make sure encoded URL is no longer than the maximum allowed length.
|
257
|
+
#
|
258
|
+
def validate_url_length
|
259
|
+
raise UrlLengthError unless to_url(true, false).size <= URL_MAX_LENGTH
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Validate a single color (this is not a normal validator).
|
264
|
+
#
|
265
|
+
def validate_color(c)
|
266
|
+
raise ColorFormatError unless (c.nil? or c.match(/^[0-9A-Fa-f]{6}$/))
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
# --- URL parameter list and methods ------------------------------------
|
271
|
+
|
272
|
+
##
|
273
|
+
# Array of names of all possible query string parameters in the order
|
274
|
+
# in which they are output (for easier testing).
|
275
|
+
#
|
276
|
+
def query_string_params
|
277
|
+
[
|
278
|
+
:cht, # type
|
279
|
+
:chs, # size
|
280
|
+
:chd, # data
|
281
|
+
|
282
|
+
:chco, # color
|
283
|
+
:chf, # fill
|
284
|
+
|
285
|
+
:chl, # labels
|
286
|
+
:chxt, # axis_type
|
287
|
+
:chxs, # axis_style
|
288
|
+
:chxl, # axis_labels
|
289
|
+
:chxp, # axis_label_positions
|
290
|
+
:chxr, # axis_range
|
291
|
+
:chma, # margins
|
292
|
+
|
293
|
+
:chbh, # bar_spacing
|
294
|
+
:chp, # bar_chart_zero_line, pie chart rotation
|
295
|
+
|
296
|
+
:chm, # markers
|
297
|
+
|
298
|
+
:chtt, # title
|
299
|
+
:chdl, # legend
|
300
|
+
:chdlp, # legend_position
|
301
|
+
|
302
|
+
:chds # data_scaling -- never used
|
303
|
+
]
|
304
|
+
end
|
305
|
+
|
306
|
+
#
|
307
|
+
# All parameter methods should return a string, or an object that
|
308
|
+
# renders itself as a string via the to_s method.
|
309
|
+
#
|
310
|
+
|
311
|
+
# cht
|
312
|
+
def cht
|
313
|
+
type
|
314
|
+
end
|
315
|
+
|
316
|
+
# chs
|
317
|
+
def chs
|
318
|
+
"#{width}x#{height}"
|
319
|
+
end
|
320
|
+
|
321
|
+
# chd
|
322
|
+
def chd
|
323
|
+
Encoder.encode(data_values, y_min, y_max)
|
324
|
+
end
|
325
|
+
|
326
|
+
# chco
|
327
|
+
def chco
|
328
|
+
data.map{ |d|
|
329
|
+
if d.is_a?(Hash) and c = d[:color]
|
330
|
+
c = [c] unless c.is_a?(Array)
|
331
|
+
c.join('|') # data point delimiter
|
332
|
+
end
|
333
|
+
}.compact.join(',') # data set delimiter
|
334
|
+
end
|
335
|
+
|
336
|
+
# chf
|
337
|
+
def chf
|
338
|
+
"bg,s,#{background}" if background
|
339
|
+
end
|
340
|
+
|
341
|
+
# chl
|
342
|
+
def chl
|
343
|
+
nil
|
344
|
+
end
|
345
|
+
|
346
|
+
# chxt
|
347
|
+
def chxt
|
348
|
+
nil
|
349
|
+
end
|
350
|
+
|
351
|
+
# chxl
|
352
|
+
def chxl
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
|
356
|
+
# chxp
|
357
|
+
def chxp
|
358
|
+
nil
|
359
|
+
end
|
360
|
+
|
361
|
+
# chxr
|
362
|
+
def chxr
|
363
|
+
nil
|
364
|
+
end
|
365
|
+
|
366
|
+
# chxs
|
367
|
+
def chxs
|
368
|
+
nil
|
369
|
+
end
|
370
|
+
|
371
|
+
##
|
372
|
+
# Are legend dimensions specified?
|
373
|
+
#
|
374
|
+
def legend_dimensions_given?
|
375
|
+
legend.is_a?(Hash) and (legend[:width] or legend[:height])
|
376
|
+
end
|
377
|
+
|
378
|
+
# chma
|
379
|
+
def chma
|
380
|
+
return nil unless (margins or legend_dimensions_given?)
|
381
|
+
value = ""
|
382
|
+
if margins.is_a?(Hash)
|
383
|
+
pixels = [:left, :right, :top, :bottom].map{ |i| margins[i] || 0 }
|
384
|
+
value << pixels.join(',')
|
385
|
+
end
|
386
|
+
if legend_dimensions_given?
|
387
|
+
value << "0,0,0,0" if value == ""
|
388
|
+
value << "|#{legend[:width] || 0},#{legend[:height] || 0}"
|
389
|
+
end
|
390
|
+
value
|
391
|
+
end
|
392
|
+
|
393
|
+
# chbh
|
394
|
+
def chbh
|
395
|
+
nil
|
396
|
+
end
|
397
|
+
|
398
|
+
# chp
|
399
|
+
def chp
|
400
|
+
nil
|
401
|
+
end
|
402
|
+
|
403
|
+
# chm
|
404
|
+
def chm
|
405
|
+
nil
|
406
|
+
end
|
407
|
+
|
408
|
+
# chtt
|
409
|
+
def chtt
|
410
|
+
nil
|
411
|
+
end
|
412
|
+
|
413
|
+
# chdl
|
414
|
+
def chdl
|
415
|
+
nil
|
416
|
+
end
|
417
|
+
|
418
|
+
# chdlp
|
419
|
+
def chdlp
|
420
|
+
nil
|
421
|
+
end
|
422
|
+
|
423
|
+
# chds -- never used
|
424
|
+
def chds
|
425
|
+
nil
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|