ruby-treemap 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +340 -0
- data/ChangeLog +7 -0
- data/EXAMPLES +48 -0
- data/README +48 -0
- data/Rakefile +71 -0
- data/TODO +6 -0
- data/lib/treemap.rb +66 -0
- data/lib/treemap/color_base.rb +61 -0
- data/lib/treemap/gradient_color.rb +71 -0
- data/lib/treemap/html_output.rb +162 -0
- data/lib/treemap/image_output.rb +77 -0
- data/lib/treemap/layout_base.rb +30 -0
- data/lib/treemap/node.rb +134 -0
- data/lib/treemap/output_base.rb +39 -0
- data/lib/treemap/rectangle.rb +38 -0
- data/lib/treemap/slice_layout.rb +97 -0
- data/lib/treemap/squarified_layout.rb +141 -0
- data/lib/treemap/svg_output.rb +96 -0
- data/test/tc_color.rb +17 -0
- data/test/tc_html.rb +22 -0
- data/test/tc_simple.rb +30 -0
- data/test/tc_slice.rb +25 -0
- data/test/tc_squarified.rb +25 -0
- data/test/tc_svg.rb +21 -0
- data/test/test_base.rb +39 -0
- metadata +73 -0
data/TODO
ADDED
data/lib/treemap.rb
ADDED
@@ -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
|