A partial implementation of the tree color scheme proposed in Tennekes, Jonge 2014, applied to a sunburst representation of the canonically sorted DBpedia hierarchical ontology.
Hue is used to differentiate subtrees, while luminance (and chroma) to encode depth.
This implementation misses the five-element permutation feature described in the paper above. The effect on the visualization is that similar pairs of different colors are repeated one after each other.
As in the original work, the effect of hue is mainly limited to the first levels. The case of DBpedia is especially bad, because of the high number of nodes at the first level.
// Generated by CoffeeScript 1.4.0
(function() {
var RADIUS, arc, height, partition, svg, treechroma, treehue, treelum, vis, width, zoom, zoomable_layer;
treehue = function(node, hue_range, fraction, rev) {
var child, child_hue_ranges, half_n, i, n, r, ri, _i, _j, _len, _ref, _results;
r = hue_range[1] - hue_range[0];
node.hue = hue_range[0] + r / 2;
if (node.children != null) {
n = node.children.length;
ri = r / n;
child_hue_ranges = [];
half_n = Math.floor(n / 2);
for (i = _i = 0; 0 <= half_n ? _i < half_n : _i > half_n; i = 0 <= half_n ? ++_i : --_i) {
child_hue_ranges.push([hue_range[0] + ri * i + ri * (1 - fraction) / 2, hue_range[0] + ri * (i + 1) - ri * (1 - fraction) / 2]);
child_hue_ranges.push([hue_range[0] + ri * (i + half_n) + ri * (1 - fraction) / 2, hue_range[0] + ri * (i + 1 + half_n) - ri * (1 - fraction) / 2]);
}
if (n % 2 === 1) {
child_hue_ranges.push([hue_range[0] + ri * half_n + ri * (1 - fraction) / 2, hue_range[0] + ri * (1 + half_n) - ri * (1 - fraction) / 2]);
}
if (rev) {
child_hue_ranges.reverse();
}
_ref = node.children;
_results = [];
for (i = _j = 0, _len = _ref.length; _j < _len; i = ++_j) {
child = _ref[i];
_results.push(treehue(child, child_hue_ranges[i], fraction, i % 2 === 0));
}
return _results;
}
};
treelum = d3.scale.linear().range([80, 10]);
treechroma = d3.scale.sqrt().range([0, 90]);
RADIUS = 250;
partition = d3.layout.partition().sort(null).size([2 * Math.PI, RADIUS * RADIUS]).value(function(node) {
return node.leaf_count;
});
arc = d3.svg.arc().startAngle(function(d) {
return d.x;
}).endAngle(function(d) {
return d.x + d.dx;
}).innerRadius(function(d) {
return Math.sqrt(d.y);
}).outerRadius(function(d) {
return Math.sqrt(d.y + d.dy);
});
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([1, 10]).on('zoom', function() {
return zoomable_layer.attr({
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")"
});
});
svg.call(zoom);
vis = zoomable_layer.append('g');
d3.json('dbpedia_canonical.json', function(tree) {
var enter_nodes, nodes;
treehue(tree, [0, 360], 0.7);
treelum.domain([0, 5]);
treechroma.domain([0, 5]);
vis.datum(tree);
nodes = vis.selectAll('.node').data(function(t) {
return partition.nodes(t);
});
enter_nodes = nodes.enter().insert('path', 'path').attr({
"class": 'node',
d: arc,
fill: function(n) {
return d3.hcl(n.hue, treechroma(n.depth), treelum(n.depth));
}
});
enter_nodes.append('title').text(function(node) {
return node.name.split('/').reverse()[0];
});
return vis.append('text').text(function(tree) {
return tree.name.split('/').reverse()[0];
}).attr({
"class": 'label',
dy: '0.35em'
});
});
}).call(this);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tree colors (sunburst, DBpedia)</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//wafi.iit.cnr.it/webvis/libs/jigmaps/zip.js"></script>
<script src="//wafi.iit.cnr.it/webvis/libs/jigmaps/tree_utils.js"></script>
</head>
<body>
<svg height="500" width="960"></svg>
<script src="index.js"></script>
</body>
</html>
treehue = (node, hue_range, fraction, rev) ->
# 0 <= hue_range[0] <= hue_range[1]
r = hue_range[1]-hue_range[0]
node.hue = hue_range[0]+r/2
if node.children?
n = node.children.length
ri = r/n
child_hue_ranges = []
half_n = Math.floor(n/2)
for i in [0...half_n]
child_hue_ranges.push [
hue_range[0] + ri*i + ri*(1-fraction)/2,
hue_range[0] + ri*(i+1) - ri*(1-fraction)/2
]
child_hue_ranges.push [
hue_range[0] + ri*(i+half_n) + ri*(1-fraction)/2,
hue_range[0] + ri*(i+1+half_n) - ri*(1-fraction)/2
]
# if n is odd we need to push the middle item
if n%2 is 1
child_hue_ranges.push [
hue_range[0] + ri*(half_n) + ri*(1-fraction)/2,
hue_range[0] + ri*(1+half_n) - ri*(1-fraction)/2
]
child_hue_ranges.reverse() if rev
for child, i in node.children
treehue(child, child_hue_ranges[i], fraction, i % 2 is 0)
treelum = d3.scale.linear()
.range([80,10])
treechroma = d3.scale.sqrt()
.range([0,90])
# layout, behaviors and scales
RADIUS = 250
partition = d3.layout.partition()
.sort(null)
.size([2 * Math.PI, RADIUS * RADIUS])
.value((node) -> node.leaf_count)
arc = d3.svg.arc()
.startAngle((d) -> d.x )
.endAngle((d) -> d.x + d.dx )
.innerRadius((d) -> Math.sqrt(d.y) )
.outerRadius((d) -> Math.sqrt(d.y + d.dy) )
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([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')
d3.json 'dbpedia_canonical.json', (tree) ->
treehue(tree, [0,360], 0.7)
treelum
.domain([0, 5])
treechroma
.domain([0, 5])
vis.datum(tree)
nodes = vis.selectAll('.node')
.data((t) -> partition.nodes(t))
enter_nodes = nodes.enter().insert('path','path')
.attr
class: 'node'
d: arc
fill: (n) -> d3.hcl(n.hue, treechroma(n.depth), treelum(n.depth))
enter_nodes.append('title')
.text((node) -> node.name.split('/').reverse()[0])
vis.append('text')
.text((tree) -> tree.name.split('/').reverse()[0])
.attr
class: 'label'
dy: '0.35em'
svg {
background: white;
}
.node {
vector-effect: non-scaling-stroke;
stroke: white;
stroke-width: 1;
}
.node:hover {
fill: #FC3;
}
.label {
pointer-events: none;
fill: #444;
text-anchor: middle;
font-family: sans-serif;
font-size: 28px;
text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;
}