├── .gitattributes
├── .gitignore
├── samples
├── area.png
├── pie.png
├── pie0.png
├── pie1.png
├── pie100.png
├── pie45.png
├── pie95.png
├── pie99.png
├── smooth.png
├── area-high.png
├── discrete.png
├── pie-large.png
└── smooth-colored.png
├── test
├── expected
│ ├── bar.png
│ ├── pie.png
│ ├── area.png
│ ├── error.png
│ ├── pie0.png
│ ├── pie1.png
│ ├── pie100.png
│ ├── pie45.png
│ ├── pie95.png
│ ├── pie99.png
│ ├── smooth.png
│ ├── zeros.png
│ ├── bar_tall.png
│ ├── bar_wide.png
│ ├── discrete.png
│ ├── nil_area.png
│ ├── nil_bar.png
│ ├── nil_pie.png
│ ├── pie_flat.png
│ ├── whisker.png
│ ├── area_high.png
│ ├── bullet_tall.png
│ ├── bullet_wide.png
│ ├── labeled_bar.png
│ ├── labeled_pie.png
│ ├── nil_smooth.png
│ ├── pie_large.png
│ ├── area_min_max.png
│ ├── bullet_basic.png
│ ├── discrete_max.png
│ ├── discrete_min.png
│ ├── discrete_wide.png
│ ├── labeled_area.png
│ ├── nil_discrete.png
│ ├── whisker_junk.png
│ ├── bar_string.png.png
│ ├── bullet_colorful.png
│ ├── labeled_discrete.png
│ ├── labeled_smooth.png
│ ├── smooth_colored.png
│ ├── bar_extreme_values.png
│ ├── smooth_with_target.png
│ ├── standard_deviation.png
│ ├── whisker_with_step.png
│ ├── bullet_full_featured.png
│ ├── labeled_bar_sprintf.png
│ ├── smooth_underneath_color.png
│ ├── standard_deviation_tall.png
│ ├── whisker_non_exceptional.png
│ ├── labeled_whisker_decimals.png
│ ├── standard_deviation_short.png
│ ├── labeled_smooth_without_last.png
│ └── smooth_similar_nonzero_values.png
└── test_all.rb
├── fonts
└── silkscreen
│ ├── slkscr.ttf
│ ├── slkscrb.ttf
│ ├── slkscre.ttf
│ ├── slkscreb.ttf
│ └── readme.txt
├── README.md
├── Rakefile
├── MIT-LICENSE
├── lib
├── sparklines_helper.rb
└── sparklines.rb
├── Manifest.txt
├── History.txt
└── sparklines.gemspec
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ttf -crlf -diff -merge
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | test/actual/*
3 | doc/*
4 | /test/output.html
5 |
--------------------------------------------------------------------------------
/samples/area.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/area.png
--------------------------------------------------------------------------------
/samples/pie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie.png
--------------------------------------------------------------------------------
/samples/pie0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie0.png
--------------------------------------------------------------------------------
/samples/pie1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie1.png
--------------------------------------------------------------------------------
/samples/pie100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie100.png
--------------------------------------------------------------------------------
/samples/pie45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie45.png
--------------------------------------------------------------------------------
/samples/pie95.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie95.png
--------------------------------------------------------------------------------
/samples/pie99.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie99.png
--------------------------------------------------------------------------------
/samples/smooth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/smooth.png
--------------------------------------------------------------------------------
/samples/area-high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/area-high.png
--------------------------------------------------------------------------------
/samples/discrete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/discrete.png
--------------------------------------------------------------------------------
/samples/pie-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/pie-large.png
--------------------------------------------------------------------------------
/test/expected/bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bar.png
--------------------------------------------------------------------------------
/test/expected/pie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie.png
--------------------------------------------------------------------------------
/test/expected/area.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/area.png
--------------------------------------------------------------------------------
/test/expected/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/error.png
--------------------------------------------------------------------------------
/test/expected/pie0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie0.png
--------------------------------------------------------------------------------
/test/expected/pie1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie1.png
--------------------------------------------------------------------------------
/test/expected/pie100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie100.png
--------------------------------------------------------------------------------
/test/expected/pie45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie45.png
--------------------------------------------------------------------------------
/test/expected/pie95.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie95.png
--------------------------------------------------------------------------------
/test/expected/pie99.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie99.png
--------------------------------------------------------------------------------
/test/expected/smooth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/smooth.png
--------------------------------------------------------------------------------
/test/expected/zeros.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/zeros.png
--------------------------------------------------------------------------------
/samples/smooth-colored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/samples/smooth-colored.png
--------------------------------------------------------------------------------
/test/expected/bar_tall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bar_tall.png
--------------------------------------------------------------------------------
/test/expected/bar_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bar_wide.png
--------------------------------------------------------------------------------
/test/expected/discrete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/discrete.png
--------------------------------------------------------------------------------
/test/expected/nil_area.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/nil_area.png
--------------------------------------------------------------------------------
/test/expected/nil_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/nil_bar.png
--------------------------------------------------------------------------------
/test/expected/nil_pie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/nil_pie.png
--------------------------------------------------------------------------------
/test/expected/pie_flat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie_flat.png
--------------------------------------------------------------------------------
/test/expected/whisker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/whisker.png
--------------------------------------------------------------------------------
/fonts/silkscreen/slkscr.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/fonts/silkscreen/slkscr.ttf
--------------------------------------------------------------------------------
/fonts/silkscreen/slkscrb.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/fonts/silkscreen/slkscrb.ttf
--------------------------------------------------------------------------------
/fonts/silkscreen/slkscre.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/fonts/silkscreen/slkscre.ttf
--------------------------------------------------------------------------------
/fonts/silkscreen/slkscreb.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/fonts/silkscreen/slkscreb.ttf
--------------------------------------------------------------------------------
/test/expected/area_high.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/area_high.png
--------------------------------------------------------------------------------
/test/expected/bullet_tall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bullet_tall.png
--------------------------------------------------------------------------------
/test/expected/bullet_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bullet_wide.png
--------------------------------------------------------------------------------
/test/expected/labeled_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_bar.png
--------------------------------------------------------------------------------
/test/expected/labeled_pie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_pie.png
--------------------------------------------------------------------------------
/test/expected/nil_smooth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/nil_smooth.png
--------------------------------------------------------------------------------
/test/expected/pie_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/pie_large.png
--------------------------------------------------------------------------------
/test/expected/area_min_max.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/area_min_max.png
--------------------------------------------------------------------------------
/test/expected/bullet_basic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bullet_basic.png
--------------------------------------------------------------------------------
/test/expected/discrete_max.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/discrete_max.png
--------------------------------------------------------------------------------
/test/expected/discrete_min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/discrete_min.png
--------------------------------------------------------------------------------
/test/expected/discrete_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/discrete_wide.png
--------------------------------------------------------------------------------
/test/expected/labeled_area.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_area.png
--------------------------------------------------------------------------------
/test/expected/nil_discrete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/nil_discrete.png
--------------------------------------------------------------------------------
/test/expected/whisker_junk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/whisker_junk.png
--------------------------------------------------------------------------------
/test/expected/bar_string.png.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bar_string.png.png
--------------------------------------------------------------------------------
/test/expected/bullet_colorful.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bullet_colorful.png
--------------------------------------------------------------------------------
/test/expected/labeled_discrete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_discrete.png
--------------------------------------------------------------------------------
/test/expected/labeled_smooth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_smooth.png
--------------------------------------------------------------------------------
/test/expected/smooth_colored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/smooth_colored.png
--------------------------------------------------------------------------------
/test/expected/bar_extreme_values.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bar_extreme_values.png
--------------------------------------------------------------------------------
/test/expected/smooth_with_target.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/smooth_with_target.png
--------------------------------------------------------------------------------
/test/expected/standard_deviation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/standard_deviation.png
--------------------------------------------------------------------------------
/test/expected/whisker_with_step.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/whisker_with_step.png
--------------------------------------------------------------------------------
/test/expected/bullet_full_featured.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/bullet_full_featured.png
--------------------------------------------------------------------------------
/test/expected/labeled_bar_sprintf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_bar_sprintf.png
--------------------------------------------------------------------------------
/test/expected/smooth_underneath_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/smooth_underneath_color.png
--------------------------------------------------------------------------------
/test/expected/standard_deviation_tall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/standard_deviation_tall.png
--------------------------------------------------------------------------------
/test/expected/whisker_non_exceptional.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/whisker_non_exceptional.png
--------------------------------------------------------------------------------
/test/expected/labeled_whisker_decimals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_whisker_decimals.png
--------------------------------------------------------------------------------
/test/expected/standard_deviation_short.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/standard_deviation_short.png
--------------------------------------------------------------------------------
/test/expected/labeled_smooth_without_last.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/labeled_smooth_without_last.png
--------------------------------------------------------------------------------
/test/expected/smooth_similar_nonzero_values.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/topfunky/sparklines/HEAD/test/expected/smooth_similar_nonzero_values.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sparklines
2 |
3 | A library for generating small sparkline graphs from Ruby. Use it in desktop apps or with Ruby on Rails.
4 |
5 | ## Source
6 |
7 | http://github.com/topfunky/sparklines
8 |
9 | ## Other info
10 |
11 | http://nubyonrails.com/pages/sparklines
12 |
13 | ## Authors
14 |
15 | Geoffrey Grosenbach
16 | boss@topfunky.com
17 | http://nubyonrails.com/pages/sparklines
18 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 |
2 | require 'rubygems'
3 | require 'hoe'
4 | $:.unshift(File.dirname(__FILE__) + "/lib")
5 | require 'sparklines'
6 |
7 | Hoe.new('Sparklines', Sparklines::VERSION) do |p|
8 | p.name = "sparklines"
9 | p.author = "Geoffrey Grosenbach"
10 | p.description = "Tiny graphs."
11 | p.email = 'boss@topfunky.com'
12 | p.summary = "Tiny graphs."
13 | p.url = "http://nubyonrails.com/pages/sparklines"
14 | p.clean_globs = ['test/actual', 'email.txt'] # Remove this directory on "rake clean"
15 | p.remote_rdoc_dir = '' # Release to root
16 | p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
17 | # * extra_deps - An array of rubygem dependencies.
18 | end
19 |
20 | desc "Release and publish documentation"
21 | task :repubdoc => [:release, :publish_docs]
22 |
23 |
24 | desc "Simple require on packaged files to make sure they are all there"
25 | task :verify => :package do
26 | # An error message will be displayed if files are missing
27 | if system %(ruby -e "require 'pkg/sparklines-#{Sparklines::VERSION}/lib/sparklines'")
28 | puts "\nThe library files are present"
29 | end
30 | end
31 |
32 | task :release => :verify
33 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2005 Geoffrey Grosenbach boss@topfunky.com
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.
21 |
22 |
--------------------------------------------------------------------------------
/fonts/silkscreen/readme.txt:
--------------------------------------------------------------------------------
1 | Thank you for downloading Silkscreen, a type family for your Web graphics
2 | by Jason Kottke (jason@kottke.org).
3 |
4 | To install the Silkscreen type family, unzip this file and drag the files
5 | into the Fonts folder in the Control Panel.
6 |
7 | If you encounter any problems in using this font, please email me and I'll
8 | see if I can try and fix it. Please note that I can't help you with any
9 | installation issues. Please consult your system's help files for assistance.
10 |
11 | This font is free for personal and corporate use and may be redistributed in
12 | this unmodified form on your Web site. I would ask that you not modify and
13 | then redistribute this font...although you may modify it for your own
14 | personal use. If you really like this font and use it often, feel free to
15 | mail me (e- or snail mail) some small token of your appreciation. A URL
16 | of your work using Silkscreen would be appreciated as well.
17 |
18 | All future bug fixes, updates, and additions to the Silkscreen type family
19 | will be available on my Web site at the following URL:
20 |
21 | http://www.kottke.org/plus/type/silkscreen/index.html
22 |
23 | Again, thanks for downloading Silkscreen. Enjoy!
24 |
25 | -jason
--------------------------------------------------------------------------------
/lib/sparklines_helper.rb:
--------------------------------------------------------------------------------
1 | require 'base64'
2 | # Provides a tag for embedding sparklines graphs into your Rails app.
3 | #
4 | module SparklinesHelper
5 |
6 | # Call with an array of data and a hash of params for the Sparklines module.
7 | #
8 | # sparkline_tag [42, 37, 43, 182], :type => 'bar', :line_color => 'black'
9 | #
10 | # You can also pass :class => 'some_css_class' ('sparkline' by default).
11 | def sparkline_tag(results=[], options={})
12 | url = { :controller => 'sparklines' }
13 | url[:results] = results.join(',') unless results.nil?
14 | options = url.merge(options)
15 | attributes = %(class="#{options[:class] || 'sparkline'}" alt="Sparkline Graph" )
16 | attributes << %(title="#{options[:title]}" ) if options[:title]
17 |
18 | attributes << %(width="#{options[:width]}" ) if options[:width]
19 | attributes << %(height="#{options[:height]}" ) if options[:height]
20 |
21 | # prefer to use a "data" URL scheme as described in {RFC 2397}[http://www.ietf.org/rfc/rfc2397.txt]
22 | # data_url = "data:image/png;base64,#{Base64.encode64(Sparklines.plot(results,options))}"
23 | # tag = %(
)
24 |
25 | # # respect some limits noted in RFC 2397 since the data: url is supposed to be 'short'
26 | # data_url.length <= 1024 && tag.length <= 2100 ? tag :
27 | %(
)
28 |
29 | end
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/Manifest.txt:
--------------------------------------------------------------------------------
1 | History.txt
2 | MIT-LICENSE
3 | Manifest.txt
4 | README.txt
5 | Rakefile
6 | lib/sparklines.rb
7 | lib/sparklines_helper.rb
8 | samples/area-high.png
9 | samples/area.png
10 | samples/discrete.png
11 | samples/pie-large.png
12 | samples/pie.png
13 | samples/pie0.png
14 | samples/pie1.png
15 | samples/pie100.png
16 | samples/pie45.png
17 | samples/pie95.png
18 | samples/pie99.png
19 | samples/smooth-colored.png
20 | samples/smooth.png
21 | test/expected/area.png
22 | test/expected/area_high.png
23 | test/expected/area_min_max.png
24 | test/expected/bar.png
25 | test/expected/bar_extreme_values.png
26 | test/expected/bar_string.png.png
27 | test/expected/bar_tall.png
28 | test/expected/bar_wide.png
29 | test/expected/bullet_basic.png
30 | test/expected/bullet_colorful.png
31 | test/expected/bullet_full_featured.png
32 | test/expected/bullet_tall.png
33 | test/expected/bullet_wide.png
34 | test/expected/discrete.png
35 | test/expected/discrete_wide.png
36 | test/expected/error.png
37 | test/expected/labeled_area.png
38 | test/expected/labeled_bar.png
39 | test/expected/labeled_discrete.png
40 | test/expected/labeled_pie.png
41 | test/expected/labeled_smooth.png
42 | test/expected/labeled_whisker_decimals.png
43 | test/expected/pie.png
44 | test/expected/pie0.png
45 | test/expected/pie1.png
46 | test/expected/pie100.png
47 | test/expected/pie45.png
48 | test/expected/pie95.png
49 | test/expected/pie99.png
50 | test/expected/pie_flat.png
51 | test/expected/pie_large.png
52 | test/expected/smooth.png
53 | test/expected/smooth_colored.png
54 | test/expected/smooth_similar_nonzero_values.png
55 | test/expected/smooth_underneath_color.png
56 | test/expected/smooth_with_target.png
57 | test/expected/standard_deviation.png
58 | test/expected/standard_deviation_short.png
59 | test/expected/standard_deviation_tall.png
60 | test/expected/whisker.png
61 | test/expected/whisker_junk.png
62 | test/expected/whisker_non_exceptional.png
63 | test/expected/whisker_with_step.png
64 | test/expected/zeros.png
65 | test/test_all.rb
66 |
--------------------------------------------------------------------------------
/History.txt:
--------------------------------------------------------------------------------
1 | == 0.5.4
2 |
3 | * Support disabling last point when label is shown [Justin Love]
4 | * Handle missing data (nil) values in area, bar, discrete, smooth [Justin Love]
5 | * Extract method: normalize [Justin Love]
6 | * Support :min and :max options to set graph scale [Justin Love]
7 |
8 | == 0.5.2
9 |
10 | * Handle case where all data is zero [Silvia Pfeiffer]
11 |
12 | == 0.5.1
13 |
14 | * Fixed drawing and normalization of bar graph
15 | * Added :target and :target_color to bar graph (draws a horizontal line at a value)
16 |
17 | == 0.5.0
18 |
19 | * Documentation for bullet graph.
20 | * More bullet options for good_color, satisfactory_color, and bad_color.
21 | * Smooth options for target line value and target_color.
22 | * Sparklinks.plot_to_image returns a Magick::Image object for further manipulation.
23 | * Step option for wider whisker.
24 |
25 | == 0.4.8
26 |
27 | * Added bullet graph. See http://en.wikipedia.org/wiki/Bullet_graph or Stephen Few's book _Information Dashboard Design_.
28 |
29 | == 0.4.7
30 |
31 | * Improved normalization for better display of close, high-values. [Matt Van Horn]
32 | * Improved rendering of closed polyline on smooth graph with undercolor. Far left, right, and bottom of polyline are drawn offscreen so they don't show in the visible graph.
33 |
34 | == 0.4.6
35 |
36 | * Added :underneath_color option to smooth sparkline. [Cory Forsyth]
37 |
38 | == 0.4.5
39 |
40 | * Several fixes by Rob Biedenharn
41 | * lib/sparklines_helper.rb: Include example code for generating "data:" URLs (but leave it commented out due to lack of widespread testing)
42 | * lib/sparklines_helper.rb: allow results to be passed in the options hash (i.e., sparkline_tag(nil, :results => [ ... ], ...))
43 | * lib/sparklines.rb: Move calculation of whisker endpoints out of loop
44 | * lib/sparklines.rb: Fix fencepost bug in whisker that caused plot to be 1 pixel short
45 |
46 | == 0.4.4
47 |
48 | * Fixed stddev rounding bug [Andrew Nutter-Upham]
49 |
50 | == 0.4.3
51 |
52 | * Minor change for use with Hoe.
53 |
54 | == 0.4.2
55 |
56 | * Added standard deviation bars [Andrew Nutter-Upham]
57 |
58 | == 0.4.1
59 |
60 | * Converted to Hoe for rakefile
61 | * Added whisker graph [Luke Francl]
62 | * General cleanup and bug fixes
63 | * Experimental label option
64 |
65 | == 0.3.0
66 |
67 | * Changed to a Class for maintainability
68 | * All values are normalized (except pie)
69 | * A single value can be passed for the pie percentage (instead of an Array)
70 |
71 | == 0.2.7
72 |
73 | * Fixed bug where last element of bar graph wouldn't go to the bottom [Esad Hajdarevic esad@esse.at]
74 |
75 | == 0.2.5
76 |
77 | * Tests now use Test::Unit
78 | * Bar type added
79 |
80 | == 0.2.1
81 |
82 | * Added line_color option for smooth graphs
83 | * Now available as a gem ('gem install sparklines') and as a rails generator ('gem install sparklines_generator')
84 |
85 |
86 |
--------------------------------------------------------------------------------
/sparklines.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = %q{sparklines}
3 | s.version = "0.5.3"
4 |
5 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6 | s.authors = ["Geoffrey Grosenbach"]
7 | s.date = %q{2008-08-20}
8 | s.description = %q{Tiny graphs.}
9 | s.email = %q{boss@topfunky.com}
10 | s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
11 | s.files = ["History.txt", "MIT-LICENSE", "Manifest.txt", "README.txt", "Rakefile", "lib/sparklines.rb", "lib/sparklines_helper.rb", "samples/area-high.png", "samples/area.png", "samples/discrete.png", "samples/pie-large.png", "samples/pie.png", "samples/pie0.png", "samples/pie1.png", "samples/pie100.png", "samples/pie45.png", "samples/pie95.png", "samples/pie99.png", "samples/smooth-colored.png", "samples/smooth.png", "test/expected/area.png", "test/expected/area_high.png", "test/expected/area_min_max.png", "test/expected/bar.png", "test/expected/bar_extreme_values.png", "test/expected/bar_string.png.png", "test/expected/bar_tall.png", "test/expected/bar_wide.png", "test/expected/bullet_basic.png", "test/expected/bullet_colorful.png", "test/expected/bullet_full_featured.png", "test/expected/bullet_tall.png", "test/expected/bullet_wide.png", "test/expected/discrete.png", "test/expected/discrete_wide.png", "test/expected/error.png", "test/expected/labeled_area.png", "test/expected/labeled_bar.png", "test/expected/labeled_discrete.png", "test/expected/labeled_pie.png", "test/expected/labeled_smooth.png", "test/expected/labeled_whisker_decimals.png", "test/expected/pie.png", "test/expected/pie0.png", "test/expected/pie1.png", "test/expected/pie100.png", "test/expected/pie45.png", "test/expected/pie95.png", "test/expected/pie99.png", "test/expected/pie_flat.png", "test/expected/pie_large.png", "test/expected/smooth.png", "test/expected/smooth_colored.png", "test/expected/smooth_similar_nonzero_values.png", "test/expected/smooth_underneath_color.png", "test/expected/smooth_with_target.png", "test/expected/standard_deviation.png", "test/expected/standard_deviation_short.png", "test/expected/standard_deviation_tall.png", "test/expected/whisker.png", "test/expected/whisker_junk.png", "test/expected/whisker_non_exceptional.png", "test/expected/whisker_with_step.png", "test/expected/zeros.png", "test/test_all.rb"]
12 | s.has_rdoc = true
13 | s.homepage = %q{http://nubyonrails.com/pages/sparklines}
14 | s.rdoc_options = ["--main", "README.txt"]
15 | s.require_paths = ["lib"]
16 | s.rubyforge_project = %q{sparklines}
17 | s.rubygems_version = %q{1.2.0}
18 | s.summary = %q{Tiny graphs.}
19 | s.test_files = ["test/test_all.rb"]
20 |
21 | if s.respond_to? :specification_version then
22 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23 | s.specification_version = 2
24 |
25 | if current_version >= 3 then
26 | s.add_development_dependency(%q, [">= 1.7.0"])
27 | else
28 | s.add_dependency(%q, [">= 1.7.0"])
29 | end
30 | else
31 | s.add_dependency(%q, [">= 1.7.0"])
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/test/test_all.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 |
3 | require 'test/unit'
4 | require 'lib/sparklines'
5 | require 'fileutils'
6 | require 'tidy_table'
7 | require 'dust'
8 |
9 | class SparklinesTest < Test::Unit::TestCase
10 |
11 | def setup
12 | @output_dir = "test/actual"
13 | FileUtils.mkdir_p(@output_dir)
14 |
15 | @data = %w( 1 5 15 20 30 50 57 58 55 48
16 | 44 43 42 42 46 48 49 53 55 59
17 | 60 65 75 90 105 106 107 110 115 120
18 | 115 120 130 140 150 160 170 100 100 10).map {|i| i.to_f}
19 | @nil_data = @data.dup
20 | [5, 10, 11, 20, 25, 26, 27].each {|i| @nil_data[i] = nil}
21 | @constant_data = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
22 | end
23 |
24 | test "basic bullet" do
25 | Sparklines.plot_to_file("#{@output_dir}/bullet_basic.png", 85, {
26 | :type => "bullet",
27 | :target => 80,
28 | :good => 100,
29 | :height => 15
30 | })
31 | end
32 |
33 | test "full-featured bullet" do
34 | Sparklines.plot_to_file("#{@output_dir}/bullet_full_featured.png", 85, {
35 | :type => "bullet",
36 | :target => 90,
37 | :bad => 60,
38 | :satisfactory => 80,
39 | :good => 100,
40 | :height => 15
41 | })
42 | end
43 |
44 | test "colorful bullet" do
45 | Sparklines.plot_to_file("#{@output_dir}/bullet_colorful.png", 85, {
46 | :type => "bullet",
47 | :target => 90,
48 | :bad => 60,
49 | :satisfactory => 80,
50 | :good => 100,
51 | :height => 15,
52 | :bad_color => '#c3e3bf',
53 | :satisfactory_color => '#96cf90',
54 | :good_color => "#6ab162"
55 | })
56 | end
57 |
58 | test "tall bullet" do
59 | Sparklines.plot_to_file("#{@output_dir}/bullet_tall.png", 85, {
60 | :type => "bullet",
61 | :target => 90,
62 | :bad => 60,
63 | :satisfactory => 80,
64 | :good => 100,
65 | :height => 30
66 | })
67 | end
68 |
69 | test "wide bullet" do
70 | Sparklines.plot_to_file("#{@output_dir}/bullet_wide.png", 85, {
71 | :type => "bullet",
72 | :target => 90,
73 | :bad => 60,
74 | :satisfactory => 80,
75 | :good => 100,
76 | :height => 15,
77 | :width => 200
78 | })
79 | end
80 |
81 | test "smooth with target" do
82 | quick_graph("smooth_with_target", {
83 | :type => "smooth",
84 | :target => 50,
85 | :target_color => '#999999',
86 | :line_color => "#6699cc",
87 | :underneath_color => "#ebf3f6"
88 | })
89 | end
90 |
91 | test "whisker with step" do
92 | quick_graph("whisker_with_step", {
93 | :type => "whisker",
94 | :step => 5
95 | })
96 | end
97 |
98 | def test_each_graph
99 | %w{pie area discrete smooth bar}.each do |type|
100 | quick_graph("#{type}", :type => type)
101 | end
102 | end
103 |
104 | def test_each_graph_with_label
105 | %w{pie area discrete smooth bar}.each do |type|
106 | quick_graph("labeled_#{type}", :type => type, :label => 'Glucose')
107 | end
108 | end
109 |
110 | test "bar graph with sprintf for label" do
111 | quick_graph("labeled_bar_sprintf", :type => "bar", :label => "Formatted", :label_format => "%i")
112 | end
113 |
114 | def test_each_graph_with_nil
115 | %w{pie area discrete smooth bar}.each do |type|
116 | nil_graph("nil_#{type}", :type => type, :label => 'Glucose')
117 | end
118 | end
119 |
120 | def test_smooth_graph_with_target_and_constant_data
121 | constant_graph("constant_smooth", :type => 'smooth', :label => 'a-label', :target => 1)
122 | end
123 |
124 | def test_whisker_decimals
125 | @data = (1..200).map {|n| n.to_f/100 }
126 | quick_graph("labeled_whisker_decimals", {
127 | :height => 30,
128 | :type => 'smooth',
129 | :label => 'png'
130 | })
131 | end
132 |
133 | def test_whisker_random
134 | # Need data ranging from -2 to +2
135 | @data = (1..40).map { |i| rand(3) * (rand(2) == 1 ? -1 : 1) }
136 | quick_graph("whisker", :type => 'whisker')
137 | end
138 |
139 | def test_whisker_non_exceptional
140 | @data = [1,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
141 | quick_graph("whisker_non_exceptional", :type => 'whisker')
142 | end
143 |
144 | ##
145 | # Send random values in the range (-9..9)
146 |
147 | def test_whisker_junk
148 | @data = (1..40).map { |i| rand(10) * (rand(2) == 1 ? -1 : 1) }
149 | quick_graph("whisker_junk", :type => 'whisker')
150 | end
151 |
152 | def test_pie
153 | # Test extremes which previously did not work right
154 | [0, 1, 45, 95, 99, 100].each do |value|
155 | Sparklines.plot_to_file("#{@output_dir}/pie#{value}.png",
156 | value, {
157 | :type => 'pie',
158 | :diameter => 128
159 | })
160 | end
161 | Sparklines.plot_to_file("#{@output_dir}/pie_flat.png",
162 | [60],
163 | {
164 | :type => 'pie'
165 | })
166 | end
167 |
168 | def test_special_conditions
169 | tests = { 'smooth_colored' =>
170 | {
171 | :type => 'smooth',
172 | :line_color => 'purple'
173 | },
174 | 'labeled_smooth_without_last' =>
175 | {
176 | :type => 'smooth',
177 | :label => 'Glucose',
178 | :has_last => false
179 | },
180 | 'pie_large' => {
181 | :type => 'pie',
182 | :diameter => 200
183 | },
184 | 'area_high' => {
185 | :type => 'area',
186 | :upper => 80,
187 | :step => 4,
188 | :height => 20
189 | },
190 | 'discrete_wide' => {
191 | :type => 'discrete',
192 | :step => 8
193 | },
194 | 'discrete_max' => {
195 | :type => 'discrete',
196 | :max => @data.max * 4
197 | },
198 | 'discrete_min' => {
199 | :type => 'discrete',
200 | :min => @data.max * -4
201 | },
202 | 'bar_wide' => {
203 | :type => 'bar',
204 | :step => 8
205 | },
206 | 'bar_tall' => {
207 | :type => 'bar',
208 | :below_color => 'blue',
209 | :above_color => 'red',
210 | :upper => 90,
211 | :height => 50,
212 | :step => 8
213 | }
214 | }
215 | tests.each do |name, options|
216 | quick_graph(name, options)
217 | end
218 | end
219 |
220 | def test_bar_extreme_values
221 | Sparklines.plot_to_file("#{@output_dir}/bar_extreme_values.png",
222 | [0,1,100,2,99,3,98,4,97,5,96,6,95,7,94,8,93,9,92,10,91], {
223 | :type => 'bar',
224 | :below_color => 'blue',
225 | :above_color => 'red',
226 | :upper => 90,
227 | :step => 4
228 | })
229 | end
230 |
231 | def test_string_args
232 | quick_graph("bar_string.png", {
233 | 'type' => 'bar',
234 | 'below_color' => 'blue',
235 | 'above_color' => 'red',
236 | 'upper' => 50,
237 | 'height' => 50,
238 | 'step' => 8
239 | })
240 | end
241 |
242 | def test_area_min_max
243 | quick_graph("area_min_max", {
244 | :has_min => true,
245 | :has_max => true,
246 | :has_first => true,
247 | :has_last => true
248 | })
249 | end
250 |
251 | def test_smooth_underneath_color
252 | quick_graph("smooth_underneath_color", {
253 | :type => 'smooth',
254 | :line_color => "#6699cc",
255 | :underneath_color => "#ebf3f6"
256 | })
257 | end
258 |
259 | def test_close_values
260 | Sparklines.plot_to_file("#{@output_dir}/smooth_similar_nonzero_values.png", [100, 90, 95, 99, 80, 90], {
261 | :type => 'smooth',
262 | :line_color => "#6699cc",
263 | :underneath_color => "#ebf3f6"
264 | })
265 | end
266 |
267 | def test_no_type
268 | Sparklines.plot_to_file("#{@output_dir}/error.png", 0, :type => 'nonexistent')
269 | end
270 |
271 | def test_standard_deviation
272 | quick_graph('standard_deviation', {
273 | :type => 'smooth',
274 | :height => 100,
275 | :line_color => '#666',
276 | :has_std_dev => true,
277 | :std_dev_color => '#cccccc'
278 | })
279 | end
280 |
281 | def test_standard_deviation_tall
282 | quick_graph('standard_deviation_tall', {
283 | :type => 'smooth',
284 | :height => 300,
285 | :line_color => '#666',
286 | :has_std_dev => true,
287 | :std_dev_color => '#cccccc'
288 | })
289 | end
290 |
291 | def test_standard_deviation_short
292 | quick_graph('standard_deviation_short', {
293 | :type => 'smooth',
294 | :height => 20,
295 | :line_color => '#666',
296 | :has_std_dev => true,
297 | :std_dev_color => '#cccccc'
298 | })
299 | end
300 |
301 | def test_zeros
302 | @data = [0,0,0,0]
303 | quick_graph('zeros', {
304 | :type => 'smooth',
305 | })
306 | end
307 |
308 | private
309 |
310 | def quick_graph(name, options)
311 | Sparklines.plot_to_file("#{@output_dir}/#{name}.png", @data, options)
312 | end
313 |
314 | def nil_graph(name, options)
315 | Sparklines.plot_to_file("#{@output_dir}/#{name}.png", @nil_data, options)
316 | end
317 |
318 | def constant_graph(name, options)
319 | Sparklines.plot_to_file("#{@output_dir}/#{name}.png", @constant_data, options)
320 | end
321 |
322 | end
323 |
324 | # HACK Make reference HTML file for viewing output
325 | END {
326 | def image_tag(image_path)
327 | %(
)
328 | end
329 |
330 | reference_files = Dir['test/expected/*']
331 | output = TidyTable.new(reference_files).to_html(%w(Expected Actual)) do |record|
332 | [image_tag(record), image_tag(record.gsub('expected', 'actual'))]
333 | end
334 | FileUtils.mkdir_p("test/actual")
335 | File.open("test/output.html", "w") do |f|
336 | f.write <<-EOL
337 |
348 | EOL
349 | f.write output
350 | end
351 |
352 | # TODO: Squawk if there are files in actual that aren't in expected
353 | }
354 |
--------------------------------------------------------------------------------
/lib/sparklines.rb:
--------------------------------------------------------------------------------
1 |
2 | require 'rubygems'
3 | require 'rmagick'
4 |
5 | =begin rdoc
6 |
7 | A library for generating small unmarked graphs (sparklines).
8 |
9 | Can be used to write an image to a file or make a web service with Rails or other Ruby CGI apps.
10 |
11 | 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 | Requires the RMagick image library.
14 |
15 | ==Authors
16 |
17 | {Dan Nugent}[mailto:nugend@gmail.com] Original port from Python Sparklines library.
18 |
19 | {Geoffrey Grosenbach}[mailto:boss@topfunky.com] -- http://nubyonrails.topfunky.com
20 | -- Conversion to module and further maintenance.
21 |
22 | ==General Usage and Defaults
23 |
24 | To use in a script:
25 |
26 | require 'rubygems'
27 | require 'sparklines'
28 | Sparklines.plot([1,25,33,46,89,90,85,77,42],
29 | :type => 'discrete',
30 | :height => 20)
31 |
32 | An image blob will be returned which you can print, write to STDOUT, etc.
33 |
34 | For use with Ruby on Rails, see the sparklines plugin:
35 |
36 | http://nubyonrails.com/pages/sparklines
37 |
38 | In your view, call it like this:
39 |
40 | <%= sparkline_tag [1,2,3,4,5,6] %>
41 |
42 | Or specify details:
43 |
44 | <%= sparkline_tag [1,2,3,4,5,6],
45 | :type => 'discrete',
46 | :height => 10,
47 | :upper => 80,
48 | :above_color => 'green',
49 | :below_color => 'blue' %>
50 |
51 | Graph types:
52 |
53 | area
54 | discrete
55 | pie
56 | smooth
57 | bar
58 | bullet
59 | whisker
60 |
61 | General Defaults:
62 |
63 | :type => 'smooth'
64 | :height => 14px
65 | :upper => 50
66 | :above_color => 'red'
67 | :below_color => 'grey'
68 | :background_color => 'white'
69 | :line_color => 'lightgrey'
70 | :label => name_of_label_after_graph
71 | :label_format => sprintf_string_to_format_label
72 |
73 | ==License
74 |
75 | Licensed under the MIT license.
76 |
77 | =end
78 | class Sparklines
79 |
80 | VERSION = '0.5.3'
81 |
82 | @@label_margin = 5.0
83 | @@pointsize = 10.0
84 |
85 | class << self
86 |
87 | ##
88 | # Plots a sparkline and returns a Magic::Image object.
89 |
90 | def plot_to_image(data=[], options={})
91 | defaults = {
92 | :type => 'smooth',
93 | :height => 14,
94 | :upper => 50,
95 | :diameter => 20,
96 | :step => 2,
97 | :line_color => 'lightgrey',
98 |
99 | :above_color => 'red',
100 | :below_color => 'grey',
101 | :background_color => 'white',
102 | :share_color => 'red',
103 | :remain_color => 'lightgrey',
104 | :min_color => 'blue',
105 | :max_color => 'green',
106 | :last_color => 'red',
107 | :std_dev_color => '#efefef',
108 |
109 | :min => nil,
110 | :max => nil,
111 | :has_min => false,
112 | :has_max => false,
113 | :has_last => nil,
114 | :has_std_dev => false,
115 |
116 | :label => nil
117 | }
118 |
119 | # HACK for HashWithIndifferentAccess
120 | options_sym = Hash.new
121 | options.keys.each do |key|
122 | options_sym[key.to_sym] = options[key]
123 | end
124 |
125 | options_sym = defaults.merge(options_sym)
126 |
127 | # Call the appropriate method for actual plotting.
128 | sparkline = self.new(data, options_sym)
129 | if %w(area bar bullet pie smooth discrete whisker).include? options_sym[:type]
130 | sparkline.send options_sym[:type]
131 | else
132 | sparkline.plot_error options_sym
133 | end
134 | end
135 |
136 | ##
137 | # Does the actual plotting of the graph.
138 | # Calls the appropriate subclass based on the :type argument.
139 | # Defaults to 'smooth'.
140 | #
141 | # Returns a blob.
142 |
143 | def plot(data=[], options={})
144 | plot_to_image(data, options).to_blob
145 | end
146 |
147 | ##
148 | # Writes a graph to disk with the specified filename, or "sparklines.png".
149 |
150 | def plot_to_file(filename="sparklines.png", data=[], options={})
151 | File.open( filename, 'wb' ) do |png|
152 | png << self.plot( data, options)
153 | end
154 | end
155 |
156 | end # class methods
157 |
158 | def initialize(data=[], options={})
159 | @data = Array(data)
160 | @options = options
161 | normalize_data
162 | end
163 |
164 | ##
165 | # Creates a continuous area sparkline. Relevant options.
166 | #
167 | # :step - An integer that determines the distance between each point on the sparkline. Defaults to 2.
168 | #
169 | # :height - An integer that determines what the height of the sparkline will be. Defaults to 14
170 | #
171 | # :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.
172 | #
173 | # :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaults to false.
174 | #
175 | # :has_max - Determines whether a dot will be drawn at the highest value or not. Defaults to false.
176 | #
177 | # :has_last - Determines whether a dot will be drawn at the last value or not. Defaults to false.
178 | #
179 | # :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.
180 | #
181 | # :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.
182 | #
183 | # :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.
184 | #
185 | # :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red.
186 | #
187 | # :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray.
188 |
189 | def area
190 |
191 | step = @options[:step].to_f
192 | height = @options[:height].to_f
193 | background_color = @options[:background_color]
194 |
195 | create_canvas((@norm_data.size - 1) * step + 4, height, background_color)
196 |
197 | upper = @options[:upper].to_f
198 |
199 | has_min = @options[:has_min]
200 | has_max = @options[:has_max]
201 | has_last = @options[:has_last]
202 |
203 | min_color = @options[:min_color]
204 | max_color = @options[:max_color]
205 | last_color = @options[:last_color]
206 | below_color = @options[:below_color]
207 | above_color = @options[:above_color]
208 |
209 |
210 | coords = [[0,(height - 3 - upper/(101.0/(height-4)))]]
211 | i=0
212 | @norm_data.each do |r|
213 | coords.push [(2 + i), (height - 3 - r/(101.0/(height-4)))] if r
214 | i += step
215 | end
216 | coords.push [(@norm_data.size - 1) * step + 4, (height - 3 - upper/(101.0/(height-4)))]
217 |
218 | # TODO Refactor! Should take a block and do both.
219 | #
220 | # Block off the bottom half of the image and draw the sparkline
221 | @draw.fill(above_color)
222 | @draw.define_clip_path('top') do
223 | @draw.rectangle(0,0,(@norm_data.size - 1) * step + 4,(height - 3 - upper/(101.0/(height-4))))
224 | end
225 | @draw.clip_path('top')
226 | @draw.polygon(*coords.flatten)
227 |
228 | # Block off the top half of the image and draw the sparkline
229 | @draw.fill(below_color)
230 | @draw.define_clip_path('bottom') do
231 | @draw.rectangle(0,(height - 3 - upper/(101.0/(height-4))),(@norm_data.size - 1) * step + 4,height)
232 | end
233 | @draw.clip_path('bottom')
234 | @draw.polygon(*coords.flatten)
235 |
236 | # The sparkline looks kinda nasty if either the above_color or below_color gets the center line
237 | @draw.fill('black')
238 | @draw.line(0,(height - 3 - upper/(101.0/(height-4))),(@norm_data.size - 1) * step + 4,(height - 3 - upper/(101.0/(height-4))))
239 |
240 | # After the parts have been masked, we need to let the whole canvas be drawable again
241 | # so a max dot can be displayed
242 | @draw.define_clip_path('all') do
243 | @draw.rectangle(0,0,@canvas.columns,@canvas.rows)
244 | end
245 | @draw.clip_path('all')
246 |
247 | drawbox(coords[@norm_data.index(@norm_data.compact.min)+1], 1, min_color) if has_min == true
248 | drawbox(coords[@norm_data.index(@norm_data.compact.max)+1], 1, max_color) if has_max == true
249 |
250 | drawbox(coords[-2], 1, last_color) if has_last == true
251 |
252 | @draw.draw(@canvas)
253 | @canvas
254 | end
255 |
256 |
257 | ##
258 | # A bar graph.
259 | #
260 | # Also takes :target option (a line will be drawn) and :upper (values
261 | # under will be drawn in :below_color, above in :above_color).
262 |
263 | def bar
264 | step = @options[:step].to_f
265 | height = @options[:height].to_f
266 | width = ((@norm_data.size - 1) * step).to_f
267 | background_color = @options[:background_color]
268 |
269 | create_canvas(@norm_data.length * step + 2, height, background_color)
270 |
271 | upper = @options[:upper].to_f
272 | below_color = @options[:below_color]
273 | above_color = @options[:above_color]
274 |
275 | target = @options.has_key?(:target) ? @options[:target].to_f : nil
276 | target_color = @options[:target_color] || 'white'
277 |
278 | i = 1
279 | # raise @norm_data.to_yaml
280 | max_normalized = normalize(@maximum_value)
281 | @norm_data.each_with_index do |r, index|
282 | color = ((@data[index] || @minimum_value) >= upper) ? above_color : below_color
283 | @draw.stroke('transparent')
284 | @draw.fill(color)
285 | bar_height_from_top = @canvas.rows - ( (r.to_f / max_normalized.to_f) * @canvas.rows)
286 | @draw.rectangle( i, @canvas.rows, i + step - 2, bar_height_from_top )
287 | i += step
288 | end
289 |
290 | unless target.nil?
291 | adjusted_target_value = (height - 3 - normalize(target)/(101.0/(height-4))).to_i
292 | @draw.stroke(target_color)
293 | open_ended_polyline([[-5, adjusted_target_value], [width + 5, adjusted_target_value]])
294 | end
295 |
296 | @draw.draw(@canvas)
297 | @canvas
298 | end
299 |
300 |
301 | ##
302 | # Creates a discretized sparkline
303 | #
304 | # :height - An integer that determines what the height of the sparkline will be. Defaults to 14
305 | #
306 | # :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.
307 | #
308 | # :above_color - A string or color code representing the color to draw values above or equal the upper value. Defaults to red.
309 | #
310 | # :below_color - A string or color code representing the color to draw values below the upper value. Defaults to gray.
311 |
312 | def discrete
313 |
314 | height = @options[:height].to_f
315 | upper = normalize(@options[:upper].to_f)
316 | background_color = @options[:background_color]
317 | step = @options[:step].to_f
318 |
319 | width = @norm_data.size * step - 1
320 |
321 | create_canvas(@norm_data.size * step - 1, height, background_color)
322 |
323 | below_color = @options[:below_color]
324 | above_color = @options[:above_color]
325 | std_dev_color = @options[:std_dev_color]
326 |
327 | drawstddevbox(width,height,std_dev_color) if @options[:has_std_dev] == true
328 |
329 | i = 0
330 | @norm_data.each do |r|
331 | if !r.nil?
332 | color = (r >= upper) ? above_color : below_color
333 | @draw.stroke(color)
334 | @draw.line(i, (@canvas.rows - r/(101.0/(height-4))-4).to_f,
335 | i, (@canvas.rows - r/(101.0/(height-4))).to_f)
336 | end
337 | i += step
338 | end
339 |
340 | @draw.draw(@canvas)
341 | @canvas
342 | end
343 |
344 |
345 | ##
346 | # Creates a pie-chart sparkline
347 | #
348 | # :diameter - An integer that determines what the size of the sparkline will be. Defaults to 20
349 | #
350 | # :share_color - A string or color code representing the color to draw the share of the pie represented by percent. Defaults to red.
351 | #
352 | # :remain_color - A string or color code representing the color to draw the pie not taken by the share color. Defaults to lightgrey.
353 |
354 | def pie
355 | diameter = @options[:diameter].to_f
356 | background_color = @options[:background_color]
357 |
358 | create_canvas(diameter, diameter, background_color)
359 |
360 | share_color = @options[:share_color]
361 | remain_color = @options[:remain_color]
362 | percent = @norm_data[0]
363 |
364 | # Adjust the radius so there's some edge left in the pie
365 | r = diameter/2.0 - 2
366 | @draw.fill(remain_color)
367 | @draw.ellipse(r + 2, r + 2, r , r , 0, 360)
368 | @draw.fill(share_color)
369 |
370 | # Special exceptions
371 | if percent == 0
372 | # For 0% return blank
373 | @draw.draw(@canvas)
374 | return @canvas
375 | elsif percent == 100
376 | # For 100% just draw a full circle
377 | @draw.ellipse(r + 2, r + 2, r , r , 0, 360)
378 | @draw.draw(@canvas)
379 | return @canvas
380 | end
381 |
382 | # Okay, this part is as confusing as hell, so pay attention:
383 | # This line determines the horizontal portion of the point on the circle where the X-Axis
384 | # should end. It's caculated by taking the center of the on-image circle and adding that
385 | # to the radius multiplied by the formula for determinig the point on a unit circle that a
386 | # angle corresponds to. 3.6 * percent gives us that angle, but it's in degrees, so we need to
387 | # convert, hence the muliplication by Pi over 180
388 | arc_end_x = r + 2 + (r * Math.cos((3.6 * percent)*(Math::PI/180)))
389 |
390 | # The same goes for here, except it's the vertical point instead of the horizontal one
391 | arc_end_y = r + 2 + (r * Math.sin((3.6 * percent)*(Math::PI/180)))
392 |
393 | # Because the SVG path format is seriously screwy, we need to set the large-arc-flag to 1
394 | # if the angle of an arc is greater than 180 degrees. I have no idea why this is, but it is.
395 | percent > 50? large_arc_flag = 1: large_arc_flag = 0
396 |
397 | # This is also confusing
398 | # M tells us to move to an absolute point on the image. We're moving to the center of the pie
399 | # h tells us to move to a relative point. We're moving to the right edge of the circle.
400 | # A tells us to start an absolute elliptical arc. The first two values are the radii of the ellipse
401 | # the third value is the x-axis-rotation (how to rotate the ellipse if we wanted to [could have some fun
402 | # with randomizing that maybe), the fourth value is our large-arc-flag, the fifth is the sweep-flag,
403 | # (again, confusing), the sixth and seventh values are the end point of the arc which we calculated previously
404 | # More info on the SVG path string format at: http://www.w3.org/TR/SVG/paths.html
405 | path = "M#{r + 2},#{r + 2} h#{r} A#{r},#{r} 0 #{large_arc_flag},1 #{arc_end_x},#{arc_end_y} z"
406 | @draw.path(path)
407 |
408 | @draw.draw(@canvas)
409 | @canvas
410 | end
411 |
412 | ##
413 | # Creates a smooth line graph sparkline.
414 | #
415 | # :step - An integer that determines the distance between each point on the sparkline. Defaults to 2.
416 | #
417 | # :height - An integer that determines what the height of the sparkline will be. Defaults to 14
418 | #
419 | # :has_min - Determines whether a dot will be drawn at the lowest value or not. Defaults to false.
420 | #
421 | # :has_max - Determines whether a dot will be drawn at the highest value or not. Defaults to false.
422 | #
423 | # :has_last - Determines whether a dot will be drawn at the last value or not. Defaults to false.
424 | #
425 | # :has_std_dev - Determines whether there will be a standard deviation bar behind the smooth graph or not. Defaults to false.
426 | #
427 | # :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.
428 | #
429 | # :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.
430 | #
431 | # :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.
432 | #
433 | # :std_dev_color - A string or color code representing the color that the standard deviation bar behind the smooth graph will be displayed as. Defaults to #efefef
434 | #
435 | # :underneath_color - A string or color code representing the color that will be used to fill in the area underneath the line. Optional.
436 | #
437 | # :target - A 1px horizontal line will be drawn at this value. Useful for showing an average.
438 | #
439 | # :target_color - Color of the target line. Defaults to white.
440 | #
441 | # :dot_values - Determines whether a dot will be drawn for all values between the first and last values. Defaults to false.
442 | #
443 | # :dot_size - The radius of the dots to be drawn if +:dot_values+ is +true+.
444 |
445 | def smooth
446 | if @options.has_key?(:dot_values) && @options[:dot_values] != false
447 | dot_size = @options.has_key?(:dot_size) ? @options[:dot_size].to_f : 2
448 | else
449 | dot_size = 0
450 | end
451 | step = @options[:step].to_f
452 | height = @options[:height].to_f + (dot_size)
453 | width = ((@norm_data.size - 1) * step + (dot_size * 2) + 2).to_f
454 |
455 | background_color = @options[:background_color]
456 | create_canvas(width, height, background_color)
457 |
458 | min_color = @options[:min_color]
459 | max_color = @options[:max_color]
460 | last_color = @options[:last_color]
461 | has_min = @options[:has_min]
462 | has_max = @options[:has_max]
463 | has_last = @options[:has_last]
464 | line_color = @options[:line_color]
465 | has_std_dev = @options[:has_std_dev]
466 | std_dev_color = @options[:std_dev_color]
467 |
468 | target = @options.has_key?(:target) ? @options[:target].to_f : nil
469 | target_color = @options[:target_color] || 'white'
470 |
471 |
472 |
473 | drawstddevbox(width,height,std_dev_color) if has_std_dev == true
474 |
475 | # @draw.stroke_antialias(false)
476 | # @draw.stroke_width = 2
477 | @draw.stroke(line_color)
478 | coords = []
479 | i=0
480 | @norm_data.each do |r|
481 | if i == 0
482 | i += dot_size
483 | end
484 |
485 | coords.push [ i, (height - (dot_size / 2.0) - 3 - r/(101.0/(height-4-(dot_size)))) ]
486 | i += step
487 | end
488 |
489 | if @options[:underneath_color]
490 | closed_polygon(height, width, coords, dot_size)
491 | @draw.stroke(line_color)
492 | open_ended_polyline(coords)
493 | else
494 | open_ended_polyline(coords)
495 | end
496 |
497 | unless target.nil?
498 | normalized_target_value = ((target.to_f - @minimum_value)/(@maximum_value - @minimum_value)) * 100.0
499 | # adjusted_target_value = (height - 3 - normalized_target_value/(101.0/(height-4))).to_i
500 | adjusted_target_value = (height - (dot_size / 2.0) - 3 - normalized_target_value/(101.0/(height-4-(dot_size)))).to_i
501 | @draw.stroke(target_color)
502 |
503 | if @options[:dot_values] == false
504 | open_ended_polyline([[-5, adjusted_target_value], [width + 5, adjusted_target_value]])
505 | else
506 | open_ended_polyline([[coords.first.first, adjusted_target_value], [coords.last.first, adjusted_target_value]])
507 | end
508 | end
509 |
510 | if @options[:dot_values] != false
511 | @draw.fill('black')
512 | @draw.stroke('black')
513 | # Rails.logger.debug { "coords.length = #{coords.length}" }
514 | coords.each_with_index do |coord, i|
515 | # Rails.logger.debug { "coord = #{coord.inspect}" }
516 | @draw.ellipse(coord[0], coord[1], dot_size, dot_size, 0, 360) unless @data[i] > @maximum_value
517 | end
518 | @draw.stroke(line_color)
519 | end
520 |
521 | if @options[:dot_values] == false
522 | drawbox(coords[@norm_data.index(@norm_data.min)], 2, min_color) if has_min == true
523 | drawbox(coords[@norm_data.index(@norm_data.max)], 2, max_color) if has_max == true
524 | drawbox(coords[-1], 2, last_color) if has_last == true
525 | else
526 | draw_circle(coords[@norm_data.index(@norm_data.min)], dot_size, min_color) if has_min == true
527 | draw_circle(coords[@norm_data.index(@norm_data.max)], dot_size, max_color) if has_max == true
528 | draw_circle(coords[-1], dot_size, last_color) if has_last == true
529 | end
530 |
531 | @draw.draw(@canvas)
532 | @canvas
533 | end
534 |
535 |
536 | ##
537 | # Creates a whisker sparkline to track on/off type data. There are five states:
538 | # on, off, no value, exceptional on, exceptional off. On values create an up
539 | # whisker and off values create a down whisker. Exceptional values may be
540 | # colored differently than regular values to indicate, for example, a shut out.
541 | # No value produces an empty row to indicate a tie.
542 | #
543 | # * results - an array of integer values between -2 and 2. -2 is exceptional
544 | # down, -1 is regular down, 0 is no value, 1 is up, and 2 is exceptional up.
545 | # * options - a hash that takes parameters
546 | #
547 | # :height - height of the sparkline
548 | #
549 | # :whisker_color - the color of regular whiskers; defaults to black
550 | #
551 | # :exception_color - the color of exceptional whiskers; defaults to red
552 | #
553 | # :step - Spacing for whiskers. Includes the whisker itself. Default 2.
554 |
555 | def whisker
556 |
557 | step = @options[:step].to_i
558 | height = @options[:height].to_f
559 | background_color = @options[:background_color]
560 |
561 | create_canvas(@data.size * step - 1, height, background_color)
562 |
563 | whisker_color = @options[:whisker_color] || 'black'
564 | exception_color = @options[:exception_color] || 'red'
565 |
566 | on_row = (@canvas.rows/2.0 - 1).ceil
567 | off_row = (@canvas.rows/2.0).floor
568 | i = 0
569 | @data.each do |r|
570 | color = whisker_color
571 |
572 | if ( (r == 2 || r == -2) && exception_color )
573 | color = exception_color
574 | end
575 |
576 | y_mid_point = (r >= 1) ? on_row : off_row
577 |
578 | y_end_point = y_mid_point
579 | if ( r > 0)
580 | y_end_point = 0
581 | end
582 |
583 | if ( r < 0 )
584 | y_end_point = @canvas.rows
585 | end
586 |
587 | @draw.stroke( color )
588 | @draw.line( i, y_mid_point, i, y_end_point )
589 | i += step
590 | end
591 |
592 | @draw.draw(@canvas)
593 | @canvas
594 | end
595 |
596 | ##
597 | # A bullet graph, a la Stephen Few in "Information Dashboard Design."
598 | #
599 | # * data - A single value for the thermometer part of the bullet.
600 | # Represents the current value.
601 | # * options - a hash
602 | #
603 | # :good - Numeric. Maximum value that will be shown on the graph. Required.
604 | #
605 | # :height - Numeric. Defaults to 15. Should be a multiple of three.
606 | #
607 | # :width - This graph expands to any specified width. Defaults to 100.
608 | #
609 | # :bad - Numeric. A darker shade background will be drawn up to this point.
610 | #
611 | # :satisfactory - Numeric. A medium background will be drawn up to this point.
612 | #
613 | # :target - Numeric value. A thin vertical bar will be drawn.
614 | #
615 | # :good_color - Color for the rightmost section of the bullet.
616 | #
617 | # :satisfactory_color - Color for the middle background of the bullet.
618 | #
619 | # :bad_color - Color for the lowest, leftmost section.
620 |
621 | def bullet
622 | height = @options[:height].to_f
623 | @graph_width = @options.has_key?(:width) ? @options[:width].to_f : 100.0
624 | good_color = @options.has_key?(:good_color) ? @options[:good_color] : '#eeeeee'
625 | satisfactory_color = @options.has_key?(:satisfactory_color) ? @options[:satisfactory_color] : '#bbbbbb'
626 | bad_color = @options.has_key?(:bad_color) ? @options[:bad_color] : '#999999'
627 | bullet_color = @options.has_key?(:bullet_color) ? @options[:bullet_color] : 'black'
628 | @thickness = height/3.0
629 |
630 | create_canvas(@graph_width, height, good_color)
631 |
632 | @value = @norm_data
633 | @good_value = @options[:good].to_f
634 |
635 | @graph_height = @options[:height]
636 |
637 | qualitative_range_colors = [satisfactory_color, bad_color]
638 | [:satisfactory, :bad].each_with_index do |indicator, index|
639 | next unless @options.has_key?(indicator)
640 | @draw = @draw.fill(qualitative_range_colors[index])
641 | indicator_width_x = @graph_width * (@options[indicator].to_f / @good_value)
642 | @draw = @draw.rectangle(0, 0, indicator_width_x.to_i, @graph_height)
643 | end
644 |
645 | if @options.has_key?(:target)
646 | @draw = @draw.fill(bullet_color)
647 | target_x = @graph_width * (@options[:target].to_f / @good_value)
648 | half_thickness = (@thickness / 2.0).to_i
649 | bar_width = 1.0
650 | @draw = @draw.rectangle(target_x.to_i, half_thickness, (target_x + bar_width).to_i, @thickness * 2 + half_thickness)
651 | end
652 |
653 | # Value
654 | @draw = @draw.fill(bullet_color)
655 | @draw = @draw.rectangle(0, @thickness.to_i, @graph_width * (@data.first.to_f / @good_value), (@thickness * 2.0).to_i)
656 |
657 | @draw.draw(@canvas)
658 | @canvas
659 | end
660 |
661 | ##
662 | # Draw the error Sparkline.
663 |
664 | def plot_error(options={})
665 | create_canvas(40, 15, 'white')
666 |
667 | @draw.fill('red')
668 | @draw.line(0,0,40,15)
669 | @draw.line(0,15,40,0)
670 |
671 | @draw.draw(@canvas)
672 | @canvas
673 | end
674 |
675 | private
676 |
677 | def normalize_data
678 | case @options[:type].to_s
679 | when 'bar'
680 | @minimum_value = @options[:min] || 0.0
681 | else
682 | @minimum_value = @options[:min] || @data.compact.min
683 | end
684 |
685 | @maximum_value = @options[:max] || @data.compact.max
686 |
687 | case @options[:type].to_s
688 | when 'pie'
689 | @norm_data = @data
690 | when 'bullet'
691 | @norm_data = @data
692 | else
693 | @norm_data = @data.map do |value|
694 | if @maximum_value == @minimum_value
695 | value = @maximum_value
696 | else
697 | value = normalize(value)
698 | end
699 | end
700 | end
701 | end
702 |
703 | ##
704 | # :arr - an array of points (represented as two element arrays)
705 |
706 | def open_ended_polyline(arr)
707 | 0.upto(arr.length - 2) { |i|
708 | @draw.line(arr[i][0], arr[i][1], arr[i+1][0], arr[i+1][1])
709 | }
710 | end
711 |
712 | # Fills in the area under the line (used for a smooth graph)
713 | def closed_polygon(height, width, coords, dot_size = 0)
714 | return if @options[:underneath_color].nil?
715 |
716 | list = []
717 |
718 | list << [coords.first.first - 1 + (dot_size / 2.0), height - 3 - (dot_size / 2.0)]
719 | list << [coords.first.first - 1 + (dot_size / 2.0), coords.first.last]
720 |
721 | list << coords
722 |
723 | list << [coords.last.first + 1 - (dot_size / 2.0), coords.last.last]
724 | list << [coords.last.first + 1 - (dot_size / 2.0), height - 3 - (dot_size / 2.0)]
725 |
726 | @draw.stroke(@options[:underneath_color])
727 | @draw.fill(@options[:underneath_color])
728 | @draw.polygon( *list.flatten )
729 | end
730 |
731 | ##
732 | # Create an image to draw on and a drawable to do the drawing with.
733 | #
734 | # TODO Refactor into smaller methods
735 |
736 | def create_canvas(w, h, bkg_col)
737 | @draw = Magick::Draw.new
738 | @draw.pointsize = @@pointsize # TODO Use height
739 | @draw.pointsize = @options[:font_size] if @options.has_key?(:font_size)
740 | @canvas = Magick::Image.new(w , h) { self.background_color = bkg_col }
741 |
742 | # Make room for label and last value
743 | unless @options[:label].nil?
744 | if (@options[:has_last].nil?)
745 | @options[:has_last] = true
746 | end
747 | @label_width = calculate_width(@options[:label])
748 | @data_last_width = calculate_width(formatted_last_data_string)
749 |
750 | # TODO: Must figure out correct spacing
751 | @label_and_data_last_width = @label_width + @data_last_width + @@label_margin * 3.0
752 | w += @label_and_data_last_width
753 | end
754 |
755 | @canvas = Magick::Image.new(w , h) { self.background_color = bkg_col }
756 | @canvas.format = "PNG"
757 |
758 | # Draw label and last value
759 | unless @options[:label].nil?
760 | if ENV.has_key?('MAGICK_FONT_PATH')
761 | vera_font_path = File.expand_path('Vera.ttf', ENV['MAGICK_FONT_PATH'])
762 | @font = File.exists?(vera_font_path) ? vera_font_path : nil
763 | else
764 | @font = nil
765 | end
766 | @font = @options[:font] if @options.has_key?(:font)
767 |
768 | @draw.fill = 'black'
769 | @draw.font = @font if @font
770 | @draw.gravity = Magick::WestGravity
771 | @draw.annotate( @canvas,
772 | @label_width, 1.0,
773 | w - @label_and_data_last_width + @@label_margin, h - calculate_caps_height/2.0,
774 | @options[:label])
775 |
776 | @draw.fill = 'red'
777 | @draw.annotate( @canvas,
778 | @data_last_width, 1.0,
779 | w - @data_last_width - @@label_margin, h - calculate_caps_height/2.0,
780 | formatted_last_data_string)
781 | end
782 | end
783 |
784 | def formatted_last_data_string
785 | if @options[:label_format]
786 | return sprintf(@options[:label_format], @data.last)
787 | end
788 | @data.last.to_s
789 | end
790 |
791 | ##
792 | # Utility to draw a coloured box
793 | # Centred on pt, offset off in each direction, fill color is col
794 |
795 | def drawbox(pt, offset, color)
796 | @draw.stroke 'transparent'
797 | @draw.fill(color)
798 | @draw.rectangle(pt[0]-offset, pt[1]-offset, pt[0]+offset, pt[1]+offset)
799 | end
800 |
801 | ##
802 | # Utility to draw a coloured circle
803 | # Centred on pt, radius of radius in each direction, fill color is color
804 |
805 | def draw_circle(pt, radius, color)
806 | # @draw.stroke('transparent')
807 | @draw.stroke(color)
808 | @draw.fill(color)
809 | @draw.ellipse(pt[0], pt[1], radius, radius, 0, 360)
810 | end
811 |
812 |
813 | ##
814 | # Utility to draw the standard deviation box
815 | #
816 | def drawstddevbox(width,height,color)
817 | mid=@norm_data.inject(0) {|sum,v| sum+=v}/@norm_data.size
818 | std_dev = standard_deviation(@norm_data)
819 | lower = (height - 3 - (mid-std_dev)/(101.0/(height-4)))
820 | upper = (height - 3 - (mid+std_dev)/(101.0/(height-4)))
821 | @draw.stroke 'transparent'
822 | @draw.fill(color)
823 | @draw.rectangle(0, lower, width, upper)
824 | end
825 |
826 | def calculate_width(text)
827 | @draw.get_type_metrics(@canvas, text.to_s).width
828 | end
829 |
830 | def calculate_caps_height
831 | @draw.get_type_metrics(@canvas, 'X').height
832 | end
833 |
834 | ##
835 | # Calculation helper for standard deviation.
836 | #
837 | # Thanks to Warren Seen
838 | # http://warrenseen.com/blog/2006/03/13/how-to-calculate-standard-deviation/
839 | def variance(population)
840 | n = 0
841 | mean = 0.0
842 | s = 0.0
843 | population.each { |x|
844 | n = n + 1
845 | delta = x - mean
846 | mean = mean + (delta / n)
847 | s = s + delta * (x - mean)
848 | }
849 | # if you want to calculate std deviation
850 | # of a sample change this to "s / (n-1)"
851 | return s / n
852 | end
853 |
854 | ##
855 | # Calculate the standard deviation of a population
856 | #
857 | # accepts: an array, the population
858 | # returns: the standard deviation
859 | def standard_deviation(population)
860 | Math.sqrt(variance(population))
861 | end
862 |
863 | def normalize(x)
864 | if x.nil?
865 | nil
866 | else
867 | @minimum_value == @maximum_value ? @maximum_value :
868 | (x.to_f - @minimum_value) * 100 / (@maximum_value - @minimum_value)
869 | end
870 | end
871 |
872 | end
873 |
--------------------------------------------------------------------------------