Skip to content

Instantly share code, notes, and snippets.

@whityiu
Last active August 29, 2015 14:20
Show Gist options
  • Save whityiu/88da150b9d314e2a8c19 to your computer and use it in GitHub Desktop.
Save whityiu/88da150b9d314e2a8c19 to your computer and use it in GitHub Desktop.
Demo: D3 Banded Force Layout
{
"nodes": [
{"x": 325, "y": 248, "recordtype": "e", "id": 3, "name": "My Project"},
{ "x": 150, "y": 410, "recordtype": "a", "id": 0, "relationshipType": 0, "name": "Lands", "dataFile": "Lands.data.json"},
{ "x": 200, "y": 364, "recordtype": "b", "id": 1, "relationshipType": 0, "name": "Climate and Energy" },
{ "x": 223, "y": 365, "recordtype": "c", "id": 2, "relationshipType": 1, "name": "NAR Renewable Energy"},
{ "x": 50, "y": 314, "recordtype": "d", "id": 2, "relationshipType": 0, "name": "LAR Sustainable Food" },
{ "x": 75, "y": 207, "recordtype": "a", "id": 2, "relationshipType": 0, "name": "Africa Lands Strategy" },
{ "x": 500, "y": 155, "recordtype": "b", "id": 1, "relationshipType": 0, "name": "Agriculture" },
{ "x": 369, "y": 196, "recordtype": "b", "id": 4, "relationshipType": 1, "name": "Chesapeake Bay" },
{ "x": 350, "y": 148, "recordtype": "c", "id": 4, "relationshipType": 0, "name": "Atlantic Coast" },
{ "x": 100, "y": 222, "recordtype": "d", "id": 5, "relationshipType": 1, "name": "China Agriculture" },
{ "x": 594, "y": 235, "recordtype": "d", "id": 5, "relationshipType": 0, "name": "Southern California" },
{ "x": 298, "y": 185, "recordtype": "a", "id": 2, "relationshipType": 1, "name": "AP Fisheries" },
{ "x": 633, "y": 200, "recordtype": "e", "id": 1, "relationshipType": 0, "name": "Aquaculture" },
{ "x": 150, "y": 410, "recordtype": "a", "id": 0, "relationshipType": 0, "name": "Climate"},
{ "x": 200, "y": 364, "recordtype": "b", "id": 1, "relationshipType": 0, "name": "Great Rivers" },
{ "x": 223, "y": 365, "recordtype": "c", "id": 2, "relationshipType": 1, "name": "AP Coral Reefs" },
{ "x": 500, "y": 155, "recordtype": "b", "id": 1, "relationshipType": 0, "name": "Securing Water" },
{ "x": 369, "y": 196, "recordtype": "b", "id": 4, "relationshipType": 1, "name": "Amazon" },
{ "x": 350, "y": 148, "recordtype": "c", "id": 4, "relationshipType": 0, "name": "Gulf of Mexico" },
{ "x": 100, "y": 222, "recordtype": "d", "id": 5, "relationshipType": 1, "name": "China Climate" },
{ "x": 594, "y": 235, "recordtype": "d", "id": 5, "relationshipType": 0, "name": "Colorado Energy" },
{ "x": 633, "y": 200, "recordtype": "e", "id": 1, "relationshipType": 0, "name": "Global Water Markets" }
]
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>D3: Force Layout</title>
<style>
.visual-area {
border: 2px solid #aa88aa;
}
.link {
stroke: #000;
stroke-width: 1.5px;
}
.node {
cursor: move;
}
.node circle {
fill: #ccc;
stroke: #000;
stroke-width: 1.5px;
}
.node.fixed {
fill: #f00;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
</style>
</head>
<body>
<h2>D3: Force Layout</h2>
<p>
Items demonstrated:
<ul>
<li>Nodes "assigned" & constrained to regions based on ID (on render & drag)</li>
<li>Node Repulsion</li>
<li>Conditional formatting of nodes &amp; links</li>
<li>Draggable Nodes</li>
<li>Node Labels</li>
<li>Context Switch - click "Lands" to load alternate data (click "My Project" load initial data)</li>
</ul>
</p>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var width = 960,
height = 500,
numRegions = 6,
radius = 12;
// Create the color scale
var color = d3.scale.category10()
.domain(d3.range(numRegions));
// Create the bands
var regions = d3.scale.ordinal()
.domain(d3.range(numRegions))
.rangeRoundBands([0, width]); // Round Bands for integer boundaries
// Set up the SVG display area
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.classed('visual-area', true);
// Draw Regions
for (var i = 0; i < numRegions; i++) {
svg.append("rect")
.attr("x", regions(i))
.attr("y", 0)
.attr("width", 160)
.attr("height", "100%")
.attr("fill", color(i));
}
// Instantiate the Force Layout object
var force = d3.layout.force()
.size([width, height])
.charge(-500)
.chargeDistance(100)
.gravity(0)
.friction(0.1) // Makes nodes static
.linkDistance(50)
.linkStrength(0)
.on("tick", tick);
var initialDataFile = "data.json";
var linkSet = null,
nodeSet = null;
function CreateForceLayout(dataFile) {
// Clear the slate
svg.selectAll(".node").remove();
svg.selectAll(".link").remove();
// Create data object sets
linkSet = svg.selectAll(".link");
nodeSet = svg.selectAll(".node");
// Read Data from JSON file
d3.json(dataFile, function (error, graph) {
var links = [];
// Save the "context" node ID for helping to place
// node labels
contextNodeId = Number(graph.nodes[0].id);
// Build the links
graph.nodes.forEach(function (d, i) {
d.x = findXRegion(d);
if (i != 0) {
links.push({ id: d.name + i, source: 0, target: i, relationshipType: d.relationshipType });
}
})
console.log(links);
force
.nodes(graph.nodes)
.links(links)
.start();
linkSet = linkSet.data(links, function(d) { return d.id; });
linkSet.enter().append("line")
.attr("class", "link");
linkSet.exit().remove();
nodeSet = nodeSet.data(graph.nodes, function(d) { return d.name; });
nodeSet.enter().append("g")
.attr("class", "node")
.on("click", clickHandler)
.call(dragHandler);
nodeSet.append("circle")
.style("fill", function (d) { return d.id == contextNodeId ? "#fff" : "#afafaf" })
.attr("r", radius);
// Add labels as a <svg:text> element
// Note: (0,0) is the center of the node
nodeSet.append("text")
.attr("dx", function(d) {
return (radius * ( (Number(d.id) < contextNodeId) ? -1 : 1 ));
})
.attr("dy", function(d) {
return (radius * ( (Number(d.id) < contextNodeId) ? -1 : 1 ));
})
.attr("text-anchor", function(d) {
return (Number(d.id) < contextNodeId) ? "end" : "start";
})
.text(function(d) {return d.name;});
});
}
// Run for each step of the Force Layout
function tick() {
nodeSet.attr("transform", function(d) {
// Calculate region's [min,max] x-coordinate
var regionMinX = regions(d.id);
var regionMaxX = regionMinX + regions.rangeBand();
// Constrain the node to the region
var newX = d.x = Math.max(regionMinX + radius, Math.min(regionMaxX - radius, d.x));
var newY = d.y = Math.max(radius, Math.min(height - radius, d.y));
return "translate(" + newX + "," + newY + ")";
});
linkSet.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; })
.attr("stroke-dasharray", function (d) { return d.relationshipType == 1 ? "10, 5" : "none" });
}
// Calculate the node's x-coordinate based on it's ID
function findXRegion(d) {
var translationFactor = 160 * d.id;
var randomX = Math.floor(Math.random() * 160);
return randomX + translationFactor;
}
function clickHandler(d) {
// Prevent this from firing on drag
if (d3.event.defaultPrevented) return;
if (d.dataFile) {
CreateForceLayout(d.dataFile);
}
}
// Drag action
var dragHandler = force.drag().on("dragstart", dragstart);
function dragstart(d) {
// This will fix the node position after it's been dragged
//d3.select(this).classed("fixed", d.fixed = true);
}
CreateForceLayout(initialDataFile);
</script>
</body>
</html>
{
"nodes": [
{ "x": 150, "y": 410, "recordtype": "a", "id": 0, "name": "Lands" },
{"x": 325, "y": 248, "recordtype": "e", "id": 3, "name": "My Project", "relationshipType": 1, "dataFile": "data.json"},
{ "x": 200, "y": 364, "recordtype": "b", "id": 1, "relationshipType": 0, "name": "Climate and Energy" },
{ "x": 75, "y": 207, "recordtype": "a", "id": 2, "relationshipType": 0, "name": "Africa Lands Strategy" },
{ "x": 350, "y": 148, "recordtype": "c", "id": 4, "relationshipType": 0, "name": "Atlantic Coast" },
{ "x": 298, "y": 185, "recordtype": "a", "id": 2, "relationshipType": 1, "name": "AP Fisheries" },
{ "x": 350, "y": 148, "recordtype": "c", "id": 4, "relationshipType": 0, "name": "Gulf of Mexico" },
{ "x": 594, "y": 235, "recordtype": "d", "id": 5, "relationshipType": 0, "name": "Colorado Energy" }
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment