Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@emeeks
Last active March 17, 2016 02:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emeeks/531f107a0ff6eff5d543 to your computer and use it in GitHub Desktop.
Save emeeks/531f107a0ff6eff5d543 to your computer and use it in GitHub Desktop.
Orbital Layout 1

This is an early draft of a hierarchical orbital layout. Like other hierarchical layouts (pack, tree, treemap, etc) It takes nested data annotates it with xy values for display, in this case arranging the data into orbits, with child nodes orbiting parents and the root node at the center.

You can set the layout size as an array but for now only circular orbits are supported (no ellipses) and as such only the first value in the array is honored.

It will likely improve from this initial version but it's already got orbital rings and animation.

d3.layout.orbit = function() {
var currentTickStep = 0;
var orbitNodes;
var orbitSize = [1,1];
var nestedNodes;
var flattenedNodes = [];
var tickRadianStep = 0.004363323129985824;
var orbitDispatch = d3.dispatch('tick');
var tickInterval;
var orbitalRings = [];
var orbitDepthAdjust = 2.95;
function _orbitLayout(_data) {
return _orbitLayout;
}
_orbitLayout.mode = function() {
//Atomic, Solar, other?
}
_orbitLayout.start = function() {
//activate animation here
tickInterval = setInterval(
function() {
currentTickStep++;
flattenedNodes.forEach(function(_node){
if (_node.parent) {
_node.x = _node.parent.x + ( (_node.parent.ring / 2) * Math.sin( _node.angle + (currentTickStep * tickRadianStep)) );
_node.y = _node.parent.y + ( (_node.parent.ring / 2) * Math.cos( _node.angle + (currentTickStep * tickRadianStep)) );
}
})
orbitalRings.forEach(function(_ring) {
_ring.x = _ring.source.x;
_ring.y = _ring.source.y;
})
orbitDispatch.tick();
},
10);
}
_orbitLayout.stop = function() {
//deactivate animation here
clearInterval(tickInterval);
}
_orbitLayout.speed = function(_degrees) {
if (!arguments.length) return tickRadianStep / (Math.PI / 360);
tickRadianStep = tickRadianStep = _degrees * (Math.PI / 360);
return this;
}
_orbitLayout.size = function(_value) {
if (!arguments.length) return orbitSize;
orbitSize = _value;
return this;
//change size here
}
_orbitLayout.orbitSize = function(_value) {
//change ring size reduction (make that into dynamic function)
if (!arguments.length) return orbitDepthAdjust;
orbitDepthAdjust = _value;
return this
}
_orbitLayout.orbitalRings = function() {
//return an array of data corresponding to orbital rings
if (!arguments.length) return orbitalRings;
return this;
}
_orbitLayout.nodes = function(_data) {
if (!arguments.length) return flattenedNodes;
nestedNodes = _data;
calculateNodes();
return this;
}
d3.rebind(_orbitLayout, orbitDispatch, "on");
return _orbitLayout;
function calculateNodes() {
var _data = nestedNodes;
//If you have an array of elements, then create a root node (center)
//In the future, maybe make a binary star kind of thing?
if (!_data.values) {
orbitNodes = {key: "root", values: _data}
orbitNodes.values.forEach(function (_node) {
_node.parent = orbitNodes;
})
}
//otherwise assume it is an object with a root node
else {
orbitNodes = _data;
}
orbitNodes.x = orbitSize[0] / 2;
orbitNodes.y = orbitSize[1] / 2;
orbitNodes.deltaX = function(_x) {return _x}
orbitNodes.deltaY = function(_y) {return _y}
orbitNodes.ring = orbitSize[0] / 2;
orbitNodes.depth = 0;
flattenedNodes.push(orbitNodes);
traverseNestedData(orbitNodes)
function traverseNestedData(_node) {
if(_node.values) {
var thisPie = d3.layout.pie().value(function() {return 1});
var piedValues = thisPie(_node.values);
orbitalRings.push({source: _node, x: _node.x, y: _node.y, r: _node.ring / 2});
for (var x = 0; x<_node.values.length;x++) {
_node.values[x].angle = piedValues[x].endAngle;
_node.values[x].parent = _node;
_node.values[x].depth = _node.depth + 1;
_node.values[x].x = _node.values[x].parent.x + ( (_node.values[x].parent.ring / 2) * Math.sin( _node.values[x].angle ) );
_node.values[x].y = _node.values[x].parent.y + ( (_node.values[x].parent.ring / 2) * Math.cos( _node.values[x].angle ) );
_node.values[x].deltaX = function(_x) {return _x}
_node.values[x].deltaY = function(_y) {return _y}
_node.values[x].ring = _node.values[x].parent.ring / orbitDepthAdjust;
flattenedNodes.push(_node.values[x]);
traverseNestedData(_node.values[x]);
}
}
}
}
}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Orbits 1</title>
<meta charset="utf-8" />
</head>
<style>
#viz, svg {
width: 500px;
height: 500px;
}
</style>
<script>
function makeViz() {
nodes = [];
///All of this is just to fake some nested data
randomCountry = d3.scale.quantize().domain([0,1]).range(["USA", "FRA", "MEX", "GBR", "CAN"])
randomStatus = d3.scale.quantize().domain([0,1]).range(["amazing","okay", "cool", "boss", "dope", "lame"])
randomRole = d3.scale.quantize().domain([0,1]).range(["capital","metropole", "port"])
trafficCategories = ["high","medium","low","fargo"];
quantizeTraffic = d3.scale.quantize().domain([0,500]).range(trafficCategories);
//200 random things with random categorical attributes
nodes = d3.range(200).map(function(d,i) {return {i: i} })
nodes.forEach(function (node) {
node.country = randomCountry(Math.random());
node.status = randomStatus(Math.random());
node.traffic = parseInt(Math.random() * 500);
node.trafficRank = quantizeTraffic(node.traffic);
node.role = randomRole(Math.random())
})
var nest = d3.nest()
.key(function(d) {return d.country})
.key(function(d) {return d.trafficRank})
.key(function(d) {return d.status})
.key(function(d) {return d.role})
var awesomeFakeNestedData = nest.entries(nodes);
//If you already have some nested data, just send it to drawOrbit
drawOrbit(awesomeFakeNestedData)
}
function drawOrbit(_data) {
//down with category20a()!!
colors = d3.scale.category20b();
orbit = d3.layout.orbit().size([500,500]).nodes(_data);
d3.select("svg").selectAll("circle").data(orbit.nodes())
.enter()
.append("circle")
.attr("r", function(d) {return Math.max(1, 5 - d.depth)})
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
.style("fill", function(d) {return colors(d.depth)})
}
</script>
<body onload="makeViz()">
<div id="viz"><svg></svg></div>
<footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="d3.layout.orbit.js" charset="utf-8" type="text/javascript"></script>
</footer>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment