block by emeeks 2476c4c938ed59b38c2f10b1e5c7b082

Decision tree IV

Full Screen

Another decision tree visualization, this one showing all 28 categories from the farmer’s market dataset and highlighting (in yellow) the particular attributes of a randomly selected market.

Earlier versions:

Decision Tree I

Decision Tree II

Decision Tree III

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 = 28;

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([2, 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", 2).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(250,250)").style("filter", "url(#gooeyCodeFilter)");

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

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

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

  var currentNode = root;
  var sampleNodes = [];
  layers.forEach(function (layer) {
    var YN = sampleMarket[layer];
    if (currentNode && currentNode.children) {
      currentNode = currentNode.children.filter(function (d) {
        return d.data.key === YN;
      })[0];
    }
    if (currentNode) {
      sampleNodes.push(currentNode);
    }
  });

  d3.selectAll("path.link").filter(function (d) {
    return sampleNodes.indexOf(d) !== -1 && sampleNodes.indexOf(d.parent) !== -1;
  }).style("fill", "#ffdc44");
});

index.html

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