tlconnor-scruffy 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) 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 +23 -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 +52 -0
  12. data/lib/scruffy/components/grid.rb +57 -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 +73 -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/mini_magick_rasterizer.rb +24 -0
  46. data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
  47. data/lib/scruffy/renderers.rb +23 -0
  48. data/lib/scruffy/renderers/axis_legend.rb +41 -0
  49. data/lib/scruffy/renderers/base.rb +95 -0
  50. data/lib/scruffy/renderers/cubed.rb +44 -0
  51. data/lib/scruffy/renderers/cubed3d.rb +53 -0
  52. data/lib/scruffy/renderers/empty.rb +22 -0
  53. data/lib/scruffy/renderers/pie.rb +20 -0
  54. data/lib/scruffy/renderers/reversed.rb +17 -0
  55. data/lib/scruffy/renderers/sparkline.rb +10 -0
  56. data/lib/scruffy/renderers/split.rb +48 -0
  57. data/lib/scruffy/renderers/standard.rb +37 -0
  58. data/lib/scruffy/themes.rb +177 -0
  59. data/lib/scruffy/version.rb +9 -0
  60. data/test/graph_creation_test.rb +286 -0
  61. data/test/test_helper.rb +2 -0
  62. metadata +150 -0
@@ -0,0 +1,52 @@
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[:border] = options[:border]
35
+ layer_options[:outline] = layer.preferred_outline || layer.outline || options[:theme].next_outline
36
+ layer_options[:opacity] = opacity_for(idx)
37
+ layer_options[:theme] = options[:theme]
38
+
39
+ svg.g(:id => "component_#{id}_graph_#{idx}", :class => 'graph_layer') {
40
+ layer.render(svg, layer_options)
41
+ }
42
+ end # applicable_layers
43
+ end # draw
44
+
45
+ protected
46
+ def opacity_for(idx)
47
+ (idx == 0) ? 1.0 : (@options[:stacked_opacity] || STACKED_OPACITY)
48
+ end
49
+
50
+ end #class
51
+ end
52
+ end
@@ -0,0 +1,57 @@
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
+ colour = options[:theme].grid || options[:theme].marker
14
+
15
+ each_marker(markers, options[:min_value], options[:max_value], bounds[:height], options, :value_formatter) do |label, y|
16
+ svg.line(:x1 => 0, :y1 => y, :x2 => bounds[:width], :y2 => y, :style => "stroke: #{colour.to_s}; stroke-width: #{stroke_width};")
17
+ end
18
+
19
+ #add a 0 line
20
+ y = (options[:max_value] * bounds[:height])/(options[:max_value] - options[:min_value])
21
+ svg.line(:x1 => 0, :y1 => y, :x2 => bounds[:width], :y2 => y, :style => "stroke: #{colour.to_s}; stroke-width: #{stroke_width};")
22
+
23
+ end
24
+ end
25
+
26
+ class VGrid < Base
27
+ attr_accessor :markers
28
+
29
+ include Scruffy::Helpers::Marker
30
+
31
+ def draw(svg, bounds, options={})
32
+ colour = options[:theme].grid || options[:theme].marker
33
+
34
+ if options[:graph].point_markers #get vertical grid lines up with points if there are labels for them
35
+ point_distance = bounds[:width] / (options[:graph].point_markers.size).to_f
36
+ stroke_width = options[:stroke_width]
37
+ (0...options[:graph].point_markers.size).map do |idx|
38
+ x = point_distance * idx + point_distance/2
39
+ svg.line(:x1 => x, :y1 => 0, :x2 => x, :y2 => bounds[:height], :style => "stroke: #{colour.to_s}; stroke-width: #{stroke_width};")
40
+ end
41
+ #add the far right and far left lines
42
+ svg.line(:x1 => 0, :y1 => 0, :x2 => 0, :y2 => bounds[:height], :style => "stroke: #{colour.to_s}; stroke-width: #{stroke_width};")
43
+ svg.line(:x1 => bounds[:width], :y1 => 0, :x2 => bounds[:width], :y2 => bounds[:height], :style => "stroke: #{colour.to_s}; stroke-width: #{stroke_width};")
44
+ else
45
+
46
+ markers = (options[:key_markers] || self.markers) || 5 #options[:point_markers].size#
47
+ stroke_width = options[:stroke_width]
48
+ each_marker(markers, options[:min_key], options[:max_key], bounds[:width], options, :key_formatter) do |label, x|
49
+ svg.line(:x1 => x, :y1 => 0, :x2 => x, :y2 => bounds[:height], :style => "stroke: #{colour.to_s}; stroke-width: #{stroke_width};")
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -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