block by emeeks 71f1f45fce636654d4b9e1b5c469db6f

Decision tree III

Full Screen

A visualization of a decision tree with SVG filters to make it look more organic animated to make it seem to grow in a similarly organic fashion.

The animation is facilitated by taking advantage of the .ancestors() function attached to every node generated by d3’s tree layout. By getting an array of ancestors and filtering it to the ancestor at a specified depth (as seen in the ancestorAtDepth function) you can easily set a descendent node’s position to any ancestor position.

index.js

"use strict";

var colorHash = {
  "Y": "#ec6c15",
  "N": "#68a296",
  "root": "#ffdc44"
};

var baseLayers = ["bakedgoods", "cheese", "crafts", "flower", "eggs", "seafood", "herbs", "vegetables", "honey", "jams", "maple", "meat", "nursery", "nuts", "plants", "poultry", "prepared", "soap", "trees", "wine", "coffee", "beans", "fruits", "grains", "juices", "mushrooms", "petfood", "tofu", "wildharvest"];

var layers = [];

var treeSize = 8;

for (var x = 0; x < treeSize; x++) {
  var splicePoint = parseInt(Math.random() * baseLayers.length);
  var spliced = baseLayers.splice(splicePoint, 1);
  layers.push(spliced[0]);
}

var rootSize = 400 / treeSize;
var trimSize = 20;

var sizeScale = d3.scaleLinear().domain([trimSize, 5600]).range([4, rootSize]).clamp(true);

var transitionDuration = 500;

d3.json("farmers-markets.json", function (error, data) {

  var sampleMarket = data[parseInt(5000 * Math.random())];

  console.l;

  var svg = d3.select("svg");

  svg.select("defs").remove();
  var filter = svg.append("defs").append("filter").attr("id", "gooeyCodeFilter");
  filter.append("feGaussianBlur").attr("id", "gaussblurrer").attr("in", "SourceGraphic").attr("stdDeviation", 6).attr("color-interpolation-filters", "sRGB").attr("result", "blur");
  filter.append("feColorMatrix").attr("in", "blur").attr("mode", "matrix").attr("values", "1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 24 -5").attr("result", "gooey");

  var g = svg.append("g").attr("id", "filterG").attr("transform", "translate(510,510)").style("filter", "url(#gooeyCodeFilter)");

  var textG = svg.append("g").attr("transform", "translate(510,510)");

  var nesting = d3.nest();

  layers.forEach(function (d) {
    nesting.key(function (p) {
      return p[d];
    });
  });

  nesting.rollup(function (leaves) {
    return leaves.length;
  });

  var nestedData = nesting.entries(data);

  nestedData.forEach(function (d) {
    trimNodes(d, trimSize);
  });

  function trimNodes(d, threshold) {
    if (d.values) {
      d.value = d3.sum(d.values.map(function (p) {
        return leafValue(p);
      }));
      d.values = d.values.filter(function (p) {
        return leafValue(p) > threshold;
      });
      d.values.forEach(function (p) {
        trimNodes(p, threshold);
      });
    }
  }

  function leafValue(d) {
    if (d.value) {
      return d.value;
    }
    return d3.sum(d.values.map(function (p) {
      return leafValue(p);
    }));
  }

  var tree = d3.tree().size([360, 500]).separation(function (a, b) {
    return (a.parent == b.parent ? sizeScale(a.data.value) / rootSize : sizeScale(a.data.value) / rootSize * 2) / a.depth;
  });

  var root = tree(d3.hierarchy({ key: "root", values: nestedData }, function (d) {
    return d.values;
  }));

  textG.selectAll("circle.ring").data(layers).enter().append("circle").attr("r", function (d, i) {
    return (i + 1) * 62.5;
  }).style("fill", "none").style("stroke", "black").style("stroke-width", "1px").style("stroke-opacity", 0.1);

  g.selectAll(".link").data(root.descendants().slice(1).filter(function (d) {
    return d.data.value > 1;
  })).enter().append("path").attr("class", "link").style("fill", function (d) {
    return colorHash[d.data.key];
  }).style("stroke", "none").style("fill-opacity", 1).attr("d", function (d) {
    return taffyEdge(d, 0);
  });

  var node = g.selectAll(".node").data(root.descendants()).enter().append("g").attr("class", function (d) {
    return "node" + (d.children ? " node-internal" : " node-leaf");
  }).attr("transform", function (d) {
    return "translate(" + project(ancestorAtDepth(d, 0)) + ")";
  });

  node.append("circle").attr("r", function (d) {
    return d.data.value ? sizeScale(d.data.value) : rootSize;
  }).style("fill", function (d) {
    return colorHash[d.data.key];
  });

  textG.selectAll("text.ring").data(layers).enter().append("text").attr("y", function (d, i) {
    return 5 - (i + 1) * 62.5;
  }).style("text-anchor", "middle").style("stroke", "white").style("stroke-width", "3px").style("stroke-opacity", 0.75).style("fill", "none").text(function (d) {
    return d;
  });

  textG.selectAll("text.ring").data(layers).enter().append("text").attr("y", function (d, i) {
    return 5 - (i + 1) * 62.5;
  }).style("text-anchor", "middle").text(function (d) {
    return d;
  });

  function project(xy) {
    var x = xy[0];
    var y = xy[1];
    var angle = (x - 90) / 180 * Math.PI,
        radius = y;
    return [radius * Math.cos(angle), radius * Math.sin(angle)];
  }

  function ancestorAtDepth(d, depth) {
    var ancestorsAtDepth = d.ancestors().filter(function (d) {
      return d.depth === depth;
    });

    if (ancestorsAtDepth.length === 1) {
      return [ancestorsAtDepth[0].x, ancestorsAtDepth[0].y];
    }
    return [d.x, d.y];
  }

  function taffyEdge(d, depth) {
    var aSource = project(ancestorAtDepth(d, depth));
    var aTarget = project(ancestorAtDepth(d.parent, depth));
    var source = { x: aSource[0], y: aSource[1] };
    var target = { x: aTarget[0], y: aTarget[1] };

    return d3_glyphEdge.d.taffy({ source: source, target: target }, sizeScale(d.data.value), d.parent.data.value ? sizeScale(d.parent.data.value) : rootSize, (sizeScale(d.data.value) + d.parent.data.value ? sizeScale(d.parent.data.value) : rootSize) * 0.75);
  }

  var linkTransition = d3.selectAll(".link").transition().ease(d3.easeElasticInOut).duration(transitionDuration).attr("d", function (d) {
    return taffyEdge(d, 1);
  });

  var nodeTransition = d3.selectAll("g.node").transition().ease(d3.easeElasticInOut).duration(transitionDuration).attr("transform", function (d) {
    return "translate(" + project(ancestorAtDepth(d, 1)) + ")";
  });

  var _loop = function _loop(_x) {
    linkTransition = linkTransition.transition().ease(d3.easeElasticInOut).duration(transitionDuration).attr("d", function (d) {
      return taffyEdge(d, _x);
    });

    nodeTransition = nodeTransition.transition().ease(d3.easeElasticInOut).duration(transitionDuration).attr("transform", function (d) {
      return "translate(" + project(ancestorAtDepth(d, _x)) + ")";
    });
  };

  for (var _x = 1; _x <= treeSize; _x++) {
    _loop(_x);
  }
});

index.html

<html>
<head>
  <title>Decision Tree III</title>
  <meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3.glyphedge.js"></script>
<script src="index.js"></script>
</head>
<style>
  svg {
    height: 1200px;
    width: 1200px;
  }
</style>
<body>

<div id="viz">
  <svg class="main">
  </svg>
</div>
</body>
</html>

d3.glyphedge.js

!function(t,a){"object"==typeof exports&&"undefined"!=typeof module?a(exports):"function"==typeof define&&define.amd?define("d3-glyphEdge",["exports"],a):a(t.d3_glyphEdge={})}(this,function(t){"use strict";function a(t,a,r,e){var s=t.target.y-t.source.y,o=t.target.x-t.source.x,c=3*e,h=Math.atan2(o,s)+Math.PI/2,M=h-Math.PI/2,u=h+Math.PI/2,n=t.source.x+e*Math.cos(M),y=t.source.y-e*Math.sin(M),x=t.source.x+e*Math.cos(u),g=t.source.y-e*Math.sin(u),i=t.target.x-e*Math.cos(M),f=t.target.y+e*Math.sin(M),L=t.source.x+r*Math.cos(M),P=t.source.y-r*Math.sin(M),I=t.source.x+r*Math.cos(u),l=t.source.y-r*Math.sin(u),d=t.target.x+r*Math.cos(M),p=t.target.y-r*Math.sin(M),v=t.source.y-t.target.y,q=t.source.x-t.target.x,m=P-p,z=L-d,A=y-f,O=n-i,b=Math.sqrt(z*z+m*m),E=Math.sqrt(q*q+v*v),j=I-z*(b-c-a)/b,w=l-m*(b-c-a)/b,T=x-O*(b-c-a)/b,_=g-A*(b-c-a)/b,k=t.source.x-q*(E-a)/E,B=t.source.y-v*(E-a)/E;return"M"+t.source.x+","+t.source.y+"L"+I+","+l+"L"+j+","+w+"L"+T+","+_+"L"+k+","+B+"L"+t.source.x+","+t.source.y+"z"}function r(t){var a=t.target.x-t.source.x,r=t.target.y-t.source.y,e=Math.sqrt(a*a+r*r);return"M"+t.source.x+","+t.source.y+"A"+e+","+e+" 0 0,1 "+t.target.x+","+t.target.y}function e(t,a){var r=t.target.y-t.source.y,e=t.target.x-t.source.x,s=Math.atan2(e,r)+Math.PI/2,o=s-Math.PI/2,c=s+Math.PI/2,h=t.source.x+a*Math.cos(o),M=t.source.y-a*Math.sin(o),u=t.source.x+a*Math.cos(c),n=t.source.y-a*Math.sin(c),y=t.target.x-a*Math.cos(o),x=t.target.y+a*Math.sin(o),g=t.target.x-a*Math.cos(c),i=t.target.y+a*Math.sin(c);return"M"+h+","+M+"L"+u+","+n+"L"+y+","+x+"L"+g+","+i+"z"}function s(t,a,r,e){var s=t.target.y-t.source.y,o=t.target.x-t.source.x,c=Math.atan2(o,s)+Math.PI/2,h=c-Math.PI/2,M=c+Math.PI/2,u=t.source.x+a*Math.cos(h),n=t.source.y-a*Math.sin(h),y=t.source.x+a*Math.cos(M),x=t.source.y-a*Math.sin(M),g=t.target.x+r*Math.cos(M),i=t.target.y-r*Math.sin(M),f=t.target.x+r*Math.cos(h),L=t.target.y-r*Math.sin(h),P=t.source.x+e*Math.cos(h),I=t.source.y-e*Math.sin(h),l=t.source.x+e*Math.cos(M),d=t.source.y-e*Math.sin(M),p=t.target.x+e*Math.cos(h),v=t.target.y-e*Math.sin(h),q=t.target.x+e*Math.cos(M),m=t.target.y-e*Math.sin(M),z=(I+v)/2,A=(P+p)/2,O=(d+m)/2,b=(l+q)/2;return"M"+u+","+n+"L"+y+","+x+" L "+b+","+O+" L "+g+","+i+" L "+f+","+L+" L "+A+","+z+"z"}function o(t,a){var r=t.target.y-t.source.y,e=t.target.x-t.source.x,s=Math.atan2(e,r)+Math.PI/2,o=s-Math.PI/2,c=s+Math.PI/2,h=t.source.x+a*Math.cos(o),M=t.source.y-a*Math.sin(o),u=t.source.x+a*Math.cos(c),n=t.source.y-a*Math.sin(c);return"M"+h+","+M+"L"+u+","+n+" L "+t.target.x+","+t.target.y+"z"}function c(t,a){var r=t.target.y-t.source.y,e=t.target.x-t.source.x,s=Math.atan2(e,r)+Math.PI/2,o=s-Math.PI/2,c=s+Math.PI/2,h=t.target.x+a*Math.cos(o),M=t.target.y-a*Math.sin(o),u=t.target.x+a*Math.cos(c),n=t.target.y-a*Math.sin(c);return"M"+h+","+M+"L"+u+","+n+" L "+t.source.x+","+t.source.y+"z"}function h(t,a,r,e){var s=t.target.y-t.source.y,o=t.target.x-t.source.x,c=3*e,h=Math.atan2(o,s)+Math.PI/2,M=h-Math.PI/2,u=h+Math.PI/2,n=t.source.x+e*Math.cos(M),y=t.source.y-e*Math.sin(M),x=t.source.x+e*Math.cos(u),g=t.source.y-e*Math.sin(u),i=t.target.x-e*Math.cos(M),f=t.target.y+e*Math.sin(M),L=t.target.x-e*Math.cos(u),P=t.target.y+e*Math.sin(u),I=t.source.x+r*Math.cos(M),l=t.source.y-r*Math.sin(M),d=t.source.x+r*Math.cos(u),p=t.source.y-r*Math.sin(u),v=t.target.x+r*Math.cos(M),q=t.target.y-r*Math.sin(M),m=t.target.x+r*Math.cos(u),z=t.target.y-r*Math.sin(u),A=t.source.y-t.target.y,O=t.source.x-t.target.x,b=l-q,E=I-v,j=p-z,w=d-m,T=y-f,_=n-i,k=g-P,B=x-L,C=Math.sqrt(E*E+b*b),D=Math.sqrt(O*O+A*A),F=d-E*(C-c-a)/C,G=p-b*(C-c-a)/C,H=I-w*(C-c-a)/C,J=l-j*(C-c-a)/C,K=n-B*(C-c-a)/C,N=y-k*(C-c-a)/C,Q=x-_*(C-c-a)/C,R=g-T*(C-c-a)/C,S=t.source.x-O*(D-a)/D,U=t.source.y-A*(D-a)/D;return"M"+d+","+p+"L"+F+","+G+"L"+Q+","+R+"L"+S+","+U+"L"+K+","+N+"L"+H+","+J+"L"+I+","+l+"z"}function M(t,a,r,e){var s=t.target.y-t.source.y,o=t.target.x-t.source.x,c=Math.atan2(o,s)+Math.PI/2,h=c+(.75*Math.PI+.25*e),M=c+(.25*Math.PI-.25*e),u=t.source.x+a*Math.cos(h),n=t.source.y-a*Math.sin(h),y=t.target.x+r*Math.cos(M),x=t.target.y-r*Math.sin(M);return{source:{x:u,y:n},target:{x:y,y:x}}}function u(t,a){var r=t.target.y-t.source.y,e=t.target.x-t.source.x,s=Math.atan2(e,r)+Math.PI/2,o=s+.75*Math.PI,c=s+.25*Math.PI,h=t.source.x+a*Math.cos(o),M=t.source.y-a*Math.sin(o),u=t.target.x+a*Math.cos(c),n=t.target.y-a*Math.sin(c);return{source:{x:h,y:M},target:{x:u,y:n}}}function n(t,a,r,e){function s(){t.particles.push({current:0,xOffset:r-r*Math.random()*2,yOffset:r-r*Math.random()*2})}if(r/=2,t.particles=t.particles.filter(function(t){return t.current<a.getTotalLength()}),t.frequency<1)Math.random()<t.frequency&&s();else for(var o=0;o<t.frequency;o++)s();t.particles.forEach(function(t){t.current=t.current+e;var r=a.getPointAtLength(t.current);t.x=r.x+t.xOffset,t.y=r.y+t.yOffset})}var y={arrowhead:h,comet:c,nail:o,taffy:s,ribbon:e,lineArc:r,halfArrow:a},x={offset:u,parallel:M},g={particle:n},i="1.1.1";t.version=i,t.d=y,t.project=x,t.mutate=g});