block by nitaku 2d3424ed038ac3004a9cde1540135ece

Isolario Powergraph Treemap

Full Screen

A cascaded treemap showing the hierarchy computed by a power graph analysis of a graph containing about 50K nodes and 100K links. The resulting treemap contains many flat regions (i.e., regions with many nodes at the same depth).

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var HEADER, OFFSET, color, h, height, svg, treemap, vis, w, width, zoom, zoomable_layer;

  OFFSET = 0.1;

  HEADER = 0;

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  w = width - 40;

  h = height - 40;

  treemap = d3.layout.treemap().size([w, h]).value(function(node) {
    return node.value;
  }).padding([OFFSET + HEADER, OFFSET, OFFSET, OFFSET]).sort(function(a, b) {
    h = d3.ascending(a.height, b.height);
    if (h === 0) {
      return d3.ascending(a.value, b.value);
    }
    return h;
  });

  svg.attr({
    viewBox: (-width / 2) + " " + (-height / 2) + " " + width + " " + height
  });

  zoomable_layer = svg.append('g');

  zoom = d3.behavior.zoom().scaleExtent([-Infinity, Infinity]).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(" + (-w / 2) + "," + (-h / 2) + ")"
  });

  color = d3.scale.linear().domain([0, 17]).range([d3.hcl(320, 0, 20), d3.hcl(200, 70, 80)]).interpolate(d3.interpolateHcl);

  d3.json('isolario_power.json', function(tree) {
    var aggregate, cells, compute_height, compute_heights, data;
    aggregate = function(node) {
      if (node.children != null) {
        node.children.forEach(aggregate);
        return node.value = d3.sum(node.children, function(d) {
          return d.value;
        });
      }
    };
    aggregate(tree);
    compute_height = function(node) {
      if (node.children != null) {
        node.children.forEach(compute_height);
        return node.height = 1 + d3.max(node.children, function(d) {
          return d.height;
        });
      } else {
        return node.height = 0;
      }
    };
    compute_height(tree);
    data = treemap.nodes(tree);
    compute_heights = function(node) {
      var bchildren, bmax, rchildren, rmax;
      if (node.children != null) {
        node.children.forEach(compute_heights);
        rmax = d3.max(node.children, function(c) {
          return c.x + c.dx;
        });
        rchildren = node.children.filter(function(d) {
          return (d.x + d.dx) >= rmax;
        });
        node.height_r = 1 + d3.max(rchildren, function(d) {
          return d.height_r;
        });
        bmax = d3.max(node.children, function(c) {
          return c.y + c.dy;
        });
        bchildren = node.children.filter(function(d) {
          return (d.y + d.dy) >= bmax;
        });
        return node.height_b = 1 + d3.max(bchildren, function(d) {
          return d.height_b;
        });
      } else {
        node.height_r = 0;
        return node.height_b = 0;
      }
    };
    compute_heights(tree);
    data.sort(function(a, b) {
      return d3.ascending(a.depth, b.depth);
    });
    cells = vis.selectAll('.cell').data(data);
    return cells.enter().append('rect').attr({
      "class": 'cell',
      x: function(d) {
        return d.x;
      },
      y: function(d) {
        return d.y;
      },
      width: function(d) {
        w = d.dx - 2 * OFFSET * d.height_r;
        if (w < 0) {
          return 0;
        } else {
          return w;
        }
      },
      height: function(d) {
        h = d.dy - 2 * OFFSET * d.height_b;
        if (h < 0) {
          return 0;
        } else {
          return h;
        }
      },
      fill: function(d) {
        return color(d.depth);
      },
      stroke: function(d) {
        return color(d.depth + 0.5);
      }
    }).classed('leaf', function(d) {
      return (d.children == null) || d.children.length === 0;
    });
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Isolario Powergraph Treemap</title>
    <link type="text/css" href="index.css" rel="stylesheet"/>
    <script src="//d3js.org/d3.v3.min.js"></script>
  </head>
  <body>
    <svg></svg>
    <script src="index.js"></script>
  </body>
</html>

index.coffee

OFFSET = 1
HEADER = 0

svg = d3.select('svg')
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height

w = width - 40
h = height - 40


treemap = d3.layout.treemap()
  .size([w, h])
  .value((node) -> node.value)
  .padding([OFFSET+HEADER,OFFSET,OFFSET,OFFSET])
  .sort (a,b) ->
    # taller subtrees first, then larger subtrees first
    h = d3.ascending(a.height, b.height)
    if h is 0
      return d3.ascending(a.value, b.value)
    return h

# 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([-Infinity,Infinity]) # 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(#{-w/2},#{-h/2})"

color = d3.scale.linear()
  .domain([0,17])
  .range([d3.hcl(320,0,20), d3.hcl(200,70,80)])
  .interpolate(d3.interpolateHcl)

d3.json 'isolario_power.json', (tree) ->
  # needed for sorting
  aggregate = (node) ->
    if node.children?
      node.children.forEach aggregate
      node.value = d3.sum node.children, (d) -> d.value

  aggregate(tree)

  # needed for sorting
  compute_height = (node) ->
    if node.children?
      node.children.forEach compute_height
      node.height = 1+d3.max node.children, (d) -> d.height
    else
      node.height = 0

  compute_height(tree)


  data = treemap.nodes(tree)


  # needed for the layout (custom algorithm, part I)
  compute_heights = (node) ->
    if node.children?
      node.children.forEach compute_heights

      rmax = d3.max node.children, (c) -> c.x+c.dx
      rchildren = node.children.filter (d) -> (d.x+d.dx) >= rmax

      node.height_r = 1+d3.max rchildren, (d) -> d.height_r

      bmax = d3.max node.children, (c) -> c.y+c.dy
      bchildren = node.children.filter (d) -> (d.y+d.dy) >= bmax

      node.height_b = 1+d3.max bchildren, (d) -> d.height_b
    else
      node.height_r = 0
      node.height_b = 0

  compute_heights(tree)


# Lu and Fogarty algorithm (sort of)
#   walk = (node) ->
#     if node.children? and node.children.length > 0
#       node.children.forEach walk
#       node.x = d3.min(node.children, (d) -> d.x)
#       node.y = d3.min(node.children, (d) -> d.y)
#       node.dx = d3.max(node.children, (d) -> d.x+d.dx) - node.x
#       node.dy = d3.max(node.children, (d) -> d.y+d.dy) - node.y# + HEADER

#       node.x -= OFFSET
#       node.y -= OFFSET# + HEADER

#   walk(tree)

  data.sort (a,b) -> d3.ascending(a.depth, b.depth)

  cells = vis.selectAll '.cell'
    .data data

  cells.enter().append 'rect'
    .attr
      class: 'cell'
      x: (d) -> d.x
      y: (d) -> d.y
      width: (d) ->
        w = d.dx - 2*OFFSET*d.height_r # custom algorithm, part II
        return if w < 0 then 0 else w
      height: (d) ->
        h = d.dy - 2*OFFSET*d.height_b
        return if h < 0 then 0 else h
      fill: (d) -> color(d.depth)
      stroke: (d) -> color(d.depth+0.5)
    .classed 'leaf', (d) -> not d.children? or d.children.length is 0

  # labels = vis.selectAll '.label'
  #   .data data.filter (d) -> d.children? and d.children.length > 0
  #
  # labels.enter().append 'text'
  #   .text (d) -> d.data.name
  #   .attr
  #     class: 'label'
  #     x: (d) -> d.x
  #     y: (d) -> d.y
  #     dx: 0.1
  #     dy: '1em'

index.css

html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
svg {
  width: 100%;
  height: 100%;
  background: black;
}
.cell {
  stroke-width: 0;
}
.leaf {
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.label {
  font-family: sans-serif;
  font-size: 1px;
  fill: black;
  pointer-events: none;
}