Public
Edited
Jan 5
5 forks
12 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

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