mtah-ruby-treemap 0.0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +340 -0
- data/ChangeLog +18 -0
- data/EXAMPLES +48 -0
- data/README +48 -0
- data/Rakefile +72 -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 +173 -0
- data/lib/treemap/image_output.rb +77 -0
- data/lib/treemap/layout_base.rb +30 -0
- data/lib/treemap/node.rb +141 -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 +114 -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 +94 -0
data/Rakefile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- coding: undecided -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/clean'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'rake/packagetask'
|
8
|
+
require 'rake/gempackagetask'
|
9
|
+
|
10
|
+
$:.unshift(File.dirname(__FILE__) + "/lib")
|
11
|
+
require 'treemap'
|
12
|
+
|
13
|
+
CLEAN.include FileList['test/*.html', 'test/*.png', 'test/*.svg']
|
14
|
+
CLEAN.include 'docs'
|
15
|
+
|
16
|
+
PKG_NAME = 'mtah-ruby-treemap'
|
17
|
+
PKG_VERSION = Treemap::VERSION
|
18
|
+
|
19
|
+
desc 'list available tasks'
|
20
|
+
task :default do
|
21
|
+
puts "Run 'rake --tasks' for the list of available tasks"
|
22
|
+
end
|
23
|
+
|
24
|
+
Rake::RDocTask.new do |rdoc|
|
25
|
+
rdoc.rdoc_dir = 'docs'
|
26
|
+
rdoc.title = "RubyTreemap"
|
27
|
+
rdoc.options << "--all"
|
28
|
+
rdoc.options << "-S"
|
29
|
+
rdoc.options << "-N"
|
30
|
+
rdoc.options << "--main=README"
|
31
|
+
rdoc.rdoc_files.include("README", "EXAMPLES", "TODO", "ChangeLog", "lib/*.rb", "lib/**/*.rb")
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Run unit tests"
|
35
|
+
Rake::TestTask.new do |t|
|
36
|
+
t.libs << "test"
|
37
|
+
begin
|
38
|
+
require 'RMagick'
|
39
|
+
t.pattern = 'test/tc_*.rb'
|
40
|
+
rescue LoadError
|
41
|
+
t.test_files = ['tc_color.rb','tc_html.rb']
|
42
|
+
end
|
43
|
+
t.verbose = true
|
44
|
+
end
|
45
|
+
|
46
|
+
spec = Gem::Specification.new do |s|
|
47
|
+
s.platform = Gem::Platform::RUBY
|
48
|
+
|
49
|
+
s.name = PKG_NAME
|
50
|
+
s.summary = "Treemap visualization in ruby"
|
51
|
+
s.description = %q{Treemap visualization in ruby}
|
52
|
+
s.version = PKG_VERSION
|
53
|
+
s.author = "Martin Häger"
|
54
|
+
s.email = "martin.haeger@gmail.com"
|
55
|
+
s.homepage = "http://github.com/mtah/ruby-treemap"
|
56
|
+
|
57
|
+
s.has_rdoc = true
|
58
|
+
s.extra_rdoc_files = [ "README", "EXAMPLES" ]
|
59
|
+
s.rdoc_options = [ "--main", "README" ]
|
60
|
+
s.requirements << 'none'
|
61
|
+
|
62
|
+
s.require_path = 'lib'
|
63
|
+
s.autorequire = 'treemap'
|
64
|
+
|
65
|
+
s.files = [ "Rakefile", "TODO", "EXAMPLES", "README", "ChangeLog", "COPYING"]
|
66
|
+
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
67
|
+
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
68
|
+
end
|
69
|
+
|
70
|
+
Rake::GemPackageTask.new(spec) do |p|
|
71
|
+
p.need_tar = true
|
72
|
+
end
|
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
|
+
require File.dirname(__FILE__) + '/treemap/svg_output'
|
23
|
+
|
24
|
+
# XXX these are still expirmental. Requires RMagick
|
25
|
+
# require File.dirname(__FILE__) + '/treemap/image_output'
|
26
|
+
|
27
|
+
module Treemap
|
28
|
+
VERSION = "0.0.3.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,173 @@
|
|
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, :base_font_size, :center_labels_at_depth, :center_labels_at_z, :stylesheets, :javascripts, :metadata)
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
super
|
22
|
+
|
23
|
+
# default options for HtmlOutput
|
24
|
+
@full_html = true
|
25
|
+
@center_labels_at_depth = nil
|
26
|
+
@center_labels_at_z = 100
|
27
|
+
@stylesheets = ""
|
28
|
+
@javascripts = ""
|
29
|
+
@base_font_size = 14
|
30
|
+
|
31
|
+
yield self if block_given?
|
32
|
+
|
33
|
+
@layout.position = :absolute
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_css
|
37
|
+
css = <<CSS
|
38
|
+
.node {
|
39
|
+
border: 1px solid black;
|
40
|
+
}
|
41
|
+
.label {
|
42
|
+
color: #FFFFFF;
|
43
|
+
font-size: 11px;
|
44
|
+
}
|
45
|
+
.label-heading {
|
46
|
+
color: #FFFFFF;
|
47
|
+
font-size: 14pt;
|
48
|
+
font-weight: bold;
|
49
|
+
text-decoration: underline;
|
50
|
+
}
|
51
|
+
CSS
|
52
|
+
end
|
53
|
+
|
54
|
+
def node_label(node)
|
55
|
+
CGI.escapeHTML(node.label)
|
56
|
+
end
|
57
|
+
|
58
|
+
def node_color(node)
|
59
|
+
color = "#CCCCCC"
|
60
|
+
|
61
|
+
if(!node.color.nil?)
|
62
|
+
if(not Numeric === node.color)
|
63
|
+
color = node.color
|
64
|
+
else
|
65
|
+
color = "#" + @color.get_hex_color(node.color)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
color
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_html(node)
|
73
|
+
@bounds = self.bounds
|
74
|
+
|
75
|
+
@layout.process(node, @bounds)
|
76
|
+
|
77
|
+
draw_map(node)
|
78
|
+
end
|
79
|
+
|
80
|
+
def draw_map(node)
|
81
|
+
html = ""
|
82
|
+
|
83
|
+
if(@full_html)
|
84
|
+
html += "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
|
85
|
+
html += "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
|
86
|
+
html += "<html><head>"
|
87
|
+
html += "<title>Treemap - #{node_label(node)}</title>"
|
88
|
+
html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
|
89
|
+
html += "<style type=\"text/css\">" + default_css + "</style>"
|
90
|
+
html += self.stylesheets
|
91
|
+
html += self.javascripts
|
92
|
+
html += "</head><body>"
|
93
|
+
end
|
94
|
+
|
95
|
+
html += draw_node(node)
|
96
|
+
|
97
|
+
if(@full_html)
|
98
|
+
html += "</body></html>"
|
99
|
+
end
|
100
|
+
|
101
|
+
html
|
102
|
+
end
|
103
|
+
|
104
|
+
def draw_label(node)
|
105
|
+
label= "<span"
|
106
|
+
if(!@center_labels_at_depth.nil? and @center_labels_at_depth == node.depth)
|
107
|
+
px_per_point = 20
|
108
|
+
label_size = node.label.length * px_per_point
|
109
|
+
|
110
|
+
label += " style=\""
|
111
|
+
label += "overflow: hidden; position: absolute;"
|
112
|
+
label += "margin-top: " + (node.bounds.height/2 - node.font_size(@base_font_size)/2).to_s + "px;"
|
113
|
+
|
114
|
+
label += "left: 0px; top: 0px; width: 100%; height: 100%; line-height: 100%; text-align: center;"
|
115
|
+
label += "z-index: #{@center_labels_at_z};"
|
116
|
+
label += "font-size:#{node.font_size(@base_font_size)}px;"
|
117
|
+
label += "\""
|
118
|
+
label += " class=\"label-heading\""
|
119
|
+
else
|
120
|
+
label += " class=\"label\""
|
121
|
+
label += " style=\"font-size:#{node.font_size(@base_font_size)}px\""
|
122
|
+
end
|
123
|
+
label += ">"
|
124
|
+
label += node_label(node)
|
125
|
+
label += "</span>"
|
126
|
+
|
127
|
+
label
|
128
|
+
end
|
129
|
+
|
130
|
+
# Subclass can override to add more html inside <div/> of node
|
131
|
+
def draw_node_body(node)
|
132
|
+
draw_label(node)
|
133
|
+
end
|
134
|
+
|
135
|
+
def draw_node(node)
|
136
|
+
return "" if node.bounds.nil?
|
137
|
+
|
138
|
+
root_class = node.depth == 0 ? " treemap-root" : ""
|
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=\"#{metadata(node)}node#{root_class}\""
|
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
|
+
|
163
|
+
private
|
164
|
+
def metadata(node)
|
165
|
+
if node.object && @metadata
|
166
|
+
metadata = @metadata.map { |c| "'#{c}': '#{node.object.send(c).to_s}'" } || []
|
167
|
+
|
168
|
+
return "" if metadata.empty?
|
169
|
+
|
170
|
+
return "{#{metadata.join(',')}} "
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|