A simple maze displayed with a D3 Hive Plot. Click to transition between nodes or peek to see the full state diagram at once.
Last active
March 26, 2016 14:28
-
-
Save MrHen/dfb27edd33acc72d4edd to your computer and use it in GitHub Desktop.
D3 Hive Maze
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
d3.hive={},d3.hive.link=function(){function t(t,s){var u,h=a(r,this,t,s),i=a(n,this,t,s);h.a>i.a&&(u=i,i=h,h=u),i.a-h.a>Math.PI&&(h.a+=2*Math.PI);var e=h.a+(i.a-h.a)/3,c=i.a-(i.a-h.a)/3;return h.r0-h.r1||i.r0-i.r1?"M"+Math.cos(h.a)*h.r0+","+Math.sin(h.a)*h.r0+"L"+Math.cos(h.a)*h.r1+","+Math.sin(h.a)*h.r1+"C"+Math.cos(e)*h.r1+","+Math.sin(e)*h.r1+" "+Math.cos(c)*i.r1+","+Math.sin(c)*i.r1+" "+Math.cos(i.a)*i.r1+","+Math.sin(i.a)*i.r1+"L"+Math.cos(i.a)*i.r0+","+Math.sin(i.a)*i.r0+"C"+Math.cos(c)*i.r0+","+Math.sin(c)*i.r0+" "+Math.cos(e)*h.r0+","+Math.sin(e)*h.r0+" "+Math.cos(h.a)*h.r0+","+Math.sin(h.a)*h.r0:"M"+Math.cos(h.a)*h.r0+","+Math.sin(h.a)*h.r0+"C"+Math.cos(e)*h.r1+","+Math.sin(e)*h.r1+" "+Math.cos(c)*i.r1+","+Math.sin(c)*i.r1+" "+Math.cos(i.a)*i.r1+","+Math.sin(i.a)*i.r1}function a(t,a,r,n){var e=t.call(a,r,n),c=+("function"==typeof s?s.call(a,e,n):s)+i,o=+("function"==typeof u?u.call(a,e,n):u),M=u===h?o:+("function"==typeof h?h.call(a,e,n):h);return{r0:o,r1:M,a:c}}var r=function(t){return t.source},n=function(t){return t.target},s=function(t){return t.angle},u=function(t){return t.radius},h=u,i=-Math.PI/2;return t.source=function(a){return arguments.length?(r=a,t):r},t.target=function(a){return arguments.length?(n=a,t):n},t.angle=function(a){return arguments.length?(s=a,t):s},t.radius=function(a){return arguments.length?(u=h=a,t):u},t.startRadius=function(a){return arguments.length?(u=a,t):u},t.endRadius=function(a){return arguments.length?(h=a,t):h},t}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<div id="hive-chart-container"></div> | |
<div id="peek" onmousedown="doPeek()" onmouseout="doUnpeek()" onmouseup="doUnpeek()">Peek</div> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script> | |
<script src="d3.hive.min.js"></script> | |
<script type="text/javascript" src="script.js"></script> | |
<script>d3.select(self.frameElement).style("height", "600px");</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var width = 600; | |
var height = 500; | |
var hiveSvg = d3.select("#hive-chart-container") | |
.append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
var peek = false; | |
var root = getJson(); | |
var connections = getConnections(); | |
drawHiveChart(); | |
function drawHiveChart() { | |
var innerRadius = 20; | |
var outerRadius = 240; | |
var angle = d3.scale.ordinal().domain(d3.range(root.children.length + 1)).rangePoints([0, 2 * Math.PI]), | |
radius = d3.scale.linear().domain([-1, 3]).range([innerRadius, outerRadius]), | |
color = d3.scale.category10().domain(d3.range(20)); | |
var hive = d3.hive.link() | |
.angle(function(d) { | |
return angle(d.x); | |
}) | |
.radius(function(d) { | |
return radius(d.y); | |
}); | |
var nodes = buildHiveNodes(root); | |
var links = buildLinks(nodes, connections); | |
hiveSvg.selectAll(".axis") | |
.data(angle.domain()) | |
.enter().append("line") | |
.attr("class", "axis") | |
.attr("transform", function(d) { | |
return "rotate(" + degrees(angle(d)) + ")"; | |
}) | |
.attr("x1", radius.range()[0]) | |
.attr("x2", radius.range()[1]); | |
var link = hiveSvg.selectAll(".link").data(links); | |
link.enter() | |
.append("path") | |
.attr('class', 'link') | |
.attr("d", hive) | |
.style('opacity', 0) | |
.style('stroke-width', 1.5) | |
.style("stroke", function(d) { | |
return color(d.source.x); | |
}); | |
link.transition().duration(400) | |
.style('opacity', function(d) { | |
return d.source.occupied ? 1 : peek ? 0.3 : 0; | |
}) | |
.style('stroke-width', function(d) { | |
return d.source.occupied ? 3 : 1.5; | |
}); | |
var node = hiveSvg.selectAll(".node") | |
.data(nodes.filter(function(d) { | |
return !d.children || !d.children.length; | |
}), function(d) { | |
return d.key; | |
}); | |
var newNodes = node.enter() | |
.append("g") | |
.attr('class', 'node') | |
.attr("transform", function(d) { | |
return "rotate(" + degrees(angle(d.x)) + ")translate(" + radius(d.y) + ",0)"; | |
}) | |
.on('click', function(d) { | |
var occupied = _.find(d.from, { occupied: true }); | |
if (occupied) { | |
occupied.occupied = false; | |
d.occupied = true; | |
drawHiveChart(); | |
} | |
}); | |
newNodes.append('circle') | |
.attr('class', 'outer'); | |
newNodes.append('circle') | |
.attr('class', 'inner') | |
.style("fill", function(d) { | |
return color(d.x); | |
}); | |
node.classed({ | |
'occupied': function(d) { return d.occupied; }, | |
'start': function(d) { return d.start; }, | |
'finish': function(d) { return d.finish; } | |
}); | |
node.select('.outer') | |
.transition().duration(200) | |
.style('r', function(d) { | |
return d.occupied || d.finish ? 10 : 5; | |
}) | |
} | |
function doPeek() { | |
if (!peek) { | |
peek = true; | |
d3.select("#peek").style('opacity', 0.5); | |
drawHiveChart(); | |
} | |
} | |
function doUnpeek() { | |
if (peek) { | |
peek = false; | |
d3.select("#peek").style('opacity', 1); | |
drawHiveChart(); | |
} | |
} | |
function degrees(radians) { | |
return radians / Math.PI * 180 - 90; | |
} | |
function getConnections() { | |
var connections = { | |
'a1': ['b2', 'c2'], | |
'b1': ['a2'], | |
'c1': ['a2'], | |
'a2': ['c3'], | |
'b2': ['c3'], | |
'c2': ['a3', 'b3'], | |
'a3': ['c4'], | |
'b3': ['c4'], | |
'c3': ['a4', 'b4'], | |
'a4': ['b5', 'c5'], | |
'b4': ['a5'], | |
'c4': ['a5'], | |
'a5': ['c6'], | |
'b5': ['c6'], | |
'c5': ['a6', 'b6'], | |
'a6': ['b7'], | |
'b6': ['a7', 'c7'], | |
'c6': ['b7'], | |
'a7': ['c8'], | |
'b7': ['c8'], | |
'c7': ['a8', 'b8'], | |
'a8': ['b1', 'c1'], | |
'b8': ['a1'], | |
'c8': ['a1'] | |
}; | |
return connections; | |
} | |
function getJson() { | |
var nodes = { | |
name: 'maze', | |
children: [{ | |
name: '1', | |
children: [{ | |
name: 'a', | |
occupied: true, | |
start: true, | |
}, { | |
name: 'b' | |
}, { | |
name: 'c', | |
finish: true | |
}] | |
}, { | |
name: '2', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}, { | |
name: '3', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}, { | |
name: '4', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}, { | |
name: '5', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}, { | |
name: '6', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}, { | |
name: '7', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}, { | |
name: '8', | |
children: [{ | |
name: 'a' | |
}, { | |
name: 'b' | |
}, { | |
name: 'c' | |
}] | |
}] | |
} | |
return nodes; | |
} | |
function buildHiveNodes(root) { | |
var nodes = []; | |
var unprocessed = [root]; | |
for (var i = 0; i < unprocessed.length; i++) { | |
var node = unprocessed[i]; | |
nodes.push(node); | |
for (var j = 0; j < (node.children || []).length; j++) { | |
unprocessed.push(node.children[j]); | |
node.children[j].parent = node; | |
} | |
if (node.parent && (!node.children || !node.children.length)) { | |
switch (node.name) { | |
case 'a': | |
node.y = 0; | |
break; | |
case 'b': | |
node.y = 1; | |
break; | |
case 'c': | |
node.y = 2; | |
break; | |
case 'S': | |
node.y = 3; | |
break; | |
case 'E': | |
node.y = 4; | |
break; | |
} | |
node.x = +node.parent.name; | |
} | |
} | |
return nodes; | |
} | |
function buildLinks(nodes, connections) { | |
var map = {}; | |
var links = []; | |
nodes.forEach(function(d) { | |
d.to = d.to || []; | |
d.from = d.from || []; | |
var key = d.name + (d.parent ? d.parent.name : ''); | |
d.key = key; | |
map[key] = d; | |
}); | |
for (var connection in connections) { | |
connections[connection].forEach(function(c) { | |
map[connection].to.push(map[c]); | |
map[c].from.push(map[connection]); | |
links.push({ | |
source: map[connection], | |
target: map[c] | |
}); | |
}); | |
} | |
return links; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
margin: auto; | |
position: relative; | |
width: 600px; | |
} | |
.link { | |
fill: none; | |
} | |
.axis, | |
.node circle { | |
stroke: #000; | |
stroke-width: 1.5px; | |
} | |
.node circle.inner { | |
r: 5; | |
} | |
.node circle.outer { | |
fill: #fff; | |
} | |
#peek { | |
color: #666; | |
border: solid 1px #666; | |
border-radius: 0.25em; | |
margin: auto; | |
padding: 0.5em; | |
text-align: center; | |
width: 100px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment