Skip to content

Instantly share code, notes, and snippets.

@cydal
Last active March 19, 2021 10:33
Show Gist options
  • Save cydal/d3c1ca9bf1366e5cdac578950ad2fc84 to your computer and use it in GitHub Desktop.
Save cydal/d3c1ca9bf1366e5cdac578950ad2fc84 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<meta charset="utf-8">
<title>Research Papers Graphing Network</title>
<script src="//d3js.org/d3.v4.min.js"></script>
<style>
text {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
svg {
width: 100%;
}
.link {
fill: none;
stroke: #ddd;
stroke-width: 1px;
}
.node {
stroke: #000;
stroke-width: 1px;
}
</style>
<svg width="960" height="600"></svg>
<script>
// dimensions
var width = 1200;
var height = 1000;
var margin = {
top: 50,
bottom: 50,
left: 50,
right: 50,
}
// create svg
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform', 'translate(' + margin.top + ',' + margin.left + ')');
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
var legend = ["Insititutional, Political(legislative) & Social Discourse",
"Online Child Protection - Vulnerabilities",
"Technological Perspective",
"Commercial Perspective & Trafficking",
"Analysis of Offenders"
];
var threshold = 0.38;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}))
.force("charge", d3.forceManyBody().strength(-200))
.force("collide", d3.forceCollide().radius(3))
.force("center", d3.forceCenter(width / 2, height / 2));
// load the graph
d3.json("nodes_links.json", function (error, graph) {
var nodes = graph.nodes;
function myfunc(link) {
if (link.value > threshold) {
return link
}
}
// links between nodes
var links = graph.links.map(myfunc).filter(x => x !== undefined);
var titles = graph.titles;
var color = d3.scaleOrdinal().domain([0, 1, 2, 3, 4, 5])
.range(["#440154", "#3b528b", "#21918c", "#5ec962", "#fde725"]);
var scaleStroke = d3.scaleLinear().domain([0, 1]).range([0, 10]);
// Legend
svg.selectAll("mydots")
.data([0, 1, 2, 3, 4])
.enter()
.append("circle")
.attr("cx", 200)
.attr("cy", function (d, i) {
return 30 + i * 35
})
.attr("r", 15)
.style("fill", function (d) {
return color(d);
});
// Legend - Labels
svg.selectAll("mylabels")
.data(legend)
.enter()
.append("text")
.attr("x", 220)
.attr("y", function (d, i) {
return 30 + i * 35
})
.style("fill", function (d, i) { return color(i) })
.text(function (d) { return d; })
.attr("text-anchor", "left")
.style("alignment-baseline", "middle");
// curved links
var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr('stroke', function (d) {
return "#ddd";
})
.attr("stroke-width", function (d) {
return scaleStroke(d.value);
});
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
node.append("circle")
.attr("class", "node")
.attr("r", 8)
.attr("fill", function (d) {
return color(d.group);
})
.on("mouseover", mouseOver(.2))
.on("mouseout", mouseOut)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));;
// hover text for the node
node.append("title")
.text(function (d) {
return titles[d.id];
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.attr("visibility", "hidden")
.text(function (d) {
return titles[d.id];
})
.style("stroke", "black")
.style("stroke-width", 0.5)
.style("fill", function (d) {
return color(d.group);
});
simulation
.nodes(nodes)
.on("tick", ticked);
simulation
.force("link")
.links(links);
function ticked() {
link.attr("d", positionLink);
node.attr("transform", positionNode);
}
function positionLink(d) {
var offset = 30;
var midpoint_x = (d.source.x + d.target.x) / 2;
var midpoint_y = (d.source.y + d.target.y) / 2;
var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);
var normalise = Math.sqrt((dx * dx) + (dy * dy));
var offSetX = midpoint_x + offset * (dy / normalise);
var offSetY = midpoint_y - offset * (dx / normalise);
return "M" + d.source.x + "," + d.source.y +
"S" + offSetX + "," + offSetY +
" " + d.target.x + "," + d.target.y;
}
// nodes within the boundaries of the svg
function positionNode(d) {
if (d.x < 0) {
d.x = 0
};
if (d.y < 0) {
d.y = 0
};
if (d.x > width) {
d.x = width
};
if (d.y > height) {
d.y = height
};
return "translate(" + d.x + "," + d.y + ")";
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
links.forEach(function (d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
function mouseOver(opacity) {
return function (d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("stroke-opacity", function (o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.style("fill-opacity", function (o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.selectAll("text")
.style("visibility", function(o) {
//debugger;
thisVisibility = isConnected(d, o) ? "visible" : "hidden";
return thisVisibility;
});
link.style("stroke-opacity", function (o) {
//debugger;
return o.source === d || o.target === d ? 1 : opacity;
});
link.style("stroke", function (o) {
if (o.source === d || o.target === d) {
return color(o.source.group);
}
else {
return "#ddd"
}
});
};
}
function mouseOut() {
node.style("stroke-opacity", 1);
node.style("fill-opacity", 1);
link.style("stroke-opacity", 1);
link.style("stroke", "#ddd");
}
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;
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment