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.
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