Published
Edited
Apr 8, 2020
Insert cell
Insert cell
Insert cell
Insert cell
graphData = d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_network.json")
Insert cell
Insert cell
allNodesNames = graphData.nodes.map(function(d){return d.name});
Insert cell
Insert cell
idToNode =
{
let dict = {};
graphData.nodes.forEach(function(n) {
dict[n.id] = n;
});
return dict;
}
Insert cell
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
Insert cell
idToTargetNodes =
{
let dict = {};
graphData.nodes.forEach(function (n) {
dict[n.id] = [];
graphData.links.forEach(function (l) {
if (l.source === n.id) {
dict[n.id].push(l.target);
}
});
});
return dict;
}
Insert cell
Insert cell
margin = ({top: 20, bottom: 20, left: 30, right: 30});
Insert cell
height = 300 - margin.top - margin.bottom;
Insert cell
Insert cell
Insert cell
xScale = d3.scalePoint()
.domain(allNodesNames)
.range([0,width])
Insert cell
Insert cell
// Adapted from https://www.d3-graph-gallery.com/graph/arc_basic.html
justNodesAndLabels = {
const radius = 8
// create a container for the svg; use standard margin notation
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
// create a group to hold the viz, adjust the coordinates using standard margin notation
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// create the nodes and labels similarly to Lab 1;
// the new part here is that x locations are determined by the xScale function;
// we also add some padding to y to move it away from the margins
// we assign an attribute named id, as this can be useful to have later
arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x", d => xScale(d.name))
.attr("y", height-20)
.attr("fill", "darkgrey")
.style("text-anchor", "middle")
.text(d => d.name)
return container.node();
}
Insert cell
Insert cell
// code goes here
Insert cell
Insert cell
// Adapted from https://www.d3-graph-gallery.com/graph/arc_basic.html
nodesAndArcs = {
const radius = 8
// select the DIV defined in the HTML above; this is where the svg will go; define its size
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
// create a group to hold the viz, adjust the coordinates
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// Place the nodes use graphData.nodes for the data;
// the x locations are determined by the xScale function as above
arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x", d => xScale(d.name))
.attr("y", height-20)
.attr("fill", "darkgrey")
.style("text-anchor", "middle")
.text(d => d.name)
function buildArc (d) {
// d.source and d.target are the locations in graphData.links
// xScale takes a node name and finds its location on the x axis from 0 to width
// So start is the location in pixels of the start of the arc
let start = xScale(idToNode[d.source].name);
let end = xScale(idToNode[d.target].name);
// This code builds up the SVG arc path element
const arcPath = ['M', // start the path
start, height-50, // declare the (x,y) of where to start
'A', // specify an eliptical curve
(start - end)/2, ',', // xradius: height of arc is proportional to start - end
(start - end)/2, // yradius
0, 0, ",", // rotation of ellipse is 0 along x and y; see arc url for details
start < end ? 1: 0, // make all arcs curve above the nodes; see arc documentation
end, height-50] // declare (x,y) of endpoint
.join(' '); // convert the bracketed array into a string
return arcPath;
};
// to create the arcs, we use graphData.links instead of graphData.nodes
arcGroup.selectAll("arcs")
.data(graphData.links)
.enter().append("path")
.attr("d", d => buildArc(d))
.style("fill", "none") // no fill color for the arcs
.attr("stroke", "black") // make the arc's lines be black
return container.node();
}
Insert cell
Insert cell
Insert cell
animatedNodes1 = {
const radius = 8
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// First place the nodes at cy position 50
// Then call transition(), with a duration of i*500, moving to cy position height-50
// For fun, we've put in a bouncing ease transition type. Try changing it around!
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", 50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
.transition()
.ease(d3.easeBounce)
.duration((d,i) => i*500)
.attr("cy", height-50);
return container.node();
}
Insert cell
Insert cell
Insert cell
animatedNodes2 = {
const radius = 8
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", 50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
.transition()
.ease(d3.easeBounce)
.duration((d,i) => i*500)
.attr("cy", height-50);
/* This invokes additional transitions on the nodes selection;
Try commenting it out and putting it back in.
Add more transitions between the fill and the cy. */
nodes
.transition()
.style("fill", "firebrick")
.transition()
.attr("cy", 50)
return container.node();
}
Insert cell
Insert cell
Insert cell
animatedNodes3 = {
const radius = 8
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", 50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
.transition()
.ease(d3.easeBounce)
.duration((d,i) => i*500)
.attr("cy", height-50)
.attr("fill", "firebrick")
.on("end", function() {
d3.select(this)
.transition()
.attr("cy", 50)
})
return container.node();
}
Insert cell
Insert cell
Insert cell
expandingAnimatedNodes = {
const radius = 8
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
// give the svg a black background
container.style("background-color", "white")
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// define a bright red color; used the color meter on the pitch interactive viz
const redcol = d3.rgb("252", "0", "8");
// first just place the red nodes at opacity 1
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", redcol)
.attr("opacity", 1)
container.transition().duration(50).style("background-color", "black")

// now do the animations. Grow the size and then fade out the circles.
// at the end, fade the black background white.
nodes
.transition(d3.easeBounce)
.duration(1000)
.attr("r", 20).attr('opacity', 0.3)
.transition()
.ease(d3.easeLinear)
.duration(2000)
.attr("opacity", 0)
.on("end", function() {
container.transition().duration(1000).style("background-color", "white");
})
return container.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
// adapted from https://www.d3-graph-gallery.com/graph/arc_highlight.html
// and https://observablehq.com/@lemonnish/svg-path-animations-d3-transition
animateArcsFromNodes = {
const radius = 8;
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// create the nodes
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
// create the node labels
arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x", d => xScale(d.name))
.attr("y", height-20)
.attr("fill", "darkgrey")
.style("text-anchor", "middle")
.text(d => d.name)
// This code builds up the SVG path element; see nodesAndArcs for details
function buildArc(d) {
let start = xScale(idToNode[d.source].name);
let end = xScale(idToNode[d.target].name);
const arcPath = ['M', start, height-50, 'A', (start - end)/2, ',', (start-end)/2, 0,0,",",
start < end ? 1: 0, end, height-50].join(' ');
return arcPath;
}
// create the arcs
const arcs = arcGroup.selectAll("arcs")
.data(graphData.links)
.enter().append("path")
.style("fill", "none")
.attr("stroke", "black")
.attr("d", d => buildArc(d))
// do the animation; see the posts on arc animation for explanation
arcs
// hide the arcs
.attr("stroke-dasharray", function () {
return this.getTotalLength()
})
.attr("stroke-dashoffset", function () {
return this.getTotalLength()
})
// reveal the arcs
.transition()
.duration(4000)
.attr("stroke-dashoffset", 0)
// hide them again
.transition()
.attr("stroke-dashoffset", function () {
return this.getTotalLength()
})
return container.node();
}
Insert cell
Insert cell
// adapted from https://www.d3-graph-gallery.com/graph/arc_highlight.html
animateArcsFromNodesThenExplode = {
const radius = 8;
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// create the nodes
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
// create the node labels
arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x", d => xScale(d.name))
.attr("y", height-20)
.attr("fill", "darkgrey")
.style("text-anchor", "middle")
.text(d => d.name)
// This code builds up the SVG path element; see nodesAndArcs for details
function buildArc(d) {
let start = xScale(idToNode[d.source].name);
let end = xScale(idToNode[d.target].name);
const arcPath = ['M', start, height-50, 'A', (start - end)/2, ',', (start-end)/2, 0,0,",",
start < end ? 1: 0, end, height-50].join(' ');
return arcPath;
}
// create the arcs
const arcs = arcGroup.selectAll("arcs")
.data(graphData.links)
.enter().append("path")
.style("fill", "none")
.attr("stroke", "black")
.attr("d", d => buildArc(d))
// do the animation; see the posts on arc animation for explanation
arcs
// hide the arcs
.attr("stroke-dasharray", function () {
return this.getTotalLength()
})
.attr("stroke-dashoffset", function () {
return this.getTotalLength()
})
// reveal the arcs
.transition()
.duration(4000)
.attr("stroke-dashoffset", 0)
.on("end", function() {
const redcol = d3.rgb("252", "0", "8");
nodes
.attr("fill", redcol)
.attr("opacity", 1)
.transition(d3.easeBounce)
.duration(1000)
.attr("r", 20).attr('opacity', 0.3)
.transition()
.ease(d3.easeLinear)
.duration(1000)
.attr("opacity", 0)
.on("end", function() {
arcs
.transition()
.ease(d3.easeLinear)
.duration(1000)
.attr("stroke-dasharray", function () {
return this.getTotalLength()
})
.attr("stroke-dashoffset", function () {
return this.getTotalLength()
})
});
})
return container.node();
}
Insert cell
Insert cell
// adapted from https://www.d3-graph-gallery.com/graph/arc_highlight.html
interactiveNodesAndArcs = {
const radius = 8
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// create the nodes
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id)
// create the node labels
arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x", d => xScale(d.name))
.attr("y", height-20)
.attr("fill", "darkgrey")
.style("text-anchor", "middle")
.text(d => d.name)
// This code builds up the SVG path element; see nodesAndArcs for details
function buildArc(d) {
let start = xScale(idToNode[d.source].name);
let end = xScale(idToNode[d.target].name);
const arcPath = ['M', start, height-50, 'A', (start - end)/2, ',', (start-end)/2, 0,0,",",
start < end ? 1: 0, end, height-50].join(' ');
return arcPath;
}
// create the arcs
const arcs = arcGroup.selectAll("arcs")
.data(graphData.links)
.enter().append("path")
.attr("d", d => buildArc(d))
.style("fill", "none")
.attr("stroke", "black")
// When the user mouses over a node,
// add interactive highlighting to see connections between nodes
nodes.on('mouseover', function(d) {
// highlight only the selected node
d3.select(this).style("fill", "firebrick");
// next, style the arcs
arcs
// the arc color and thickness stays as the default unless connected to the selected node d
// notice how embedding the reference to arcs within nodes.on() allows the code to connect d to arcd
// this code iterates through all the arcs so we can compare each to the selected node d
.style('stroke', function (arcd) {
return arcd.source === d.id || arcd.target === d.id ? 'firebrick' : 'black';})
.style('stroke-width', function (arcd) {
return arcd.source === d.id || arcd.target === d.id ? 4 : 1;})
});
// remove highlighting when user mouse moves out of node by restoring default colors and thickness
nodes.on('mouseout', function (d) {
nodes.style("fill", "steelblue");
arcs.style('stroke', 'black');
arcs.style('stroke-width', 1);
});
return container.node();
}
Insert cell
Insert cell
md`### Change the colors of the circles that the animated arcs link to.

Use on("end") to change the colors of the circles that are interactively animated to after the arcs are drawn.
This was very tricky for me to figure out.
`
Insert cell
// adapted from https://www.d3-graph-gallery.com/graph/arc_highlight.html
animatedInteractiveNodesAndArcs = {
const radius = 8;
const container = d3.select(DOM.svg(width+margin.left+margin.right,
height+margin.top+margin.bottom))
const arcGroup = container
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// create the nodes
const nodes = arcGroup.selectAll("nodes")
.data(graphData.nodes)
.enter().append("circle")
.attr("cx", d => xScale(d.name))
.attr("cy", height-50)
.attr("r", radius)
.attr("fill", "steelblue")
.attr("id", d => d.id) // need to add the ID to use below
// create the node labels
arcGroup.selectAll("nodeLabels")
.data(graphData.nodes)
.enter().append("text")
.attr("x", d => xScale(d.name))
.attr("y", height-20)
.attr("fill", "darkgrey")
.style("text-anchor", "middle")
.text(d => d.name)
// This code builds up the SVG path element; see nodesAndArcs for details
function buildArc(d) {
let start = xScale(idToNode[d.source].name);
let end = xScale(idToNode[d.target].name);
const arcPath = ['M', start, height-50, 'A', (start - end)/2, ',', (start-end)/2, 0,0,",",
start < end ? 1: 0, end, height-50].join(' ');
return arcPath;
}
// create the arcs
let arcs = arcGroup.selectAll("arcs")
.data(graphData.links)
.enter().append("path")
.style("fill", "none")
.attr("stroke", "none")
.attr("d", d => buildArc(d));
// When the user mouses over a node,
// add interactive highlighting to see connections between nodes
nodes.on('mouseover', function(d) {
// remember which node is the selected node
let thisNode = this;
// highlight the selected node
d3.select(this).style("fill", "firebrick");
// style the arcs
arcs
.style('stroke', function (arcd) {
return arcd.source === d.id ? 'firebrick' : 'none';})
.style('stroke-width', function (arcd) {
return arcd.source === d.id ? 4 : 1;})
.attr("stroke-dasharray", function(arcd) {
return arcd.source === d.id ? this.getTotalLength() : 0;})
.attr("stroke-dashoffset", function(arcd)
{ return arcd.source === d.id ? this.getTotalLength() : 0;})
// reveal the arcs
.transition()
.duration(3000)
.attr("stroke-dashoffset", 0)
/* To make this code clean, I added the idToTargetNodes function in the Data section;
it gets list of nodes linked to from the user-selected node.
This function uses a filter on the nodes selection to find those nodes that are on the target
list or to find the source node itself. The style code that follows is applied only to those
nodes that make it through the filter.
The + before the node IDs makes them both integers instead of strings so that === works.
*/
.on("end", function(d, i) {
// the if statement is to make this function run only once at the end. It isn't required.
if (i === 0)
nodes
.filter(function(noded) {
return idToTargetNodes[thisNode.id].includes(noded.id) || +thisNode.id === +noded.id})
.style("fill", "firebrick");
});
});
// remove highlighting when user mouse moves out of node by restoring default colors and thickness
nodes.on('mouseout', function () {
nodes.style("fill", "steelblue");
arcs.style('stroke', 'none');
arcs.style('stroke-width', 1);
});
return container.node();
}
Insert cell
md`**Exercise:** Copy this code and change it so that, instead of making the target circles turn red when the arcs hit them, the target circles turn red and explode.
`
Insert cell
// code goes here

Insert cell
Insert cell
d3 = require("d3@^5.8")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more