Skip to content

Instantly share code, notes, and snippets.

@Thanaporn-sk
Created March 11, 2017 05:16
Show Gist options
  • Save Thanaporn-sk/34640307bdc69bad9598c6f576f1e4bf to your computer and use it in GitHub Desktop.
Save Thanaporn-sk/34640307bdc69bad9598c6f576f1e4bf to your computer and use it in GitHub Desktop.
Treemap navigator II (dbpedia)
license: mit
global = {}
global.subtrees_data = []
# layout, behaviors and scales
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((node) -> node.size)
# define a drag behavior
drag = d3.behavior.drag()
.origin((d) -> d)
drag.on 'dragstart', () ->
# silence other listeners (disable pan when dragging)
# see https://github.com/mbostock/d3/wiki/Drag-Behavior
d3.event.sourceEvent.stopPropagation()
drag.on 'drag', (d) ->
# update the datum of the dragged node
d.x = d3.event.x
d.y = d3.event.y
redraw()
svg = d3.select('svg')
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height
# translate the viewBox to have (0,0) at the center of the vis
svg
.attr
viewBox: "#{-width/2} #{-height/2} #{width} #{height}"
# append a group for zoomable content
zoomable_layer = svg.append('g')
# define a zoom behavior
zoom = d3.behavior.zoom()
.scaleExtent([0.1,10]) # min-max zoom
.on 'zoom', () ->
# GEOMETRIC ZOOM
zoomable_layer
.attr
transform: "translate(#{zoom.translate()})scale(#{zoom.scale()})"
# bind the zoom behavior to the main SVG
svg.call(zoom)
# group the visualization
vis = zoomable_layer.append('g')
.attr
transform: "translate(#{-SCALE/2},#{-SCALE/2})"
d3.json 'http://wafi.iit.cnr.it/webvis/tmp/dbpedia/realOntologySunburst2.json', (tree) ->
treemap.nodes(tree)
subtree_data = {tree: tree, x: 0, y: 0}
tree.subtree = subtree_data
global.subtrees_data.push subtree_data
redraw()
redraw = (duration) ->
duration = if duration? then duration else 0
# links
links = vis.selectAll('.link')
.data(global.subtrees_data.filter((subtree_data) -> subtree_data.tree.depth isnt 0))
links.enter().append('path')
.attr
class: 'link'
links.transition().duration(duration)
.attr
d: (subtree_data) ->
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
subtrees = vis.selectAll('.subtree')
.data(global.subtrees_data, (st) -> st.tree.depth + '_' + st.tree.name) # FIXME not universally valid
enter_subtrees = subtrees.enter().append('g')
.call(drag)
.attr
class: 'subtree'
enter_subtrees.append('rect')
.attr
class: 'handle'
x: (subtree_data) -> subtree_data.tree.x - PADDING
y: (subtree_data) -> subtree_data.tree.y - PADDING
width: (subtree_data) -> subtree_data.tree.dx + 2*PADDING
height: (subtree_data) -> subtree_data.tree.dy + 2*PADDING
.append('title')
.text((st) -> get_info(st.tree))
enter_subtrees.append('text')
.text((subtree_data) -> subtree_data.tree.name + if subtree_data.tree.parent? and subtree_data.tree.name is subtree_data.tree.parent.name then ' alone' else '')
.attr
class: 'label'
x: (subtree_data) -> subtree_data.tree.x + subtree_data.tree.dx/2
y: (subtree_data) -> subtree_data.tree.y
dy: '-0.35em'
subtrees.transition().duration(duration)
.attr
transform: (subtree_data) -> "translate(#{subtree_data.x},#{subtree_data.y})"
subtrees.exit()
.remove()
nodes = subtrees.selectAll('.node')
.data((subtree_data) -> if subtree_data.tree.children? then subtree_data.tree.children else [])
enter_nodes = nodes.enter().append('rect')
.attr
class: 'node'
x: (node) -> node.x
y: (node) -> node.y
width: (node) -> node.dx
height: (node) -> node.dy
fill: (node) -> get_color(node)
enter_nodes.on 'click', (node) ->
# disable exploding when dragging
# see https://github.com/mbostock/d3/wiki/Drag-Behavior
return if d3.event.defaultPrevented
if node.exploded is 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()
# push new subtrees away from the point of creation
subtree_data.x += 30
subtree_data.y += 30
window.requestAnimationFrame () -> redraw(1000)
enter_nodes.append('title')
.text((node) -> get_info(node))
nodes.classed('exploded', (node) -> node.exploded)
nodes.exit()
.remove()
collapse = (node) ->
if node.children?
node.children.filter((n) -> n.exploded).forEach (n) -> collapse(n)
node.exploded = false
global.subtrees_data = global.subtrees_data.filter (st) -> st.tree isnt node
get_color = (node) ->
if not node.parent?
return '#7E7F7E'
if node.name not in color.domain()
return get_color(node.parent)
return color(node.name)
get_info = (node) ->
if node.children?
subclasses = node.children.reduce(((count, n) -> if n.name isnt node.name then count+1 else count), 0)
else
subclasses = 'no'
return node.name + (if node.parent? and node.name is node.parent.name then ' alone' else '') + '\n' + subclasses + ' subclasses'+ '\n' + d3.format(',')(node.size) + ' instances'
svg {
background: white;
}
.node {
stroke-width: 1px;
shape-rendering: crispEdges;
vector-effect: non-scaling-stroke;
stroke: white;
fill-opacity: 0.8;
}
.handle {
stroke-width: 1px;
shape-rendering: crispEdges;
vector-effect: non-scaling-stroke;
fill: white;
stroke: #333;
}
.label {
text-anchor: middle;
font-family: sans-serif;
/*text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;*/
text-shadow: -2px 0 white, 0 2px white, 2px 0 white, 0 -2px white, -1px -1px white, 1px -1px white, 1px 1px white, -1px 1px white;
cursor: move;
}
.link {
fill: rgba(0,0,0,0.6);
stroke: none;
}
.node.exploded {
fill-opacity: 0.2;
}
.node:hover {
fill-opacity: 0.6;
cursor: pointer;
}
.handle:hover {
stroke-width: 2px;
cursor: move;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Treemap navigator II (dbpedia)" />
<title>Treemap navigator II (dbpedia)</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500" width="960"></svg>
<script src="index.js"></script>
</body>
</html>
(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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment