sparklines 0.2.7 → 0.4.0
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/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
|