Forked from Denise Mauldin's block - removing the bounding box over here
-
-
Save larsvers/8a20222c0b90acb18101fe545b536ee5 to your computer and use it in GitHub Desktop.
Filtering Nodes on Force-Directed Graphs (D3 V4)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nodes": [ | |
{ "id": "0", "group": "1" }, | |
{ "id": "1", "group": "2" }, | |
{ "id": "2", "group": "2" }, | |
{ "id": "3", "group": "2" }, | |
{ "id": "4", "group": "2" }, | |
{ "id": "5", "group": "3" }, | |
{ "id": "6", "group": "3" }, | |
{ "id": "7", "group": "3" }, | |
{ "id": "8", "group": "3" } | |
], | |
"links1": [ | |
{ "source": "0", "target": "1", "id": "0"}, | |
{ "source": "0", "target": "2", "id": "1"}, | |
{ "source": "0", "target": "3", "id": "2"}, | |
{ "source": "0", "target": "4", "id": "3"}, | |
{ "source": "1", "target": "5", "id": "4"}, | |
{ "source": "2", "target": "6", "id": "5"}, | |
{ "source": "3", "target": "7", "id": "6"}, | |
{ "source": "4", "target": "8", "id": "7"}, | |
{ "source": "1", "target": "8", "id": "8"}, | |
{ "source": "2", "target": "5", "id": "9"}, | |
{ "source": "3", "target": "6", "id": "10"}, | |
{ "source": "4", "target": "7", "id": "11"} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Click to view more! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<meta charset="UTF-8"> | |
<style> | |
/* style definitions */ | |
svg { | |
border: 1px dotted #777; | |
} | |
button { | |
position: absolute; | |
} | |
#blue { | |
top: 1em; | |
left: 1em; | |
} | |
#orange { | |
top: 1em; | |
left: 8em; | |
} | |
#green { | |
top: 1em; | |
left: 16em; | |
} | |
.node { | |
stroke: white; | |
stroke-width: 2px; | |
} | |
.link { | |
stroke: gray; | |
stroke-width: 4px; | |
} | |
</style> | |
<button type="button" class="filter-btn" id="blue" value="1">Filter Blue</button> | |
<button type="button" class="filter-btn" id="orange" value="2">Filter Orange</button> | |
<button type="button" class="filter-btn" id="green" value="3">Filter Green</button> | |
<svg width="960" height="500"></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script | |
src="https://code.jquery.com/jquery-3.2.1.min.js" | |
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" | |
crossorigin="anonymous"></script> | |
<script> | |
// data stores | |
var graph, store; | |
// svg selection and sizing | |
var svg = d3.select("svg"), | |
width = +svg.attr("width"), | |
height = +svg.attr("height"), | |
radius = 10; | |
// d3 color scales | |
var color = d3.scaleOrdinal(d3.schemeCategory10); | |
var link = svg.append("g").selectAll(".link"), | |
node = svg.append("g").selectAll(".node"); | |
// force simulation initialization | |
var simulation = d3.forceSimulation() | |
.force("link", d3.forceLink() | |
.id(function(d) { return d.id; })) | |
.force("charge", d3.forceManyBody() | |
.strength(function(d) { return -500;})) | |
.force("center", d3.forceCenter(width / 2, height / 2)); | |
// filtered types | |
typeFilterList = []; | |
// filter button event handlers | |
$(".filter-btn").on("click", function() { | |
var id = $(this).attr("value"); | |
if (typeFilterList.includes(id)) { | |
typeFilterList.splice(typeFilterList.indexOf(id), 1) | |
} else { | |
typeFilterList.push(id); | |
} | |
filter(); | |
update(); | |
}); | |
// data read and store | |
d3.json("blocks-data.json", function(err, g) { | |
if (err) throw err; | |
var nodeByID = {}; | |
g.nodes.forEach(function(n) { | |
nodeByID[n.id] = n; | |
}); | |
g.links1.forEach(function(l) { | |
l.sourceGroup = nodeByID[l.source].group.toString(); | |
l.targetGroup = nodeByID[l.target].group.toString(); | |
}); | |
graph = g; | |
store = $.extend(true, {}, g); | |
update(); | |
}); | |
// general update pattern for updating the graph | |
function update() { | |
// UPDATE | |
node = node.data(graph.nodes, function(d) { return d.id;}); | |
// EXIT | |
node.exit().remove(); | |
// ENTER | |
var newNode = node.enter().append("circle") | |
.attr("class", "node") | |
.attr("r", radius) | |
.attr("fill", function(d) {return color(d.group);}) | |
.call(d3.drag() | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended) | |
) | |
newNode.append("title") | |
.text(function(d) { return "group: " + d.group + "\n" + "id: " + d.id; }); | |
// ENTER + UPDATE | |
node = node.merge(newNode); | |
// UPDATE | |
link = link.data(graph.links1, function(d) { return d.id;}); | |
// EXIT | |
link.exit().remove(); | |
// ENTER | |
newLink = link.enter().append("line") | |
.attr("class", "link"); | |
newLink.append("title") | |
.text(function(d) { return "source: " + d.source + "\n" + "target: " + d.target; }); | |
// ENTER + UPDATE | |
link = link.merge(newLink); | |
// update simulation nodes, links, and alpha | |
simulation | |
.nodes(graph.nodes) | |
.on("tick", ticked); | |
simulation.force("link") | |
.links(graph.links1); | |
simulation.alpha(1).alphaTarget(0).restart(); | |
} | |
// drag event handlers | |
function dragstarted(d) { | |
if (!d3.event.active) simulation.alphaTarget(0.3).restart(); | |
d.fx = d.x; | |
d.fy = d.y; | |
} | |
function dragged(d) { | |
d.fx = d3.event.x; | |
d.fy = d3.event.y; | |
} | |
function dragended(d) { | |
if (!d3.event.active) simulation.alphaTarget(0); | |
d.fx = null; | |
d.fy = null; | |
} | |
// tick event handler without a bounded box | |
function ticked() { | |
node | |
// .attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); }) | |
// .attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); }); | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }); | |
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; }); | |
} | |
// filter function | |
function filter() { | |
// add and remove nodes from data based on type filters | |
store.nodes.forEach(function(n) { | |
if (!typeFilterList.includes(n.group) && n.filtered) { | |
n.filtered = false; | |
graph.nodes.push($.extend(true, {}, n)); | |
} else if (typeFilterList.includes(n.group) && !n.filtered) { | |
n.filtered = true; | |
graph.nodes.forEach(function(d, i) { | |
if (n.id === d.id) { | |
graph.nodes.splice(i, 1); | |
} | |
}); | |
} | |
}); | |
// add and remove links from data based on availability of nodes | |
store.links1.forEach(function(l) { | |
if (!(typeFilterList.includes(l.sourceGroup) || typeFilterList.includes(l.targetGroup)) && l.filtered) { | |
l.filtered = false; | |
graph.links1.push($.extend(true, {}, l)); | |
} else if ((typeFilterList.includes(l.sourceGroup) || typeFilterList.includes(l.targetGroup)) && !l.filtered) { | |
l.filtered = true; | |
graph.links1.forEach(function(d, i) { | |
if (l.id === d.id) { | |
graph.links1.splice(i, 1); | |
} | |
}); | |
} | |
}); | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment