Skip to content

Instantly share code, notes, and snippets.

@hahastudio
Last active May 31, 2017 08:27
Show Gist options
  • Save hahastudio/68426ce79aabe771ebfeb04cf15b60a6 to your computer and use it in GitHub Desktop.
Save hahastudio/68426ce79aabe771ebfeb04cf15b60a6 to your computer and use it in GitHub Desktop.
D3.js Force Layout
{
"nodes": [{
"url": "https://www.example.com/index.html"
},
{
"url": "https://www.example.com/category.html"
},
{
"url": "https://www.example.com/post/1"
},
{
"url": "https://www.example.com/post/2"
},
{
"url": "https://www.example.com/post/3"
},
{
"url": "https://www.example.com/post/4"
},
{
"url": "https://www.example.com/category/1"
},
{
"url": "https://www.example.com/category/2"
},
{
"url": "https://www.example.com/category/3"
}
],
"links": [{
"target": 1,
"source": 0
},
{
"target": 2,
"source": 0
},
{
"target": 3,
"source": 0
},
{
"target": 4,
"source": 0
},
{
"target": 5,
"source": 0
},
{
"target": 6,
"source": 1
},
{
"target": 7,
"source": 1
},
{
"target": 8,
"source": 1
}
]
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Force Layout Example 1</title>
<style>
.node {
fill: #ccc;
stroke: #fff;
stroke-width: 2px;
}
.node.fixed {
fill: #f00;
}
.link {
stroke: #777;
stroke-width: 2px;
}
a {
color: white;
}
div.tooltip {
position: absolute;
text-align: center;
height: 28px;
padding: 2px 6px;
font: 12px sans-serif;
background-color: black;
color: white
border: 0px;
border-radius: 8px;
pointer-events: none;
line-height: 28px;
}
</style>
</head>
<body>
<script
src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
<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;
var force = d3.layout.force();
//d3.json("graph.json", function (error, root) {
// if (error) throw error;
// 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.")
// For the simplest possible example we only define two nodes. 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 which lets us see the
// effects of different properties.
var graph = {};
graph.nodes = [{
"url": "http://www.example.com"
}]//root.nodes;
// 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.
graph.links = []//root.links;
// Here's were the code begins. We start off by creating an SVG
// container to hold the visualization. We only need to specify
// the dimensions for this container.
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
svg.append("svg:defs").append("svg:marker")
.attr("id", "triangle")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
// Now we create a force layout object and define its properties.
// Those include the dimensions of the visualization and the arrays
// of nodes and links.
force.size([width, height])
.nodes(graph.nodes)
.links(graph.links);
var nodes = force.nodes();
var links = force.links();
// There's one more property of the layout we need to define,
// its `linkDistance`. That's generally a configurable value and,
// for a first example, we'd normally leave it at its default.
// Unfortunately, the default value results in a visualization
// that's not especially clear. This parameter defines the
// distance (normally in pixels) that we'd like to have between
// nodes that are connected. (It is, thus, the length we'd
// like our links to have.)
force.linkDistance(width/8)
.charge(-300);
// Next 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, and
// we're not even going to specify their coordinates. (We'll
// let the force layout take care of that.) Without any
// coordinates, the lines won't even be visible, but the
// markup will be sitting inside the SVG container ready
// and waiting for the force layout.
var link = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr({
"class": "link",
"marker-end": "url(#triangle)"
});
// Now it's the nodes turn. Each node is drawn as a circle.
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', 6)
.call(force.drag);
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
node.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html($('<a/>', { href: d.url, text: d.url })[0].outerHTML)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.on("click", function (d) {
if (d3.event.defaultPrevented) return; // ignore drag
d3.select(this).classed("fixed", d.fixed = true);
for (var i = 0; i < 3; i++) {
var n = {};
n.url = d.url + "/" + i.toString();
n.x = d.x;
n.y = d.y;
nodes.push(n);
links.push({ source: n, target: d });
}
restart();
})
.on("dblclick", function (d) {
d3.select(this).classed("fixed", d.fixed = false);
});
// We're about to tell the force layout to start its
// calculations. We do, however, want to know when those
// calculations are complete, so before we kick things off
// we'll define a function that we want the layout to call
// once the calculations are done.
force.on('tick', function() {
// When this function executes, the force layout
// calculations have concluded. 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. We also have to
// give the node a non-zero radius so that it's visible
// in the container.
node.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.
link.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; });
});
// Okay, everything is set up now so it's time to turn
// things over to the force layout. Here we go.
force.start();
function restart() {
link = link.data(links);
link.enter().append('line')
.attr({
"class": "link",
"marker-end": "url(#triangle)"
});
node = node.data(nodes);
node.enter().append('circle')
.attr('class', 'node')
.attr('r', 6)
.call(force.drag);
node.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html($('<a/>', { href: d.url, text: d.url })[0].outerHTML)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.on("click", function (d) {
if (d3.event.defaultPrevented) return; // ignore drag
d3.select(this).classed("fixed", d.fixed = true);
for (var i = 0; i < 3; i++) {
var n = {};
n.url = d.url + "/" + i.toString();
n.x = d.x;
n.y = d.y;
nodes.push(n);
links.push({ source: n, target: d });
}
restart();
})
.on("dblclick", function (d) {
d3.select(this).classed("fixed", d.fixed = false);
});
force.start();
}
// By the time you've read this far in the code, the force
// layout has undoubtedly finished its work. Unless something
// went horribly wrong, you should see two light grey circles
// connected by a single dark grey line. If you have a screen
// ruler (such as [xScope](http://xscopeapp.com) handy, measure
// the distance between the centers of the two circles. It
// should be somewhere close to the `linkDistance` parameter we
// set way up in the beginning (480 pixels). That, in the most
// basic of all nutshells, is what a force layout does. We
// tell it how far apart we want connected nodes to be, and
// the layout keeps moving the nodes around until they get
// reasonably close to that value.
// Of course, there's quite a bit more than that going on
// under the hood. We'll take a closer look starting with
// the next example.
//});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment