Skip to content

Instantly share code, notes, and snippets.

@colbenkharrl
Last active September 26, 2024 20:39
Show Gist options
  • Save colbenkharrl/dcb5590173931bb594e195020aaa959d to your computer and use it in GitHub Desktop.
Save colbenkharrl/dcb5590173931bb594e195020aaa959d to your computer and use it in GitHub Desktop.
Filtering Nodes on Force-Directed Graphs (D3 V4)
{
"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"}
]
}
Click to view more!
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<style>
/* style definitions */
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 with 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)); });
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