Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nitaku/79f16392f7e1bed77f07 to your computer and use it in GitHub Desktop.
Save nitaku/79f16392f7e1bed77f07 to your computer and use it in GitHub Desktop.
Word cloud treemap (flare)

TBC This experiment tries to solve some problems of word clouds by using a treemap layout to create one. See the chapter about Wordle in Steele and Iliinski's Beautiful Visualization (page 37).

# layout, behaviors and scales
SCALE = 400
treemap = d3.layout.treemap()
.size([SCALE, SCALE])
.value((node) -> node.size)
correct_x = d3.scale.linear()
.domain([0, SCALE])
.range([0, 420])
correct_y = d3.scale.linear()
.domain([0, SCALE])
.range([0, 300])
color = (txt, light) ->
Math.seedrandom(txt+'abcdef')
noise = (W) -> Math.random()*W - W/2
d3.hcl(0+noise(360), 20, if light then 65 else 25)
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')
.attr
transform: "translate(#{-SCALE/2},#{-SCALE/2})"
d3.json 'http://wafi.iit.cnr.it/webvis/tmp/flare.json', (tree) ->
nodes_data = treemap.nodes(tree)
#nodes = vis.selectAll('.node')
# .data(nodes_data.filter((node) -> node.depth is 1))
#
#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) -> color(node.name, true)
labels = vis.selectAll('.label')
.data(nodes_data.filter((node) -> node.depth is 1))
enter_labels = labels.enter().append('svg')
.attr
class: 'label'
enter_labels.append('text')
.text((node) -> node.name.toUpperCase())
.attr
dy: '0.35em'
fill: (node) -> color(node.name, false)
.each (node) ->
bbox = this.getBBox()
bbox_aspect = bbox.width / bbox.height
node_bbox = {width: node.dx, height: node.dy}
node_bbox_aspect = node_bbox.width / node_bbox.height
rotate = bbox_aspect >= 1 and node_bbox_aspect < 1 or bbox_aspect < 1 and node_bbox_aspect >= 1
node.label_bbox = {
x: bbox.x+(bbox.width-correct_x(bbox.width))/2,
y: bbox.y+(bbox.height-correct_y(bbox.height))/2,
width: correct_x(bbox.width),
height: correct_y(bbox.height)
}
if rotate
node.label_bbox = {
x: node.label_bbox.y,
y: node.label_bbox.x,
width: node.label_bbox.height,
height: node.label_bbox.width
}
d3.select(this).attr('transform', 'rotate(-90)')
enter_labels
.attr
x: (node) -> node.x
y: (node) -> node.y
width: (node) -> node.dx
height: (node) -> node.dy
viewBox: (node) -> "#{node.label_bbox.x} #{node.label_bbox.y} #{node.label_bbox.width} #{node.label_bbox.height}"
preserveAspectRatio: 'none'
svg {
background: white;
}
.node {
shape-rendering: crispEdges;
vector-effect: non-scaling-stroke;
stroke: white;
stroke-width: 2;
}
.label {
pointer-events: none;
text-anchor: middle;
font-family: Impact;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Word cloud treemap (flare)" />
<title>Word cloud treemap (flare)</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="http://davidbau.com/encode/seedrandom-min.js"></script>
<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 SCALE, color, correct_x, correct_y, height, svg, treemap, vis, width, zoom, zoomable_layer;
SCALE = 400;
treemap = d3.layout.treemap().size([SCALE, SCALE]).value(function(node) {
return node.size;
});
correct_x = d3.scale.linear().domain([0, SCALE]).range([0, 420]);
correct_y = d3.scale.linear().domain([0, SCALE]).range([0, 300]);
color = function(txt, light) {
var noise;
Math.seedrandom(txt + 'abcdef');
noise = function(W) {
return Math.random() * W - W / 2;
};
return d3.hcl(0 + noise(360), 20, light ? 65 : 25);
};
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').attr({
transform: "translate(" + (-SCALE / 2) + "," + (-SCALE / 2) + ")"
});
d3.json('http://wafi.iit.cnr.it/webvis/tmp/flare.json', function(tree) {
var enter_labels, labels, nodes_data;
nodes_data = treemap.nodes(tree);
labels = vis.selectAll('.label').data(nodes_data.filter(function(node) {
return node.depth === 1;
}));
enter_labels = labels.enter().append('svg').attr({
"class": 'label'
});
enter_labels.append('text').text(function(node) {
return node.name.toUpperCase();
}).attr({
dy: '0.35em',
fill: function(node) {
return color(node.name, false);
}
}).each(function(node) {
var bbox, bbox_aspect, node_bbox, node_bbox_aspect, rotate;
bbox = this.getBBox();
bbox_aspect = bbox.width / bbox.height;
node_bbox = {
width: node.dx,
height: node.dy
};
node_bbox_aspect = node_bbox.width / node_bbox.height;
rotate = bbox_aspect >= 1 && node_bbox_aspect < 1 || bbox_aspect < 1 && node_bbox_aspect >= 1;
node.label_bbox = {
x: bbox.x + (bbox.width - correct_x(bbox.width)) / 2,
y: bbox.y + (bbox.height - correct_y(bbox.height)) / 2,
width: correct_x(bbox.width),
height: correct_y(bbox.height)
};
if (rotate) {
node.label_bbox = {
x: node.label_bbox.y,
y: node.label_bbox.x,
width: node.label_bbox.height,
height: node.label_bbox.width
};
return d3.select(this).attr('transform', 'rotate(-90)');
}
});
return enter_labels.attr({
x: function(node) {
return node.x;
},
y: function(node) {
return node.y;
},
width: function(node) {
return node.dx;
},
height: function(node) {
return node.dy;
},
viewBox: function(node) {
return "" + node.label_bbox.x + " " + node.label_bbox.y + " " + node.label_bbox.width + " " + node.label_bbox.height;
},
preserveAspectRatio: 'none'
});
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment