willbryant-scruffy 0.2.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGES.txt +115 -0
  2. data/LICENCE.txt +20 -0
  3. data/Manifest.txt +74 -0
  4. data/README.txt +66 -0
  5. data/lib/scruffy.rb +30 -0
  6. data/lib/scruffy/components.rb +22 -0
  7. data/lib/scruffy/components/axes.rb +18 -0
  8. data/lib/scruffy/components/background.rb +24 -0
  9. data/lib/scruffy/components/base.rb +57 -0
  10. data/lib/scruffy/components/data_markers.rb +41 -0
  11. data/lib/scruffy/components/graphs.rb +51 -0
  12. data/lib/scruffy/components/grid.rb +54 -0
  13. data/lib/scruffy/components/label.rb +17 -0
  14. data/lib/scruffy/components/legend.rb +147 -0
  15. data/lib/scruffy/components/style_info.rb +22 -0
  16. data/lib/scruffy/components/title.rb +19 -0
  17. data/lib/scruffy/components/value_markers.rb +25 -0
  18. data/lib/scruffy/components/viewport.rb +37 -0
  19. data/lib/scruffy/formatters.rb +233 -0
  20. data/lib/scruffy/graph.rb +205 -0
  21. data/lib/scruffy/graph_state.rb +29 -0
  22. data/lib/scruffy/helpers.rb +13 -0
  23. data/lib/scruffy/helpers/canvas.rb +41 -0
  24. data/lib/scruffy/helpers/layer_container.rb +119 -0
  25. data/lib/scruffy/helpers/marker_helper.rb +25 -0
  26. data/lib/scruffy/helpers/meta.rb +5 -0
  27. data/lib/scruffy/helpers/point_container.rb +99 -0
  28. data/lib/scruffy/layers.rb +28 -0
  29. data/lib/scruffy/layers/all_smiles.rb +137 -0
  30. data/lib/scruffy/layers/area.rb +46 -0
  31. data/lib/scruffy/layers/average.rb +67 -0
  32. data/lib/scruffy/layers/bar.rb +68 -0
  33. data/lib/scruffy/layers/base.rb +211 -0
  34. data/lib/scruffy/layers/box.rb +114 -0
  35. data/lib/scruffy/layers/line.rb +46 -0
  36. data/lib/scruffy/layers/multi.rb +74 -0
  37. data/lib/scruffy/layers/multi_bar.rb +51 -0
  38. data/lib/scruffy/layers/pie.rb +123 -0
  39. data/lib/scruffy/layers/pie_slice.rb +119 -0
  40. data/lib/scruffy/layers/scatter.rb +29 -0
  41. data/lib/scruffy/layers/sparkline_bar.rb +39 -0
  42. data/lib/scruffy/layers/stacked.rb +87 -0
  43. data/lib/scruffy/rasterizers.rb +14 -0
  44. data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
  45. data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
  46. data/lib/scruffy/renderers.rb +23 -0
  47. data/lib/scruffy/renderers/axis_legend.rb +41 -0
  48. data/lib/scruffy/renderers/base.rb +95 -0
  49. data/lib/scruffy/renderers/cubed.rb +44 -0
  50. data/lib/scruffy/renderers/cubed3d.rb +53 -0
  51. data/lib/scruffy/renderers/empty.rb +22 -0
  52. data/lib/scruffy/renderers/pie.rb +20 -0
  53. data/lib/scruffy/renderers/reversed.rb +17 -0
  54. data/lib/scruffy/renderers/sparkline.rb +10 -0
  55. data/lib/scruffy/renderers/split.rb +48 -0
  56. data/lib/scruffy/renderers/standard.rb +37 -0
  57. data/lib/scruffy/themes.rb +175 -0
  58. data/lib/scruffy/version.rb +9 -0
  59. data/test/graph_creation_test.rb +286 -0
  60. data/test/test_helper.rb +2 -0
  61. metadata +136 -0
@@ -0,0 +1,51 @@
1
+ module Scruffy
2
+ module Components
3
+
4
+ # Component for displaying Graphs layers.
5
+ #
6
+ # Is passed all graph layers from the Graph object.
7
+ #
8
+ # (This may change as the capability for Graph filtering and such fills out.)
9
+ class Graphs < Base
10
+ STACKED_OPACITY = 0.85;
11
+
12
+ def draw(svg, bounds, options={})
13
+ # If Graph is limited to a category, reject layers outside of it's scope.
14
+ applicable_layers = options[:layers].reject do |l|
15
+ if @options[:only]
16
+ (l.options[:category].nil? && l.options[:categories].nil?) ||
17
+ (!l.options[:category].nil? && l.options[:category] != @options[:only]) ||
18
+ (!l.options[:categories].nil? && !l.options[:categories].include?(@options[:only]))
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ applicable_layers.each_with_index do |layer, idx|
25
+ layer_options = {}
26
+ layer_options[:index] = idx
27
+ layer_options[:min_value] = options[:min_value]
28
+ layer_options[:max_value] = options[:max_value]
29
+ layer_options[:min_key] = options[:min_key]
30
+ layer_options[:max_key] = options[:max_key]
31
+ layer_options[:complexity] = options[:complexity]
32
+ layer_options[:size] = [bounds[:width], bounds[:height]]
33
+ layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
34
+ layer_options[:outline] = layer.preferred_outline || layer.outline || options[:theme].next_outline
35
+ layer_options[:opacity] = opacity_for(idx)
36
+ layer_options[:theme] = options[:theme]
37
+
38
+ svg.g(:id => "component_#{id}_graph_#{idx}", :class => 'graph_layer') {
39
+ layer.render(svg, layer_options)
40
+ }
41
+ end # applicable_layers
42
+ end # draw
43
+
44
+ protected
45
+ def opacity_for(idx)
46
+ (idx == 0) ? 1.0 : (@options[:stacked_opacity] || STACKED_OPACITY)
47
+ end
48
+
49
+ end #class
50
+ end
51
+ end
@@ -0,0 +1,54 @@
1
+ module Scruffy
2
+ module Components
3
+ class Grid < Base
4
+ attr_accessor :markers
5
+
6
+ include Scruffy::Helpers::Marker
7
+
8
+ def draw(svg, bounds, options={})
9
+ markers = (options[:markers] || self.markers) || 5
10
+
11
+ stroke_width = options[:stroke_width]
12
+
13
+ each_marker(markers, options[:min_value], options[:max_value], bounds[:height], options, :value_formatter) do |label, y|
14
+ svg.line(:x1 => 0, :y1 => y, :x2 => bounds[:width], :y2 => y, :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
15
+ end
16
+
17
+ #add a 0 line
18
+ y = (options[:max_value] * bounds[:height])/(options[:max_value] - options[:min_value])
19
+ svg.line(:x1 => 0, :y1 => y, :x2 => bounds[:width], :y2 => y, :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
20
+
21
+ end
22
+ end
23
+
24
+ class VGrid < Base
25
+ attr_accessor :markers
26
+
27
+ include Scruffy::Helpers::Marker
28
+
29
+ def draw(svg, bounds, options={})
30
+
31
+ if options[:graph].point_markers #get vertical grid lines up with points if there are labels for them
32
+ point_distance = bounds[:width] / (options[:graph].point_markers.size).to_f
33
+ stroke_width = options[:stroke_width]
34
+ (0...options[:graph].point_markers.size).map do |idx|
35
+ x = point_distance * idx + point_distance/2
36
+ svg.line(:x1 => x, :y1 => 0, :x2 => x, :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
37
+ end
38
+ #add the far right and far left lines
39
+ svg.line(:x1 => 0, :y1 => 0, :x2 => 0, :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
40
+ svg.line(:x1 => bounds[:width], :y1 => 0, :x2 => bounds[:width], :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
41
+ else
42
+
43
+ markers = (options[:key_markers] || self.markers) || 5 #options[:point_markers].size#
44
+ stroke_width = options[:stroke_width]
45
+ each_marker(markers, options[:min_key], options[:max_key], bounds[:width], options, :key_formatter) do |label, x|
46
+ svg.line(:x1 => x, :y1 => 0, :x2 => x, :y2 => bounds[:height], :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: #{stroke_width};")
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,17 @@
1
+ module Scruffy
2
+ module Components
3
+ class Label < Base
4
+ def draw(svg, bounds, options={})
5
+ svg.text(@options[:text],
6
+ :class => 'text',
7
+ :x => (bounds[:width] / 2),
8
+ :y => bounds[:height],
9
+ 'font-size' => relative(100),
10
+ 'font-family' => options[:theme].font_family,
11
+ :fill => options[:theme].marker,
12
+ :stroke => 'none', 'stroke-width' => '0',
13
+ 'text-anchor' => (@options[:text_anchor] || 'middle'))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,147 @@
1
+ module Scruffy::Components
2
+
3
+ class XLegend < Base
4
+ def draw(svg, bounds, options={})
5
+ if options[:title]
6
+ svg.text(options[:x_legend],
7
+ :class => 'title',
8
+ :x => (bounds[:width] / 2),
9
+ :y => bounds[:height],
10
+ 'font-size' => options[:theme].legend_font_size || relative(100),
11
+ 'font-family' => options[:theme].font_family,
12
+ :fill => options[:theme].marker,
13
+ :stroke => 'none', 'stroke-width' => '0',
14
+ 'text-anchor' => (@options[:text_anchor] || 'middle'))
15
+ end
16
+ end
17
+ end #XLegend
18
+
19
+
20
+
21
+ class YLegend < Base
22
+ def draw(svg, bounds, options={})
23
+ if options[:title]
24
+ svg.text(options[:y_legend],
25
+ :class => 'title',
26
+ :x => (0),
27
+ :y => 0,
28
+ 'font-size' => options[:theme].legend_font_size || relative(100),
29
+ 'font-family' => options[:theme].font_family,
30
+ :transform => "translate(#{bounds[:width] / 2},#{bounds[:height]/2}) rotate(#{-90})",
31
+ :fill => options[:theme].marker,
32
+ :stroke => 'none', 'stroke-width' => '0',
33
+ 'text-anchor' => (@options[:text_anchor] || 'middle'))
34
+ end
35
+ end
36
+ end #YLegend
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+ class Legend < Base
46
+ FONT_SIZE = 80
47
+
48
+ def draw(svg, bounds, options={})
49
+ vertical = options[:vertical_legend]
50
+ legend_info = relevant_legend_info(options[:layers])
51
+ @line_height, x, y, size = 0
52
+ if vertical
53
+ set_line_height = 0.08 * bounds[:height]
54
+ @line_height = bounds[:height] / legend_info.length
55
+ @line_height = set_line_height if @line_height >
56
+ set_line_height
57
+ else
58
+ set_line_height = 0.90 * bounds[:height]
59
+ @line_height = set_line_height
60
+ end
61
+
62
+ text_height = @line_height * FONT_SIZE / 100
63
+ # #TODO how does this related to @points?
64
+ active_width, points = layout(legend_info, vertical)
65
+
66
+ offset = (bounds[:width] - active_width) / 2 # Nudge over a bit for true centering
67
+
68
+ # Render Legend
69
+ points.each_with_index do |point, idx|
70
+ if vertical
71
+ x = 0
72
+ y = point
73
+ size = @line_height * 0.5
74
+ else
75
+ x = offset + point
76
+ y = 0
77
+ size = relative(50)
78
+ end
79
+
80
+ # "#{x} #{y} #{@line_height} #{size}"
81
+
82
+ svg.rect(:x => x,
83
+ :y => y,
84
+ :width => size,
85
+ :height => size,
86
+ :fill => legend_info[idx][:color])
87
+
88
+ svg.text(legend_info[idx][:title],
89
+ :x => x + @line_height,
90
+ :y => y + text_height * 0.75,
91
+ 'font-size' => text_height,
92
+ 'font-family' => options[:theme].font_family,
93
+ :style => "color: #{options[:theme].marker || 'white'}",
94
+ :fill => (options[:theme].marker || 'white'))
95
+ end
96
+ end # draw
97
+
98
+ protected
99
+ # Collects Legend Info from the provided Layers.
100
+ #
101
+ # Automatically filters by legend's categories.
102
+ def relevant_legend_info(layers, categories=(@options[:category] ? [@options[:category]] : @options[:categories]))
103
+ legend_info = layers.inject([]) do |arr, layer|
104
+ if categories.nil? ||
105
+ (categories.include?(layer.options[:category]) ||
106
+ (layer.options[:categories] && (categories & layer.options[:categories]).size > 0) )
107
+
108
+ data = layer.legend_data
109
+ arr << data if data.is_a?(Hash)
110
+ arr = arr + data if data.is_a?(Array)
111
+ end
112
+ arr
113
+ end
114
+ end # relevant_legend_info
115
+
116
+ # Returns an array consisting of the total width needed by the legend
117
+ # information, as well as an array of @x-coords for each element. If
118
+ # vertical, then these are @y-coords, and @x is 0
119
+ #
120
+ # ie: [200, [0, 50, 100, 150]]
121
+ def layout(legend_info_array, vertical = false)
122
+ if vertical
123
+ longest = 0
124
+ legend_info_array.each {|elem|
125
+ cur_length = relative(50) * elem[:title].length
126
+ longest = longest < cur_length ? cur_length : longest
127
+ }
128
+ y_positions = []
129
+ (0..legend_info_array.length - 1).each {|y|
130
+ y_positions << y * @line_height
131
+ }
132
+ [longest, y_positions]
133
+ else
134
+ legend_info_array.inject([0, []]) do |enum, elem|
135
+ enum[0] += (relative(50) * 2) if enum.first != 0 # Add spacer between elements
136
+ enum[1] << enum.first # Add location to points
137
+ enum[0] += relative(50) # Add room for color box
138
+ enum[0] += (relative(50) * elem[:title].length) # Add room for text
139
+
140
+ [enum.first, enum.last]
141
+ end
142
+ end
143
+ end
144
+
145
+ end # class Legend
146
+
147
+ end # Scruffy::Components
@@ -0,0 +1,22 @@
1
+ module Scruffy
2
+ module Components
3
+ # Component used for adding CSS styling to SVG graphs.
4
+ #
5
+ # In hindsight, ImageMagick and Mozilla SVG's handling of CSS styling is
6
+ # extremely inconsistant, so use this at your own risk.
7
+ class StyleInfo < Base
8
+ def initialize(*args)
9
+ super
10
+
11
+ @visible = false
12
+ end
13
+ def process(svg, options={})
14
+ svg.defs {
15
+ svg.style(:type => "text/css") {
16
+ svg.cdata!("\n#{options[:selector]} {\n #{options[:style]}\n}\n")
17
+ }
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Scruffy
2
+ module Components
3
+ class Title < Base
4
+ def draw(svg, bounds, options={})
5
+ if options[:title]
6
+ svg.text(options[:title],
7
+ :class => 'title',
8
+ :x => (bounds[:width] / 2),
9
+ :y => bounds[:height],
10
+ 'font-size' => options[:theme].title_font_size || relative(100),
11
+ 'font-family' => options[:theme].font_family,
12
+ :fill => options[:theme].marker,
13
+ :stroke => 'none', 'stroke-width' => '0',
14
+ 'text-anchor' => (@options[:text_anchor] || 'middle'))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module Scruffy
2
+ module Components
3
+ class ValueMarkers < Base
4
+
5
+ include Scruffy::Helpers::Marker
6
+
7
+ attr_accessor :markers
8
+
9
+ def draw(svg, bounds, options={})
10
+ markers = (options[:markers] || self.markers) || 5
11
+
12
+ each_marker(markers, options[:min_value], options[:max_value], bounds[:height], options, :value_formatter) do |label, y|
13
+
14
+ svg.text( label,
15
+ :x => bounds[:width],
16
+ :y => (bounds[:height] - y),
17
+ 'font-size' => options[:theme].marker_font_size || relative(8),
18
+ 'font-family' => options[:theme].font_family,
19
+ :fill => ((options[:marker_color_override] || options[:theme].marker) || 'white').to_s,
20
+ 'text-anchor' => 'end')
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ module Scruffy::Components
2
+ # Component used to limit other visual components to a certain area on the graph.
3
+ class Viewport < Base
4
+ include Scruffy::Helpers::Canvas
5
+
6
+ def initialize(*args, &block)
7
+ super(*args)
8
+
9
+ self.components = []
10
+ block.call(self.components) if block
11
+ end
12
+
13
+ def draw(svg, bounds, options={})
14
+ svg.g(options_for) {
15
+ self.components.each do |component|
16
+ component.render(svg,
17
+ bounds_for([bounds[:width], bounds[:height]],
18
+ component.position,
19
+ component.size),
20
+ options)
21
+ end
22
+ }
23
+ end
24
+
25
+ private
26
+ def options_for
27
+ options = {}
28
+ %w(skewX skewY).each do |option|
29
+ if @options[option.to_sym]
30
+ options[:transform] ||= ''
31
+ options[:transform] = options[:transform] + "#{option.to_s}(#{@options[option.to_sym]})"
32
+ end
33
+ end
34
+ options
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,233 @@
1
+ # ===Scruffy Formatters
2
+ #
3
+ # Author:: Brasten Sager
4
+ # Date:: August 16th, 2006
5
+ #
6
+ # Formatters are used to format the values displayed on the y-axis by
7
+ # setting graph.value_formatter.
8
+ #
9
+ # Example:
10
+ #
11
+ # graph.value_formatter = Scruffy::Formatters::Currency.new(:precision => 0)
12
+ #
13
+ module Scruffy::Formatters
14
+
15
+ # == Scruffy::Formatters::Base
16
+ #
17
+ # Author:: Brasten Sager
18
+ # Date:: August 16th, 2006
19
+ #
20
+ # Formatters are used to format the values displayed on the y-axis by
21
+ # setting graph.value_formatter.
22
+ class Base
23
+
24
+ # Called by the value marker component. Routes the format call
25
+ # to one of a couple possible methods.
26
+ #
27
+ # If the formatter defines a #format method, the returned value is used
28
+ # as the value. If the formatter defines a #format! method, the value passed is
29
+ # expected to be modified, and is used as the value. (This may not actually work,
30
+ # in hindsight.)
31
+ def route_format(target, idx, options = {})
32
+ args = [target, idx, options]
33
+ if respond_to?(:format)
34
+ send :format, *args[0...self.method(:format).arity]
35
+ elsif respond_to?(:format!)
36
+ send :format!, *args[0...self.method(:format!).arity]
37
+ target
38
+ else
39
+ raise NameError, "Formatter subclass must container either a format() method or format!() method."
40
+ end
41
+ end
42
+
43
+ protected
44
+ def number_with_precision(number, precision=3) #:nodoc:
45
+ sprintf("%01.#{precision}f", number)
46
+ end
47
+ end
48
+
49
+ # Allows you to pass in a Proc for use as a formatter.
50
+ #
51
+ # Use:
52
+ #
53
+ # graph.value_formatter = Scruffy::Formatters::Custom.new { |value, idx, options| "Displays Returned Value" }
54
+ class Custom < Base
55
+ attr_reader :proc
56
+
57
+ def initialize(&block)
58
+ @proc = block
59
+ end
60
+
61
+ def format(target, idx, options)
62
+ proc.call(target, idx, options)
63
+ end
64
+ end
65
+
66
+
67
+
68
+ # Default number formatter.
69
+ # Limits precision, beautifies numbers.
70
+ class Number < Base
71
+ attr_accessor :precision, :separator, :delimiter, :precision_limit
72
+
73
+ # Returns a new Number formatter.
74
+ #
75
+ # Options:
76
+ # precision:: precision to use for value. Can be set to an integer, :none or :auto.
77
+ # :auto will use whatever precision is necessary to portray all the numerical
78
+ # information, up to :precision_limit.
79
+ #
80
+ # Example: [100.1, 100.44, 200.323] will result in [100.100, 100.440, 200.323]
81
+ #
82
+ # separator:: decimal separator. Defaults to '.'
83
+ # delimiter:: delimiter character. Defaults to ','
84
+ # precision_limit:: upper limit for auto precision. (Ignored if roundup is specified)
85
+ # roundup:: round up the number to the given interval
86
+ def initialize(options = {})
87
+ @precision = options[:precision] || :none
88
+ @roundup = options[:roundup] || :none
89
+ @separator = options[:separator] || '.'
90
+ @delimiter = options[:delimiter] || ','
91
+ @precision_limit = options[:precision_limit] || 4
92
+ end
93
+
94
+ # Formats the value.
95
+ def format(target, idx, options)
96
+ my_precision = @precision
97
+
98
+ if @precision == :auto
99
+ my_precision = options[:all_values].inject(0) do |highest, current|
100
+ cur = current.to_f.to_s.split(".").last.size
101
+ cur > highest ? cur : highest
102
+ end
103
+
104
+ my_precision = @precision_limit if my_precision > @precision_limit
105
+ elsif @precision == :none
106
+ my_precision = 0
107
+ end
108
+
109
+ my_separator = @separator
110
+ my_separator = "" unless my_precision > 0
111
+ begin
112
+ number = ""
113
+
114
+ if @roundup == :none
115
+ parts = number_with_precision(target, my_precision).split('.')
116
+ number = parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + my_separator + parts[1].to_s
117
+ else
118
+ number = roundup(target.to_f, @roundup).to_i.to_s
119
+ end
120
+
121
+ number
122
+ rescue StandardError => e
123
+ target
124
+ end
125
+ end
126
+
127
+
128
+ def roundup(target, nearest=100)
129
+ target % nearest == 0 ? target : target + nearest - (target % nearest)
130
+ end
131
+ def rounddown(target, nearest=100)
132
+ target % nearest == 0 ? target : target - (target % nearest)
133
+ end
134
+ end
135
+
136
+
137
+
138
+ # Currency formatter.
139
+ #
140
+ # Provides formatting for currencies.
141
+ class Currency < Base
142
+
143
+ # Returns a new Currency class.
144
+ #
145
+ # Options:
146
+ # precision:: precision of value
147
+ # unit:: Defaults to '$'
148
+ # separator:: Defaults to '.'
149
+ # delimiter:: Defaults to ','
150
+ # negative_color:: Color of value marker for negative values. Defaults to 'red'
151
+ # special_negatives:: If set to true, parenthesizes negative numbers. ie: -$150.50 becomes ($150.50).
152
+ # Defaults to false.
153
+ def initialize(options = {})
154
+ @precision = options[:precision] || 2
155
+ @unit = options[:unit] || '$'
156
+ @separator = options[:separator] || '.'
157
+ @delimiter = options[:delimiter] || ','
158
+ @negative_color = options[:negative_color] || 'red'
159
+ @special_negatives = options[:special_negatives] || false
160
+ end
161
+
162
+ # Formats value marker.
163
+ def format(target, idx, options)
164
+ @separator = "" unless @precision > 0
165
+ begin
166
+ parts = number_with_precision(target, @precision).split('.')
167
+ if @special_negatives && (target.to_f < 0)
168
+ number = "(" + @unit + parts[0].to_i.abs.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s + ")"
169
+ else
170
+ number = @unit + parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s
171
+ number.gsub!(@unit + '-', '-' + @unit)
172
+ end
173
+ if (target.to_f < 0) && @negative_color
174
+ options[:marker_color_override] = @negative_color
175
+ else
176
+ options[:marker_color_override] = nil
177
+ end
178
+ number
179
+ rescue
180
+ target
181
+ end
182
+ end
183
+ end
184
+
185
+ # Percentage formatter.
186
+ #
187
+ # Provides formatting for percentages.
188
+ class Percentage < Base
189
+
190
+ # Returns new Percentage formatter.
191
+ #
192
+ # Options:
193
+ # precision:: Defaults to 3.
194
+ # separator:: Defaults to '.'
195
+ def initialize(options = {})
196
+ @precision = options[:precision] || 3
197
+ @separator = options[:separator] || '.'
198
+ end
199
+
200
+ # Formats percentages.
201
+ def format(target)
202
+ begin
203
+ number = number_with_precision(target, @precision)
204
+ parts = number.split('.')
205
+ if parts.at(1).nil?
206
+ parts[0] + "%"
207
+ else
208
+ parts[0] + @separator + parts[1].to_s + "%"
209
+ end
210
+ rescue
211
+ target
212
+ end
213
+ end
214
+ end
215
+
216
+
217
+ class Date < Base
218
+
219
+ def initialize(format_string, options = {})
220
+ @format_string = format_string
221
+ end
222
+
223
+ # Formats percentages.
224
+ def format(target, idx, options)
225
+ begin
226
+ target.strftime(@format_string)
227
+ rescue
228
+ target
229
+ end
230
+ end
231
+ end
232
+
233
+ end