Skip to content

Instantly share code, notes, and snippets.

@dianaow
Last active June 12, 2019 13:25
Show Gist options
  • Save dianaow/583e601c752927f17c371670f7d36395 to your computer and use it in GitHub Desktop.
Save dianaow/583e601c752927f17c371670f7d36395 to your computer and use it in GitHub Desktop.
D3 V4: Static Network Visualization
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="http://d3js.org/d3.v4.min.js"></script>
<style>
#wrapper {
background-image:
radial-gradient(
circle closest-side,
white,
lightgray
);
text-align: center;
}
.btn {
color: mediumblue;
background: transparent;
border: 2px solid mediumblue;
border-radius: 6px;
padding: 8px 16px;
text-align: center;
display: inline-block;
font-size: 0.8em;
margin: 4px 2px;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
cursor: pointer;
text-decoration: none;
text-transform: uppercase;
}
.btn:hover {
background-color: mediumblue;
color: white;
}
.btn:focus {
background-color: mediumblue;
color: white;
}
</style>
</head>
<body>
<div id='wrapper'>
<input name="type"
type="button"
class="btn morph"
value="Morph"/>
<input name="type"
type="button"
class="btn reset"
value="Reset"/>
<div id="chart"></div>
</div>
<script>
var morph = function () {
///////////////////////////////////////////////////////////////////////////
///////////////////////////////// Globals /////////////////////////////////
///////////////////////////////////////////////////////////////////////////
var canvasDim = { width: 600, height: 900}
var margin = {top: -20, right: 5, bottom: 5, left: 5}
var width = canvasDim.width - margin.left - margin.right
var height = canvasDim.height - margin.top - margin.bottom
var radius = canvasDim.width * 0.4
var circleG, pathG, circle, path
var clicked = 1
var nodeRadius = 10
var modal = d3.select("#chart")
///////////////////////////////////////////////////////////////////////////
////////////////////////////////// MORPH //////////////////////////////////
///////////////////////////////////////////////////////////////////////////
return {
clear : function () {
modal.select("svg").remove()
},
run : function () {
//////////////////// Set up and initiate containers ///////////////////////
var svg = modal.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
pathG = svg.append("g")
.attr("class", "pathG")
circleG = svg.append("g")
.attr("class", "circleG")
var data = createData()
Graph1(data.nodes, data.links) // initial render of layout 1
d3.select(".morph")
.on("click", function(e) { clicked==1 ? Graph2(data.nodes, data.links) : Graph1(data.nodes, data.links)}) // toggle between the 2 layouts on click
d3.select(".reset")
.on("click", function(e) {
data = createData()
Graph1(data.nodes, data.links)
})
}
}
///////////////////////////////////////////////////////////////////////////
/////////////////////// Generate random data /////////////////////////////
///////////////////////////////////////////////////////////////////////////
function createData() {
var nodes = []
d3.range(0,20).map(function(d,i) {
nodes.push({
id: d
})
})
var links = []
d3.range(0,25).map(function(d,i) {
links.push({
id: i,
source: getRandomInt(0, 19),
target: getRandomInt(0, 19)
})
})
return {nodes: nodes, links: links}
}
///////////////////////////////////////////////////////////////////////////
//////////////////// Update node and link positions ///////////////////////
///////////////////////////////////////////////////////////////////////////
function update(nodes, links) {
circle = circleG.selectAll('circle').data(nodes, d=>d.id)
circle.exit().remove()
entered_circle = circle
.enter().append('circle')
.attr('r', nodeRadius)
.attr('stroke-width', 2)
.attr('stroke', 'mediumblue')
.attr('fill', 'mediumblue')
.attr('cx', function(d) {return d.x})
.attr('cy', function(d) {return d.y})
circle = circle.merge(entered_circle)
circle.transition().duration(1000)
.attr('cx', function(d) {return d.x})
.attr('cy', function(d) {return d.y})
.attr('id', function(d) {return "node-" + d.id.toString() + "-"})
path = pathG.selectAll('path').data(links, d=>d.id)
path.exit().remove()
entered_path = path
.enter().append('path')
.attr('opacity', 1)
.attr('stroke-linecap', 'round')
.attr('stroke-width', '2px')
.attr('fill', 'transparent')
path = path.merge(entered_path)
path.transition().duration(1000)
.attr('id', function(d) { return "link" + "-source-" + d.source.id.toString() + "-target-" + d.target.id.toString() + "-" })
.attr('stroke', 'mediumblue')
.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "L" +
d.target.x + "," +
d.target.y
})
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Render Layout 1 /////////////////////////////
///////////////////////////////////////////////////////////////////////////
function Graph1(nodes, links){
nodes.forEach((d,i) => {
var radian = (2 * Math.PI) / 20 * i - (Math.PI / 2);
d.fx = radius * Math.cos(radian) + (width / 2)
d.fy = radius * Math.sin(radian) + (height / 2)
})
var simulation1 = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(function(d) { return d.id; }))
for (var i = 0; i < 100; ++i) simulation1.tick()
update(nodes, links)
clicked=1
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Render Layout 2 /////////////////////////////
///////////////////////////////////////////////////////////////////////////
function Graph2(nodes, links){
nodes.forEach((d,i) => {
d.fx = null
d.fy = null
})
var simulation2 = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(function(d) { return d.id; }).strength(0.1))
.force('charge', d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
for (var i = 0; i < 100; ++i) simulation2.tick()
update(nodes, links)
clicked=2
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Helper functions /////////////////////////////
///////////////////////////////////////////////////////////////////////////
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}()
</script>
<script>
morph.run()
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment