ruby-treemap 0.0.1

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/TODO ADDED
@@ -0,0 +1,6 @@
1
+ = RubyTreemap - TODO
2
+
3
+ * Lots of testing. Need to create solid unit tests.
4
+ * HTML output seems to be a few pixels off when drawing treemap squares.
5
+ * Better image output support. Need to add the ability to draw labels.
6
+ * Add option to pad squares in treemap.
@@ -0,0 +1,66 @@
1
+ #
2
+ # treemap.rb - RubyTreemap
3
+ #
4
+ # Copyright (c) 2006 by Andrew Bruno <aeb@qnot.org>
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ #
12
+
13
+ require File.dirname(__FILE__) + '/treemap/node'
14
+ require File.dirname(__FILE__) + '/treemap/layout_base'
15
+ require File.dirname(__FILE__) + '/treemap/output_base'
16
+ require File.dirname(__FILE__) + '/treemap/color_base'
17
+ require File.dirname(__FILE__) + '/treemap/slice_layout'
18
+ require File.dirname(__FILE__) + '/treemap/squarified_layout'
19
+ require File.dirname(__FILE__) + '/treemap/html_output'
20
+ require File.dirname(__FILE__) + '/treemap/rectangle'
21
+ require File.dirname(__FILE__) + '/treemap/gradient_color'
22
+
23
+ # XXX these are still expirmental. Requires RMagick
24
+ # require File.dirname(__FILE__) + '/treemap/image_output'
25
+ # require File.dirname(__FILE__) + '/treemap/svg_output'
26
+
27
+ module Treemap
28
+ VERSION = "0.0.1"
29
+
30
+ def Treemap::dump_tree(node)
31
+ puts "#{node.label}: #{node.bounds.to_s}"
32
+ node.children.each do |c|
33
+ dump_tree(c)
34
+ end
35
+ end
36
+
37
+ def Treemap::tree_from_xml(file)
38
+ doc = REXML::Document.new(file)
39
+ node_from_xml(doc.root)
40
+ end
41
+
42
+ def Treemap::node_from_xml(xmlnode)
43
+ node = Treemap::Node.new
44
+
45
+ node.label = xmlnode.attributes["label"]
46
+ id = xmlnode.attributes["id"]
47
+ if(!id.nil?)
48
+ node.id = id.to_s
49
+ end
50
+
51
+ node.size = xmlnode.attributes["size"]
52
+ node.size = node.size.to_f unless node.size.nil?
53
+
54
+ node.color = xmlnode.attributes["change"]
55
+ node.color = node.color.to_f unless node.color.nil?
56
+
57
+
58
+ xmlnode.elements.each do |c|
59
+ child = node_from_xml(c)
60
+ node.add_child(child) if !child.nil?
61
+ end
62
+
63
+ return nil if node.size < 5
64
+ node
65
+ end
66
+ end
@@ -0,0 +1,61 @@
1
+ #
2
+ # color_base.rb - RubyTreemap
3
+ #
4
+ # Copyright (c) 2006 by Andrew Bruno <aeb@qnot.org>
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ #
12
+
13
+ module Treemap
14
+ class ColorBase
15
+ def get_hex_color(value)
16
+ # base classes override
17
+ end
18
+
19
+ def get_rgb_color(value)
20
+ # base classes override
21
+ end
22
+
23
+ def red(color)
24
+ color.sub!(/^#/, "")
25
+ color[0,2].hex
26
+ end
27
+
28
+ def green(color)
29
+ color.sub!(/^#/, "")
30
+ color[2,2].hex
31
+ end
32
+
33
+ def blue(color)
34
+ color.sub!(/^#/, "")
35
+ color[4,2].hex
36
+ end
37
+
38
+ def to_rgb(color)
39
+ [red(color), green(color), blue(color)]
40
+ end
41
+
42
+ def to_html(width=1, height=20)
43
+ html = "<div>"
44
+ index = @min
45
+
46
+ while(index <= @max)
47
+ html += "<span style=\"display: block; float: left;"
48
+ html += "width: " + width.to_s + "px;"
49
+ html += "height: " + height.to_s + "px;"
50
+ html += "background-color: #" + get_hex_color(index) + ";"
51
+ html += "\">"
52
+ html += "<img width=\"1\" height=\"1\" />"
53
+ html += "</span>"
54
+ index += @increment
55
+ end
56
+
57
+ html += "</div>"
58
+ html
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,71 @@
1
+ #
2
+ # gradient_color.rb - RubyTreemap
3
+ #
4
+ # Copyright (c) 2006 by Andrew Bruno <aeb@qnot.org>
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ #
12
+
13
+ require File.dirname(__FILE__) + "/color_base"
14
+
15
+ class Treemap::GradientColor < Treemap::ColorBase
16
+ attr_accessor(:min, :max, :min_color, :mean_color, :max_color, :increment)
17
+
18
+ def initialize
19
+ @min = -100
20
+ @max = 100
21
+ @min_color = "FF0000" # red
22
+ @mean_color = "000000" # black
23
+ @max_color = "00FF00" # green
24
+ @increment = 1
25
+
26
+ yield self if block_given?
27
+
28
+ # XXX add in error checking. if min >= max, if colors aren't hex, etc.
29
+ @min = @min.to_f
30
+ @max = @max.to_f
31
+ @mean = (@min + @max) / 2.to_f
32
+ @slices = (1.to_f / @increment.to_f) * (@max - @mean).to_f
33
+ end
34
+
35
+ def get_hex_color(value)
36
+ value = @max if(value > @max)
37
+ vaue = @min if(value < @min)
38
+
39
+
40
+ r1, g1, b1 = to_rgb(@mean_color)
41
+ r2, g2, b2 = to_rgb(@min_color)
42
+ if(value >= @mean)
43
+ r2, g2, b2 = to_rgb(@max_color)
44
+ end
45
+
46
+ rfactor = ((r1 -r2).abs.to_f / @slices) * value.abs
47
+ gfactor = ((g1 -g2).abs.to_f / @slices) * value.abs
48
+ bfactor = ((b1 -b2).abs.to_f / @slices) * value.abs
49
+
50
+ newr = r1 + rfactor
51
+ if(r1 > r2)
52
+ newr = r1 - rfactor
53
+ end
54
+
55
+ newg = g1 + gfactor
56
+ if(g1 > g2)
57
+ newg = g1 - gfactor
58
+ end
59
+
60
+ newb = b1 + bfactor
61
+ if(b1 > b2)
62
+ newb = b1 - bfactor
63
+ end
64
+
65
+ sprintf("%02X", newr.round) + sprintf("%02X", newg.round) + sprintf("%02X", newb.round)
66
+ end
67
+
68
+ def get_rgb_color(value)
69
+ to_rgb(get_hex_color(value))
70
+ end
71
+ end
@@ -0,0 +1,162 @@
1
+ #
2
+ # html_output.rb - RubyTreemap
3
+ #
4
+ # Copyright (c) 2006 by Andrew Bruno <aeb@qnot.org>
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ #
12
+
13
+ require 'cgi'
14
+ require File.dirname(__FILE__) + "/output_base"
15
+ require File.dirname(__FILE__) + "/slice_layout"
16
+
17
+ class Treemap::HtmlOutput < Treemap::OutputBase
18
+ attr_accessor(:full_html, :center_labels_at_depth, :stylesheets, :javascripts)
19
+
20
+ def initialize
21
+ super
22
+
23
+ # default options for HtmlOutput
24
+ @full_html = true
25
+ @center_labels_at_depth = nil
26
+ @stylesheets = ""
27
+ @javascripts = ""
28
+
29
+ yield self if block_given?
30
+
31
+ @layout.position = :absolute
32
+ end
33
+
34
+ def default_css
35
+ css = <<CSS
36
+ .node {
37
+ border: 1px solid black;
38
+ }
39
+ .label {
40
+ color: #FFFFFF;
41
+ font-size: 11px;
42
+ }
43
+ .label-heading {
44
+ color: #FFFFFF;
45
+ font-size: 14pt;
46
+ font-weight: bold;
47
+ text-decoration: underline;
48
+ }
49
+ CSS
50
+ end
51
+
52
+ def node_label(node)
53
+ CGI.escapeHTML(node.label)
54
+ end
55
+
56
+ def node_color(node)
57
+ color = "#CCCCCC"
58
+
59
+ if(!node.color.nil?)
60
+ if(not Numeric === node.color)
61
+ color = node.color
62
+ else
63
+ color = "#" + @color.get_hex_color(node.color)
64
+ end
65
+ end
66
+
67
+ color
68
+ end
69
+
70
+ def to_html(node)
71
+ @bounds = self.bounds
72
+
73
+ @layout.process(node, @bounds)
74
+
75
+ draw_map(node)
76
+ end
77
+
78
+ def draw_map(node)
79
+ html = ""
80
+
81
+ if(@full_html)
82
+ html += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
83
+ html += "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
84
+ html += "<html><head>"
85
+ html += "<title>Treemap - #{node_label(node)}</title>"
86
+ html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
87
+ html += "<style type=\"text/css\">" + default_css + "</style>"
88
+ html += self.stylesheets
89
+ html += self.javascripts
90
+ html += "</head><body>"
91
+ end
92
+
93
+ html += draw_node(node)
94
+
95
+ if(@full_html)
96
+ html += "</body></html>"
97
+ end
98
+
99
+ html
100
+ end
101
+
102
+ def draw_label(node)
103
+ label= "<span"
104
+ if(!@center_labels_at_depth.nil? and @center_labels_at_depth == node.depth)
105
+ px_per_point = 20
106
+ label_size = node.label.length * px_per_point
107
+
108
+ label += " style=\""
109
+ label += "overflow: hidden; position: absolute;"
110
+ label += "margin-top: " + (node.bounds.height/2).to_s + "px;"
111
+
112
+ left_margin = 0
113
+ if(label_size < node.bounds.width)
114
+ left_margin = (node.bounds.width - label_size) / 2
115
+ end
116
+ label += "margin-left: " + left_margin.to_s + "px;"
117
+ #label += "left: #{node.bounds.x1}px; top: #{node.bounds.y1}px;"
118
+ label += "left: 0px; top: 0px;"
119
+ label += "z-index: 100;"
120
+ label += "\""
121
+ label += " class=\"label-heading\""
122
+ else
123
+ label += " class=\"label\""
124
+ end
125
+ label += ">"
126
+ label += node_label(node)
127
+ label += "</span>"
128
+
129
+ label
130
+ end
131
+
132
+ # Subclass can override to add more html inside <div/> of node
133
+ def draw_node_body(node)
134
+ draw_label(node)
135
+ end
136
+
137
+ def draw_node(node)
138
+ return "" if node.bounds.nil?
139
+
140
+ html = "<div id=\"node-#{node.id}\""
141
+ html += " style=\""
142
+ html += "overflow: hidden; position: absolute; display: inline;"
143
+ html += "left: #{node.bounds.x1}px; top: #{node.bounds.y1}px;"
144
+ html += "width: #{node.bounds.width}px; height: #{node.bounds.height}px;"
145
+ html += "background-color: " + node_color(node) + ";"
146
+ if(!@center_labels_at_depth.nil? and @center_labels_at_depth == node.depth)
147
+ html += "border: 1px solid black;"
148
+ end
149
+ html += "\" class=\"node\""
150
+ html += ">"
151
+
152
+ html += draw_node_body(node)
153
+
154
+ if(!node.children.nil? and node.children.size > 0)
155
+ node.children.each do |c|
156
+ html += draw_node(c)
157
+ end
158
+ end
159
+
160
+ html += "</div>"
161
+ end
162
+ end
@@ -0,0 +1,77 @@
1
+ #
2
+ # image_output.rb - RubyTreemap
3
+ #
4
+ # Copyright (c) 2006 by Andrew Bruno <aeb@qnot.org>
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ #
12
+
13
+ require 'RMagick'
14
+ require File.dirname(__FILE__) + "/output_base"
15
+
16
+ class Treemap::ImageOutput < Treemap::OutputBase
17
+ def initialize
18
+ super
19
+
20
+ # default options for ImageOutput
21
+
22
+ yield self if block_given?
23
+ end
24
+
25
+ def setup_draw
26
+ draw = Magick::Draw.new
27
+ draw.stroke_width(1)
28
+ draw.stroke("#000000")
29
+ draw.stroke_opacity(1)
30
+ draw.fill_opacity(1)
31
+ draw.font_family = "Verdana"
32
+ draw.pointsize = 12
33
+ draw.gravity = Magick::WestGravity
34
+
35
+ return draw
36
+ end
37
+
38
+ def new_image
39
+ Magick::Image.new(@width, @height) {self.background_color = "white"}
40
+ end
41
+
42
+ def to_png(node, filename="treemap.png")
43
+ #
44
+ # XXX Need to flesh out this method. Add in label drawing.
45
+ #
46
+
47
+ image = self.new_image
48
+ draw = self.setup_draw
49
+
50
+ @bounds = self.bounds
51
+
52
+ # Pad for root border
53
+ @bounds.x2 -= 1
54
+ @bounds.y2 -= 1
55
+
56
+ @layout.process(node, @bounds)
57
+
58
+ draw_map(node, draw, image)
59
+
60
+ # render image
61
+ draw.draw(image)
62
+ image.write(filename)
63
+ end
64
+
65
+ def draw_map(node, draw, image)
66
+ return "" if node.nil?
67
+ if(node.color.nil?)
68
+ draw.fill("#CCCCCC")
69
+ else
70
+ draw.fill("#" + @color.get_hex_color(node.color))
71
+ end
72
+ draw.rectangle(node.bounds.x1, node.bounds.y1, node.bounds.x2, node.bounds.y2)
73
+ node.children.each do |c|
74
+ draw_map(c, draw, image)
75
+ end
76
+ end
77
+ end