block by nitaku 10718152ec982ab3bca7

(Almost) stable word cloud

Full Screen

-

index.js

(function() {
  var SCALE, color, correct_x, correct_y, height, redraw, scores, svg, treemap, vis, width, zoom, zoomable_layer;

  SCALE = 400;

  treemap = d3.layout.treemap().size([SCALE * 1.3, 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*1.3 / 2) + "," + (-SCALE / 2) + ")"
  });

  scores = [7, 6, 5, 4, 3, 2, 1];

  redraw = function(duration) {
    var enter_labels, labels, nodes_data, tree;

    scores[Math.floor(Math.random() * 7)] += Math.random() * 3;
    tree = {
      children: [
        {
          name: 'alpha',
          size: scores[0]
        }, {
          name: 'beta',
          size: scores[1]
        }, {
          name: 'gamma',
          size: scores[2]
        }, {
          name: 'delta',
          size: scores[3]
        }, {
          name: 'epsilon',
          size: scores[4]
        }, {
          name: 'zeta',
          size: scores[5]
        }, {
          name: 'eta',
          size: scores[6]
        }
      ]
    };
    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',
      preserveAspectRatio: 'none'
    });
    enter_labels.append('text').text(function(node) {
      return node.name.toUpperCase();
    }).attr({
      dy: '0.35em',
      fill: function(node) {
        return color(node.name, false);
      }
    });
    labels.select('text').each(function(node) {
      var bbox, bbox_aspect, node_bbox, node_bbox_aspect;

      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;
      node.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 (node.rotate) {
        return node.label_bbox = {
          x: node.label_bbox.y,
          y: node.label_bbox.x,
          width: node.label_bbox.height,
          height: node.label_bbox.width
        };
      }
    });
    return labels.transition().duration(duration).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;
      }
    }).select('text').attr({
      transform: function(node) {
        if (node.rotate) {
          return 'rotate(-90)';
        } else {
          return 'rotate(0)';
        }
      }
    });
  };

  setInterval((function() {
    return redraw(1200);
  }), 2000);

  redraw(0);

}).call(this);

index.html

<!DOCTYPE html>
<html>
	<head>
        <meta charset="utf-8">
        <meta name="description" content="(Almost) stable word cloud" />
        <title>(Almost) stable word cloud</title>
		<link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//davidbau.com/encode/seedrandom-min.js"></script>
        <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

treemap = d3.layout.treemap()
  .size([SCALE*1.3, 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*1.3/2},#{-SCALE/2})"

scores = [7, 6, 5, 4, 3, 2, 1]  

redraw = (duration) ->
  scores[Math.floor(Math.random()*7)] += Math.random()*3
  tree = {
    children: [
      {name: 'alpha',   size: scores[0]},
      {name: 'beta',    size: scores[1]},
      {name: 'gamma',   size: scores[2]},
      {name: 'delta',   size: scores[3]},
      {name: 'epsilon', size: scores[4]},
      {name: 'zeta',    size: scores[5]},
      {name: 'eta',     size: scores[6]}
    ]
  }
  
  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'
        preserveAspectRatio: 'none'
    
  enter_labels.append('text')
      .text((node) -> node.name.toUpperCase())
      .attr
        dy: '0.35em'
        fill: (node) -> color(node.name, false)
  
  labels.select('text').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
        
        node.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 node.rotate
          node.label_bbox = {
            x: node.label_bbox.y,
            y: node.label_bbox.x,
            width: node.label_bbox.height,
            height: node.label_bbox.width
          }
        
  labels.transition().duration(duration)
    .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}"
    .select('text')
      .attr
        transform: (node) -> if node.rotate then 'rotate(-90)' else 'rotate(0)'
      
setInterval((() -> redraw(1200)), 2000)
redraw(0)

index.css

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;
}