An example of d3.layout.orbit forked from emeeks.
A small viz to present myself.
Hello !
An example of d3.layout.orbit forked from emeeks.
A small viz to present myself.
Hello !
{ | |
"name": "me", | |
"children": [ | |
{ | |
"name": "hobby", | |
"children" : [ | |
{ "name" : "ski" },{"name":"hiking"},{"name":"running"},{"name":"play guitar"},{"name":"play bass"}, | |
{ | |
"name":"listen to", | |
"children": | |
[ | |
{"name" : "good music" , | |
"children": | |
[ {"name":"Vintage Rock"},{"name":"Electro"},{"name":"Folk"},{"name":"West Coast Jazz"}] | |
} | |
] | |
} | |
] | |
}, | |
{ "name": "speaks", | |
"children" : [ | |
{"name" : "Français"}, | |
{"name" : "English"}, | |
{"name" : "Deutsch"}, | |
{"name" : "Nothing ,sometimes"} | |
] | |
}, | |
{ | |
"name": "identity", | |
"children": [ | |
{"name": "Chabbey"}, | |
{"name": "François"}, | |
{"name": "Human"}, | |
{"name": "Male", | |
"children":[{"name":"single"}] | |
}, | |
{"name": "36-year-old"}, | |
{"name": "Swiss"}, | |
{"name": "Ayent"} | |
] | |
}, | |
{ | |
"name": "works", | |
"children": [ | |
{ | |
"name" : "web", | |
"children" : | |
[ | |
{ | |
"name" :"frontend", | |
"children" : [ | |
{"name" : "HTML"}, | |
{"name" : "CSS"}, | |
{"name" : "JS"}, | |
{"name" : "libs","children" : | |
[ | |
{ "name" : "jQuery"}, | |
{ "name" : "snap"}, | |
{ "name" : "D3 (surprise)"}, | |
{ "name" : "Angular"} | |
] | |
} | |
] | |
}, | |
{"name" : "backend", | |
"children" : [ {"name":"RoR"},{"name":"node.JS"}] | |
} | |
] | |
}, | |
{"name": "database", | |
"children": [{"name" : "SQL-LIKE"},{"name":"MongoDB"}] | |
}, | |
{"name": "mobile", | |
"children" : [ | |
{"name":"iOS", | |
"children": [ | |
{ | |
"name":"Objective-C" | |
}, | |
{ | |
"name":"Swift" | |
} | |
] | |
}, | |
{"name":"Bluetooth"} | |
] | |
} | |
] | |
} | |
] | |
} |
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>A presentation with Orbit Layout</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(150,150)") | |
.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> |