scruffy 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGES +37 -2
  2. data/lib/scruffy.rb +4 -6
  3. data/lib/scruffy/components.rb +11 -0
  4. data/lib/scruffy/components/background.rb +24 -0
  5. data/lib/scruffy/components/base.rb +46 -0
  6. data/lib/scruffy/components/data_markers.rb +25 -0
  7. data/lib/scruffy/components/graphs.rb +48 -0
  8. data/lib/scruffy/components/grid.rb +14 -0
  9. data/lib/scruffy/components/label.rb +12 -0
  10. data/lib/scruffy/components/legend.rb +64 -0
  11. data/lib/scruffy/components/style_info.rb +17 -0
  12. data/lib/scruffy/components/title.rb +12 -0
  13. data/lib/scruffy/components/value_markers.rb +31 -0
  14. data/lib/scruffy/components/viewport.rb +30 -0
  15. data/lib/scruffy/formatters.rb +111 -0
  16. data/lib/scruffy/graph.rb +38 -68
  17. data/lib/scruffy/helpers.rb +2 -0
  18. data/lib/scruffy/helpers/canvas.rb +22 -0
  19. data/lib/scruffy/helpers/layer_container.rb +69 -0
  20. data/lib/scruffy/layers.rb +1 -0
  21. data/lib/scruffy/layers/all_smiles.rb +4 -0
  22. data/lib/scruffy/layers/area.rb +3 -2
  23. data/lib/scruffy/layers/average.rb +3 -12
  24. data/lib/scruffy/layers/bar.rb +1 -1
  25. data/lib/scruffy/layers/base.rb +35 -20
  26. data/lib/scruffy/layers/line.rb +2 -2
  27. data/lib/scruffy/layers/stacked.rb +75 -0
  28. data/lib/scruffy/rasterizers.rb +2 -1
  29. data/lib/scruffy/rasterizers/batik_rasterizer.rb +25 -0
  30. data/lib/scruffy/rasterizers/rmagick_rasterizer.rb +6 -1
  31. data/lib/scruffy/renderers.rb +5 -0
  32. data/lib/scruffy/renderers/base.rb +37 -0
  33. data/lib/scruffy/renderers/cubed.rb +36 -0
  34. data/lib/scruffy/renderers/reversed.rb +18 -0
  35. data/lib/scruffy/renderers/split.rb +34 -0
  36. data/lib/scruffy/renderers/standard.rb +14 -152
  37. data/lib/scruffy/themes.rb +63 -19
  38. data/lib/scruffy/version.rb +1 -1
  39. data/spec/graph_spec.rb +33 -1
  40. data/spec/layers/line_spec.rb +9 -0
  41. metadata +26 -4
  42. data/lib/scruffy/resolver.rb +0 -14
  43. data/lib/scruffy/transformer.rb +0 -73
@@ -1 +1,2 @@
1
- require 'scruffy/rasterizers/rmagick_rasterizer.rb'
1
+ require 'scruffy/rasterizers/rmagick_rasterizer.rb'
2
+ require 'scruffy/rasterizers/batik_rasterizer.rb'
@@ -0,0 +1,25 @@
1
+ module Scruffy
2
+ module Rasterizers
3
+ class BatikRasterizer
4
+ def initialize(options={})
5
+ @command = options[:command]
6
+ @temp_folder = options[:temp_folder]
7
+ end
8
+
9
+ def rasterize(svg, options={})
10
+ File.open(@temp_folder + '/temp_svg.svg', 'w') { |file|
11
+ file.write(svg)
12
+ }
13
+
14
+ `#{@command} -d #{@temp_folder} -m image/#{options[:as].downcase} #{@temp_folder}/temp_svg.svg`
15
+
16
+ image = ""
17
+ File.open(@temp_folder + '/temp_svg.' + options[:as].downcase, 'r') { |file|
18
+ image = file.read
19
+ }
20
+
21
+ image
22
+ end
23
+ end
24
+ end
25
+ end
@@ -12,9 +12,14 @@ module Scruffy
12
12
  module Rasterizers
13
13
  class RMagickRasterizer
14
14
  def rasterize(svg, options={})
15
- require 'RMagick'
16
15
 
16
+ # I know this seems weird, I'm open to suggestions.
17
+ # I didn't want RMagick required unless absolutely necessary.
18
+ require 'RMagick'
19
+
17
20
  image = Magick::Image::from_blob(svg)[0]
21
+
22
+ image.resize!(options[:size][0], options[:size][1], Magick::BoxFilter, 1.25) if options[:actual_size]
18
23
  if options[:to]
19
24
  image.write(options[:to]) { self.format = options[:as] }
20
25
  end
@@ -0,0 +1,5 @@
1
+ require 'scruffy/renderers/base'
2
+ require 'scruffy/renderers/standard'
3
+ require 'scruffy/renderers/reversed'
4
+ require 'scruffy/renderers/cubed'
5
+ require 'scruffy/renderers/split'
@@ -0,0 +1,37 @@
1
+ module Scruffy
2
+ module Renderers
3
+ class Base
4
+ include Scruffy::Helpers::Canvas
5
+
6
+ attr_accessor :components
7
+
8
+ def render(options = {})
9
+ options[:graph_id] ||= 'scruffy_graph'
10
+ options[:complexity] ||= (global_complexity || :normal)
11
+
12
+ svg = Builder::XmlMarkup.new(:indent => 2)
13
+ svg.instruct!
14
+ svg.instruct! 'DOCTYPE', 'svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" type'
15
+ svg.svg(:xmlns => "http://www.w3.org/2000/svg", 'xmlns:xlink' => "http://www.w3.org/1999/xlink", :width => options[:size].first, :height => options[:size].last) {
16
+ svg.g(:id => options[:graph_id]) {
17
+ self.components.each do |component|
18
+ component.render(svg,
19
+ bounds_for( options[:size], component.position, component.size ),
20
+ options)
21
+ end
22
+ }
23
+ }
24
+ svg.target!
25
+ end
26
+
27
+ private
28
+ def global_complexity
29
+ if Kernel.const_defined? "SCRUFFY_COMPLEXITY"
30
+ SCRUFFY_COMPLEXITY
31
+ else
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ module Scruffy
2
+ module Renderers
3
+ # Renderer that splits the graphs up into four other little graphs.
4
+ class Cubed < Base
5
+ def initialize
6
+ self.components = []
7
+ self.components << Scruffy::Components::Background.new(:background, :position => [0,0], :size =>[100, 100])
8
+ self.components << Scruffy::Components::Title.new(:title, :position => [5, 2], :size => [90, 7])
9
+
10
+ # Viewports
11
+ self.components << Scruffy::Components::Viewport.new(:top_left, :position => [10, 25],
12
+ :size => [35, 30], &graph_block(:top_left))
13
+ self.components << Scruffy::Components::Viewport.new(:top_left, :position => [55, 25],
14
+ :size => [35, 30], &graph_block(:top_right))
15
+ self.components << Scruffy::Components::Viewport.new(:top_left, :position => [10, 65],
16
+ :size => [35, 30], &graph_block(:bottom_left))
17
+ self.components << Scruffy::Components::Viewport.new(:top_left, :position => [55, 65],
18
+ :size => [35, 30], &graph_block(:bottom_right))
19
+
20
+ self.components << Scruffy::Components::Legend.new(:legend, :position => [5, 13], :size => [90, 5])
21
+ end
22
+
23
+ private
24
+ def graph_block(graph_filter)
25
+ block = Proc.new { |components|
26
+ components << Scruffy::Components::Grid.new(:grid, :position => [10, 0], :size => [90, 89])
27
+ components << Scruffy::Components::ValueMarkers.new(:value_markers, :position => [0, 2], :size => [8, 89])
28
+ components << Scruffy::Components::DataMarkers.new(:data_markers, :position => [10, 92], :size => [90, 8])
29
+ components << Scruffy::Components::Graphs.new(:graphs, :position => [10, 0], :size => [90, 89], :only => graph_filter)
30
+ }
31
+
32
+ block
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module Scruffy
2
+ module Renderers
3
+ class Reversed < Base
4
+
5
+ def initialize
6
+ self.components = []
7
+ self.components << Scruffy::Components::Background.new(:background, :position => [0,0], :size =>[100, 100])
8
+ self.components << Scruffy::Components::Title.new(:title, :position => [98, 95], :size => [1, 3], :text_anchor => 'end')
9
+ #self.components << Scruffy::Components::Grid.new(:grid, :position => [14, 12], :size => [78.5, 70])
10
+ self.components << Scruffy::Components::ValueMarkers.new(:value_markers, :position => [2, 14], :size => [10, 70])
11
+ self.components << Scruffy::Components::DataMarkers.new(:data_markers, :position => [14, 3.5], :size => [78.5, 4])
12
+ self.components << Scruffy::Components::Graphs.new(:graphs, :position => [14, 12], :size => [78.5, 70])
13
+ self.components << Scruffy::Components::Legend.new(:legend, :position => [3, 90], :size => [55, 6])
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ module Scruffy
2
+ module Renderers
3
+ # Renderer that splits the graphs up into four other little graphs.
4
+ class Split < Base
5
+ def initialize(options = {})
6
+ self.components = []
7
+ self.components << Scruffy::Components::Background.new(:background, :position => [0,0], :size =>[100, 100])
8
+ self.components << Scruffy::Components::Title.new(:title, :position => [5, 2], :size => [90, 7])
9
+ self.components << Scruffy::Components::Label.new(:label_one, :text => options[:split_label] || '',
10
+ :position => [30, 54.5], :size => [40, 3])
11
+
12
+ # Viewports
13
+ self.components << Scruffy::Components::Viewport.new(:top_left, :position => [3, 20],
14
+ :size => [90, 30], &graph_block(:top))
15
+ self.components << Scruffy::Components::Viewport.new(:top_left, :position => [3, 65],
16
+ :size => [90, 30], &graph_block(:bottom))
17
+
18
+ self.components << Scruffy::Components::Legend.new(:legend, :position => [5, 11], :size => [90, 4])
19
+ end
20
+
21
+ private
22
+ def graph_block(graph_filter)
23
+ block = Proc.new { |components|
24
+ components << Scruffy::Components::Grid.new(:grid, :position => [10, 0], :size => [90, 89])
25
+ components << Scruffy::Components::ValueMarkers.new(:value_markers, :position => [0, 2], :size => [8, 89])
26
+ components << Scruffy::Components::DataMarkers.new(:data_markers, :position => [10, 92], :size => [90, 8])
27
+ components << Scruffy::Components::Graphs.new(:graphs, :position => [10, 0], :size => [90, 89], :only => graph_filter)
28
+ }
29
+
30
+ block
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,158 +1,20 @@
1
1
  module Scruffy
