|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset='utf-8'> |
|
<title>Force Layout Example 8</title> |
|
<link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" |
|
rel="stylesheet"> |
|
<style> |
|
|
|
.node1 { |
|
fill: #0000cc; |
|
stroke: #fff; |
|
stroke-width: 2px; |
|
} |
|
|
|
.node2 { |
|
fill: #cc0000; |
|
stroke: #fff; |
|
stroke-width: 2px; |
|
} |
|
|
|
.link1 { |
|
stroke: #0000aa; |
|
stroke-width: 1px; |
|
} |
|
|
|
.link2 { |
|
stroke: #aa0000; |
|
stroke-width: 1px; |
|
} |
|
|
|
button { |
|
position: absolute; |
|
width: 30px; |
|
} |
|
button#slow { |
|
margin-top: 28px; |
|
} |
|
button#play { |
|
margin-top: 54px; |
|
} |
|
button#reset { |
|
margin-top: 80px; |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<button id='advance' title='Advance Layout One Increment'> |
|
<i class='fa fa-step-forward'></i> |
|
</button> |
|
<button id='slow' title='Run Layout in Slow Motion'> |
|
<i class='fa fa-play' ></i> |
|
</button> |
|
<button id='play' title='Run Layout at Full Speed'> |
|
<i class='fa fa-fast-forward'></i> |
|
</button> |
|
<button id='reset' title='Reset Layout to Beginning'> |
|
<i class='fa fa-undo'></i> |
|
</button> |
|
|
|
<script src='http://d3js.org/d3.v3.min.js'></script> |
|
<script> |
|
|
|
// Define the dimensions of the visualization. We're using |
|
// a size that's convenient for displaying the graphic on |
|
// http://jsDataV.is |
|
|
|
var width = 640, |
|
height = 480; |
|
|
|
// One other parameter for our visualization determines how |
|
// fast (or slow) the animation executes. It's a time value |
|
// measured in milliseconds. |
|
|
|
var animationStep = 400; |
|
|
|
// Next define the main object for the layouts. We'll also |
|
// define a couple of objects to keep track of the D3 selections |
|
// for the nodes and the links. All of these objects are |
|
// initialized later on. This example is a bit unusual because |
|
// we're running two separate and distinct layouts within |
|
// the same SVG container. |
|
|
|
var force1 = null, force2 = null, |
|
nodes1 = null, nodes2 = null, |
|
links1 = null, links2 = null; |
|
|
|
// We can also create the SVG container that will hold the |
|
// visualization. D3 makes it easy to set this container's |
|
// dimensions and add it to the DOM. |
|
|
|
var svg = d3.select('body').append('svg') |
|
.attr('width', width) |
|
.attr('height', height); |
|
|
|
// Now we'll define a few helper functions. You might not |
|
// need to make these named function in a typical visualization, |
|
// but they'll make it easy to control the visualization in |
|
// this case. |
|
|
|
// First up is a function to initialize our visualization. |
|
|
|
var initForce = function() { |
|
|
|
// Before we do anything else, we clear out the contents |
|
// of the SVG container. This step makes it possible to |
|
// restart the layout without refreshing the page. |
|
|
|
svg.selectAll('*').remove(); |
|
|
|
// Define the data for the example. In general, a force layout |
|
// requires two data arrays. The first array, here named `nodes`, |
|
// contains the object that are the focal point of the visualization. |
|
// The second array, called `links` below, identifies all the links |
|
// between the nodes. (The more mathematical term is "edges.") |
|
|
|
// As far as D3 is concerned, nodes are arbitrary objects. |
|
// Normally the objects wouldn't be initialized with `x` and `y` |
|
// properties like we're doing below. When those properties are |
|
// present, they tell D3 where to place the nodes before the force |
|
// layout starts its magic. More typically, they're left out of the |
|
// nodes and D3 picks random locations for each node. We're defining |
|
// them here so we can get a consistent application of the layout. |
|
|
|
// Because we're running two layouts in parallel, we have two |
|
// separate sets of nodes and links. The only difference between |
|
// the two, however, is that one is an inverted version of the other |
|
|
|
var dataNodes1 = [ |
|
{ x: 4*width/10, y: 6*height/9 }, |
|
{ x: 6*width/10, y: 6*height/9 }, |
|
{ x: width/2, y: 7*height/9 }, |
|
{ x: 4*width/10, y: 7*height/9 }, |
|
{ x: 6*width/10, y: 7*height/9 }, |
|
{ x: width/2, y: 5*height/9 } |
|
]; |
|
|
|
var dataNodes2 = [ |
|
{ x: 4*width/10, y: 3*height/9 }, |
|
{ x: 6*width/10, y: 3*height/9 }, |
|
{ x: width/2, y: 2*height/9 }, |
|
{ x: 4*width/10, y: 2*height/9 }, |
|
{ x: 6*width/10, y: 2*height/9 }, |
|
{ x: width/2, y: 4*height/9 } |
|
]; |
|
|
|
// The `links` array contains objects with a `source` and a `target` |
|
// property. The values of those properties are the indices in |
|
// the `nodes` array of the two endpoints of the link. Our links |
|
// bind the first three nodes into one graph and leave the last |
|
// two nodes isolated. |
|
|
|
var dataLinks1 = [ |
|
{ source: 0, target: 1}, |
|
{ source: 1, target: 2}, |
|
{ source: 2, target: 0} |
|
]; |
|
|
|
var dataLinks2 = [ |
|
{ source: 0, target: 1}, |
|
{ source: 1, target: 2}, |
|
{ source: 2, target: 0} |
|
]; |
|
|
|
// Now we create a force layout objects and define their properties. |
|
// Those include the dimensions of the visualization and the arrays |
|
// of nodes and links. |
|
|
|
force1 = d3.layout.force() |
|
.size([width, height]) |
|
.nodes(dataNodes1) |
|
.links(dataLinks1); |
|
|
|
force2 = d3.layout.force() |
|
.size([width, height]) |
|
.nodes(dataNodes2) |
|
.links(dataLinks2); |
|
|
|
// The only difference between the two layouts is that we remove |
|
// the gravity from the second layout. The first retains its |
|
// default value. |
|
|
|
force2.gravity(0); |
|
|
|
// Define the `linkDistance` for the graph. This is the |
|
// distance we desire between connected nodes. |
|
|
|
force1.linkDistance(height/2); |
|
force2.linkDistance(height/2); |
|
|
|
// Now we'll add the nodes and links to the visualization. |
|
// Note that we're just sticking them into the SVG container |
|
// at this point. We start with the links. The order here is |
|
// important because we want the nodes to appear "on top of" |
|
// the links. SVG doesn't really have a convenient equivalent |
|
// to HTML's `z-index`; instead it relies on the order of the |
|
// elements in the markup. By adding the nodes _after_ the |
|
// links we ensure that nodes appear on top of links. |
|
|
|
// Links are pretty simple. They're just SVG lines. We're going |
|
// to position the lines according to the centers of their |
|
// source and target nodes. You'll note that the `source` |
|
// and `target` properties are indices into the `nodes` |
|
// array. That's how our data is structured and that's how |
|
// D3's force layout expects its inputs. As soon as the layout |
|
// begins executing, however, it's going to replace those |
|
// properties with references to the actual node objects |
|
// instead of indices. |
|
|
|
links1 = svg.selectAll('.link1') |
|
.data(dataLinks1) |
|
.enter().append('line') |
|
.attr('class', 'link1') |
|
.attr('x1', function(d) { return dataNodes1[d.source].x; }) |
|
.attr('y1', function(d) { return dataNodes1[d.source].y; }) |
|
.attr('x2', function(d) { return dataNodes1[d.target].x; }) |
|
.attr('y2', function(d) { return dataNodes1[d.target].y; }); |
|
|
|
// Now it's the nodes turn. Each node is drawn as a circle and |
|
// given a radius and initial position within the SVG container. |
|
// As is normal with SVG circles, the position is specified by |
|
// the `cx` and `cy` attributes, which define the center of the |
|
// circle. We actually don't have to position the nodes to start |
|
// off, as the force layout is going to immediately move them. |
|
// But this makes it a little easier to see what's going on |
|
// before we start the layout executing. |
|
|
|
nodes1 = svg.selectAll('.node1') |
|
.data(dataNodes1) |
|
.enter().append('circle') |
|
.attr('class', 'node1') |
|
.attr('r', width/40) |
|
.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }); |
|
|
|
// Same code but for the second layout. |
|
|
|
links2 = svg.selectAll('.link2') |
|
.data(dataLinks2) |
|
.enter().append('line') |
|
.attr('class', 'link2') |
|
.attr('x1', function(d) { return dataNodes2[d.source].x; }) |
|
.attr('y1', function(d) { return dataNodes2[d.source].y; }) |
|
.attr('x2', function(d) { return dataNodes2[d.target].x; }) |
|
.attr('y2', function(d) { return dataNodes2[d.target].y; }); |
|
|
|
nodes2 = svg.selectAll('.node2') |
|
.data(dataNodes2) |
|
.enter().append('circle') |
|
.attr('class', 'node2') |
|
.attr('r', width/40) |
|
.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }); |
|
|
|
// Finally we tell D3 that we want it to call the step |
|
// function at each iteration. Although it's less efficient, |
|
// we'll let the same function handle both layouts. |
|
|
|
force1.on('tick', stepForce1); |
|
force2.on('tick', stepForce2); |
|
|
|
}; |
|
|
|
// The next function is the event handler that will execute |
|
// at each iteration of the layout. |
|
|
|
var stepForce1 = function() { |
|
|
|
// When this function executes, the force layout |
|
// calculations have been updated. The layout will |
|
// have set various properties in our nodes and |
|
// links objects that we can use to position them |
|
// within the SVG container. |
|
|
|
// First let's reposition the nodes. As the force |
|
// layout runs it updates the `x` and `y` properties |
|
// that define where the node should be centered. |
|
// To move the node, we set the appropriate SVG |
|
// attributes to their new values. |
|
|
|
// The code here differs depending on whether or |
|
// not we're running the layout at full speed. |
|
// In full speed we simply set the new positions. |
|
|
|
if (force1.fullSpeed) { |
|
|
|
nodes1.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }); |
|
|
|
// Otherwise, we use a transition to move them to |
|
// their positions instead of simply setting the |
|
// values abruptly. |
|
|
|
} else { |
|
|
|
nodes1.transition().ease('linear').duration(animationStep) |
|
.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }); |
|
} |
|
|
|
// We also need to update positions of the links. |
|
// For those elements, the force layout sets the |
|
// `source` and `target` properties, specifying |
|
// `x` and `y` values in each case. |
|
|
|
// Here's where you can see how the force layout has |
|
// changed the `source` and `target` properties of |
|
// the links. Now that the layout has executed at least |
|
// one iteration, the indices have been replaced by |
|
// references to the node objects. |
|
|
|
// As with the nodes, at full speed we don't use any |
|
// transitions. |
|
|
|
if (force1.fullSpeed) { |
|
|
|
links1.attr('x1', function(d) { return d.source.x; }) |
|
.attr('y1', function(d) { return d.source.y; }) |
|
.attr('x2', function(d) { return d.target.x; }) |
|
.attr('y2', function(d) { return d.target.y; }); |
|
|
|
} else { |
|
|
|
links1.transition().ease('linear').duration(animationStep) |
|
.attr('x1', function(d) { return d.source.x; }) |
|
.attr('y1', function(d) { return d.source.y; }) |
|
.attr('x2', function(d) { return d.target.x; }) |
|
.attr('y2', function(d) { return d.target.y; }); |
|
} |
|
|
|
// Unless the layout is operating at normal speed, we |
|
// only want to show one step at a time. |
|
|
|
if (!force1.fullSpeed) { |
|
force1.stop(); |
|
} |
|
|
|
// If we're animating the layout in slow motion, continue |
|
// after a delay to allow the animation to take effect. |
|
|
|
if (force1.slowMotion) { |
|
setTimeout( |
|
function() { force1.start(); }, |
|
animationStep |
|
); |
|
} |
|
|
|
} |
|
|
|
// Same function but for the second layout. This could |
|
// obviously be simplified to reuse code for both layouts, |
|
// but we'll keep them separate so that the code itself |
|
// is a little easier to read. |
|
|
|
var stepForce2 = function() { |
|
|
|
// When this function executes, the force layout |
|
// calculations have been updated. The layout will |
|
// have set various properties in our nodes and |
|
// links objects that we can use to position them |
|
// within the SVG container. |
|
|
|
// First let's reposition the nodes. As the force |
|
// layout runs it updates the `x` and `y` properties |
|
// that define where the node should be centered. |
|
// To move the node, we set the appropriate SVG |
|
// attributes to their new values. |
|
|
|
// The code here differs depending on whether or |
|
// not we're running the layout at full speed. |
|
// In full speed we simply set the new positions. |
|
|
|
if (force2.fullSpeed) { |
|
|
|
nodes2.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }); |
|
|
|
// Otherwise, we use a transition to move them to |
|
// their positions instead of simply setting the |
|
// values abruptly. |
|
|
|
} else { |
|
|
|
nodes2.transition().ease('linear').duration(animationStep) |
|
.attr('cx', function(d) { return d.x; }) |
|
.attr('cy', function(d) { return d.y; }); |
|
|
|
} |
|
|
|
// We also need to update positions of the links. |
|
// For those elements, the force layout sets the |
|
// `source` and `target` properties, specifying |
|
// `x` and `y` values in each case. |
|
|
|
// Here's where you can see how the force layout has |
|
// changed the `source` and `target` properties of |
|
// the links. Now that the layout has executed at least |
|
// one iteration, the indices have been replaced by |
|
// references to the node objects. |
|
|
|
// As with the nodes, at full speed we don't use any |
|
// transitions. |
|
|
|
if (force2.fullSpeed) { |
|
|
|
links2.attr('x1', function(d) { return d.source.x; }) |
|
.attr('y1', function(d) { return d.source.y; }) |
|
.attr('x2', function(d) { return d.target.x; }) |
|
.attr('y2', function(d) { return d.target.y; }); |
|
|
|
} else { |
|
|
|
links2.transition().ease('linear').duration(animationStep) |
|
.attr('x1', function(d) { return d.source.x; }) |
|
.attr('y1', function(d) { return d.source.y; }) |
|
.attr('x2', function(d) { return d.target.x; }) |
|
.attr('y2', function(d) { return d.target.y; }); |
|
|
|
} |
|
|
|
// Unless the layout is operating at normal speed, we |
|
// only want to show one step at a time. |
|
|
|
if (!force2.fullSpeed) { |
|
force2.stop(); |
|
} |
|
|
|
// If we're animating the layout in slow motion, continue |
|
// after a delay to allow the animation to take effect. |
|
|
|
if (force2.slowMotion) { |
|
setTimeout( |
|
function() { force2.start(); }, |
|
animationStep |
|
); |
|
} |
|
|
|
} |
|
|
|
// Now let's take care of the user interaction controls. |
|
// We'll add functions to respond to clicks on the individual |
|
// buttons. |
|
|
|
// When the user clicks on the "Advance" button, we |
|
// start the force layout (The tick handler will stop |
|
// the layout after one iteration.) |
|
|
|
d3.select('#advance').on('click', function() { |
|
|
|
force1.start(); |
|
force2.start(); |
|
|
|
}); |
|
|
|
// When the user clicks on the "Slow Motion" button, we're |
|
// going to run the force layouts until they concludes. |
|
|
|
d3.select('#slow').on('click', function() { |
|
|
|
// Indicate that the animation is in progress. |
|
|
|
force1.slowMotion = force2.slowMotion = true; |
|
force1.fullSpeed = force2.fullSpeed = false; |
|
|
|
// Get the animations rolling |
|
|
|
force1.start(); |
|
force2.start(); |
|
|
|
}); |
|
|
|
// When the user clicks on the "Slow Motion" button, we're |
|
// going to run the force layout until it concludes. |
|
|
|
d3.select('#play').on('click', function() { |
|
|
|
// Indicate that the full speed operation is in progress. |
|
|
|
force1.slowMotion = force2.slowMotion = false; |
|
force1.fullSpeed = force2.fullSpeed = true; |
|
|
|
// Get the animations rolling |
|
|
|
force1.start(); |
|
force2.start(); |
|
|
|
}); |
|
|
|
// When the user clicks on the "Reset" button, we'll |
|
// start the whole process over again. |
|
|
|
d3.select('#reset').on('click', function() { |
|
|
|
// If we've already started the layout, stop it. |
|
if (force1) { |
|
force1.stop(); |
|
} |
|
if (force2) { |
|
force2.stop(); |
|
} |
|
|
|
// Re-initialize to start over again. |
|
|
|
initForce(); |
|
|
|
}); |
|
|
|
// Now we can initialize the force layout so that it's ready |
|
// to run. |
|
|
|
initForce(); |
|
|
|
</script> |
|
</body> |
|
</html> |