Public
Edited
Mar 23, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Specify the dimensions of the chart.
width
Insert cell
height = 600
Insert cell
Insert cell
margins = ({
top: 60,
right: 20,
bottom: 50,
left: 40
})
Insert cell
Insert cell
Insert cell
x = d3.scaleLinear()
// .domain: data range, sorted first by electric vehicle population, and then county name
.domain(d3.extent(data.nodes, d => d.x))
// .range: canvas range
.range([margins.left, width - margins.right])
Insert cell
y = d3.scaleLinear()
.domain(d3.extent(data.nodes, d => d.y))
.range([height - margins.bottom, margins.top])
.nice()
Insert cell
// Specify the color scale.
color = d3.scaleOrdinal(d3.schemeCategory10)
Insert cell
Insert cell
Insert cell
import {Swatches} from "@d3/color-legend"
Insert cell
// Specify the color scale.
legendSimple = Swatches(simpleChart.scales.color, {columns: "150px"})
Insert cell
simpleChart = {
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;")

// Add a line for each link, and a circle for each node.
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(data.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value))
.attr("x1",l => {
const x1 = d3.filter(data.nodes, (d, i) => d.id == l.source)[0]
return x(x1.x)
})
.attr("y1",l => {
const y1 = d3.filter(data.nodes, (d, i) => d.id == l.source)[0]
return y(y1.y)
}
)
.attr("x2", l => {
const x2 = d3.filter(data.nodes, (d, i) => d.id == l.target)[0]
return x(x2.x)
}
)
.attr("y2", l => {
const y2 = d3.filter(data.nodes, (d, i) => d.id == l.target)[0]
return y(y2.y)
}
)

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(data.nodes)
.join("circle")
.attr("r", 7)
.attr("fill", d => color(d.group))
.attr("cx", d => x(d.x))
.attr("cy", d => y(d.y))

return Object.assign(svg.node(), {scales: {color}})
}
Insert cell
Insert cell
Insert cell
networkData = ({
links: data.links.map(d => ({...d})),
nodes: data.nodes.map(d => ({...d}))
})
Insert cell
Insert cell
simulation =
// instantiates a new simulation using the passed data array as the simulation's nodes
d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
Insert cell
Insert cell
forceChart = {

// Create a simulation with several forces.
simulation
.on("tick", ticked)
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;")

// Add a line for each link, and a circle for each node.
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(networkData.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value))

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(networkData.nodes)
.join("circle")
.attr("r", 7)
.attr("fill", d => color(d.group))

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)

node
.attr("cx", d => d.x)
.attr("cy", d => d.y)
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop())

return Object.assign(svg.node(), {scales: {color}})
}
Insert cell
Insert cell
Insert cell
drag = {
// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
}
Insert cell
Insert cell
draggingChart = {

// Create a simulation with several forces.
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked)
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

// Add a line for each link, and a circle for each node.
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(networkData.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value));

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(networkData.nodes)
.join("circle")
.attr("r", 7)
.attr("fill", d => color(d.group));

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

return Object.assign(svg.node(), {scales: {color}})
}
Insert cell
Insert cell
draggingWithoutBounceChart = {

// Create a simulation with several forces.
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked)
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");

// Add a line for each link, and a circle for each node.
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(networkData.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value));

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(networkData.nodes)
.join("circle")
.attr("r", 7)
.attr("fill", d => color(d.group));

node.append("title")
.text(d => d.id);

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged))
// .on("end", dragended));

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
// function dragended(event) {
// if (!event.active) simulation.alphaTarget(0);
// event.subject.fx = null;
// event.subject.fy = null;
// }

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

return Object.assign(svg.node(), {scales: {color}})
}
Insert cell
Insert cell
Insert cell
zoomChart = {
// Create a simulation with several forces.
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked)
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;")
.call(d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed))

const g = svg.append("g")

// Add a line for each link, and a circle for each node.
const link = g.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(networkData.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value))

const node = g.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(networkData.nodes)
.join("circle")
.attr("r", 7)
.attr("fill", d => color(d.group))
.attr("class", "nodes")

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

function zoomed(event){
const {transform} = event
g.attr("transform", transform)
g.attr("stroke-width", 1/transform.k)
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

return Object.assign(svg.node(), {scales: {color}})
}
Insert cell
Insert cell
Insert cell
titleChart = {

const radius = 7
const highlightColor = "pink"

const zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed)
// Create a simulation with several forces.
const simulation = d3.forceSimulation(networkData.nodes)
.force("link", d3.forceLink(networkData.links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked)
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;")

const g = svg.append("g")

// Add a line for each link, and a circle for each node.
const link = g.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll()
.data(networkData.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value))

const node = g.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll()
.data(networkData.nodes)
.join("circle")
.attr("r", radius)
.attr("fill", d => color(d.group))
.attr("class", "nodes")

const label = g.append("text")

node
.on("mouseover", function(e,d){

d3.select(this)
.transition()
.duration(200)
.attr("fill", highlightColor)
.attr("r", radius*2)
.attr("stroke", "white")
.attr("stroke-width", radius/2)

console.log(d)

const labelText = `${d.label}`
label
.attr("display", null)
.attr("font-size", 15)
.attr("transform", `translate(${d3.select(this).attr("cx")},${d3.select(this).attr("cy")})`)
.attr("dx", +14)
.attr("dy", -10)
.text(d => labelText)

})
.on("mouseleave", function(e, d){

d3.selectAll(".nodes")
.transition()
.duration(200)
.attr("fill", d => color(d.group))
.attr("r", radius)
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)

label
.attr("display", "none")
})

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))

svg.call(zoom)

// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

function zoomed(event){
const {transform} = event
g.attr("transform", transform)
g.attr("stroke-width", 1/transform.k)
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

// Add a title
const title = svg.append("g")
.append("text")
.attr("x", width / 2)
.attr("y", (margins.top / 2))
.attr("text-anchor", "middle")
.attr("front-weight", "bold")
.attr("font-family", "Helvetica Neue, Arial")
.attr("font-size", "20px")
.text("Some ridiculous network data visualization")

return Object.assign(svg.node(), {scales: {color}})
}
Insert cell
Insert cell
Insert cell
Insert cell
import {textcolor} from "@observablehq/text-color-annotations-in-markdown"
Insert cell
import {toc} from "@jonfroehlich/collapsible-toc"
Insert cell
d3 = require("d3@7")
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