2
- class StandardRenderer
3
- GRAPH_VIEWPORT = { :x => 0.20, :y => 0.30, :width => 0.70, :height => 0.55 }
4
- MARKER_ALIGNMENT = { :x => 0.17, :y => 0.31}
5
- MARKERS = 5
6
-
7
- def render(options = {})
8
- title = options[:title]
9
- theme = options[:theme]
10
- layers = options[:layers]
11
- size = options[:size]
12
- graph_id = options[:graph_id] || 'scruffy_graph'
13
-
14
-
15
- options[:complexity] ||= (global_complexity || :normal)
16
- options[:resolver] ||= Scruffy::Resolver.new(options[:size].last)
17
-
18
- svg = Builder::XmlMarkup.new(:indent => 2)
19
- svg.instruct!
20
- svg.instruct! 'DOCTYPE', 'svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" type'
21
- svg.svg(:xmlns => "http://www.w3.org/2000/svg", 'xmlns:xlink' => "http://www.w3.org/1999/xlink", :width => size[0], :height => size[1]) {
22
- svg.g(:id => graph_id) {
23
- render_background(svg, options)
24
- render_markers(svg, options)
25
- render_title(svg, options)
26
- render_layers(svg, options)
27
- render_legend(svg, options)
28
-
29
- }
30
- }
31
- svg.target!
32
- end
33
-
34
- protected
35
- def render_background(svg, options)
36
- fill = "#EEEEEE"
37
- case options[:theme][:background]
38
- when Symbol, String
39
- fill = options[:theme][:background].to_s
40
- when Array
41
- fill = "url(#BackgroundGradient)"
42
- svg.defs {
43
- svg.linearGradient(:id=>'BackgroundGradient', :x1 => '0%', :y1 => '0%', :x2 => '0%', :y2 => '100%') {
44
- svg.stop(:offset => '5%', 'stop-color' => options[:theme][:background][0])
45
- svg.stop(:offset => '95%', 'stop-color' => options[:theme][:background][1])
46
- }
47
- }
48
- when Proc
49
- theme[:background].call(svg, options)
50
- fill = nil
51
- end
52
-
53
- # Render background (maybe)
54
- svg.rect(:width => "100%", :height => "100%", :x => "0", :y => "0", :fill => fill) unless fill.nil?
55
- end
56
-
57
- def render_layers(svg, options)
58
- svg.g(:id => 'graphs', :transform => "translate(#{GRAPH_VIEWPORT[:x] * options[:size][0]},#{GRAPH_VIEWPORT[:y] * options[:size][1]})") {
59
- options[:legend_information] = {}
60
-
61
- options[:layers].each_with_index do |layer, idx|
62
-
63
- layer_options = {}
64
- layer_options[:index] = idx
65
- layer_options[:color] = options[:theme][:colors].values[ (idx % options[:theme][:colors].size) ]
66
- layer_options[:size] = [ GRAPH_VIEWPORT[:width] * options[:size][0], GRAPH_VIEWPORT[:height] * options[:size][1] ]
67
- layer_options[:min_value] = options[:min_value]
68
- layer_options[:max_value] = options[:max_value]
69
- layer_options[:opacity] = (idx > 0) ? 0.6 : 1.0
70
- layer_options[:resolver] = options[:resolver]
71
- layer_options[:complexity] = options[:complexity]
72
- layer_options[:theme] = options[:theme]
73
-
74
- # puts options[:theme][:colors].values
75
- # puts layer_options[:color]
76
- # puts "#{idx} : #{options[:layers].size} == #{idx % options[:layers].size}"
77
-
78
- svg.g {
79
- layer.render(svg, layer_options)
80
- (options[:legend_information][layer.title] = layer.color) unless layer.title.nil?
81
- }
82
- end
83
- }
84
- end
85
-
86
- def render_markers(svg, options)
87
- svg.g(:id => 'markers', :transform => "translate(#{GRAPH_VIEWPORT[:x] * options[:size][0]},#{GRAPH_VIEWPORT[:y] * options[:size][1]})") {
88
- (0...MARKERS).each do |idx|
89
- marker = ((1 / (MARKERS - 1).to_f) * idx) * (options[:size].last * GRAPH_VIEWPORT[:height])
90
- svg.line(:x1 => 0, :y1 => marker, :x2 => (GRAPH_VIEWPORT[:width] * options[:size].first), :y2 => marker, :style => "stroke: #{options[:theme][:marker].to_s}; stroke-width: #{options[:resolver].to_point(2)};")
91
- end
92
- }
93
-
94
- svg.g(:id => 'marker-values', :transform => "translate(#{MARKER_ALIGNMENT[:x] * options[:size][0]},#{MARKER_ALIGNMENT[:y] * options[:size][1]})") {
95
- (0...MARKERS).each do |idx|
96
- marker = ((GRAPH_VIEWPORT[:height]) * options[:size].last) - ((1 / (MARKERS - 1).to_f) * idx) * (options[:size].last * GRAPH_VIEWPORT[:height])
97
- marker_value = (options[:max_value] - options[:min_value]) * ((1 / (MARKERS - 1).to_f) * idx) + options[:min_value]
98
- marker_value = options[:marker_transformer].route_transform(marker_value, idx, options) if options[:marker_transformer]
99
-
100
- svg.text(marker_value.to_s, :x => 0, :y => marker, :style => "font-size: #{options[:resolver].to_point(18)}px; fill: #{((options.delete(:marker_color_override) || options[:theme][:marker]) || 'white').to_s}; text-anchor: end;")
101
- end
102
- }
103
-
104
- unless options[:point_markers].nil?
105
- svg.g(:id => 'point-markers', :transform => "translate(#{GRAPH_VIEWPORT[:x] * options[:size][0]},#{(GRAPH_VIEWPORT[:y] + GRAPH_VIEWPORT[:height]) * options[:size][1]})") {
106
- point_distance = (options[:size].first * GRAPH_VIEWPORT[:width]) / (options[:point_markers].size - 1).to_f
107
-
108
- (0...options[:point_markers].size).map do |idx|
109
- x_coord = point_distance * idx
110
- svg.text(options[:point_markers][idx], :x => x_coord, :y => options[:resolver].to_point(20),
111
- 'font-size' => options[:resolver].to_point(16),
112
- :fill => (options[:theme][:marker] || 'white').to_s,
113
- 'text-anchor' => 'middle') unless options[:point_markers][idx].nil?
114
- end
115
- }
2
+ module Renderers
3
+ class Standard < Base
4
+
5
+ def initialize
6
+ self.components = []
7
+ self.components << Scruffy::Components::Background.new(:background, :position => [0,0], :size =>[100, 100])
8
+ self.components << Scruffy::Components::Title.new(:title, :position => [5, 2], :size => [90, 7])
9
+ self.components << Scruffy::Components::Viewport.new(:graph_viewport, :position => [2, 26], :size => [89, 66]) do |view|
10
+ view << Scruffy::Components::Grid.new(:grid, :position => [20, 0], :size => [80, 90])
11
+ view << Scruffy::Components::ValueMarkers.new(:value_markers, :position => [0, 2], :size => [17.5, 90])
12
+ view << Scruffy::Components::DataMarkers.new(:data_markers, :position => [20, 94], :size => [80, 6])
13
+ view << Scruffy::Components::Graphs.new(:graphs, :position => [20, 0], :size => [80, 90])
116
14
  end
15
+ self.components << Scruffy::Components::Legend.new(:legend, :position => [5, 13], :size => [90, 6])
117
16
  end
118
17
 
119
- def render_legend(svg, options)
120
- scaled = options[:resolver]
121
-
122
- active_width = scaled.to_point(100) * options[:legend_information].size
123
-
124
- svg.g(:transform => "translate(#{(options[:size][0] * 0.5) - (active_width * 0.5) + scaled.to_point(50)}, #{options[:size][1] * 0.12})") {
125
- point_distance = scaled.to_point(100)
126
- points = (0...options[:legend_information].size).map { |idx| idx * point_distance }
127
-
128
-
129
- points.each_with_index do |point, idx|
130
- svg.rect(:x => point, :y => scaled.to_point(18),
131
- :width => scaled.to_point(13), :height => scaled.to_point(13),
132
- :fill => options[:legend_information].values[idx])
133
- svg.text( options[:legend_information].keys[idx], :x => point + scaled.to_point(25),
134
- :y => scaled.to_point(30), 'font-size' => scaled.to_point(15),
135
- :fill => (options[:theme][:marker] || 'white'))
136
-
137
- end
138
- }
139
- end
140
-
141
- def render_title(svg, options)
142
- svg.g(:transform => "translate(0, #{options[:size][1] * 0.09})") {
143
- svg.text( options[:title], :x => options[:size][0] / 2, :y => 0,
144
- 'font-size' => options[:resolver].to_point(28), 'text-anchor' => 'middle',
145
- :fill => 'white')
146
- }
147
- end
148
-
149
- private
150
- def global_complexity
151
- if Kernel.const_defined? "SCRUFFY_COMPLEXITY"
152
- SCRUFFY_COMPLEXITY
153
- else
154
- nil
155
- end
156
- end
18
+ end
157
19
  end
158
20
  end
@@ -1,26 +1,70 @@
1
1
  module Scruffy
2
2
  module Themes
3
- KEYNOTE = {
4
- :background => [:black, '#4A465A'],
5
- :marker => :white,
6
- :colors => {:blue => '#6886B4',
7
- :green => '#72AE6E', :red => '#D1695E',
8
- :purple => '#8A6EAF', :orange => '#EFAA43'}
9
- }
3
+ class Base
4
+ attr_accessor :background
5
+ attr_accessor :colors
6
+ attr_accessor :marker
7
+ attr_accessor :font_family
8
+
9
+ def initialize(descriptor)
10
+ self.background = descriptor[:background]
11
+ self.colors = descriptor[:colors]
12
+ self.marker = descriptor[:marker]
13
+ self.font_family = descriptor[:font_family]
14
+ end
10
15
 
16
+ def next_color
17
+ @previous_color = 0 if @previous_color.nil?
18
+ @previous_color += 1
19
+
20
+ self.colors[(@previous_color-1) % self.colors.size]
21
+ end
11
22
 
12
- MEPHISTO = {
13
- :background => ['#101010', '#999977'],
14
- :marker => :white,
15
- :colors => {:orange => '#DD3300', :blue => '#66AABB', :green => '#225533',
16
- :red => '#992200'}
17
- }
23
+ def darken(color, shift=20)
24
+
25
+ end
26
+
27
+ def lighten(color, shift=20)
28
+
29
+ end
30
+ end
31
+
32
+ # Keynote theme, based on Apple's Keynote presentation software.
33
+ #
34
+ # Color values used from Gruff's default theme.
35
+ class Keynote < Base
36
+ def initialize
37
+ super({
38
+ :background => [:black, '#4A465A'],
39
+ :marker => :white,
40
+ :colors => %w(#6886B4 #FDD84E #72AE6E #D1695E #8A6EAF #EFAA43 white)
41
+ })
42
+ end
43
+ end
44
+
45
+ # Roughly, roughly based on the color scheme of www.mephistoblog.com.
46
+ class Mephisto < Base
47
+ def initialize
48
+ super({
49
+ :background => ['#101010', '#999977'],
50
+ :marker => :white,
51
+ :colors => %w(#DD3300 #66AABB #225533 #992200)
52
+ })
53
+
54
+ end
55
+ end
56
+
57
+ # Based on the color scheme used by almost every Ruby blogger.
58
+ class RubyBlog < Base
59
+ def initialize
60
+ super({
61
+ :background => ['#670A0A', '#831515'],
62
+ :marker => '#DBD1C1',
63
+ :colors => %w(#007777 #444477 #994444 #77FFBB #D75A20)
64
+ })
65
+ end
66
+ end
67
+
18
68
 
19
- RUBY_BLOG = {
20
- :background => ['#670A0A', '#831515'],
21
- :marker => '#DBD1C1',
22
- :colors => {:teal => '#007777', :purple => '#444477', :pink => '#994444',
23
- :green => '#77FFBB', :orange => '#D75A20'}
24
- }
25
69
  end
26
70
  end