ruby-treemap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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