block by nitaku 3424eed86f55550622ef

Labeled treemap (flare)

Full Screen

An improvement of the previous example on treemaps. A simple label placement algorithm is used to display the first level of the hierarchy.

index.js

(function() {
  var LABEL_SCALE, PADDING, SCALE, TIP, height, svg, treemap, vis, width, zoom, zoomable_layer;

  SCALE = 400;

  PADDING = 8;

  TIP = 10;

  LABEL_SCALE = 0.7;

  treemap = d3.layout.treemap().size([SCALE, SCALE]).value(function(node) {
    return node.size;
  });

  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_nodes, nodes, nodes_data;

    nodes_data = treemap.nodes(tree);
    nodes = vis.selectAll('.node').data(nodes_data);
    enter_nodes = nodes.enter().insert('rect', '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;
      },
      stroke: function(node) {
        switch (node.depth) {
          case 0:
            return '#333';
          case 1:
            return '#999';
          default:
            return '#DDD';
        }
      },
      'stroke-width': function(node) {
        switch (node.depth) {
          case 0:
            return 3;
          case 1:
            return 2;
          default:
            return 1;
        }
      }
    });
    return vis.selectAll('.label').data(nodes_data.filter(function(node) {
      return node.depth === 1;
    })).enter().append('text').text(function(node) {
      return node.name;
    }).attr({
      "class": 'label',
      dy: '0.35em',
      transform: function(node) {
        var bbox, bbox_aspect, bbox_height, bbox_width, h_ratio, node_bbox, node_bbox_aspect, ratio, rotate, w_ratio;

        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;
        if (rotate) {
          bbox_width = node_bbox.height;
          bbox_height = node_bbox.width;
        } else {
          bbox_width = node_bbox.width;
          bbox_height = node_bbox.height;
        }
        w_ratio = bbox_width / bbox.width;
        h_ratio = bbox_height / bbox.height;
        ratio = Math.min(w_ratio, h_ratio) * LABEL_SCALE;
        return "translate(" + (node.x + node.dx / 2) + "," + (node.y + node.dy / 2) + "),scale(" + ratio + "),rotate(" + (rotate ? -90 : 0) + ")";
      }
    });
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
	<head>
        <meta charset="utf-8">
        <meta name="description" content="Labeled treemap (flare)" />
        <title>Labeled treemap (flare)</title>
		<link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
	</head>
	<body>
        <svg height="500" width="960"></svg>
        <script src="index.js"></script>
	</body>
</html>

index.coffee

# layout, behaviors and scales
SCALE = 400
PADDING = 8
TIP = 10
LABEL_SCALE = 0.7

treemap = d3.layout.treemap()
  .size([SCALE, SCALE])
  .value((node) -> node.size)
  
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)
    
  enter_nodes = nodes.enter().insert('rect','rect')
    .attr
      class: 'node'
      x: (node) -> node.x
      y: (node) -> node.y
      width: (node) -> node.dx
      height: (node) -> node.dy
      stroke: (node) ->
        switch node.depth
          when 0 then '#333'
          when 1 then '#999'
          else '#DDD'
      'stroke-width': (node) ->
        switch node.depth
          when 0 then 3
          when 1 then 2
          else 1
  
  vis.selectAll('.label')
      .data(nodes_data.filter((node) -> node.depth is 1))
    .enter().append('text')
      .text((node) -> node.name)
      .attr
        class: 'label'
        dy: '0.35em'
        transform: (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
          if rotate
            bbox_width = node_bbox.height
            bbox_height = node_bbox.width
          else
            bbox_width = node_bbox.width
            bbox_height = node_bbox.height
            
          w_ratio = bbox_width / bbox.width
          h_ratio = bbox_height / bbox.height
          ratio = Math.min(w_ratio, h_ratio)*LABEL_SCALE
          
          return "translate(#{node.x+node.dx/2},#{node.y+node.dy/2}),scale(#{ratio}),rotate(#{if rotate then -90 else 0})"
        

index.css

svg {
  background: white;
}
.node {
  shape-rendering: crispEdges;
  vector-effect: non-scaling-stroke;
  fill: none;
}
.label {
  pointer-events: none;
  fill: #444;
  text-anchor: middle;
  font-family: sans-serif;
  text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;
}