Skip to content

Instantly share code, notes, and snippets.

@ZJONSSON
Created May 17, 2012 18:21
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ZJONSSON/2720730 to your computer and use it in GitHub Desktop.
Save ZJONSSON/2720730 to your computer and use it in GitHub Desktop.
d3.layout.force3d (a quick hack to get force layout into the third dimension)

This is a quick hack to be able to produce http://d3js.org force layout with a third dimension. I use two force layouts in combination, the first one for the x,y axis and the third one for the z axis (I enforce x=y at each step). Each chained action of the 3d force is then applied to both of the underlying forces. Finally I use X3DOM to render the visualization.

List of controls can be found here http://www.x3dom.org/?page_id=293

(function() {
// D3.layout.force3d.js
// (C) 2012 ziggy.jonsson.nyc@gmail.com
// BSD license (http://opensource.org/licenses/BSD-3-Clause)
d3.layout.force3d = function() {
var forceXY = d3.layout.force()
,forceZ = d3.layout.force()
,zNodes = {}
,zLinks = {}
,nodeID = 1
,linkID = 1
,tickFunction = Object
var force3d = {}
Object.keys(forceXY).forEach(function(d) {
force3d[d] = function() {
var result = forceXY[d].apply(this,arguments)
if (d !="nodes" && d!="links") forceZ[d].apply(this,arguments)
return (result == forceXY) ? force3d : result
}
})
force3d.on = function(name,fn) {
tickFunction = fn
return force3d
}
forceXY.on("tick",function() {
// Refresh zNodes add new, delete removed
var _zNodes = {}
forceXY.nodes().forEach(function(d,i) {
if (!d.id) d.id = nodeID++
_zNodes[d.id] = zNodes[d.id] || {x:d.z,px:d.z,py:d.z,y:d.z,id:d.id}
d.z = _zNodes[d.id].x
})
zNodes = _zNodes
// Refresh zLinks add new, delete removed
var _zLinks = {}
forceXY.links().forEach(function(d) {
var nytt = false
if (!d.linkID) { d.linkID = linkID++;nytt=true}
_zLinks[d.linkID] = zLinks[d.linkID] || {target:zNodes[d.target.id],source:zNodes[d.source.id]}
})
zLinks = _zLinks
// Update the nodes/links in forceZ
forceZ.nodes(d3.values(zNodes))
forceZ.links(d3.values(zLinks))
forceZ.start() // Need to kick forceZ so we don't lose the update mechanism
// And run the user defined function, if defined
tickFunction()
})
// Expose the sub-forces for debugging purposes
force3d.xy = forceXY
force3d.z = forceZ
return force3d
}
})()
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v2.js?2.9.1"></script>
<script type="text/javascript" src="http://x3dom.org/x3dom/example/x3dom.js"></script>
<script type="text/javascript" src="d3.layout.force3d.js"></script>
</head>
<body>
<script type="text/javascript">
// Create the x3d scene
d3.ns.prefix.x3da="http://www.web3d.org/specifications/x3d-namespace"
var x3d = d3.select("body")
.append("x3d:x3d")
.attr("height","500px")
.attr("width","960px")
var scene = x3d.append("x3d:scene")
// Define the 3d force
var force = d3.layout.force3d()
.nodes(data=[])
.links(links=[])
.size([50, 50])
.gravity(0.3)
.charge(-5)
function addBubble() {
data.push({x:Math.random()*50,y:Math.random()*50,z:Math.random()*50})
if (data.length > 100) return clearInterval(timer)
if (data.length == 1) return force.start()
var selected = Math.round(Math.random()*(data.length-2))
links.push({target:data[selected],source:data[data.length-1]})
force.start()
}
force.on("tick", function(e) {
// Select the nodes, add new as spheres
var datapoints=scene.selectAll(".datapoints").data(data)
datapoints.exit().remove() // Remove any excess datapoints, if needed
datapoints.enter() // Draw a box for each new datapoint
.append("x3d:transform")
.attr("class","datapoints")
.html("<shape><appearance><material diffuseColor='"+Math.random()+" "+Math.random()+" "+Math.random()+"'></appearance><sphere radius='0.2'></shape>")
// Relocate all based on new data
datapoints.attr("translation",function(d) { return x(d.x)+" "+y(d.y)+" "+z(d.z)})
scene.selectAll(".links").data(links)
.enter().append("x3d:shape").attr("class","links")
.html("<indexedlineset coordindex='0 1 ' ><coordinate point='"+Math.random()*3+" "+Math.random()*3+" "+Math.random()*3+" 0.987 1.431 -1.654' class='line'></coordinate></indexedlineset>").select(".line")
scene.selectAll(".line").attr("point",function(d) {
return x(d.target.x)+" "+y(d.target.y)+" "+z(d.target.z)+" "
+x(d.source.x)+" "+y(d.source.y)+" "+z(d.source.z)})
});
// set up the axes
var x = d3.scale.linear().domain([0, 100]).range([0, 10]),
y = d3.scale.linear().domain([0, 100]).range([0, 10]),
z = d3.scale.linear().domain([0, 100]).range([0, 10]);
// Old axis routine... included to have bearings on the 3d space
function plotAxis(scale,location,size,numTicks) {
// the axis line
scene.append("x3d:transform")
.attr("translation",location.replace("D",(scale.range()[0]+scale.range()[1])/2))
.append("x3d:shape")
.append("x3d:box")
.attr("size",location.replace(/0/g,size).replace("D",scale.range()[1]))
// ticks along the axis
ticks=scene.selectAll("abcd").data(scale.ticks(numTicks))
.enter()
.append("x3d:transform")
.attr("translation", function(d) { return location.replace("D",scale(d))})
ticks
.append("x3d:shape")
.append("x3d:box")
.attr("size",size*3+" "+size*3+" "+size*3);
ticks
.append("x3d:billboard").append("x3d:shape")
.html(function(d) { return "<text string='"+scale.tickFormat(10)(d)+"'><fontstyle size=25></text>"})
}
plotAxis(x,"D 0 0",0.01,10)
plotAxis(y,"0 D 0",0.01,10)
plotAxis(z,"0 0 D",0.01,10)
// Start making bubbgles and zoom out the viewport
timer = setInterval(addBubble,300)
setTimeout(function() {x3d[0][0].runtime.showAll()},500);
</SCRIPT>
</body>
@jamjamg
Copy link

jamjamg commented Sep 18, 2012

I placed your code on jsfiddle: http://jsfiddle.net/P4fkJ/

@jedzme
Copy link

jedzme commented Apr 29, 2014

do you have a data file for this? like a .json file?
seems missing...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment