Skip to content

Instantly share code, notes, and snippets.

@sjengle
Last active April 16, 2020 05:56
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save sjengle/f6f522f3969752b384cfec5449eacd98 to your computer and use it in GitHub Desktop.
Graph Demos

Demonstrates some graph layouts on the Coauthorship in Network Science network in D3.js version 5.

Used NetworkX to convert the GML files into JSON, and calculate some basic node metrics.

Data

See the following Gist for more information about the dataset used in these examples:

🔗 https://gist.github.com/sjengle/510ffd96ad06fc3921aee0425e962c1e/

D3 APIs

The following D3.js APIs may be useful:

Inspiration

The following bl.ocks served as inspiration for these examples:

function toCartesian(radial, theta) {
var x = radial * Math.cos(theta);
var y = radial * Math.sin(theta);
return {"x": x, "y": y};
}
function distance(source, target) {
// sqrt( (x2 - x1)^2 + (y2 - y1)^2 )
var dx2 = Math.pow(target.x - source.x, 2);
var dy2 = Math.pow(target.y - source.y, 2);
return Math.sqrt(dx2 + dy2);
}
// tailored for this example (assumes g#plot, d.id)
function setupTooltip(nodes) {
nodes.on("mouseover.tooltip", function(d) {
let tooltip = d3.select("text#tooltip");
// initialize if missing
if (tooltip.size() < 1) {
tooltip = d3.select("g#plot").append("text").attr("id", "tooltip");
}
// calculate bounding box of plot WITHOUT tooltip
tooltip.style("display", "none");
updatePosition(d);
let bbox = {
plot: d3.select("g#plot").node().getBBox()
}
// restore tooltip display but keep it invisible
tooltip.style("display", null);
tooltip.style("visibility", "hidden");
// now set tooltip text and attributes
tooltip.text(d.id);
tooltip.attr("text-anchor", "end");
tooltip.attr("dx", -5);
tooltip.attr("dy", -5);
// calculate resulting bounding box of text
bbox.text = tooltip.node().getBBox();
// determine if need to show right of pointer
if (bbox.text.x < bbox.plot.x) {
tooltip.attr("text-anchor", "start");
tooltip.attr("dx", 5);
}
// determine if need to show below pointer
if (bbox.text.y < bbox.plot.y) {
tooltip.attr("dy", bbox.text.height / 2);
// also need to fix dx in this case
// so it doesn't overlap the mouse pointer
if (bbox.text.x < bbox.plot.x) {
tooltip.attr("dx", 15);
}
}
tooltip.style("visibility", "visible");
d3.select(this).classed("selected", true);
});
nodes.on("mousemove.tooltip", updatePosition);
nodes.on("mouseout.tooltip", function(d) {
d3.select(this).classed("selected", false);
d3.select("text#tooltip").style("visibility", "hidden");
});
}
function updatePosition(d) {
let coords = d3.mouse(d3.select("g#plot").node());
d3.select("text#tooltip").attr("x", coords[0]);
d3.select("text#tooltip").attr("y", coords[1]);
}
// not an efficient approach but a simple one
// can otherwise save the links associated with each node
function setupHighlight(nodes, links) {
nodes.on("mouseover.brush", function(v) {
// highlight all edges
links.filter(function (e) {
if (Array.isArray(e)) {
// edge bundling example
return e[0].id === v.id || e[e.length - 1].id === v.id;
}
return e.source.id === v.id || e.target.id === v.id;
})
.classed("selected", true)
.raise();
});
nodes.on("mouseout.brush", function(v) {
links.classed("selected", false);
});
}
<html lang="en">
<head>
<meta charset="utf-8">
<title>Graph Demo</title>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900|Source+Code+Pro:300" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Coauthorship in Network Science</h2>
<p>
<a href="layout-circle.html">
<img src="thumbnail-circle.png" width=300>
</a>
<a href="layout-bundle.html">
<img src="thumbnail-bundle.png" width=300>
</a>
<a href="layout-force.html">
<img src="thumbnail-force.png" width=300>
</a>
<br/>
<a href="layout-arc.html">
<img src="thumbnail-arc.png" height=300>
</a>
</p>
<p>
<b>Data:</b> M. E. J. Newman, Finding Community Structure in Networks using the Eigenvectors of Matrices, Preprint Physics/0605087 (2006).
[<a href="http://www-personal.umich.edu/~mejn/netdata/">Data</a>]
[<a href="https://arxiv.org/abs/physics/0605087">Paper</a>]
</p>
</body>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Graph Demo</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="hover.js"></script>
<script src="helpers.js"></script>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Coauthorship in Network Science</h2>
<svg></svg>
<p>
<em>*Only showing the second largest connected component.</em>
<br/>
<b>Data:</b> M. E. J. Newman, Finding Community Structure in Networks using the Eigenvectors of Matrices, Preprint Physics/0605087 (2006).
[<a href="http://www-personal.umich.edu/~mejn/netdata/">Data</a>]
[<a href="https://arxiv.org/abs/physics/0605087">Paper</a>]<br/>
<b>Inspiration:</b> <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Arcs">https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Arcs</a>
</p>
<script>
let graph = null;
const w = 900; // svg width
const h = 500; // svg height
const r = 4; // node radius
// calculate fixed radial amount
const radial = (Math.min(w, h) / 2) - (2 * r);
const scales = {
color: d3.scaleSequential(d3.interpolateYlGnBu),
position: d3.scaleLinear().range([2 * r, w - 2 * r])
};
const svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
// craft url where datafile is located
const base = 'https://gist.githubusercontent.com';
const user = 'sjengle';
const gist = '510ffd96ad06fc3921aee0425e962c1e';
const file = 'netscience-second.json';
const path = [base, user, gist, 'raw', file].join('/');
d3.json(path).then(callback);
function callback(data) {
// save data globally for debugging
graph = data;
console.log(file, 'loaded:', graph);
// setup color scale using closeness
const closeness = d3.extent(graph.nodes, v => v.closeness)
scales.color.domain(closeness);
// calculate node layout
scales.position.domain([0, graph.nodes.length - 1]);
// make graph links easier to work with by replacing
// node indices with node objects
graph.links.forEach(function(e, i) {
e.source = isNaN(e.source) ? e.source : graph.nodes[e.source];
e.target = isNaN(e.target) ? e.target : graph.nodes[e.target];
});
// now we can safely sort the nodes without causing issues with links
graph.nodes.sort((a, b) => d3.ascending(a.closeness, b.closeness));
// calculate node positions
graph.nodes.forEach(function(v, i) {
v.x = scales.position(i);
});
const plot = svg.append('g').attr('id', 'plot');
drawArcs(plot.append('g'), 2 * r);
drawArcs(plot.append('g'), h - 2 * r);
}
function drawArcs(plot, y) {
// place links underneath nodes
const g = {
links: plot.append('g').attr('id', 'links'),
nodes: plot.append('g').attr('id', 'nodes')
};
// draw nodes
const nodes = g.nodes.selectAll('circle.node')
.data(graph.nodes)
.enter()
.append('circle')
.attr('class', 'node')
.attr('r', r)
.attr('cx', v => v.x)
.attr('cy', y)
.style('fill', v => scales.color(v.closeness))
.style('stroke-width', 1);
// create our own arc generator (many other options available)
// https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Arcs
// http://codepen.io/lingtalfi/pen/yaLWJG
const arcGenerator = function(e) {
const length = Math.abs(e.target.x - e.source.x) / 2;
// figure out the left versus right point in the edge
const p0 = (e.target.x < e.source.x) ? e.source : e.target;
const p1 = (e.target.x < e.source.x) ? e.target : e.source;
// figure out if should sweep upwards or downwards for arcs
const sweep = (y < h / 2) ? '1' : '0';
return 'M' + p0.x + ' ' + y +
' A ' + length + ' ' + length + ' ' +
'0 0 ' + sweep + ' ' + p1.x + ' ' + y;
};
const links = g.links.selectAll('path.link')
.data(graph.links)
.enter()
.append('path')
.attr('class', 'link')
.attr('d', arcGenerator)
.style('stroke', e => scales.color(d3.mean([e.source.closeness, e.target.closeness])));
setupTooltip(nodes);
setupHighlight(nodes, links);
}
</script>
</body>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Demo</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="hover.js"></script>
<script src="helpers.js"></script>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Coauthorship in Network Science</h2>
<svg></svg>
<p>
<em>*Only showing the second largest connected component.</em>
<br/>
<b>Data:</b> M. E. J. Newman, Finding Community Structure in Networks using the Eigenvectors of Matrices, Preprint Physics/0605087 (2006).
[<a href="http://www-personal.umich.edu/~mejn/netdata/">Data</a>]
[<a href="https://arxiv.org/abs/physics/0605087">Paper</a>]
</p>
<script>
let graph = null;
const w = 700; // svg width
const h = 700; // svg height
const r = 6; // node radius
// calculate fixed radial amount
const radial = (Math.min(w, h) / 2) - (2 * r);
const scales = {
color: d3.scaleSequential(d3.interpolateYlGnBu),
theta: d3.scaleLinear().range([0, 2 * Math.PI])
};
const svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
const g = {
plot: svg.append('g').attr('id', 'plot')
};
// shift plot so (0, 0) is in center
g.plot.attr('transform', `translate(${w / 2}, ${h / 2})`);
// place links underneath nodes
g.links = g.plot.append('g').attr('id', 'links');
g.nodes = g.plot.append('g').attr('id', 'nodes');
// craft url where datafile is located
const base = 'https://gist.githubusercontent.com';
const user = 'sjengle';
const gist = '510ffd96ad06fc3921aee0425e962c1e';
const file = 'netscience-second.json';
const path = [base, user, gist, 'raw', file].join('/');
d3.json(path).then(callback);
function callback(data) {
// save data globally for debugging
graph = data;
console.log(file, 'loaded:', graph);
// setup color scale using closeness
const closeness = d3.extent(graph.nodes, v => v.closeness)
scales.color.domain(closeness);
// calculate node layout, break up 360° for the nodes
scales.theta.domain([0, graph.nodes.length]);
g.control = g.plot.append('g')
.attr('id', 'control')
.style('pointer-events', 'none');
// make graph links easier to work with by replacing
// node indices with node objects
graph.links.forEach(function(e, i) {
e.source = isNaN(e.source) ? e.source : graph.nodes[e.source];
e.target = isNaN(e.target) ? e.target : graph.nodes[e.target];
});
// now we can safely sort the nodes without causing issues with links
graph.nodes.sort((a, b) => d3.ascending(a.closeness, b.closeness));
// and next we can calculate node positions
graph.nodes.forEach(function(v, i) {
v.radial = radial;
v.theta = scales.theta(i);
const coords = toCartesian(v.radial, v.theta);
v.x = coords.x;
v.y = coords.y;
});
// draw nodes
const nodes = g.nodes.selectAll('circle.node')
.data(graph.nodes)
.enter()
.append('circle')
.attr('class', 'node')
.attr('r', r)
.attr('cx', v => v.x)
.attr('cy', v => v.y)
.style('fill', v => scales.color(v.closeness));
const bundle = generateSegments(graph);
console.log('bundle:', bundle);
// draw control nodes for reference
const control = g.control.selectAll('circle.control')
.data(bundle.nodes)
.enter()
.append('circle')
.attr('class', 'control')
.attr('r', 2)
.attr('cx', c => c.x)
.attr('cy', c => c.y)
.style('fill', '#252525')
.style('stroke', 'silver')
.style('pointer-events', 'none');
// want line segments drawn smoothly
// use line generator with basis interpolation
const line = d3.line()
.curve(d3.curveCardinal)
.x(v => v.x)
.y(v => v.y);
// draw edges
const links = g.links.selectAll('path.link')
.data(bundle.paths)
.enter()
.append('path')
.attr('d', line)
.attr('class', 'link')
.style('stroke-width', 2)
.style('stroke', e => scales.color(d3.mean([e[0].closeness, e[e.length - 1].closeness])));
// setup node tooltips
setupTooltip(nodes);
setupHighlight(nodes, links);
const layout = d3.forceSimulation()
.force('collide', d3.forceCollide(2))
.force('charge', d3.forceManyBody().strength(0.3))
.force('link', d3.forceLink().strength(0.5).distance(0));
layout.on('tick', function(v) {
control.attr('cx', v => v.x);
control.attr('cy', v => v.y);
links.attr('d', line);
})
.on('end', function(v) {
control.remove();
});
layout.nodes(bundle.nodes);
layout.force('link').links(bundle.links);
}
/*
* Turns a single edge into several segments that can
* be used for simple edge bundling.
*/
function generateSegments(graph) {
// number of inner nodes depends on how far nodes are apart
const inner = d3.scaleLinear()
.domain([0, radial * 2])
.range([3, 30]);
// generate separate graph for edge bundling
// nodes: all nodes including control nodes
// links: all individual links (source to target)
// paths: all segments combined into single path for drawing
const bundle = {nodes: [], links: [], paths: []};
// make existing nodes fixed
bundle.nodes = graph.nodes.map(function(d, i) {
d.fx = d.x;
d.fy = d.y;
return d;
});
graph.links.forEach(function(d, i) {
// fix graph links to map to objects instead of indices
d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
// calculate the distance between the source and target
const length = distance(d.source, d.target);
// calculate total number of inner nodes for this link
const total = Math.round(inner(length));
// create scales from source to target
const xscale = d3.scaleLinear()
.domain([0, total + 1]) // source, inner nodes, target
.range([d.source.x, d.target.x]);
const yscale = d3.scaleLinear()
.domain([0, total + 1])
.range([d.source.y, d.target.y]);
// initialize source node
let source = d.source;
let target = null;
// add all points to local path
let local = [source];
for (let j = 1; j <= total; j++) {
// calculate target node
target = {
x: xscale(j),
y: yscale(j)
};
local.push(target);
bundle.nodes.push(target);
bundle.links.push({
source: source,
target: target
});
source = target;
}
local.push(d.target);
// add last link to target node
bundle.links.push({
source: target,
target: d.target
});
bundle.paths.push(local);
});
return bundle;
}
</script>
</body>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Graph Demo</title>
<script src='https://d3js.org/d3.v5.min.js'></script>
<script src='hover.js'></script>
<script src='helpers.js'></script>
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900' rel='stylesheet' type='text/css'>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<h2>Coauthorship in Network Science</h2>
<svg></svg>
<p>
<em>*Only showing the largest connected component.</em>
<br/>
<b>Data:</b> M. E. J. Newman, Finding Community Structure in Networks using the Eigenvectors of Matrices, Preprint Physics/0605087 (2006).
[<a href='http://www-personal.umich.edu/~mejn/netdata/'>Data</a>]
[<a href='https://arxiv.org/abs/physics/0605087'>Paper</a>]
</p>
<script>
let graph = null;
const w = 700; // svg width
const h = 700; // svg height
const r = 2; // node radius
// calculate fixed radial amount
const radial = (Math.min(w, h) / 2) - (2 * r);
const scales = {
color: d3.scaleSequential(d3.interpolateYlGnBu),
theta: d3.scaleLinear().range([0, 2 * Math.PI])
};
const svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
const g = {
plot: svg.append('g').attr('id', 'plot')
};
// shift plot so (0, 0) is in center
g.plot.attr('transform', `translate(${w / 2}, ${h / 2})`);
// place links underneath nodes
g.links = g.plot.append('g').attr('id', 'links');
g.nodes = g.plot.append('g').attr('id', 'nodes');
// craft url where datafile is located
const base = 'https://gist.githubusercontent.com';
const user = 'sjengle';
const gist = '510ffd96ad06fc3921aee0425e962c1e';
const file = 'netscience-largest.json';
const path = [base, user, gist, 'raw', file].join('/');
d3.json(path).then(callback);
function callback(data) {
// save data globally for debugging
graph = data;
console.log(file, 'loaded:', graph);
// setup color scale using closeness
const closeness = d3.extent(graph.nodes, v => v.closeness)
scales.color.domain(closeness);
// calculate node layout, break up 360° for the nodes
scales.theta.domain([0, graph.nodes.length]);
// make graph links easier to work with by replacing
// node indices with node objects
graph.links.forEach(function(e, i) {
e.source = isNaN(e.source) ? e.source : graph.nodes[e.source];
e.target = isNaN(e.target) ? e.target : graph.nodes[e.target];
});
// now we can safely sort the nodes without causing issues with links
// try different sort orders
graph.nodes.sort((a, b) => d3.ascending(a.closeness, b.closeness));
// and next we can calculate node positions
graph.nodes.forEach(function(v, i) {
v.radial = radial;
v.theta = scales.theta(i);
const coords = toCartesian(v.radial, v.theta);
v.x = coords.x;
v.y = coords.y;
});
// draw nodes
const nodes = g.nodes.selectAll('circle.node')
.data(graph.nodes)
.enter()
.append('circle')
.attr('class', 'node')
.attr('r', r)
.attr('cx', v => v.x)
.attr('cy', v => v.y)
.style('fill', v => scales.color(v.closeness))
.style('stroke-width', 1);
// draw straight edges
const links = g.links.selectAll('line.link')
.data(graph.links)
.enter()
.append('line')
.attr('class', 'link')
.attr('x1', e => e.source.x)
.attr('y1', e => e.source.y)
.attr('x2', e => e.target.x)
.attr('y2', e => e.target.y)
.style('stroke', e => scales.color(d3.mean([e.source.closeness, e.target.closeness])));
setupTooltip(nodes);
setupHighlight(nodes, links);
}
</script>
</body>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Graph Demo</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="hover.js"></script>
<script src="helpers.js"></script>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Coauthorship in Network Science</h2>
<svg></svg>
<p>
<em>*Only showing connected components with 5 or more nodes.</em>
<br/>
<b>Data:</b> M. E. J. Newman, Finding Community Structure in Networks using the Eigenvectors of Matrices, Preprint Physics/0605087 (2006).
[<a href="http://www-personal.umich.edu/~mejn/netdata/">Data</a>]
[<a href="https://arxiv.org/abs/physics/0605087">Paper</a>]
<br/>
<b>Inspiration:</b> <a href="https://bl.ocks.org/mbostock/2675ff61ea5e063ede2b5d63c08020c7">https://bl.ocks.org/mbostock/2675ff61ea5e063ede2b5d63c08020c7</a> and
<a href="https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03">https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03</a>
</p>
<script>
let graph = null;
const w = 700; // svg width
const h = 700; // svg height
const r = 2; // node radius
// calculate fixed radial amount
const radial = (Math.min(w, h) / 2) - (2 * r);
const scales = {
color: d3.scaleSequential(d3.interpolateYlGnBu),
radius: d3.scaleSqrt().range([2, 12]),
stroke: d3.scaleSqrt().range([1, 8])
};
const svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
const g = {
plot: svg.append('g').attr('id', 'plot')
};
// shift plot so (0, 0) is in center
g.plot.attr('transform', `translate(${w / 2}, ${h / 2})`);
// place links underneath nodes
g.links = g.plot.append('g').attr('id', 'links');
g.nodes = g.plot.append('g').attr('id', 'nodes');
// craft url where datafile is located
const base = 'https://gist.githubusercontent.com';
const user = 'sjengle';
const gist = '510ffd96ad06fc3921aee0425e962c1e';
const file = 'netscience-filtered.json';
const path = [base, user, gist, 'raw', file].join('/');
// setup layout with default values
const layout = d3.forceSimulation()
.force('center', d3.forceCenter())
.force('forceX', d3.forceX())
.force('forceY', d3.forceY())
.force('collide', d3.forceCollide())
.force('charge', d3.forceManyBody())
.force('link', d3.forceLink());
// stop layout until we are ready
layout.stop();
d3.json(path).then(callback);
function callback(data) {
// save data globally for debugging
graph = data;
console.log(file, 'loaded:', graph);
// make graph links easier to work with by replacing
// node indices with node objects
graph.links.forEach(function(e, i) {
e.source = isNaN(e.source) ? e.source : graph.nodes[e.source];
e.target = isNaN(e.target) ? e.target : graph.nodes[e.target];
});
// now we can safely sort the nodes without causing issues with links
// try different sort orders
graph.nodes.sort((a, b) => d3.ascending(a.closeness, b.closeness));
// update scales
scales.color.domain(d3.extent(graph.nodes, v => v.closeness));
scales.radius.domain(d3.extent(graph.nodes, v => v.degree));
scales.stroke.domain(d3.extent(graph.links, e => e.value));
// output node and link before and after layout
const last_node = graph.nodes[graph.nodes.length - 1];
const last_link = graph.links[graph.links.length - 1];
console.log('node (before):', last_node);
layout.nodes(graph.nodes);
console.log('node (after):', last_node);
console.log('link (before):', last_link);
layout.force('link').links(graph.links);
console.log('link (after):', last_link);
// draw nodes at initial positions
const nodes = g.nodes.selectAll('circle.node')
.data(graph.nodes)
.enter()
.append('circle')
.attr('class', 'node')
.attr('r', v => scales.radius(v.degree))
.attr('cx', v => v.x)
.attr('cy', v => v.y)
.style('fill', v => scales.color(v.closeness));
// draw links at initial positions
const links = g.links.selectAll('line.link')
.data(graph.links)
.enter()
.append('line')
.attr('class', 'link')
.attr('x1', e => e.source.x)
.attr('y1', e => e.source.y)
.attr('x2', e => e.target.x)
.attr('y2', e => e.target.y)
.style('stroke-width', e => scales.stroke(e.value))
.style('stroke', e => scales.color(d3.mean([e.source.closeness, e.target.closeness])));
// now, lets setup different force-directed layout parameters
layout.force('center').x(0).y(0);
layout.force('collide')
.strength(1)
.radius(v => scales.radius(v.degree) + 2);
layout.force('charge').strength(-12);
layout.force('link').strength(0.4).distance(function(e) {
return 1.5 * (scales.radius(e.source.degree) + scales.radius(e.target.degree));
});
// updates node and link positions every tick
layout.on('tick', function(v) {
nodes.attr('cx', v => v.x);
nodes.attr('cy', v => v.y);
links.attr('x1', e => e.source.x);
links.attr('y1', e => e.source.y);
links.attr('x2', e => e.target.x);
links.attr('y2', e => e.target.y);
});
// setup node dragging
// https://github.com/d3/d3-drag
const drag = d3.drag()
.on('start', function(v) {
// avoid restarting except on the first drag start event
if (!d3.event.active) layout.alphaTarget(0.3).restart();
// fix this node position in the layout
// https://github.com/d3/d3-force#simulation_nodes
v.fx = v.x;
v.fy = v.y;
})
.on('drag', function(v) {
v.fx = d3.event.x;
v.fy = d3.event.y;
updatePosition(v);
})
.on('end', function(v) {
// restore alphaTarget to normal value
if (!d3.event.active) layout.alphaTarget(0);
// no longer fix the node position after drag ended
// allows layout to calculate its position again
v.fx = null;
v.fy = null;
});
nodes.call(drag);
// setup node tooltips
setupTooltip(nodes);
setupHighlight(nodes, links);
// restart the layout now that everything is set
layout.restart();
}
</script>
</body>
body {
font-family: 'Source Sans Pro', sans-serif;
font-weight: 300;
text-align: center;
color: #eeeeee;
background-color: #252525;
}
svg {
background-color: #252525;
border: 0.5px dotted #626262;
}
a {
color: #3EB5C4;
}
b {
font-weight: 900;
}
.node {
stroke: silver;
stroke-width: 1px;
}
.link {
fill: none;
stroke: silver;
stroke-opacity: 0.8;
}
.selected {
stroke: red !important;
stroke-opacity: 1;
}
/* shadow trick from bl.ocks.org */
#tooltip {
font-size: 10pt;
font-weight: 900;
fill: white;
text-shadow: 1px 1px 0 #252525, 1px -1px 0 #252525, -1px 1px 0 #252525, -1px -1px 0 #252525;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment