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.
"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);
}
});
<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>
!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});