block by emeeks c2d226aea702ce6bf3ad6a77a91e548b

Decision tree II

Full Screen

This is a decision tree visualization with SVG filters to make it look more organic. Props to Nadieh Bremer for popularizing Gooey Data Viz.

Here it is without the SVG filters

index.js

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

var layers = ["jams", "juices", "beans", "plants", "herbs", "flower", "crafts", "meat"];

var rootSize = 40;
var sizeScale = d3.scaleLinear().domain([20, 5600]).range([2.5, rootSize]).clamp(true);

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

  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", 8).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 34 -7").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);

  var trimSize = 50;

  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 ? 1 : 2) / a.depth;
  });

  var tree2 = 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;
  }));

  var root2 = tree2(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.parent.data.key];
  }).style("stroke", "none").style("fill-opacity", 1).attr("d", function (d) {
    return d3_glyphEdge.d.taffy({ source: { x: project(d.x, d.y)[0], y: project(d.x, d.y)[1] }, target: { x: project(d.parent.x, d.parent.y)[0], y: project(d.parent.x, d.parent.y)[1] } }, 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 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(d.x, d.y) + ")";
  });

  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(x, y) {
    var angle = (x - 90) / 180 * Math.PI,
        radius = y;
    return [radius * Math.cos(angle), radius * Math.sin(angle)];
  }
});

index.html

<html>
<head>
  <title>Decision Tree II</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});