Skip to content

Instantly share code, notes, and snippets.

@wcjohnson11
Created November 8, 2015 15:00
Show Gist options
  • Save wcjohnson11/7a0309cc11757a3e5b3e to your computer and use it in GitHub Desktop.
Save wcjohnson11/7a0309cc11757a3e5b3e to your computer and use it in GitHub Desktop.
Orbital Layout of D3.js API

An example of d3.layout.orbit that visualizes the D3 API.

When you mouseover a node, it displays its label and centers the orbit layout on that node. It also draws spokes to children and parents where applicable.

{
"name": "d3",
"children": [
{"name": "version"},
{"name": "ascending"},
{"name": "descending"},
{"name": "min"},
{"name": "max"},
{"name": "extent"},
{"name": "sum"},
{"name": "mean"},
{"name": "quantile"},
{"name": "median"},
{"name": "variance"},
{"name": "deviation"},
{"name": "bisectLeft"},
{"name": "bisectRight"},
{"name": "bisect"},
{"name": "bisector"},
{"name": "shuffle"},
{"name": "permute"},
{"name": "pairs"},
{"name": "zip"},
{"name": "transpose"},
{"name": "keys"},
{"name": "values"},
{"name": "entries"},
{"name": "merge"},
{"name": "range"},
{"name": "map"},
{"name": "nest"},
{"name": "set"},
{
"name": "behavior",
"children": [
{"name": "drag"},
{"name": "zoom"}
]
},
{"name": "rebind"},
{"name": "dispatch"},
{"name": "event"},
{"name": "requote"},
{
"name": "selection",
"children": [{"name": "enter"}]
},
{
"name": "ns",
"children": [
{
"name": "prefix",
"children": [
{"name": "svg"},
{"name": "xhtml"},
{"name": "xlink"},
{"name": "xml"},
{"name": "xmlns"}
]
},
{"name": "qualify"}
]
},
{"name": "select"},
{"name": "selectAll"},
{"name": "mouse"},
{"name": "touch"},
{"name": "touches"},
{"name": "interpolateZoom"},
{"name": "color"},
{"name": "hsl"},
{"name": "hcl"},
{"name": "lab"},
{"name": "rgb"},
{"name": "functor"},
{"name": "xhr"},
{"name": "dsv"},
{
"name": "csv",
"children": [
{"name": "parse"},
{"name": "parseRows"},
{"name": "format"},
{"name": "formatRows"}
]
},
{
"name": "tsv",
"children": [
{"name": "parse"},
{"name": "parseRows"},
{"name": "format"},
{"name": "formatRows"}
]
},
{
"name": "timer",
"children": [{"name": "flush"}]
},
{"name": "round"},
{"name": "formatPrefix"},
{
"name": "time",
"children": [
{
"name": "year",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "years",
"children": [{"name": "utc"}]
},
{
"name": "day",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "days",
"children": [{"name": "utc"}]
},
{"name": "dayOfYear"},
{
"name": "sunday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "sundays",
"children": [{"name": "utc"}]
},
{"name": "sundayOfYear"},
{
"name": "monday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "mondays",
"children": [{"name": "utc"}]
},
{"name": "mondayOfYear"},
{
"name": "tuesday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "tuesdays",
"children": [{"name": "utc"}]
},
{"name": "tuesdayOfYear"},
{
"name": "wednesday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "wednesdays",
"children": [{"name": "utc"}]
},
{"name": "wednesdayOfYear"},
{
"name": "thursday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "thursdays",
"children": [{"name": "utc"}]
},
{"name": "thursdayOfYear"},
{
"name": "friday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "fridays",
"children": [{"name": "utc"}]
},
{"name": "fridayOfYear"},
{
"name": "saturday",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "saturdays",
"children": [{"name": "utc"}]
},
{"name": "saturdayOfYear"},
{
"name": "week",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "weeks",
"children": [{"name": "utc"}]
},
{"name": "weekOfYear"},
{
"name": "format",
"children": [
{
"name": "utc",
"children": [{"name": "multi"}]
},
{"name": "multi"},
{
"name": "iso",
"children": [
{"name": "parse"},
{"name": "toString"}
]
}
]
},
{
"name": "second",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "seconds",
"children": [{"name": "utc"}]
},
{
"name": "minute",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "minutes",
"children": [{"name": "utc"}]
},
{
"name": "hour",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "hours",
"children": [{"name": "utc"}]
},
{
"name": "month",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{
"name": "range",
"children": [{"name": "utc"}]
},
{
"name": "utc",
"children": [
{"name": "round"},
{"name": "ceil"},
{"name": "offset"},
{"name": "range"}
]
}
]
},
{
"name": "months",
"children": [{"name": "utc"}]
},
{
"name": "scale",
"children": [{"name": "utc"}]
}
]
},
{"name": "locale"},
{"name": "format"},
{
"name": "geo",
"children": [
{"name": "stream"},
{"name": "area"},
{"name": "bounds"},
{"name": "centroid"},
{"name": "clipExtent"},
{
"name": "conicEqualArea",
"children": [{"name": "raw"}]
},
{"name": "albers"},
{"name": "albersUsa"},
{"name": "path"},
{"name": "transform"},
{"name": "projection"},
{"name": "projectionMutator"},
{
"name": "equirectangular",
"children": [{"name": "raw"}]
},
{"name": "rotation"},
{"name": "circle"},
{"name": "distance"},
{"name": "graticule"},
{"name": "greatArc"},
{"name": "interpolate"},
{"name": "length"},
{
"name": "azimuthalEqualArea",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
},
{
"name": "azimuthalEquidistant",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
},
{
"name": "conicConformal",
"children": [{"name": "raw"}]
},
{
"name": "conicEquidistant",
"children": [{"name": "raw"}]
},
{
"name": "gnomonic",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
},
{
"name": "mercator",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
},
{
"name": "orthographic",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
},
{
"name": "stereographic",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
},
{
"name": "transverseMercator",
"children": [
{
"name": "raw",
"children": [{"name": "invert"}]
}
]
}
]
},
{
"name": "geom",
"children": [
{"name": "hull"},
{"name": "polygon"},
{"name": "voronoi"},
{"name": "delaunay"},
{"name": "quadtree"}
]
},
{"name": "interpolateRgb"},
{"name": "interpolateObject"},
{"name": "interpolateNumber"},
{"name": "interpolateString"},
{"name": "interpolate"},
{"name": "interpolators"},
{"name": "interpolateArray"},
{"name": "ease"},
{"name": "interpolateHcl"},
{"name": "interpolateHsl"},
{"name": "interpolateLab"},
{"name": "interpolateRound"},
{"name": "transform"},
{"name": "interpolateTransform"},
{
"name": "layout",
"children": [
{"name": "bundle"},
{"name": "chord"},
{"name": "force"},
{"name": "hierarchy"},
{"name": "partition"},
{"name": "pie"},
{"name": "stack"},
{"name": "histogram"},
{"name": "pack"},
{"name": "tree"},
{"name": "cluster"},
{"name": "treemap"}
]
},
{
"name": "random",
"children": [
{"name": "normal"},
{"name": "logNormal"},
{"name": "bates"},
{"name": "irwinHall"}
]
},
{
"name": "scale",
"children": [
{"name": "linear"},
{"name": "log"},
{"name": "pow"},
{"name": "sqrt"},
{"name": "ordinal"},
{"name": "category10"},
{"name": "category20"},
{"name": "category20b"},
{"name": "category20c"},
{"name": "quantile"},
{"name": "quantize"},
{"name": "threshold"},
{"name": "identity"}
]
},
{
"name": "svg",
"children": [
{"name": "arc"},
{
"name": "line",
"children": [{"name": "radial"}]
},
{
"name": "area",
"children": [{"name": "radial"}]
},
{"name": "chord"},
{
"name": "diagonal",
"children": [{"name": "radial"}]
},
{"name": "symbol"},
{"name": "symbolTypes"},
{"name": "axis"},
{"name": "brush"}
]
},
{"name": "transition"},
{"name": "text"},
{"name": "json"},
{"name": "html"},
{"name": "xml"},
{"name": "hexbin"}
]
}
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 = function() {return 2.95};
var childrenAccessor = function(d) {return d.children};
var tickRadianFunction = function() {return 1};
var fixedOrbitArray = [99];
var orbitMode = "flat";
function _orbitLayout() {
return _orbitLayout;
}
_orbitLayout.mode = function(_mode) {
//Atomic, Solar, other?
if (!arguments.length) return orbitMode;
if (_mode == "solar") {
fixedOrbitArray = [1]
}
if (_mode == "atomic") {
fixedOrbitArray = [2,8]
}
if (_mode == "flat") {
fixedOrbitArray = [99]
}
orbitMode = _mode;
if (Array.isArray(_mode)) {
fixedOrbitArray = _mode;
orbitMode = "custom";
}
return this
}
_orbitLayout.start = function() {
//activate animation here
tickInterval = setInterval(
function() {
currentTickStep++;
flattenedNodes.forEach(function(_node){
if (_node.parent) {
_node.x = _node.parent.x + ( (_node.ring) * Math.sin( _node.angle + (currentTickStep * tickRadianStep * tickRadianFunction(_node))) );
_node.y = _node.parent.y + ( (_node.ring) * Math.cos( _node.angle + (currentTickStep * tickRadianStep * tickRadianFunction(_node))) );
}
})
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.revolution = function(_function) {
//change ring size reduction (make that into dynamic function)
if (!arguments.length) return tickRadianFunction;
tickRadianFunction = _function;
return this
}
_orbitLayout.orbitSize = function(_function) {
//change ring size reduction (make that into dynamic function)
if (!arguments.length) return orbitDepthAdjust;
orbitDepthAdjust = _function;
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;
}
_orbitLayout.children = function(_function) {
if (!arguments.length) return childrenAccessor;
//Probably should use d3.functor to turn a string into an object key
childrenAccessor = _function;
return this;
}
d3.rebind(_orbitLayout, orbitDispatch, "on");
return _orbitLayout;
function calculateNodes() {
orbitalRings = [];
orbitNodes = nestedNodes;
orbitNodes.x = orbitSize[0] / 2;
orbitNodes.y = orbitSize[1] / 2;
orbitNodes.ring = orbitSize[0] / 2;
orbitNodes.depth = 0;
flattenedNodes.push(orbitNodes);
traverseNestedData(orbitNodes);
function traverseNestedData(_node) {
if(childrenAccessor(_node)) {
var y = 0;
var totalChildren = childrenAccessor(_node).length;
var _rings = 0;
var _total_positions = 0;
var _p = 0;
while (_total_positions < totalChildren) {
if (fixedOrbitArray[_p]) {
_total_positions += fixedOrbitArray[_p];
}
else {
_total_positions += fixedOrbitArray[fixedOrbitArray.length - 1];
}
_p++;
_rings++;
}
while (y < totalChildren) {
var _pos = 0;
var _currentRing = 0;
var _p = 0;
var _total_positions = 0;
while (_total_positions <= y) {
if (fixedOrbitArray[_p]) {
_total_positions += fixedOrbitArray[_p];
}
else {
_total_positions += fixedOrbitArray[fixedOrbitArray.length-1];
}
_p++;
_currentRing++;
}
var ringSize = fixedOrbitArray[fixedOrbitArray.length-1];
if (fixedOrbitArray[_currentRing-1]) {
ringSize = fixedOrbitArray[_currentRing-1];
}
if (_node.parent) {
var _ring = {source: _node, x: _node.x, y: _node.y, r: _node.parent.ring / orbitDepthAdjust(_node) * (_currentRing / _rings)};
}
else {
var _ring = {source: _node, x: _node.x, y: _node.y, r: (orbitSize[0] / 2) * (_currentRing / _rings)};
}
var thisPie = d3.layout.pie().value(function(d) {return childrenAccessor(d) ? 4 : 1});
var piedValues = thisPie(childrenAccessor(_node).filter(function(d,i) {return i >= y && i <= y+ringSize-1}));
for (var x = y; x<y+ringSize && x<totalChildren;x++) {
childrenAccessor(_node)[x].angle = ((piedValues[x - y].endAngle - piedValues[x - y].startAngle) / 2) + piedValues[x - y].startAngle;
childrenAccessor(_node)[x].parent = _node;
childrenAccessor(_node)[x].depth = _node.depth + 1;
childrenAccessor(_node)[x].x = childrenAccessor(_node)[x].parent.x + ( (childrenAccessor(_node)[x].parent.ring / 2) * Math.sin( childrenAccessor(_node)[x].angle ) );
childrenAccessor(_node)[x].y = childrenAccessor(_node)[x].parent.y + ( (childrenAccessor(_node)[x].parent.ring / 2) * Math.cos( childrenAccessor(_node)[x].angle ) );
childrenAccessor(_node)[x].ring = _ring.r;
flattenedNodes.push(childrenAccessor(_node)[x]);
traverseNestedData(childrenAccessor(_node)[x]);
}
orbitalRings.push(_ring);
y+=ringSize;
}
}
}
}
}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Orbit Layout Modes</title>
<meta charset="utf-8" />
</head>
<style>
#viz, svg {
width: 100%;
height: 100%;
}
text {
pointer-events: none;
}
#buttons {
position: fixed;
top:0;
left:0;
}
circle.ring {
fill: none;
stroke: black;
stroke-width: 1px;
stroke-opacity: .15;
}
</style>
<script>
function makeViz() {
d3.json("d3.json", function(data) {drawOrbit(data)});
}
function drawOrbit(_data) {
var center = {};
var recenter = false;
for (var x=0;x<_data.children.length;x++) {
_data.children[x].size = _data.children[x].children ? _data.children[x].children.length : 0;
}
_data.children.sort(function(a,b) {
if (a.size > b.size) {
return 1;
}
if (a.size < b.size) {
return -1;
}
return 0;
})
sizeScale = d3.scale.linear().domain([0,1,5,10,20]).range([4,6,8,10,12]).clamp(true);
colorScale = d3.scale.linear().domain([0,1,2,3,4]).range(["rgb(161,208,120)","rgb(247,148,72)","rgb(225,203,208)","rgb(174,223,228)","rgb(245,132,102)"]);
planetColors = {Mercury: "gray", Venus: "#d6bb87", Earth: "#677188", Mars: "#7c5541", Jupiter: "#a36a3e", Saturn: "#e9ba85", Uranus: "#73cbf0", Neptune: "#6383d1"}
orbit = d3.layout.orbit().size([800,800])
.revolution(customRevolution)
.orbitSize(function(d) {return d.depth >= 2 ? 6 : 4})
.speed(.25)
.mode([35,36,8,3,1])
.nodes(_data);
center = orbit.nodes()[0];
d3.select("svg")
.append("g")
.attr("class", "viz")
.attr("transform", "translate(50,50)")
.selectAll("g.node").data(orbit.nodes())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {return "translate(" +d.x +"," + d.y+")"})
.on("mouseover", nodeOver)
.on("click", recenter)
d3.selectAll("g.node")
.append("circle")
.attr("class", "satellite")
.attr("r", function(d) {return sizeScale(d.children ? d.children.length : 0)})
.style("fill", function(d) {return colorScale(d.depth)})
.style("stroke", "brown")
.style("stroke-width", "1px")
d3.selectAll("g.node").filter(function(d) {return d.depth == 1})
.append("text")
.text(function(d) {return d.depth == 0 ? "Sun" : d.key})
.attr("y", 20)
.style("text-anchor", "middle")
d3.select("g.viz")
.selectAll("circle.ring")
.data(orbit.orbitalRings())
.enter()
.insert("circle", "g")
.attr("class", "ring")
.attr("r", function(d) {return d.r})
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
orbit.on("tick", orbitTick);
orbit.start();
function orbitTick() {
var newX = 200- center.x;
var newY = 200 - center.y;
d3.select("g.viz")
.attr("transform", "scale("+(1 + (center.depth *.1)) +") translate(" + newX + "," + newY + ")")
d3.selectAll("g.node")
.attr("transform", function(d) {return "translate(" +d.x +"," + d.y+")"});
d3.selectAll("circle.ring")
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y});
d3.selectAll("line.visible")
.attr("x1", function(p) {return p.source.x})
.attr("x2", function(p) {return p.target.x})
.attr("y1", function(p) {return p.source.y})
.attr("y2", function(p) {return p.target.y})
}
function changeCenter() {
recenter = false;
orbit.stop();
var newX = 200 - center.x;
var newY = 200 - center.y;
d3.select("g.viz")
.transition()
.duration(1000)
.attr("transform", "scale("+(1 + (center.depth *.1)) +") translate(" + newX + "," + newY + ")")
.each("end", function() {orbit.start()})
}
function customRevolution(d)
{
if (d.name == "time") {
return d.depth * .25;
}
if (d.name == "geo") {
return -d.depth * .25;
}
return d.depth
}
function nodeOver(d) {
orbit.stop();
center = d;
changeCenter();
d3.selectAll("text.sat").remove();
d3.selectAll("line.visible").remove();
if (d.children) {
var lines = d.children.map(function(p) {return {source: d, target: p}})
d3.select("g.viz").selectAll("line.visible")
.data(lines)
.enter()
.insert("line", "g")
.attr("x1", function(p) {return p.source.x})
.attr("x2", function(p) {return p.target.x})
.attr("y1", function(p) {return p.source.y})
.attr("y2", function(p) {return p.target.y})
.attr("class", "visible")
.style("stroke", "rgb(73,106,154)")
.style("stroke-width", 2)
}
if (d.parent) {
d3.select("g.viz").selectAll("line.fake")
.data([{source:d, target: d.parent}])
.enter()
.insert("line", "g")
.attr("x1", function(p) {return p.source.x})
.attr("x2", function(p) {return p.target.x})
.attr("y1", function(p) {return p.source.y})
.attr("y2", function(p) {return p.target.y})
.attr("class", "visible")
.style("stroke", "rgb(165,127,124)")
.style("stroke-width", 3)
}
d3.selectAll("g.node")
.filter(function(p) {return p == d || p == d.parent || (d.children ? d.children.indexOf(p) > -1 : false)})
.append("text")
.text(function(p) {return p.name})
.style("text-anchor", "middle")
.attr("y", 15)
.attr("class", "sat")
.style("fill", "none")
.style("stroke", "white")
.style("stroke-width", 3)
.style("stroke-opacity", .7);
d3.selectAll("g.node")
.filter(function(p) {return p == d || p == d.parent || (d.children ? d.children.indexOf(p) > -1 : false)})
.append("text")
.text(function(p) {return p.name})
.style("text-anchor", "middle")
.attr("y", 15)
.attr("class", "sat");
d3.selectAll("g.node > circle").style("stroke", "brown").style("stroke-width", 1);
d3.select(this).select("circle").style("stroke", "black").style("stroke-width", 3);
}
}
</script>
<body onload="makeViz()">
<div id="viz"><svg></svg><div id="buttons"></div></div>
<footer>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8" type="text/javascript"></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