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.
- data/CHANGES.txt +115 -0
- data/LICENCE.txt +20 -0
- data/Manifest.txt +74 -0
- data/README.txt +66 -0
- data/lib/scruffy.rb +30 -0
- data/lib/scruffy/components.rb +22 -0
- data/lib/scruffy/components/axes.rb +23 -0
- data/lib/scruffy/components/background.rb +24 -0
- data/lib/scruffy/components/base.rb +57 -0
- data/lib/scruffy/components/data_markers.rb +41 -0
- data/lib/scruffy/components/graphs.rb +52 -0
- data/lib/scruffy/components/grid.rb +57 -0
- data/lib/scruffy/components/label.rb +17 -0
- data/lib/scruffy/components/legend.rb +147 -0
- data/lib/scruffy/components/style_info.rb +22 -0
- data/lib/scruffy/components/title.rb +19 -0
- data/lib/scruffy/components/value_markers.rb +25 -0
- data/lib/scruffy/components/viewport.rb +37 -0
- data/lib/scruffy/formatters.rb +233 -0
- data/lib/scruffy/graph.rb +205 -0
- data/lib/scruffy/graph_state.rb +29 -0
- data/lib/scruffy/helpers.rb +13 -0
- data/lib/scruffy/helpers/canvas.rb +41 -0
- data/lib/scruffy/helpers/layer_container.rb +119 -0
- data/lib/scruffy/helpers/marker_helper.rb +25 -0
- data/lib/scruffy/helpers/meta.rb +5 -0
- data/lib/scruffy/helpers/point_container.rb +99 -0
- data/lib/scruffy/layers.rb +28 -0
- data/lib/scruffy/layers/all_smiles.rb +137 -0
- data/lib/scruffy/layers/area.rb +46 -0
- data/lib/scruffy/layers/average.rb +67 -0
- data/lib/scruffy/layers/bar.rb +73 -0
- data/lib/scruffy/layers/base.rb +211 -0
- data/lib/scruffy/layers/box.rb +114 -0
- data/lib/scruffy/layers/line.rb +46 -0
- data/lib/scruffy/layers/multi.rb +74 -0
- data/lib/scruffy/layers/multi_bar.rb +51 -0
- data/lib/scruffy/layers/pie.rb +123 -0
- data/lib/scruffy/layers/pie_slice.rb +119 -0
- data/lib/scruffy/layers/scatter.rb +29 -0
- data/lib/scruffy/layers/sparkline_bar.rb +39 -0
- data/lib/scruffy/layers/stacked.rb +87 -0
- data/lib/scruffy/rasterizers.rb +14 -0
- data/lib/scruffy/rasterizers/batik_rasterizer.rb +39 -0
- data/lib/scruffy/rasterizers/mini_magick_rasterizer.rb +24 -0
- data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +27 -0
- data/lib/scruffy/renderers.rb +23 -0
- data/lib/scruffy/renderers/axis_legend.rb +41 -0
- data/lib/scruffy/renderers/base.rb +95 -0
- data/lib/scruffy/renderers/cubed.rb +44 -0
- data/lib/scruffy/renderers/cubed3d.rb +53 -0
- data/lib/scruffy/renderers/empty.rb +22 -0
- data/lib/scruffy/renderers/pie.rb +20 -0
- data/lib/scruffy/renderers/reversed.rb +17 -0
- data/lib/scruffy/renderers/sparkline.rb +10 -0
- data/lib/scruffy/renderers/split.rb +48 -0
- data/lib/scruffy/renderers/standard.rb +37 -0
- data/lib/scruffy/themes.rb +177 -0
- data/lib/scruffy/version.rb +9 -0
- data/test/graph_creation_test.rb +286 -0
- data/test/test_helper.rb +2 -0
- 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
|