|
(function() { |
|
var PADDING, SCALE, TIP, collapse, color, drag, get_color, get_info, global, height, redraw, svg, treemap, vis, width, zoom, zoomable_layer, |
|
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; |
|
|
|
global = {}; |
|
|
|
global.subtrees_data = []; |
|
|
|
SCALE = 200; |
|
|
|
PADDING = 0; |
|
|
|
TIP = 2; |
|
|
|
color = d3.scale.ordinal().domain(['Person', 'Organisation', 'Place', 'Work', 'Species', 'Event']).range(['#E14E5F', '#A87621', '#43943E', '#AC5CC4', '#2E99A0', '#2986EC']); |
|
|
|
treemap = d3.layout.treemap().size([SCALE, SCALE]).round(false).value(function(node) { |
|
return node.size; |
|
}); |
|
|
|
drag = d3.behavior.drag().origin(function(d) { |
|
return d; |
|
}); |
|
|
|
drag.on('dragstart', function() { |
|
return d3.event.sourceEvent.stopPropagation(); |
|
}); |
|
|
|
drag.on('drag', function(d) { |
|
d.x = d3.event.x; |
|
d.y = d3.event.y; |
|
return redraw(); |
|
}); |
|
|
|
svg = d3.select('svg'); |
|
|
|
width = svg.node().getBoundingClientRect().width; |
|
|
|
height = svg.node().getBoundingClientRect().height; |
|
|
|
svg.attr({ |
|
viewBox: "" + (-width / 2) + " " + (-height / 2) + " " + width + " " + height |
|
}); |
|
|
|
zoomable_layer = svg.append('g'); |
|
|
|
zoom = d3.behavior.zoom().scaleExtent([0.1, 10]).on('zoom', function() { |
|
return zoomable_layer.attr({ |
|
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")" |
|
}); |
|
}); |
|
|
|
svg.call(zoom); |
|
|
|
vis = zoomable_layer.append('g').attr({ |
|
transform: "translate(" + (-SCALE / 2) + "," + (-SCALE / 2) + ")" |
|
}); |
|
|
|
d3.json('http://wafi.iit.cnr.it/webvis/tmp/dbpedia/realOntologySunburst2.json', function(tree) { |
|
var subtree_data; |
|
|
|
treemap.nodes(tree); |
|
subtree_data = { |
|
tree: tree, |
|
x: 0, |
|
y: 0 |
|
}; |
|
tree.subtree = subtree_data; |
|
global.subtrees_data.push(subtree_data); |
|
return redraw(); |
|
}); |
|
|
|
redraw = function(duration) { |
|
var enter_nodes, enter_subtrees, links, nodes, subtrees; |
|
|
|
duration = duration != null ? duration : 0; |
|
links = vis.selectAll('.link').data(global.subtrees_data.filter(function(subtree_data) { |
|
return subtree_data.tree.depth !== 0; |
|
})); |
|
links.enter().append('path').attr({ |
|
"class": 'link' |
|
}); |
|
links.transition().duration(duration).attr({ |
|
d: function(subtree_data) { |
|
var alpha, parent, tip, x1, x2, y1, y2; |
|
|
|
parent = subtree_data.tree.parent.subtree; |
|
x1 = parent.x + subtree_data.tree.x + subtree_data.tree.dx / 2; |
|
y1 = parent.y + subtree_data.tree.y + subtree_data.tree.dy / 2; |
|
x2 = subtree_data.x + subtree_data.tree.x + subtree_data.tree.dx / 2; |
|
y2 = subtree_data.y + subtree_data.tree.y + subtree_data.tree.dy / 2; |
|
alpha = Math.atan2(y1 - y2, x2 - x1); |
|
tip = Math.min(TIP, subtree_data.tree.dx / 2, subtree_data.tree.dy / 2); |
|
return "M" + x1 + " " + y1 + " L" + (x2 - tip * Math.sin(alpha)) + " " + (y2 - tip * Math.cos(alpha)) + " L" + (x2 + tip * Math.sin(alpha)) + " " + (y2 + tip * Math.cos(alpha)); |
|
} |
|
}); |
|
links.exit().remove(); |
|
subtrees = vis.selectAll('.subtree').data(global.subtrees_data, function(st) { |
|
return st.tree.depth + '_' + st.tree.name; |
|
}); |
|
enter_subtrees = subtrees.enter().append('g').call(drag).attr({ |
|
"class": 'subtree' |
|
}); |
|
enter_subtrees.append('rect').attr({ |
|
"class": 'handle', |
|
x: function(subtree_data) { |
|
return subtree_data.tree.x - PADDING; |
|
}, |
|
y: function(subtree_data) { |
|
return subtree_data.tree.y - PADDING; |
|
}, |
|
width: function(subtree_data) { |
|
return subtree_data.tree.dx + 2 * PADDING; |
|
}, |
|
height: function(subtree_data) { |
|
return subtree_data.tree.dy + 2 * PADDING; |
|
} |
|
}).append('title').text(function(st) { |
|
return get_info(st.tree); |
|
}); |
|
enter_subtrees.append('text').text(function(subtree_data) { |
|
return subtree_data.tree.name + ((subtree_data.tree.parent != null) && subtree_data.tree.name === subtree_data.tree.parent.name ? ' alone' : ''); |
|
}).attr({ |
|
"class": 'label', |
|
x: function(subtree_data) { |
|
return subtree_data.tree.x + subtree_data.tree.dx / 2; |
|
}, |
|
y: function(subtree_data) { |
|
return subtree_data.tree.y; |
|
}, |
|
dy: '-0.35em' |
|
}); |
|
subtrees.transition().duration(duration).attr({ |
|
transform: function(subtree_data) { |
|
return "translate(" + subtree_data.x + "," + subtree_data.y + ")"; |
|
} |
|
}); |
|
subtrees.exit().remove(); |
|
nodes = subtrees.selectAll('.node').data(function(subtree_data) { |
|
if (subtree_data.tree.children != null) { |
|
return subtree_data.tree.children; |
|
} else { |
|
return []; |
|
} |
|
}); |
|
enter_nodes = nodes.enter().append('rect').attr({ |
|
"class": 'node', |
|
x: function(node) { |
|
return node.x; |
|
}, |
|
y: function(node) { |
|
return node.y; |
|
}, |
|
width: function(node) { |
|
return node.dx; |
|
}, |
|
height: function(node) { |
|
return node.dy; |
|
}, |
|
fill: function(node) { |
|
return get_color(node); |
|
} |
|
}); |
|
enter_nodes.on('click', function(node) { |
|
var subtree_data, x, y; |
|
|
|
if (d3.event.defaultPrevented) { |
|
return; |
|
} |
|
if (node.exploded === true) { |
|
collapse(node); |
|
redraw(); |
|
return; |
|
} |
|
node.exploded = true; |
|
x = d3.select(this.parentNode).datum().x; |
|
y = d3.select(this.parentNode).datum().y; |
|
subtree_data = { |
|
tree: node, |
|
x: x, |
|
y: y |
|
}; |
|
node.subtree = subtree_data; |
|
global.subtrees_data.push(subtree_data); |
|
redraw(); |
|
subtree_data.x += 30; |
|
subtree_data.y += 30; |
|
return window.requestAnimationFrame(function() { |
|
return redraw(1000); |
|
}); |
|
}); |
|
enter_nodes.append('title').text(function(node) { |
|
return get_info(node); |
|
}); |
|
nodes.classed('exploded', function(node) { |
|
return node.exploded; |
|
}); |
|
return nodes.exit().remove(); |
|
}; |
|
|
|
collapse = function(node) { |
|
if (node.children != null) { |
|
node.children.filter(function(n) { |
|
return n.exploded; |
|
}).forEach(function(n) { |
|
return collapse(n); |
|
}); |
|
} |
|
node.exploded = false; |
|
return global.subtrees_data = global.subtrees_data.filter(function(st) { |
|
return st.tree !== node; |
|
}); |
|
}; |
|
|
|
get_color = function(node) { |
|
var _ref; |
|
|
|
if (node.parent == null) { |
|
return '#7E7F7E'; |
|
} |
|
if (_ref = node.name, __indexOf.call(color.domain(), _ref) < 0) { |
|
return get_color(node.parent); |
|
} |
|
return color(node.name); |
|
}; |
|
|
|
get_info = function(node) { |
|
var subclasses; |
|
|
|
if (node.children != null) { |
|
subclasses = node.children.reduce((function(count, n) { |
|
if (n.name !== node.name) { |
|
return count + 1; |
|
} else { |
|
return count; |
|
} |
|
}), 0); |
|
} else { |
|
subclasses = 'no'; |
|
} |
|
return node.name + ((node.parent != null) && node.name === node.parent.name ? ' alone' : '') + '\n' + subclasses + ' subclasses' + '\n' + d3.format(',')(node.size) + ' instances'; |
|
}; |
|
|
|
}).call(this); |
Update: Setting
round
to false is apparently needed to solve a bad behavior of d3's treemap layout. When left to its default value, the layout produces incorrect representations (a region in the upper left corner is depicted smaller than it actually should be). I don't know if this is a known limitation of the rounding feature or a bug in d3 code.