Public
Edited
Jan 5
5 forks
13 stars
Insert cell
Insert cell
nodes = [
{ id: 'A' },
{ id: 'B' },
{ id: 'C' },
{ id: 'D' }
]
Insert cell
edges = [
{ source: 'A', target: 'B', weight: 83},
{ source: 'A', target: 'C', weight: 97 },
{ source: 'B', target: 'C', weight: 42 },
{ source: 'D', target: 'C', weight: 11 },
]
Insert cell
Insert cell
Insert cell
margin = ({top: 30, right: 0, bottom: 0, left: 50})
Insert cell
width = 100 + margin.left + margin.right
Insert cell
height = 100 + margin.top + margin.bottom
Insert cell
Insert cell
color = d3.scaleSequential()
.domain([0, 100])
.interpolator(d3.interpolateReds)
.unknown("#eeeeee")
Insert cell
y = d3.scaleBand()
.domain(nodes.map(d => d.id))
.range([margin.top, height - margin.bottom])
.paddingInner(0.1)
Insert cell
x = d3.scaleBand()
.domain(nodes.map(d => d.id))
.range([margin.left, width - margin.right])
.paddingInner(0.1)
Insert cell
Insert cell
xAxis = g => g
.attr('transform', `translate(0,${margin.top})`)
.call(d3.axisTop(x).tickSize(0))
.call(g => g.selectAll(".domain").remove())
.append("text")
.attr("x", margin.left + (width - margin.left - margin.right) / 2)
.attr("y", -margin.top)
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.text("Target");
Insert cell
yAxis = g => g
.attr('transform', `translate(${margin.left})`)
.call(d3.axisLeft(y).tickSize(0))
.call(g => g.selectAll(".domain").remove())
.append("text")
.attr("x", -margin.left)
.attr("y", margin.top + (height - margin.top - margin.bottom) / 2)
.attr("fill", "black")
.attr("dominant-baseline", "middle")
.attr("text-anchor", "start")
.text("Source")
Insert cell
Insert cell
{
// set up

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// axes
svg.append("g").call(yAxis);
svg.append("g").call(xAxis);
// draw points

svg.selectAll('rect')
.data(edges)
.join('rect')
.attr('x', d => x(d.target))
.attr('y', d => y(d.source))
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('fill', d => color(d.weight))
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
// set up

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// axes
svg.append("g").call(yAxis);
svg.append("g").call(xAxis);
// add gray background and white grid
const background = svg.append('g');
// one gray rectangle the size of the matrix
background.append('rect')
.attr('x', margin.left)
.attr('y', margin.top)
.attr('width', width - margin.left - margin.right)
.attr('height', height - margin.top - margin.bottom)
.attr('fill', '#eeeeee');
// grid lines
// make the line width match the amount of padding between squares in the matrix
// https://d3js.org/d3-scale/band
const xPaddingSize = x.step() * x.paddingInner();
const yPaddingSize = y.step() * y.paddingInner();

background.append('g')
.selectAll('line')
.data(nodes.slice(1))
.join('line')
.attr('x1', d => x(d.id) - xPaddingSize / 2)
.attr('x2', d => x(d.id) - xPaddingSize / 2)
.attr('y1', margin.top)
.attr('y2', height - margin.bottom)
.attr('stroke-width', xPaddingSize)
.attr('stroke', 'white');
background.append('g')
.selectAll('line')
.data(nodes.slice(1))
.join('line')
.attr('y1', d => y(d.id) - yPaddingSize / 2)
.attr('y2', d => y(d.id) - yPaddingSize / 2)
.attr('x1', margin.left)
.attr('x2', width - margin.right)
.attr('stroke-width', yPaddingSize)
.attr('stroke', 'white');
// draw squares for links

svg.append('g')
.selectAll('rect')
.data(edges)
.join('rect')
.attr('x', d => x(d.target))
.attr('y', d => y(d.source))
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('fill', d => color(d.weight));
return svg.node();
}
Insert cell
Insert cell
edgeToWeight = new Map(edges.map(edge => [`${edge.source}-${edge.target}`, edge.weight]))
Insert cell
Insert cell
nodeIds = nodes.map(d => d.id)
Insert cell
allEdges = d3.cross(nodeIds, nodeIds, (source, target) => ({
source,
target,
// if this link isn't in the linkToWeight map, then set the weight to null
weight: edgeToWeight.get(`${source}-${target}`) ?? null,
}))
Insert cell
Insert cell
{
// set up

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// axes
svg.append("g").call(yAxis);
svg.append("g").call(xAxis);
// draw points

svg.selectAll('rect')
.data(allEdges)
.join('rect')
.attr('x', d => x(d.target))
.attr('y', d => y(d.source))
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('fill', d => color(d.weight))
return svg.node();
}
Insert cell
Insert cell
{
// set up
const margin = {top: 30, right: 25, bottom: 5, left: 25};
const width = 150;
const height = 200;

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// create scales

const y = d3.scalePoint()
.domain(nodes.map(d => d.id))
.range([margin.top, height - margin.bottom]);

const lineWidth = d3.scaleLinear()
.domain([0, 100])
.range([0, 5]);
// create and add axes
const leftAxis = d3.axisLeft(y).tickSize(0);
const rightAxis = d3.axisRight(y).tickSize(0);
svg.append("g")
.attr('transform', `translate(${margin.left})`)
.call(leftAxis)
.call(g => g.selectAll(".domain").remove())
.append("text")
.attr("y", 5)
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.text("Source");
svg.append("g")
.attr('transform', `translate(${width - margin.right})`)
.call(rightAxis)
.call(g => g.selectAll(".domain").remove())
.append("text")
.attr("y", 5)
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.text("Target");
// draw lines

svg.append('g')
.selectAll('line')
.data(edges)
.join('line')
.attr('x1', margin.left)
.attr('x2', width - margin.right)
.attr('y1', d => y(d.source))
.attr('y2', d => y(d.target))
.attr('stroke', 'black')
.attr('stroke-width', d => lineWidth(d.weight))
.attr('stroke-linecap', 'round')
return svg.node();
}
Insert cell
Insert cell
{
// set up
const width = 200;
const height = 200;

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// copy the data

const nodesForce = nodes.map(d => ({...d}));
const edgesForce = edges.map(d => ({...d}))
const nodeRadius = 10;
// create the simulation of the forces
// see the d3-force documentation for more detail about all of the forces that can be added
const simulation = d3.forceSimulation(nodesForce)
// The first argument is a name for the force, the second argument is the force.
// pull nodes with links together
.force('link', d3.forceLink(edgesForce).id(d => d.id))
// set mean position of nodes to be center of vis
.force('center', d3.forceCenter(width / 2, height / 2))
// make nodes repel each other
.force('manyBody', d3.forceManyBody().strength(-400))
// prevent overlapping circles
.force('collision', d3.forceCollide(nodeRadius));
// add lines for edges, we don't need to set their positions yet
const links = svg.append('g')
.selectAll('line')
.data(edgesForce)
.join('line')
.attr('stroke', 'black')
.attr('fill', 'none')
.attr('stroke-width', 1);
// add a group for each node, we don't need to set their positions yet
const vertices = svg.append('g')
.selectAll('g')
.data(nodesForce)
.join('g');

// add circle to each group
vertices.append('circle')
.attr('r', nodeRadius)
.attr('fill', 'steelblue');
// add text to each group
vertices.append('text')
.attr('fill', 'white')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-size', '16')
.text(d => d.id);
// update the positions of the vertices and edges on each simulation tick
simulation.on('tick', () => {
// move the group for each vertex
vertices
.attr('transform', d => `translate(${d.x},${d.y})`);
// set the starting and ending coordinates for each edge
links
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
});

// this line is not needed off of observable
// it stops the current simulation when the cell is re-run
invalidation.then(() => simulation.stop());
return svg.node();
}
Insert cell
Insert cell
{
// set up
const width = 200;
const height = 200;

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// copy the data

const nodesForce = nodes.map(d => ({...d}));
const edgesForce = edges.map(d => ({...d}))
const nodeRadius = 10;
// create the simulation of the forces
// see the d3-force documentation for more detail about all of the forces that can be added
const simulation = d3.forceSimulation(nodesForce)
// The first argument is a name for the force, the second argument is the force.
// pull nodes with links together
.force('link', d3.forceLink(edgesForce).id(d => d.id))
// set mean position of nodes to be center of vis
.force('center', d3.forceCenter(width / 2, height / 2))
// make nodes repel each other
.force('manyBody', d3.forceManyBody().strength(-400))
// prevent overlapping circles
.force('collision', d3.forceCollide(nodeRadius));
// defining a triangle from
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-end
svg.append('defs')
.append('marker')
.attr('id', 'triangle')
.attr('viewBox', '0 0 10 10')
.attr('refX', 1)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
.attr('fill', 'black')
// add polyline for edges, we don't need to set their positions yet.
// we are using a polyline here instead of a line so that it will
// be easier to place the arrow in the middle of the line
const links = svg.append('g')
.selectAll('polyline')
.data(edgesForce)
.join('polyline')
.attr('stroke', 'black')
.attr('fill', 'none')
.attr('stroke-width', 1)
// put triangle in the middle of the polyline
.attr('marker-mid', 'url(#triangle)')
// add a group for each node, we don't need to set their positions yet
const vertices = svg.append('g')
.selectAll('g')
.data(nodesForce)
.join('g');

// add circle to each group
vertices.append('circle')
.attr('r', nodeRadius)
.attr('fill', 'steelblue');
// add text to each group
vertices.append('text')
.attr('fill', 'white')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-size', '16')
.text(d => d.id);
// update the positions of the vertices and edges on each simulation tick
simulation.on('tick', () => {
vertices
.attr('transform', d => `translate(${d.x},${d.y})`);
links.attr('points', d => {
// get the midpoint of the line
// this is where the triangle will go
const midX = (d.source.x + d.target.x) / 2;
const midY = (d.source.y + d.target.y) / 2;
// polylines are specified by space sepatated string of x,y coordinates
return `${d.source.x},${d.source.y} ${midX},${midY} ${d.target.x},${d.target.y}`
});
})

// this line is not needed off of observable
// it stops the current simulation when the cell is re-run
invalidation.then(() => simulation.stop());
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more