sparklines 0.2.7 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README +12 -26
- data/lib/sparklines.rb +448 -421
- data/rakefile +16 -0
- data/test/all_test.rb +89 -44
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
|
2
|
+
0.3.0
|
3
|
+
|
4
|
+
* Changed to a Class for maintainability
|
5
|
+
* All values are normalized (except pie)
|
6
|
+
* A single value can be passed for the pie percentage (instead of an Array)
|
7
|
+
|
2
8
|
0.2.7
|
3
9
|
|
4
10
|
* Fixed bug where last element of bar graph wouldn't go to the bottom [Esad Hajdarevic esad@esse.at]
|
data/README
CHANGED
@@ -1,36 +1,22 @@
|
|
1
|
-
|
2
|
-
** Spark Graph Library for Ruby **
|
3
|
-
**********************************
|
1
|
+
== Sparklines
|
4
2
|
|
5
|
-
|
6
|
-
boss@topfunky.com
|
7
|
-
http://nubyonrails.topfunky.com
|
8
|
-
|
9
|
-
Daniel Nugent
|
10
|
-
nugend@gmail.com
|
11
|
-
|
12
|
-
|
13
|
-
*** What is it? ***
|
3
|
+
A library for generating small sparkline graphs from Ruby. Use it in desktop apps or with Ruby on Rails.
|
14
4
|
|
15
|
-
|
5
|
+
== Other info
|
16
6
|
|
7
|
+
http://nubyonrails.com/pages/sparklines
|
17
8
|
|
18
|
-
|
9
|
+
== Rails plugin
|
19
10
|
|
20
|
-
|
11
|
+
http://topfunky.net/svn/plugins/sparklines
|
21
12
|
|
22
|
-
|
13
|
+
== Authors
|
23
14
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
helper :sparklines
|
28
|
-
|
29
|
-
In your view, call it like this:
|
30
|
-
|
31
|
-
<%= sparklines_tag [1,2,3,4,5,6] %> <!-- Gives you a smooth graph -->
|
15
|
+
Geoffrey Grosenbach
|
16
|
+
boss@topfunky.com
|
17
|
+
http://nubyonrails.com/pages/sparklines
|
32
18
|
|
33
|
-
|
19
|
+
Daniel Nugent
|
20
|
+
nugend@gmail.com
|
34
21
|
|
35
|
-
<%= sparklines_tag [1,2,3,4,5,6], :type => 'discrete', :height => 10, :upper => 80, :above_color => 'green', :below_color => 'blue' %>
|
36
22
|
|
data/lib/sparklines.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
|
2
|
+
require 'rubygems'
|
2
3
|
require 'RMagick'
|
3
4
|
require 'mathn'
|
4
5
|
|
5
6
|
=begin rdoc
|
6
7
|
|
7
|
-
A library
|
8
|
+
A library for generating small unmarked graphs (sparklines).
|
8
9
|
|
9
|
-
Can be used to write to a file or make a web service with Rails or other Ruby CGI apps.
|
10
|
+
Can be used to write an image to a file or make a web service with Rails or other Ruby CGI apps.
|
10
11
|
|
11
12
|
Idea and much of the outline for the source lifted directly from {Joe Gregorio's Python Sparklines web service script}[http://bitworking.org/projects/sparklines].
|
12
13
|
|
@@ -14,28 +15,10 @@ Requires the RMagick image library.
|
|
14
15
|
|
15
16
|
==Authors
|
16
17
|
|
17
|
-
{Dan Nugent}[mailto:nugend@gmail.com]
|
18
|
-
Original port from Python Sparklines library.
|
19
|
-
|
18
|
+
{Dan Nugent}[mailto:nugend@gmail.com] Original port from Python Sparklines library.
|
20
19
|
|
21
20
|
{Geoffrey Grosenbach}[mailto:boss@topfunky.com] -- http://nubyonrails.topfunky.com
|
22
|
-
-- Conversion to module and
|
23
|
-
|
24
|
-
===Tangent regarding RMagick
|
25
|
-
|
26
|
-
The easiest way to use RMagick on Mac OS X is to use darwinports. There are packages for libpng, freetype, and all the other libraries you need.
|
27
|
-
|
28
|
-
I had a heck of a time getting RMagick to work on my system so in the interests of saving other people the trouble here's a little set of instructions on how to get RMagick working properly and with the right image formats.
|
29
|
-
|
30
|
-
1. Install the zlib[http://www.libpng.org/pub/png/libpng.html] library
|
31
|
-
2. With zlib in the same directory as the libpng[http://www.libpng.org/pub/png/libpng.html] library, install libpng
|
32
|
-
3. Option step: Install the {jpeg library}[ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz] (You need it to use jpegs and you might want to have it)
|
33
|
-
4. Install ImageMagick from *source*[http://www.imagemagick.org/script/install-source.php]. RMagick requires the ImageMagick headers, so this is important.
|
34
|
-
5. Install RMagick from source[http://rubyforge.org/projects/rmagick/]. The gem is not reliable.
|
35
|
-
6. Edit Magick-conf if necessary. I had to remove -lcms and -ltiff since I didn't want those to be built and the libraries weren't on my system.
|
36
|
-
|
37
|
-
Please keep in mind that these were only the steps that made RMagick work on my machine. This is a tricky library to get working.
|
38
|
-
Consider using Joe Gregorio's version for Python if the installation proves to be too cumbersome.
|
21
|
+
-- Conversion to module and further maintenance.
|
39
22
|
|
40
23
|
==General Usage and Defaults
|
41
24
|
|
@@ -43,29 +26,28 @@ To use in a script:
|
|
43
26
|
|
44
27
|
require 'rubygems'
|
45
28
|
require 'sparklines'
|
46
|
-
Sparklines.plot([1,25,33,46,89,90,85,77,42],
|
29
|
+
Sparklines.plot([1,25,33,46,89,90,85,77,42],
|
30
|
+
:type => 'discrete',
|
31
|
+
:height => 20)
|
47
32
|
|
48
33
|
An image blob will be returned which you can print, write to STDOUT, etc.
|
49
34
|
|
50
|
-
|
51
|
-
|
52
|
-
* Install the 'sparklines_generator' gem ('gem install sparklines_generator')
|
53
|
-
* Call 'ruby script/generate sparklines'. This will copy the Sparklines controller and helper to your rails directories
|
54
|
-
* Add "require 'sparklines'" to the bottom of your config/environment.rb
|
55
|
-
* Restart your fcgi's or your WEBrick if necessary
|
35
|
+
For use with Ruby on Rails, see the sparklines plugin:
|
56
36
|
|
57
|
-
|
58
|
-
|
59
|
-
helper :sparklines
|
37
|
+
http://nubyonrails.com/pages/sparklines
|
60
38
|
|
61
39
|
In your view, call it like this:
|
62
40
|
|
63
|
-
<%= sparkline_tag [1,2,3,4,5,6] %>
|
41
|
+
<%= sparkline_tag [1,2,3,4,5,6] %>
|
64
42
|
|
65
43
|
Or specify details:
|
66
44
|
|
67
|
-
<%= sparkline_tag [1,2,3,4,5,6],
|
68
|
-
|
45
|
+
<%= sparkline_tag [1,2,3,4,5,6],
|
46
|
+
:type => 'discrete',
|
47
|
+
:height => 10,
|
48
|
+
:upper => 80,
|
49
|
+
:above_color => 'green',
|
50
|
+
:below_color => 'blue' %>
|
69
51
|
|
70
52
|
Graph types:
|
71
53
|
|
@@ -73,413 +55,458 @@ Graph types:
|
|
73
55
|
discrete
|
74
56
|
pie
|
75
57
|
smooth
|
76
|
-
bar
|
58
|
+
bar
|
77
59
|
|
78
60
|
General Defaults:
|
79
61
|
|
80
|
-
:type
|
81
|
-
:height
|
82
|
-
:upper
|
83
|
-
:above_color
|
84
|
-
:below_color
|
85
|
-
:background_color =>
|
86
|
-
:line_color
|
62
|
+
:type => 'smooth'
|
63
|
+
:height => 14px
|
64
|
+
:upper => 50
|
65
|
+
:above_color => 'red'
|
66
|
+
:below_color => 'grey'
|
67
|
+
:background_color => 'white'
|
68
|
+
:line_color => 'lightgrey'
|
87
69
|
|
88
70
|
==License
|
89
71
|
|
90
72
|
Licensed under the MIT license.
|
91
73
|
|
92
74
|
=end
|
75
|
+
class Sparklines
|
76
|
+
|
77
|
+
VERSION = '0.4.0'
|
78
|
+
|
79
|
+
@@label_margin = 5.0
|
80
|
+
@@pointsize = 10.0
|
81
|
+
|
82
|
+
class << self
|
83
|
+
|
84
|
+
# Does the actual plotting of the graph.
|
85
|
+
# Calls the appropriate subclass based on the :type argument.
|
86
|
+
# Defaults to 'smooth'
|
87
|
+
def plot(data=[], options={})
|
88
|
+
defaults = {
|
89
|
+
:type => 'smooth',
|
90
|
+
:height => 14,
|
91
|
+
:upper => 50,
|
92
|
+
:diameter => 20,
|
93
|
+
:step => 2,
|
94
|
+
:line_color => 'lightgrey',
|
95
|
+
|
96
|
+
:above_color => 'red',
|
97
|
+
:below_color => 'grey',
|
98
|
+
:background_color => 'white',
|
99
|
+
:share_color => 'red',
|
100
|
+
:remain_color => 'lightgrey',
|
101
|
+
:min_color => 'blue',
|
102
|
+
:max_color => 'green',
|
103
|
+
:last_color => 'red',
|
104
|
+
|
105
|
+
:has_min => false,
|
106
|
+
:has_max => false,
|
107
|
+
:has_last => false,
|
108
|
+
|
109
|
+
:label => nil
|
110
|
+
}
|
111
|
+
|
112
|
+
# Hack for HashWithIndifferentAccess
|
113
|
+
options_sym = Hash.new
|
114
|
+
options.keys.each do |key|
|
115
|
+
options_sym[key.to_sym] = options[key]
|
116
|
+
end
|
117
|
+
|
118
|
+
options_sym = defaults.merge(options_sym)
|
119
|
+
|
120
|
+
# Call the appropriate method for actual plotting.
|
121
|
+
sparkline = self.new(data, options_sym)
|
122
|
+
if %w(area bar pie smooth discrete).include? options_sym[:type]
|
123
|
+
sparkline.send options_sym[:type]
|
124
|
+
else
|
125
|
+
sparkline.plot_error options_sym
|
126
|
+
end
|
127
|
+
end
|
93
128
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
options_sym = defaults.merge(options_sym)
|
127
|
-
|
128
|
-
# Minimal normalization
|
129
|
-
maximum_value = self.get_max_value(results).to_f
|
130
|
-
|
131
|
-
# Call the appropriate function for actual plotting
|
132
|
-
self.send(options_sym[:type], results, options_sym, maximum_value)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Writes a graph to disk with the specified filename, or "Sparklines.png"
|
136
|
-
def Sparklines.plot_to_file(filename="sparklines.png", results=[], options={})
|
137
|
-
File.open( filename, 'wb' ) do |png|
|
138
|
-
png << self.plot( results, options)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Creates a pie-chart sparkline
|
143
|
-
#
|
144
|
-
# * results - an array of integer values between 0 and 100 inclusive. Only the first integer will be accepted. It will be used to determine the percentage of the pie that is filled by the share_color
|
145
|
-
#
|
146
|
-
# * options - a hash that takes parameters:
|
147
|
-
#
|
148
|
-
# :diameter - An integer that determines what the size of the sparkline will be. Defaults to 20
|
149
|
-
#
|
150
|
-
# :share_color - A string or color code representing the color to draw the share of the pie represented by percent. Defaults to blue.
|
151
|
-
#
|
152
|
-
# :remain_color - A string or color code representing the color to draw the pie not taken by the share color. Defaults to lightgrey.
|
153
|
-
def self.pie(results=[],options={}, maximum_value=100.0)
|
154
|
-
|
155
|
-
diameter = options[:diameter].to_i
|
156
|
-
share_color = options[:share_color]
|
157
|
-
remain_color = options[:remain_color]
|
158
|
-
percent = results[0]
|
159
|
-
|
160
|
-
img = Magick::Image.new(diameter , diameter) {self.background_color = options[:background_color]}
|
161
|
-
img.format = "PNG"
|
162
|
-
draw = Magick::Draw.new
|
163
|
-
|
164
|
-
#Adjust the radius so there's some edge left n the pie
|
165
|
-
r = diameter/2.0 - 2
|
166
|
-
draw.fill(remain_color)
|
167
|
-
draw.ellipse(r + 2, r + 2, r , r , 0, 360)
|
168
|
-
draw.fill(share_color)
|
169
|
-
|
170
|
-
# Special exceptions
|
171
|
-
if percent == 0
|
172
|
-
# For 0% return blank
|
173
|
-
draw.draw(img)
|
174
|
-
return img.to_blob
|
175
|
-
elsif percent == 100
|
176
|
-
# For 100% just draw a full circle
|
177
|
-
draw.ellipse(r + 2, r + 2, r , r , 0, 360)
|
178
|
-
draw.draw(img)
|
179
|
-
return img.to_blob
|
180
|
-
end
|
181
|
-
|
182
|
-
#Okay, this part is as confusing as hell, so pay attention:
|
183
|
-
#This line determines the horizontal portion of the point on the circle where the X-Axis
|
184
|
-
#should end. It's caculated by taking the center of the on-image circle and adding that
|
185
|
-
#to the radius multiplied by the formula for determinig the point on a unit circle that a
|
186
|
-
#angle corresponds to. 3.6 * percent gives us that angle, but it's in degrees, so we need to
|
187
|
-
#convert, hence the muliplication by Pi over 180
|
188
|
-
arc_end_x = r + 2 + (r * Math.cos((3.6 * percent)*(Math::PI/180)))
|
189
|
-
|
190
|
-
#The same goes for here, except it's the vertical point instead of the horizontal one
|
191
|
-
arc_end_y = r + 2 + (r * Math.sin((3.6 * percent)*(Math::PI/180)))
|
192
|
-
|
193
|
-
#Because the SVG path format is seriously screwy, we need to set the large-arc-flag to 1
|
194
|
-
#if the angle of an arc is greater than 180 degrees. I have no idea why this is, but it is.
|
195
|
-
percent > 50? large_arc_flag = 1: large_arc_flag = 0
|
196
|
-
|
197
|
-
#This is also confusing
|
198
|
-
#M tells us to move to an absolute point on the image. We're moving to the center of the pie
|
199
|
-
#h tells us to move to a relative point. We're moving to the right edge of the circle.
|
200
|
-
#A tells us to start an absolute elliptical arc. The first two values are the radii of the ellipse
|
201
|
-
#the third value is the x-axis-rotation (how to rotate the ellipse if we wanted to [could have some fun
|
202
|
-
#with randomizing that maybe), the fourth value is our large-arc-flag, the fifth is the sweep-flag,
|
203
|
-
#(again, confusing), the sixth and seventh values are the end point of the arc which we calculated previously
|
204
|
-
#More info on the SVG path string format at: http://www.w3.org/TR/SVG/paths.html
|
205
|
-
path = "M#{r + 2},#{r + 2} h#{r} A#{r},#{r} 0 #{large_arc_flag},1 #{arc_end_x},#{arc_end_y} z"
|
206
|
-
draw.path(path)
|
207
|
-
|
208
|
-
draw.draw(img)
|
209
|
-
img.to_blob
|
210
|
-
end
|
211
|
-
|
212
|
-
# Creates a discretized sparkline
|
213
|
-
#
|
214
|
-
# * results is an array of integer values between 0 and 100 inclusive
|
215
|
-
#
|
216
|
-
# * options is a hash that takes 4 parameters:
|
217
|
-
#
|
218
|
-
# :height - An integer that determines what the height of the sparkline will be. Defaults to 14
|
219
|
-
#
|
220
|
-
# :upper - An integer that determines the threshold for colorization purposes. Any value less than upper will be colored using below_color, anything above and equal to upper will use above_color. Defaults to 50.
|
221
|
-
#
|
222
|
-
# :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red.
|
223
|
-
#
|
224
|
-
# :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray.
|
225
|
-
def self.discrete(results=[], options = {}, maximum_value=100.0)
|
226
|
-
|
227
|
-
height = options[:height].to_i
|
228
|
-
upper = options[:upper].to_i
|
229
|
-
below_color = options[:below_color]
|
230
|
-
above_color = options[:above_color]
|
231
|
-
|
232
|
-
img = Magick::Image.new(results.size * 2 - 1, height) {self.background_color = options[:background_color]}
|
233
|
-
img.format = "PNG"
|
234
|
-
draw = Magick::Draw.new
|
235
|
-
|
236
|
-
i = 0
|
237
|
-
results.each do |r|
|
238
|
-
color = (r >= upper) ? above_color : below_color
|
239
|
-
draw.stroke(color)
|
240
|
-
draw.line(i, (img.rows - r/(101.0/(height-4))-4).to_i,
|
241
|
-
i, (img.rows - r/(101.0/(height-4))).to_i)
|
242
|
-
|
243
|
-
i += 2
|
244
|
-
end
|
245
|
-
|
246
|
-
draw.draw(img)
|
247
|
-
img.to_blob
|
248
|
-
end
|
249
|
-
|
250
|
-
# Creates a continuous area sparkline
|
251
|
-
#
|
252
|
-
# * results is an array of integer values between 0 and 100 inclusive
|
253
|
-
#
|
254
|
-
# * options is a hash that takes 4 parameters:
|
255
|
-
#
|
256
|
-
# :step - An integer that determines the distance between each point on the sparkline. Defaults to 2.
|
257
|
-
#
|
258
|
-
# :height - An integer that determines what the height of the sparkline will be. Defaults to 14
|
259
|
-
#
|
260
|
-
# :upper - An ineger that determines the threshold for colorization purposes. Any value less than upper will be colored using below_color, anything above and equal to upper will use above_color. Defaults to 50.
|
261
|
-
#
|
262
|
-
# :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaulst to false.
|
263
|
-
#
|
264
|
-
# :has_max - Determines whether a dot will be drawn at the highest value or not. Defaulst to false.
|
265
|
-
#
|
266
|
-
# :has_last - Determines whether a dot will be drawn at the last value or not. Defaulst to false.
|
267
|
-
#
|
268
|
-
# :min_color - A string or color code representing the color that the dot drawn at the smallest value will be displayed as. Defaults to blue.
|
269
|
-
#
|
270
|
-
# :max_color - A string or color code representing the color that the dot drawn at the largest value will be displayed as. Defaults to green.
|
271
|
-
#
|
272
|
-
# :last_color - A string or color code representing the color that the dot drawn at the last value will be displayed as. Defaults to red.
|
273
|
-
#
|
274
|
-
# :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red.
|
275
|
-
#
|
276
|
-
# :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray.
|
277
|
-
def self.area(results=[], options={}, maximum_value=100.0)
|
278
|
-
|
279
|
-
step = options[:step].to_i
|
280
|
-
height = options[:height].to_i
|
281
|
-
upper = options[:upper].to_i
|
282
|
-
|
283
|
-
has_min = options[:has_min]
|
284
|
-
has_max = options[:has_max]
|
285
|
-
has_last = options[:has_last]
|
286
|
-
|
287
|
-
min_color = options[:min_color]
|
288
|
-
max_color = options[:max_color]
|
289
|
-
last_color = options[:last_color]
|
290
|
-
below_color = options[:below_color]
|
291
|
-
above_color = options[:above_color]
|
292
|
-
|
293
|
-
img = Magick::Image.new((results.size - 1) * step + 4, height) {self.background_color = options[:background_color]}
|
294
|
-
img.format = "PNG"
|
295
|
-
draw = Magick::Draw.new
|
296
|
-
|
297
|
-
coords = [[0,(height - 3 - upper/(101.0/(height-4)))]]
|
298
|
-
i=0
|
299
|
-
results.each do |r|
|
300
|
-
coords.push [(2 + i), (height - 3 - r/(101.0/(height-4)))]
|
301
|
-
i += step
|
302
|
-
end
|
303
|
-
coords.push [(results.size - 1) * step + 4, (height - 3 - upper/(101.0/(height-4)))]
|
304
|
-
|
305
|
-
#Block off the bottom half of the image and draw the sparkline
|
306
|
-
draw.fill(above_color)
|
307
|
-
draw.define_clip_path('top') do
|
308
|
-
draw.rectangle(0,0,(results.size - 1) * step + 4,(height - 3 - upper/(101.0/(height-4))))
|
309
|
-
end
|
310
|
-
draw.clip_path('top')
|
311
|
-
draw.polygon *coords.flatten
|
312
|
-
|
313
|
-
#Block off the top half of the image and draw the sparkline
|
314
|
-
draw.fill(below_color)
|
315
|
-
draw.define_clip_path('bottom') do
|
316
|
-
draw.rectangle(0,(height - 3 - upper/(101.0/(height-4))),(results.size - 1) * step + 4,height)
|
317
|
-
end
|
318
|
-
draw.clip_path('bottom')
|
319
|
-
draw.polygon *coords.flatten
|
320
|
-
|
321
|
-
#The sparkline looks kinda nasty if either the above_color or below_color gets the center line
|
322
|
-
draw.fill('black')
|
323
|
-
draw.line(0,(height - 3 - upper/(101.0/(height-4))),(results.size - 1) * step + 4,(height - 3 - upper/(101.0/(height-4))))
|
324
|
-
|
325
|
-
#After the parts have been masked, we need to let the whole canvas be drawable again
|
326
|
-
#so a max dot can be displayed
|
327
|
-
draw.define_clip_path('all') do
|
328
|
-
draw.rectangle(0,0,img.columns,img.rows)
|
329
|
-
end
|
330
|
-
draw.clip_path('all')
|
331
|
-
if has_min == 'true'
|
332
|
-
min_pt = coords[results.index(results.min)+1]
|
333
|
-
draw.fill(min_color)
|
334
|
-
draw.rectangle(min_pt[0]-1, min_pt[1]-1, min_pt[0]+1, min_pt[1]+1)
|
335
|
-
end
|
336
|
-
if has_max == 'true'
|
337
|
-
max_pt = coords[results.index(results.max)+1]
|
338
|
-
draw.fill(max_color)
|
339
|
-
draw.rectangle(max_pt[0]-1, max_pt[1]-1, max_pt[0]+1, max_pt[1]+1)
|
340
|
-
end
|
341
|
-
if has_last == 'true'
|
342
|
-
last_pt = coords[-2]
|
343
|
-
draw.fill(last_color)
|
344
|
-
draw.rectangle(last_pt[0]-1, last_pt[1]-1, last_pt[0]+1, last_pt[1]+1)
|
345
|
-
end
|
346
|
-
|
347
|
-
draw.draw(img)
|
348
|
-
img.to_blob
|
349
|
-
end
|
350
|
-
|
351
|
-
# Creates a smooth sparkline
|
352
|
-
#
|
353
|
-
# * results - an array of integer values between 0 and 100 inclusive
|
354
|
-
#
|
355
|
-
# * options - a hash that takes these optional parameters:
|
356
|
-
#
|
357
|
-
# :step - An integer that determines the distance between each point on the sparkline. Defaults to 2.
|
358
|
-
#
|
359
|
-
# :height - An integer that determines what the height of the sparkline will be. Defaults to 14
|
360
|
-
#
|
361
|
-
# :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaulst to false.
|
362
|
-
#
|
363
|
-
# :has_max - Determines whether a dot will be drawn at the highest value or not. Defaulst to false.
|
364
|
-
#
|
365
|
-
# :has_last - Determines whether a dot will be drawn at the last value or not. Defaulst to false.
|
366
|
-
#
|
367
|
-
# :min_color - A string or color code representing the color that the dot drawn at the smallest value will be displayed as. Defaults to blue.
|
368
|
-
#
|
369
|
-
# :max_color - A string or color code representing the color that the dot drawn at the largest value will be displayed as. Defaults to green.
|
370
|
-
#
|
371
|
-
# :last_color - A string or color code representing the color that the dot drawn at the last value will be displayed as. Defaults to red.
|
372
|
-
def self.smooth(results, options, maximum_value=100.0)
|
373
|
-
|
374
|
-
step = options[:step].to_i
|
375
|
-
height = options[:height].to_i
|
376
|
-
min_color = options[:min_color]
|
377
|
-
max_color = options[:max_color]
|
378
|
-
last_color = options[:last_color]
|
379
|
-
has_min = options[:has_min]
|
380
|
-
has_max = options[:has_max]
|
381
|
-
has_last = options[:has_last]
|
382
|
-
line_color = options[:line_color]
|
383
|
-
|
384
|
-
img = Magick::Image.new((results.size - 1) * step + 4, height.to_i) {self.background_color = options[:background_color]}
|
385
|
-
img.format = "PNG"
|
386
|
-
draw = Magick::Draw.new
|
387
|
-
|
388
|
-
draw.stroke(line_color)
|
389
|
-
coords = []
|
390
|
-
i=0
|
391
|
-
results.each do |r|
|
392
|
-
coords.push [ 2 + i, (height - 3 - r/(101.0/(height-4))) ]
|
393
|
-
i += step
|
394
|
-
end
|
395
|
-
|
396
|
-
my_polyline(draw, coords)
|
397
|
-
|
398
|
-
if has_min == true
|
399
|
-
min_pt = coords[results.index(results.min)]
|
400
|
-
draw.fill(min_color)
|
401
|
-
draw.rectangle(min_pt[0]-2, min_pt[1]-2, min_pt[0]+2, min_pt[1]+2)
|
402
|
-
end
|
403
|
-
if has_max == true
|
404
|
-
max_pt = coords[results.index(results.max)]
|
405
|
-
draw.fill(max_color)
|
406
|
-
draw.rectangle(max_pt[0]-2, max_pt[1]-2, max_pt[0]+2, max_pt[1]+2)
|
407
|
-
end
|
408
|
-
if has_last == true
|
409
|
-
last_pt = coords[-1]
|
410
|
-
draw.fill(last_color)
|
411
|
-
draw.rectangle(last_pt[0]-2, last_pt[1]-2, last_pt[0]+2, last_pt[1]+2)
|
412
|
-
end
|
413
|
-
|
414
|
-
draw.draw(img)
|
415
|
-
img.to_blob
|
416
|
-
end
|
417
|
-
|
418
|
-
|
419
|
-
# This is a function to replace the RMagick polyline function because it doesn't seem to work properly.
|
420
|
-
#
|
421
|
-
# * draw - a RMagick::Draw object.
|
422
|
-
#
|
423
|
-
# * arr - an array of points (represented as two element arrays)
|
424
|
-
def self.my_polyline (draw, arr)
|
425
|
-
i = 0
|
426
|
-
while i < arr.size - 1
|
427
|
-
draw.line(arr[i][0], arr[i][1], arr[i+1][0], arr[i+1][1])
|
428
|
-
i += 1
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
# Draw the error Sparkline. Not implemented yet.
|
433
|
-
def self.plot_error(options={})
|
434
|
-
img = Magick::Image.new(40,15) {self.background_color = options[:background_color]}
|
435
|
-
img.format = "PNG"
|
436
|
-
draw = Magick::Draw.new
|
437
|
-
draw.fill('red')
|
438
|
-
draw.line(0,0,40,15)
|
439
|
-
draw.line(0,15,40,0)
|
440
|
-
draw.draw(img)
|
441
|
-
|
442
|
-
img.to_blob
|
443
|
-
end
|
444
|
-
|
445
|
-
|
446
|
-
# Draws a bar graph, normalized.
|
129
|
+
# Writes a graph to disk with the specified filename, or "sparklines.png"
|
130
|
+
def plot_to_file(filename="sparklines.png", data=[], options={})
|
131
|
+
File.open( filename, 'wb' ) do |png|
|
132
|
+
png << self.plot( data, options)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
def initialize(data=[], options={})
|
139
|
+
@data = Array(data)
|
140
|
+
@options = options
|
141
|
+
normalize_data
|
142
|
+
end
|
143
|
+
|
144
|
+
# Creates a continuous area sparkline. Relevant options.
|
145
|
+
#
|
146
|
+
# :step - An integer that determines the distance between each point on the sparkline. Defaults to 2.
|
147
|
+
#
|
148
|
+
# :height - An integer that determines what the height of the sparkline will be. Defaults to 14
|
149
|
+
#
|
150
|
+
# :upper - An integer that determines the threshold for colorization purposes. Any value less than upper will be colored using below_color, anything above and equal to upper will use above_color. Defaults to 50.
|
151
|
+
#
|
152
|
+
# :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaults to false.
|
153
|
+
#
|
154
|
+
# :has_max - Determines whether a dot will be drawn at the highest value or not. Defaults to false.
|
155
|
+
#
|
156
|
+
# :has_last - Determines whether a dot will be drawn at the last value or not. Defaults to false.
|
157
|
+
#
|
158
|
+
# :min_color - A string or color code representing the color that the dot drawn at the smallest value will be displayed as. Defaults to blue.
|
159
|
+
#
|
160
|
+
# :max_color - A string or color code representing the color that the dot drawn at the largest value will be displayed as. Defaults to green.
|
447
161
|
#
|
448
|
-
#
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
162
|
+
# :last_color - A string or color code representing the color that the dot drawn at the last value will be displayed as. Defaults to red.
|
163
|
+
#
|
164
|
+
# :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red.
|
165
|
+
#
|
166
|
+
# :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray.
|
167
|
+
def area
|
168
|
+
|
169
|
+
step = @options[:step].to_i
|
170
|
+
height = @options[:height].to_i
|
171
|
+
background_color = @options[:background_color]
|
172
|
+
|
173
|
+
create_canvas((@norm_data.size - 1) * step + 4, height, background_color)
|
174
|
+
|
175
|
+
upper = @options[:upper].to_i
|
176
|
+
|
177
|
+
has_min = @options[:has_min]
|
178
|
+
has_max = @options[:has_max]
|
179
|
+
has_last = @options[:has_last]
|
180
|
+
|
181
|
+
min_color = @options[:min_color]
|
182
|
+
max_color = @options[:max_color]
|
183
|
+
last_color = @options[:last_color]
|
184
|
+
below_color = @options[:below_color]
|
185
|
+
above_color = @options[:above_color]
|
186
|
+
|
187
|
+
|
188
|
+
coords = [[0,(height - 3 - upper/(101.0/(height-4)))]]
|
189
|
+
i=0
|
190
|
+
@norm_data.each do |r|
|
191
|
+
coords.push [(2 + i), (height - 3 - r/(101.0/(height-4)))]
|
192
|
+
i += step
|
193
|
+
end
|
194
|
+
coords.push [(@norm_data.size - 1) * step + 4, (height - 3 - upper/(101.0/(height-4)))]
|
195
|
+
|
196
|
+
# TODO Refactor! Should take a block and do both.
|
197
|
+
#
|
198
|
+
# Block off the bottom half of the image and draw the sparkline
|
199
|
+
@draw.fill(above_color)
|
200
|
+
@draw.define_clip_path('top') do
|
201
|
+
@draw.rectangle(0,0,(@norm_data.size - 1) * step + 4,(height - 3 - upper/(101.0/(height-4))))
|
202
|
+
end
|
203
|
+
@draw.clip_path('top')
|
204
|
+
@draw.polygon *coords.flatten
|
205
|
+
|
206
|
+
# Block off the top half of the image and draw the sparkline
|
207
|
+
@draw.fill(below_color)
|
208
|
+
@draw.define_clip_path('bottom') do
|
209
|
+
@draw.rectangle(0,(height - 3 - upper/(101.0/(height-4))),(@norm_data.size - 1) * step + 4,height)
|
210
|
+
end
|
211
|
+
@draw.clip_path('bottom')
|
212
|
+
@draw.polygon *coords.flatten
|
213
|
+
|
214
|
+
# The sparkline looks kinda nasty if either the above_color or below_color gets the center line
|
215
|
+
@draw.fill('black')
|
216
|
+
@draw.line(0,(height - 3 - upper/(101.0/(height-4))),(@norm_data.size - 1) * step + 4,(height - 3 - upper/(101.0/(height-4))))
|
217
|
+
|
218
|
+
# After the parts have been masked, we need to let the whole canvas be drawable again
|
219
|
+
# so a max dot can be displayed
|
220
|
+
@draw.define_clip_path('all') do
|
221
|
+
@draw.rectangle(0,0,@canvas.columns,@canvas.rows)
|
222
|
+
end
|
223
|
+
@draw.clip_path('all')
|
224
|
+
|
225
|
+
drawbox(coords[@norm_data.index(@norm_data.min)+1], 1, min_color) if has_min == true
|
226
|
+
drawbox(coords[@norm_data.index(@norm_data.max)+1], 1, max_color) if has_max == true
|
227
|
+
|
228
|
+
drawbox(coords[-2], 1, last_color) if has_last == true
|
229
|
+
|
230
|
+
@draw.draw(@canvas)
|
231
|
+
@canvas.to_blob
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# Draws a bar graph.
|
236
|
+
#
|
237
|
+
def bar
|
238
|
+
step = @options[:step].to_i
|
239
|
+
height = @options[:height].to_f
|
240
|
+
background_color = @options[:background_color]
|
241
|
+
|
242
|
+
create_canvas(@norm_data.length * step + 2, height, background_color)
|
243
|
+
|
244
|
+
upper = @options[:upper].to_i
|
245
|
+
below_color = @options[:below_color]
|
246
|
+
above_color = @options[:above_color]
|
461
247
|
|
462
248
|
i = 1
|
463
|
-
|
249
|
+
@norm_data.each_with_index do |r, index|
|
464
250
|
color = (r >= upper) ? above_color : below_color
|
465
|
-
draw.stroke('transparent')
|
466
|
-
draw.fill(color)
|
467
|
-
draw.rectangle( i,
|
468
|
-
|
251
|
+
@draw.stroke('transparent')
|
252
|
+
@draw.fill(color)
|
253
|
+
@draw.rectangle( i, @canvas.rows,
|
254
|
+
i + step - 2, @canvas.rows - ( (r / @maximum_value) * @canvas.rows) )
|
255
|
+
i += step
|
256
|
+
end
|
257
|
+
|
258
|
+
@draw.draw(@canvas)
|
259
|
+
@canvas.to_blob
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
# Creates a discretized sparkline
|
264
|
+
#
|
265
|
+
# :height - An integer that determines what the height of the sparkline will be. Defaults to 14
|
266
|
+
#
|
267
|
+
# :upper - An integer that determines the threshold for colorization purposes. Any value less than upper will be colored using below_color, anything above and equal to upper will use above_color. Defaults to 50.
|
268
|
+
#
|
269
|
+
# :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red.
|
270
|
+
#
|
271
|
+
# :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray.
|
272
|
+
def discrete
|
273
|
+
|
274
|
+
height = @options[:height].to_i
|
275
|
+
upper = @options[:upper].to_i
|
276
|
+
background_color = @options[:background_color]
|
277
|
+
step = @options[:step].to_i
|
278
|
+
|
279
|
+
create_canvas(@norm_data.size * step - 1, height, background_color)
|
280
|
+
|
281
|
+
below_color = @options[:below_color]
|
282
|
+
above_color = @options[:above_color]
|
283
|
+
|
284
|
+
i = 0
|
285
|
+
@norm_data.each do |r|
|
286
|
+
color = (r >= upper) ? above_color : below_color
|
287
|
+
@draw.stroke(color)
|
288
|
+
@draw.line(i, (@canvas.rows - r/(101.0/(height-4))-4).to_i,
|
289
|
+
i, (@canvas.rows - r/(101.0/(height-4))).to_i)
|
290
|
+
i += step
|
291
|
+
end
|
292
|
+
|
293
|
+
@draw.draw(@canvas)
|
294
|
+
@canvas.to_blob
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
# Creates a pie-chart sparkline
|
299
|
+
#
|
300
|
+
# :diameter - An integer that determines what the size of the sparkline will be. Defaults to 20
|
301
|
+
#
|
302
|
+
# :share_color - A string or color code representing the color to draw the share of the pie represented by percent. Defaults to red.
|
303
|
+
#
|
304
|
+
# :remain_color - A string or color code representing the color to draw the pie not taken by the share color. Defaults to lightgrey.
|
305
|
+
def pie
|
306
|
+
|
307
|
+
diameter = @options[:diameter].to_i
|
308
|
+
background_color = @options[:background_color]
|
309
|
+
|
310
|
+
create_canvas(diameter, diameter, background_color)
|
311
|
+
|
312
|
+
share_color = @options[:share_color]
|
313
|
+
remain_color = @options[:remain_color]
|
314
|
+
percent = @norm_data[0]
|
315
|
+
|
316
|
+
# Adjust the radius so there's some edge left in the pie
|
317
|
+
r = diameter/2.0 - 2
|
318
|
+
@draw.fill(remain_color)
|
319
|
+
@draw.ellipse(r + 2, r + 2, r , r , 0, 360)
|
320
|
+
@draw.fill(share_color)
|
321
|
+
|
322
|
+
# Special exceptions
|
323
|
+
if percent == 0
|
324
|
+
# For 0% return blank
|
325
|
+
@draw.draw(@canvas)
|
326
|
+
return @canvas.to_blob
|
327
|
+
elsif percent == 100
|
328
|
+
# For 100% just draw a full circle
|
329
|
+
@draw.ellipse(r + 2, r + 2, r , r , 0, 360)
|
330
|
+
@draw.draw(@canvas)
|
331
|
+
return @canvas.to_blob
|
332
|
+
end
|
333
|
+
|
334
|
+
# Okay, this part is as confusing as hell, so pay attention:
|
335
|
+
# This line determines the horizontal portion of the point on the circle where the X-Axis
|
336
|
+
# should end. It's caculated by taking the center of the on-image circle and adding that
|
337
|
+
# to the radius multiplied by the formula for determinig the point on a unit circle that a
|
338
|
+
# angle corresponds to. 3.6 * percent gives us that angle, but it's in degrees, so we need to
|
339
|
+
# convert, hence the muliplication by Pi over 180
|
340
|
+
arc_end_x = r + 2 + (r * Math.cos((3.6 * percent)*(Math::PI/180)))
|
341
|
+
|
342
|
+
# The same goes for here, except it's the vertical point instead of the horizontal one
|
343
|
+
arc_end_y = r + 2 + (r * Math.sin((3.6 * percent)*(Math::PI/180)))
|
344
|
+
|
345
|
+
# Because the SVG path format is seriously screwy, we need to set the large-arc-flag to 1
|
346
|
+
# if the angle of an arc is greater than 180 degrees. I have no idea why this is, but it is.
|
347
|
+
percent > 50? large_arc_flag = 1: large_arc_flag = 0
|
348
|
+
|
349
|
+
# This is also confusing
|
350
|
+
# M tells us to move to an absolute point on the image. We're moving to the center of the pie
|
351
|
+
# h tells us to move to a relative point. We're moving to the right edge of the circle.
|
352
|
+
# A tells us to start an absolute elliptical arc. The first two values are the radii of the ellipse
|
353
|
+
# the third value is the x-axis-rotation (how to rotate the ellipse if we wanted to [could have some fun
|
354
|
+
# with randomizing that maybe), the fourth value is our large-arc-flag, the fifth is the sweep-flag,
|
355
|
+
# (again, confusing), the sixth and seventh values are the end point of the arc which we calculated previously
|
356
|
+
# More info on the SVG path string format at: http://www.w3.org/TR/SVG/paths.html
|
357
|
+
path = "M#{r + 2},#{r + 2} h#{r} A#{r},#{r} 0 #{large_arc_flag},1 #{arc_end_x},#{arc_end_y} z"
|
358
|
+
@draw.path(path)
|
359
|
+
|
360
|
+
@draw.draw(@canvas)
|
361
|
+
@canvas.to_blob
|
362
|
+
end
|
363
|
+
|
364
|
+
|
365
|
+
# Creates a smooth sparkline.
|
366
|
+
#
|
367
|
+
# :step - An integer that determines the distance between each point on the sparkline. Defaults to 2.
|
368
|
+
#
|
369
|
+
# :height - An integer that determines what the height of the sparkline will be. Defaults to 14
|
370
|
+
#
|
371
|
+
# :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaults to false.
|
372
|
+
#
|
373
|
+
# :has_max - Determines whether a dot will be drawn at the highest value or not. Defaults to false.
|
374
|
+
#
|
375
|
+
# :has_last - Determines whether a dot will be drawn at the last value or not. Defaults to false.
|
376
|
+
#
|
377
|
+
# :min_color - A string or color code representing the color that the dot drawn at the smallest value will be displayed as. Defaults to blue.
|
378
|
+
#
|
379
|
+
# :max_color - A string or color code representing the color that the dot drawn at the largest value will be displayed as. Defaults to green.
|
380
|
+
#
|
381
|
+
# :last_color - A string or color code representing the color that the dot drawn at the last value will be displayed as. Defaults to red.
|
382
|
+
def smooth
|
383
|
+
|
384
|
+
step = @options[:step].to_i
|
385
|
+
height = @options[:height].to_i
|
386
|
+
background_color = @options[:background_color]
|
387
|
+
|
388
|
+
create_canvas((@norm_data.size - 1) * step + 4, height, background_color)
|
389
|
+
|
390
|
+
min_color = @options[:min_color]
|
391
|
+
max_color = @options[:max_color]
|
392
|
+
last_color = @options[:last_color]
|
393
|
+
has_min = @options[:has_min]
|
394
|
+
has_max = @options[:has_max]
|
395
|
+
has_last = @options[:has_last]
|
396
|
+
line_color = @options[:line_color]
|
397
|
+
|
398
|
+
@draw.stroke(line_color)
|
399
|
+
coords = []
|
400
|
+
i=0
|
401
|
+
@norm_data.each do |r|
|
402
|
+
coords.push [ 2 + i, (height - 3 - r/(101.0/(height-4))) ]
|
469
403
|
i += step
|
470
404
|
end
|
405
|
+
|
406
|
+
open_ended_polyline(coords)
|
407
|
+
|
408
|
+
drawbox(coords[@norm_data.index(@norm_data.min)], 2, min_color) if has_min == true
|
409
|
+
|
410
|
+
drawbox(coords[@norm_data.index(@norm_data.max)], 2, max_color) if has_max == true
|
411
|
+
|
412
|
+
drawbox(coords[-1], 2, last_color) if has_last == true
|
413
|
+
|
414
|
+
@draw.draw(@canvas)
|
415
|
+
@canvas.to_blob
|
416
|
+
end
|
417
|
+
|
418
|
+
|
419
|
+
# Draw the error Sparkline.
|
420
|
+
def plot_error(options={})
|
421
|
+
create_canvas(40, 15, 'white')
|
422
|
+
|
423
|
+
@draw.fill('red')
|
424
|
+
@draw.line(0,0,40,15)
|
425
|
+
@draw.line(0,15,40,0)
|
426
|
+
|
427
|
+
@draw.draw(@canvas)
|
428
|
+
@canvas.to_blob
|
429
|
+
end
|
471
430
|
|
472
|
-
|
473
|
-
|
431
|
+
private
|
432
|
+
|
433
|
+
def normalize_data
|
434
|
+
@maximum_value = @data.max
|
435
|
+
if @options[:type].to_s == 'pie'
|
436
|
+
@norm_data = @data
|
437
|
+
else
|
438
|
+
@norm_data = @data.map { |value| value = (value.to_f / @maximum_value) * 100.0 }
|
439
|
+
end
|
474
440
|
end
|
475
441
|
|
442
|
+
# * arr - an array of points (represented as two element arrays)
|
443
|
+
def open_ended_polyline(arr)
|
444
|
+
0.upto(arr.length - 2) { |i|
|
445
|
+
@draw.line(arr[i][0], arr[i][1], arr[i+1][0], arr[i+1][1])
|
446
|
+
}
|
447
|
+
end
|
476
448
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
449
|
+
# Create an image to draw on and a drawable to do the drawing with.
|
450
|
+
#
|
451
|
+
# TODO Refactor into smaller functions
|
452
|
+
def create_canvas(w, h, bkg_col)
|
453
|
+
@draw = Magick::Draw.new
|
454
|
+
@draw.pointsize = @@pointsize # TODO Use height
|
455
|
+
@canvas = Magick::Image.new(w , h) { self.background_color = bkg_col }
|
456
|
+
|
457
|
+
# Make room for label and last value
|
458
|
+
if !@options[:label].nil?
|
459
|
+
@options[:has_last] = true
|
460
|
+
@label_width = calculate_width(@options[:label])
|
461
|
+
@data_last_width = calculate_width(@data.last)
|
462
|
+
# HACK The 7.0 is a severe hack. Must figure out correct spacing
|
463
|
+
@label_and_data_last_width = @label_width + @data_last_width + @@label_margin * 7.0
|
464
|
+
w += @label_and_data_last_width
|
465
|
+
end
|
466
|
+
|
467
|
+
@canvas = Magick::Image.new(w , h) { self.background_color = bkg_col }
|
468
|
+
@canvas.format = "PNG"
|
469
|
+
|
470
|
+
# Draw label and last value
|
471
|
+
if !@options[:label].nil?
|
472
|
+
if ENV.has_key?('MAGICK_FONT_PATH')
|
473
|
+
vera_font_path = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
|
474
|
+
@font = File.exists?(vera_font_path) ? vera_font_path : nil
|
475
|
+
else
|
476
|
+
@font = nil
|
477
|
+
end
|
478
|
+
|
479
|
+
@draw.fill = 'black'
|
480
|
+
@draw.font = @font if @font
|
481
|
+
@draw.gravity = Magick::WestGravity
|
482
|
+
@draw.annotate( @canvas,
|
483
|
+
@label_width, 1.0,
|
484
|
+
w - @label_and_data_last_width + @@label_margin, h - calculate_caps_height/2.0,
|
485
|
+
@options[:label])
|
486
|
+
|
487
|
+
@draw.fill = 'red'
|
488
|
+
@draw.annotate( @canvas,
|
489
|
+
@data_last_width, 1.0,
|
490
|
+
w - @data_last_width - @@label_margin * 2.0, h - calculate_caps_height/2.0,
|
491
|
+
@data.last.to_s)
|
481
492
|
end
|
482
|
-
return max_value
|
483
493
|
end
|
484
494
|
|
495
|
+
# Utility to draw a coloured box
|
496
|
+
# Centred on pt, offset off in each direction, fill color is col
|
497
|
+
def drawbox(pt, offset, color)
|
498
|
+
@draw.stroke 'transparent'
|
499
|
+
@draw.fill(color)
|
500
|
+
@draw.rectangle(pt[0]-offset, pt[1]-offset, pt[0]+offset, pt[1]+offset)
|
501
|
+
end
|
502
|
+
|
503
|
+
def calculate_width(text)
|
504
|
+
@draw.get_type_metrics(@canvas, text.to_s).width
|
505
|
+
end
|
506
|
+
|
507
|
+
def calculate_caps_height
|
508
|
+
@draw.get_type_metrics(@canvas, 'X').height
|
509
|
+
end
|
510
|
+
|
511
|
+
|
485
512
|
end
|
data/rakefile
CHANGED
@@ -27,6 +27,17 @@ task :clean do
|
|
27
27
|
rm_rf "pkg"
|
28
28
|
end
|
29
29
|
|
30
|
+
# Build a graphic file by running a single test.
|
31
|
+
#
|
32
|
+
# rake bar_extreme_values.png
|
33
|
+
# => Runs test_extreme_values
|
34
|
+
|
35
|
+
rule ".png" do |t|
|
36
|
+
test_name = t.name.gsub(/\.png/, '')
|
37
|
+
Rake::Task[:clean].invoke
|
38
|
+
sh "ruby -Ilib:test test/all_test.rb -n /^test_#{test_name}/"
|
39
|
+
end
|
40
|
+
|
30
41
|
# Run the unit tests
|
31
42
|
Rake::TestTask.new { |t|
|
32
43
|
t.libs << "test"
|
@@ -76,6 +87,11 @@ Rake::GemPackageTask.new(spec) do |p|
|
|
76
87
|
p.need_zip = true
|
77
88
|
end
|
78
89
|
|
90
|
+
desc "Hackish copy to sparklines plugin for Rails"
|
91
|
+
task :update_plugin do
|
92
|
+
cp "lib/sparklines.rb", "../plugins/sparklines/lib/sparklines.rb"
|
93
|
+
end
|
94
|
+
|
79
95
|
desc "Publish the API documentation"
|
80
96
|
task :pgem => [:package] do
|
81
97
|
Rake::SshFilePublisher.new("boss@topfunky.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
data/test/all_test.rb
CHANGED
@@ -6,71 +6,116 @@ require 'lib/sparklines'
|
|
6
6
|
class SparklinesTest < Test::Unit::TestCase
|
7
7
|
|
8
8
|
def setup
|
9
|
-
@
|
9
|
+
@output_dir = "test/output"
|
10
|
+
@data = %w( 1 5 15 20 30 50 57 58 55 48
|
11
|
+
44 43 42 42 46 48 49 53 55 59
|
12
|
+
60 65 75 90 105 106 107 110 115 120
|
13
|
+
115 120 130 140 150 160 170 100 100 10).map {|i| i.to_f}
|
10
14
|
end
|
11
15
|
|
12
16
|
def test_each_graph
|
13
17
|
%w{pie area discrete smooth bar}.each do |type|
|
14
|
-
|
18
|
+
quick_graph("#{type}", :type => type)
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
18
|
-
def
|
22
|
+
def test_each_graph_with_label
|
23
|
+
%w{pie area discrete smooth bar}.each do |type|
|
24
|
+
quick_graph("labeled_#{type}", :type => type, :label => 'Comments for you')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_pie
|
19
29
|
# Test extremes which previously did not work right
|
20
30
|
[0, 1, 45, 95, 99, 100].each do |value|
|
21
|
-
Sparklines.plot_to_file("
|
22
|
-
|
31
|
+
Sparklines.plot_to_file("#{@output_dir}/pie#{value}.png",
|
32
|
+
value,
|
33
|
+
:type => 'pie',
|
34
|
+
:diameter => 128)
|
35
|
+
end
|
36
|
+
Sparklines.plot_to_file("#{@output_dir}/pie_flat.png",
|
37
|
+
[60],
|
38
|
+
:type => 'pie')
|
23
39
|
end
|
24
40
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
def test_special_conditions
|
42
|
+
tests = { 'smooth_colored' => {
|
43
|
+
:type => 'smooth',
|
44
|
+
:line_color => 'purple'
|
45
|
+
},
|
46
|
+
'pie_large' => {
|
47
|
+
:type => 'pie',
|
48
|
+
:diameter => 200
|
49
|
+
},
|
50
|
+
'area_high' => {
|
51
|
+
:type => 'area',
|
52
|
+
:upper => 80,
|
53
|
+
:step => 4,
|
54
|
+
:height => 20
|
55
|
+
},
|
56
|
+
'discrete_wide' => {
|
57
|
+
:type => 'discrete',
|
58
|
+
:step => 8
|
59
|
+
},
|
60
|
+
'bar_wide' => {
|
61
|
+
:type => 'bar',
|
62
|
+
:step => 8
|
63
|
+
},
|
64
|
+
'bar_tall' => {
|
65
|
+
:type => 'bar',
|
66
|
+
:below_color => 'blue',
|
67
|
+
:above_color => 'red',
|
68
|
+
:upper => 90,
|
69
|
+
:height => 50,
|
70
|
+
:step => 8
|
71
|
+
}
|
37
72
|
}
|
38
|
-
tests.
|
39
|
-
|
73
|
+
tests.each do |name, options|
|
74
|
+
quick_graph(name, options)
|
40
75
|
end
|
41
76
|
end
|
42
77
|
|
43
|
-
|
44
|
-
|
78
|
+
# def test_smooth_graph
|
79
|
+
#
|
80
|
+
# end
|
81
|
+
|
82
|
+
def test_bar_extreme_values
|
83
|
+
Sparklines.plot_to_file("#{@output_dir}/bar_extreme_values.png",
|
84
|
+
[0,1,100,2,99,3,98,4,97,5,96,6,95,7,94,8,93,9,92,10,91],
|
85
|
+
:type => 'bar',
|
86
|
+
:below_color => 'blue',
|
87
|
+
:above_color => 'red',
|
88
|
+
:upper => 90,
|
89
|
+
:step => 4 )
|
45
90
|
end
|
46
91
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
92
|
+
def test_string_args
|
93
|
+
quick_graph("bar_string.png",
|
94
|
+
'type' => 'bar',
|
95
|
+
'below_color' => 'blue',
|
96
|
+
'above_color' => 'red',
|
97
|
+
'upper' => 50,
|
98
|
+
'height' => 50,
|
99
|
+
'step' => 8 )
|
55
100
|
end
|
56
101
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
:step => 4 )
|
102
|
+
def test_area_min_max
|
103
|
+
quick_graph("area_min_max",
|
104
|
+
:has_min => true,
|
105
|
+
:has_max => true,
|
106
|
+
:has_first => true,
|
107
|
+
:has_last => true)
|
64
108
|
end
|
65
109
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
110
|
+
|
111
|
+
def test_no_type
|
112
|
+
Sparklines.plot_to_file("#{@output_dir}/error.png", 0, :type => 'nonexistent')
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def quick_graph(name, options)
|
118
|
+
Sparklines.plot_to_file("#{@output_dir}/#{name}.png", @data, options)
|
74
119
|
end
|
75
120
|
|
76
121
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: sparklines
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.4.0
|
7
|
+
date: 2006-07-31 00:00:00 -07:00
|
8
8
|
summary: Tiny graphs for concise data.
|
9
9
|
require_paths:
|
10
10
|
- lib
|