Skip to content

Instantly share code, notes, and snippets.

@MrHen
Last active March 26, 2016 14:28
Show Gist options
  • Save MrHen/dfb27edd33acc72d4edd to your computer and use it in GitHub Desktop.
Save MrHen/dfb27edd33acc72d4edd to your computer and use it in GitHub Desktop.
D3 Hive Maze

A simple maze displayed with a D3 Hive Plot. Click to transition between nodes or peek to see the full state diagram at once.

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