scruffy 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +37 -2
- data/lib/scruffy.rb +4 -6
- data/lib/scruffy/components.rb +11 -0
- data/lib/scruffy/components/background.rb +24 -0
- data/lib/scruffy/components/base.rb +46 -0
- data/lib/scruffy/components/data_markers.rb +25 -0
- data/lib/scruffy/components/graphs.rb +48 -0
- data/lib/scruffy/components/grid.rb +14 -0
- data/lib/scruffy/components/label.rb +12 -0
- data/lib/scruffy/components/legend.rb +64 -0
- data/lib/scruffy/components/style_info.rb +17 -0
- data/lib/scruffy/components/title.rb +12 -0
- data/lib/scruffy/components/value_markers.rb +31 -0
- data/lib/scruffy/components/viewport.rb +30 -0
- data/lib/scruffy/formatters.rb +111 -0
- data/lib/scruffy/graph.rb +38 -68
- data/lib/scruffy/helpers.rb +2 -0
- data/lib/scruffy/helpers/canvas.rb +22 -0
- data/lib/scruffy/helpers/layer_container.rb +69 -0
- data/lib/scruffy/layers.rb +1 -0
- data/lib/scruffy/layers/all_smiles.rb +4 -0
- data/lib/scruffy/layers/area.rb +3 -2
- data/lib/scruffy/layers/average.rb +3 -12
- data/lib/scruffy/layers/bar.rb +1 -1
- data/lib/scruffy/layers/base.rb +35 -20
- data/lib/scruffy/layers/line.rb +2 -2
- data/lib/scruffy/layers/stacked.rb +75 -0
- data/lib/scruffy/rasterizers.rb +2 -1
- data/lib/scruffy/rasterizers/batik_rasterizer.rb +25 -0
- data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +6 -1
- data/lib/scruffy/renderers.rb +5 -0
- data/lib/scruffy/renderers/base.rb +37 -0
- data/lib/scruffy/renderers/cubed.rb +36 -0
- data/lib/scruffy/renderers/reversed.rb +18 -0
- data/lib/scruffy/renderers/split.rb +34 -0
- data/lib/scruffy/renderers/standard.rb +14 -152
- data/lib/scruffy/themes.rb +63 -19
- data/lib/scruffy/version.rb +1 -1
- data/spec/graph_spec.rb +33 -1
- data/spec/layers/line_spec.rb +9 -0
- metadata +26 -4
- data/lib/scruffy/resolver.rb +0 -14
- data/lib/scruffy/transformer.rb +0 -73
data/CHANGES
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
= Scruffy Changelog
|
2
2
|
|
3
|
+
== Version 0.2.0
|
4
|
+
(August 14th, 2006)
|
5
|
+
|
6
|
+
- Lots of changes, hold on tight:
|
7
|
+
|
8
|
+
* Redesigned rendering system to a component-based design.
|
9
|
+
All objects on the canvas are components that can be re-arranged via renderers.
|
10
|
+
* Created default renderer for basic Gruff-like layout.
|
11
|
+
* Added Reversed and Cubed renderers to demonstrate the customization abilities (plus, they're cool).
|
12
|
+
* Added Split renderer.
|
13
|
+
* Created Viewport component to help with Cubed.
|
14
|
+
- Viewport lets you scale it's inner components and move around the
|
15
|
+
graph. Its components' sizes and positions are relative to the viewport,
|
16
|
+
not the graph.
|
17
|
+
* Set title to respect marker color if available.
|
18
|
+
* Respects :to option in Graph#render for SVG output to file.
|
19
|
+
* Stacked layer type -- accepts layers which it then uses to create a stacked graph. Such as Bar graphs
|
20
|
+
and Area graphs.
|
21
|
+
* Abstracted out layer_container functionality to helper module (for stacked graph)
|
22
|
+
* Renamed value_transformers to value_formatters.
|
23
|
+
* Refined Value Formatters.
|
24
|
+
- Created default: Number.
|
25
|
+
- Respects float precision
|
26
|
+
- Allows for "auto-precision", which will use the largest precision (up to a customizable limit)
|
27
|
+
necessary to portray the values correctly. ie: 5.1, 6.32, 7.142 becomes '5.100', '6.320', '7.142'
|
28
|
+
* Modified Legend component, Layers, and Graph component to respect categories.
|
29
|
+
- ie: Creating a Bar layer with :category => :sales and a Graph with :category => :qa will result in
|
30
|
+
the Bay layer not being displayed. Allows for more than one Graph viewport on a screen with different
|
31
|
+
layers.
|
32
|
+
* Improved rasterizing at smaller sizes( < 300px) by rasterizing the image at a larger size first, then
|
33
|
+
allowing RMagick to resize the image with specific filtering/blurring. Actually looks better than just
|
34
|
+
rasterizing the SVG at the small size from the beginning.
|
35
|
+
* Fixed Opacity on stacked graphs.
|
36
|
+
* Added Style (invisible) components to allow for CSS styling. (Not recommended, however.)
|
37
|
+
* Added Label component for arbitrary text.
|
38
|
+
* Created Theme object in place of theme hash.
|
39
|
+
|
3
40
|
== Version 0.1.0
|
4
41
|
(August 11th, 2006)
|
5
42
|
|
@@ -7,8 +44,6 @@
|
|
7
44
|
* Legend rendering
|
8
45
|
* Rasterizing graph to multiple image types (graph.render :as => 'PNG')
|
9
46
|
|
10
|
-
|
11
|
-
|
12
47
|
== Version 0.0.12
|
13
48
|
(August 10th, 2006)
|
14
49
|
This is not a public release.
|
data/lib/scruffy.rb
CHANGED
@@ -4,14 +4,12 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
4
4
|
require 'rubygems'
|
5
5
|
require_gem 'builder', '>= 2.0'
|
6
6
|
|
7
|
-
|
7
|
+
require 'scruffy/helpers'
|
8
8
|
require 'scruffy/graph'
|
9
9
|
require 'scruffy/themes'
|
10
10
|
require 'scruffy/version'
|
11
|
-
require 'scruffy/
|
11
|
+
require 'scruffy/formatters'
|
12
12
|
require 'scruffy/rasterizers'
|
13
|
-
require 'scruffy/resolver'
|
14
13
|
require 'scruffy/layers'
|
15
|
-
|
16
|
-
|
17
|
-
require 'scruffy/renderers/standard'
|
14
|
+
require 'scruffy/components'
|
15
|
+
require 'scruffy/renderers'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'scruffy/components/base'
|
2
|
+
require 'scruffy/components/title'
|
3
|
+
require 'scruffy/components/background'
|
4
|
+
require 'scruffy/components/graphs'
|
5
|
+
require 'scruffy/components/grid'
|
6
|
+
require 'scruffy/components/value_markers'
|
7
|
+
require 'scruffy/components/data_markers'
|
8
|
+
require 'scruffy/components/legend'
|
9
|
+
require 'scruffy/components/style_info'
|
10
|
+
require 'scruffy/components/viewport'
|
11
|
+
require 'scruffy/components/label'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Background < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
fill = "#EEEEEE"
|
6
|
+
case options[:theme].background
|
7
|
+
when Symbol, String
|
8
|
+
fill = options[:theme].background.to_s
|
9
|
+
when Array
|
10
|
+
fill = "url(#BackgroundGradient)"
|
11
|
+
svg.defs {
|
12
|
+
svg.linearGradient(:id=>'BackgroundGradient', :x1 => '0%', :y1 => '0%', :x2 => '0%', :y2 => '100%') {
|
13
|
+
svg.stop(:offset => '5%', 'stop-color' => options[:theme].background[0])
|
14
|
+
svg.stop(:offset => '95%', 'stop-color' => options[:theme].background[1])
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Render background (maybe)
|
20
|
+
svg.rect(:width => bounds[:width], :height => bounds[:height], :x => "0", :y => "0", :fill => fill) unless fill.nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Base
|
4
|
+
attr_reader :id
|
5
|
+
|
6
|
+
# In terms of percentages: [10, 10] == 10% by 10%
|
7
|
+
attr_reader :position
|
8
|
+
attr_reader :size
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
def initialize(id, options = {})
|
12
|
+
@id = id.to_sym
|
13
|
+
@position = options[:position]
|
14
|
+
@size = options[:size]
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def render(svg, bounds, options={})
|
19
|
+
unless bounds.nil?
|
20
|
+
@render_height = bounds[:height]
|
21
|
+
|
22
|
+
svg.g(:id => id.to_s,
|
23
|
+
:transform => "translate(#{bounds.delete(:x)}, #{bounds.delete(:y)})") {
|
24
|
+
|
25
|
+
draw(svg, bounds, options.merge(@options))
|
26
|
+
}
|
27
|
+
else
|
28
|
+
process(svg, options.merge(@options))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def draw(svg, bounds, options={})
|
33
|
+
# Override this if visual component
|
34
|
+
end
|
35
|
+
|
36
|
+
def process(svg, options={})
|
37
|
+
# Override this NOT a visual component
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def relative(pct)
|
42
|
+
@render_height * ( pct / 100.to_f )
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
|
4
|
+
class DataMarkers < Base
|
5
|
+
|
6
|
+
def draw(svg, bounds, options={})
|
7
|
+
unless options[:point_markers].nil?
|
8
|
+
point_distance = bounds[:width] / (options[:point_markers].size - 1).to_f
|
9
|
+
|
10
|
+
(0...options[:point_markers].size).map do |idx|
|
11
|
+
x_coord = point_distance * idx
|
12
|
+
svg.text(options[:point_markers][idx], :x => x_coord, :y => bounds[:height],
|
13
|
+
'font-size' => relative(90),
|
14
|
+
:fill => (options[:theme].marker || 'white').to_s,
|
15
|
+
'text-anchor' => 'middle',
|
16
|
+
'font-family' => options[:theme].font_family,
|
17
|
+
'text-rendering' => 'optimizeLegibility') unless options[:point_markers][idx].nil?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end # draw
|
21
|
+
|
22
|
+
end # class
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
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[:complexity] = options[:complexity]
|
30
|
+
layer_options[:size] = [bounds[:width], bounds[:height]]
|
31
|
+
layer_options[:color] = layer.preferred_color || layer.color || options[:theme].next_color
|
32
|
+
layer_options[:opacity] = opacity_for(idx)
|
33
|
+
layer_options[:theme] = options[:theme]
|
34
|
+
|
35
|
+
svg.g(:id => "component_#{id}_graph_#{idx}", :class => 'graph_layer') {
|
36
|
+
layer.render(svg, layer_options)
|
37
|
+
}
|
38
|
+
end # applicable_layers
|
39
|
+
end # draw
|
40
|
+
|
41
|
+
protected
|
42
|
+
def opacity_for(idx)
|
43
|
+
(idx == 0) ? 1.0 : (@options[:stacked_opacity] || STACKED_OPACITY)
|
44
|
+
end
|
45
|
+
|
46
|
+
end #class
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Grid < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
markers = options[:markers] || 5
|
6
|
+
|
7
|
+
(0...markers).each do |idx|
|
8
|
+
marker = ((1 / (markers - 1).to_f) * idx) * bounds[:height]
|
9
|
+
svg.line(:x1 => 0, :y1 => marker, :x2 => bounds[:width], :y2 => marker, :style => "stroke: #{options[:theme].marker.to_s}; stroke-width: 2;")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Label < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
svg.text(@options[:text], :class => 'text', :x => (bounds[:width] / 2), :y => bounds[:height],
|
6
|
+
'font-size' => relative(100), :fill => options[:theme].marker, :stroke => 'none', 'stroke-width' => '0',
|
7
|
+
'text-anchor' => (@options[:text_anchor] || 'middle'),
|
8
|
+
'font-family' => options[:theme].font_family, 'text-rendering' => 'optimizeLegibility')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Scruffy::Components
|
2
|
+
|
3
|
+
class Legend < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
legend_info = relevant_legend_info(options[:layers])
|
6
|
+
active_width, points = layout(legend_info)
|
7
|
+
|
8
|
+
offset = (bounds[:width] - active_width) / 2 # Nudge over a bit for true centering
|
9
|
+
|
10
|
+
# Render Legend
|
11
|
+
points.each_with_index do |point, idx|
|
12
|
+
|
13
|
+
svg.rect( :x => offset + point,
|
14
|
+
:y => relative(25),
|
15
|
+
:width => relative(60),
|
16
|
+
:height => relative(50),
|
17
|
+
:fill => legend_info[idx][:color])
|
18
|
+
|
19
|
+
svg.text( legend_info[idx][:title],
|
20
|
+
:x => offset + point + relative(100),
|
21
|
+
:y => relative(80),
|
22
|
+
'font-size' => relative(80),
|
23
|
+
:fill => (options[:theme].marker || 'white'),
|
24
|
+
'font-family' => options[:theme].font_family,
|
25
|
+
'text-rendering' => 'optimizeLegibility')
|
26
|
+
end
|
27
|
+
end # draw
|
28
|
+
|
29
|
+
protected
|
30
|
+
# Collects Legend Info from the provided Layers.
|
31
|
+
#
|
32
|
+
# Automatically filters by legend's categories.
|
33
|
+
def relevant_legend_info(layers, categories=(@options[:category] ? [@options[:category]] : @options[:categories]))
|
34
|
+
legend_info = layers.inject([]) do |arr, layer|
|
35
|
+
if categories.nil? ||
|
36
|
+
(categories.include?(layer.options[:category]) ||
|
37
|
+
(layer.options[:categories] && (categories & layer.options[:categories]).size > 0) )
|
38
|
+
|
39
|
+
data = layer.legend_data
|
40
|
+
arr << data if data.is_a?(Hash)
|
41
|
+
arr = arr + data if data.is_a?(Array)
|
42
|
+
end
|
43
|
+
arr
|
44
|
+
end
|
45
|
+
end # relevant_legend_info
|
46
|
+
|
47
|
+
# Returns an array consisting of the total width needed by the legend information,
|
48
|
+
# as well as an array of x-coords for each element.
|
49
|
+
#
|
50
|
+
# ie: [200, [0, 50, 100, 150]]
|
51
|
+
def layout(legend_info_array)
|
52
|
+
legend_info_array.inject([0, []]) do |enum, elem|
|
53
|
+
enum[0] += (relative(50) * 2) if enum.first != 0 # Add spacer between elements
|
54
|
+
enum[1] << enum.first # Add location to points
|
55
|
+
enum[0] += relative(50) # Add room for color box
|
56
|
+
enum[0] += (relative(50) * elem[:title].length) # Add room for text
|
57
|
+
|
58
|
+
[enum.first, enum.last]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end # class Legend
|
63
|
+
|
64
|
+
end # Scruffy::Components
|
@@ -0,0 +1,17 @@
|
|
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 process(svg, options={})
|
9
|
+
svg.defs {
|
10
|
+
svg.style(:type => "text/css") {
|
11
|
+
svg.cdata!("\n#{options[:selector]} {\n #{options[:style]}\n}\n")
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class Title < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
svg.text(options[:title], :class => 'title', :x => (bounds[:width] / 2), :y => bounds[:height],
|
6
|
+
'font-size' => relative(100), :fill => options[:theme].marker, :stroke => 'none', 'stroke-width' => '0',
|
7
|
+
'text-anchor' => (@options[:text_anchor] || 'middle'),
|
8
|
+
'font-family' => options[:theme].font_family, 'text-rendering' => 'optimizeLegibility')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
class ValueMarkers < Base
|
4
|
+
def draw(svg, bounds, options={})
|
5
|
+
markers = options[:markers] || 5
|
6
|
+
all_values = []
|
7
|
+
|
8
|
+
(0...markers).each do |idx|
|
9
|
+
marker = ((1 / (markers - 1).to_f) * idx) * bounds[:height]
|
10
|
+
all_values << (options[:max_value] - options[:min_value]) * ((1 / (markers - 1).to_f) * idx) + options[:min_value]
|
11
|
+
end
|
12
|
+
|
13
|
+
(0...markers).each do |idx|
|
14
|
+
marker = ((1 / (markers - 1).to_f) * idx) * bounds[:height]
|
15
|
+
marker_value = (options[:max_value] - options[:min_value]) * ((1 / (markers - 1).to_f) * idx) + options[:min_value]
|
16
|
+
marker_value = options[:value_formatter].route_format(marker_value, idx, options.merge({:all_values => all_values})) if options[:value_formatter]
|
17
|
+
|
18
|
+
svg.text( marker_value.to_s,
|
19
|
+
:x => bounds[:width],
|
20
|
+
:y => (bounds[:height] - marker),
|
21
|
+
'font-size' => relative(10),
|
22
|
+
:fill => ((options.delete(:marker_color_override) || options[:theme].marker) || 'white').to_s,
|
23
|
+
'text-anchor' => 'end',
|
24
|
+
'font-family' => options[:theme].font_family,
|
25
|
+
'text-rendering' => 'optimizeLegibility')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Components
|
3
|
+
# Component used to limit other visual components to a certain area on the graph.
|
4
|
+
class Viewport < Base
|
5
|
+
include Scruffy::Helpers::Canvas
|
6
|
+
|
7
|
+
attr_accessor :components
|
8
|
+
|
9
|
+
def initialize(*args, &block)
|
10
|
+
super(*args)
|
11
|
+
|
12
|
+
self.components = []
|
13
|
+
if block
|
14
|
+
block.call(self.components)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def draw(svg, bounds, options={})
|
19
|
+
self.components.each do |component|
|
20
|
+
component.render(svg,
|
21
|
+
bounds_for( [bounds[:width], bounds[:height]],
|
22
|
+
component.position,
|
23
|
+
component.size ),
|
24
|
+
options)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Scruffy
|
2
|
+
module Formatters
|
3
|
+
class Base
|
4
|
+
def route_format(target, idx, options = {})
|
5
|
+
args = [target, idx, options]
|
6
|
+
if respond_to?(:format)
|
7
|
+
send :format, *args[0...self.method(:format).arity]
|
8
|
+
elsif respond_to?(:format!)
|
9
|
+
send :format!, *args[0...self.method(:format!).arity]
|
10
|
+
target
|
11
|
+
else
|
12
|
+
raise NameError, "Formatter subclass must container either a format() method or format!() method."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
def number_with_precision(number, precision=3)
|
18
|
+
sprintf("%01.#{precision}f", number)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Default number formatter. Limits precision, beautifies numbers.
|
23
|
+
class Number < Base
|
24
|
+
attr_accessor :precision, :separator, :delimiter, :precision_limit
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@precision = options[:precision] || :none
|
28
|
+
@separator = options[:separator] || '.'
|
29
|
+
@delimiter = options[:delimiter] || ','
|
30
|
+
@precision_limit = options[:precision_limit] || 4
|
31
|
+
end
|
32
|
+
|
33
|
+
def format(target, idx, options)
|
34
|
+
my_precision = @precision
|
35
|
+
|
36
|
+
if @precision == :auto
|
37
|
+
my_precision = options[:all_values].inject(0) do |highest, current|
|
38
|
+
cur = current.to_f.to_s.split(".").last.size
|
39
|
+
cur > highest ? cur : highest
|
40
|
+
end
|
41
|
+
|
42
|
+
my_precision = @precision_limit if my_precision > @precision_limit
|
43
|
+
elsif @precision == :none
|
44
|
+
my_precision = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
my_separator = @separator
|
48
|
+
my_separator = "" unless my_precision > 0
|
49
|
+
begin
|
50
|
+
parts = number_with_precision(target, my_precision).split('.')
|
51
|
+
|
52
|
+
number = parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + my_separator + parts[1].to_s
|
53
|
+
number
|
54
|
+
rescue StandardError => e
|
55
|
+
target
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Currency < Base
|
61
|
+
def initialize(options = {})
|
62
|
+
@precision = options[:precision] || 2
|
63
|
+
@unit = options[:unit] || '$'
|
64
|
+
@separator = options[:separator] || '.'
|
65
|
+
@delimiter = options[:delimiter] || ','
|
66
|
+
@negative_color = options[:negative_color] || 'red'
|
67
|
+
@special_negatives = options[:special_negatives] || false
|
68
|
+
end
|
69
|
+
|
70
|
+
def format(target, idx, options)
|
71
|
+
@separator = "" unless @precision > 0
|
72
|
+
begin
|
73
|
+
parts = number_with_precision(target, @precision).split('.')
|
74
|
+
if @special_negatives && (target.to_f < 0)
|
75
|
+
number = "(" + @unit + parts[0].to_i.abs.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s + ")"
|
76
|
+
else
|
77
|
+
number = @unit + parts[0].to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{@delimiter}") + @separator + parts[1].to_s
|
78
|
+
end
|
79
|
+
if (target.to_f < 0) && @negative_color
|
80
|
+
options[:marker_color_override] = @negative_color
|
81
|
+
end
|
82
|
+
number
|
83
|
+
rescue
|
84
|
+
target
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Percentage < Base
|
90
|
+
def initialize(options = {})
|
91
|
+
@precision = options[:precision] || 3
|
92
|
+
@separator = options[:separator] || '.'
|
93
|
+
end
|
94
|
+
|
95
|
+
def format(target)
|
96
|
+
begin
|
97
|
+
number = number_with_precision(target, @precision)
|
98
|
+
parts = number.split('.')
|
99
|
+
if parts.at(1).nil?
|
100
|
+
parts[0] + "%"
|
101
|
+
else
|
102
|
+
parts[0] + @separator + parts[1].to_s + "%"
|
103
|
+
end
|
104
|
+
rescue
|
105
|
+
target
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|