Public
Edited
Oct 30, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
forFakeData = {
const fakeData = [];

const count = 100;

for (let i = 0; i < count; i++) {
fakeData.push({
x: Math.random(),
y: Math.random(),
z: Math.random()
});
}

return fakeData;
}
Insert cell
Insert cell
Insert cell
d3.range(10)
Insert cell
d3.range(5, 15)
Insert cell
d3.range(2, 6, 0.25)
Insert cell
Insert cell
mapFakeData = d3.range(100).map((d) => {
return { x: Math.random(), y: Math.random(), z: Math.random() };
})
Insert cell
Insert cell
evenOdds = d3.range(100).map((d) => {
return d % 2 == 0 ? "even" : "odd";
})
Insert cell
perfectSquares = d3.range(100).map((d) => {
return Number.isInteger(Math.sqrt(d));
})
Insert cell
Insert cell
Insert cell
Insert cell
random_bouncing = {
//svg variables
const height = 400;
const margin = 50;

//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#002");

//generate fake data array
const fakeData = d3.range(maximumDots).map((d) => {
return { x: Math.random(), y: Math.random(), z: Math.random() };
});

//scales for converting data to pixels
//usually we would want these elswhere on the Observable page...
//...but they're added here to keep this code self-contained.
const xScale = d3
.scaleLinear()
.domain([0, 1])
.range([margin, width - margin]);

const yScale = d3
.scaleLinear()
.domain([0, 1])
.range([height - margin, margin]);

//standard data binding with our dataset and SVG circles
//every data point will be passed to .enter() since we have no existing SVG elements
svg
.selectAll(".fakeDots")
.data(fakeData)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("fill", (d) => d3.interpolateMagma(d.z))
.attr("stroke", "white")
.attr("stroke-width", 0)
.attr("r", 5)
.attr("class", "fakeDots");

//create svg group to hold button and button label
const g = svg.append("g");

//add button background rectangle
g.append("rect")
.attr("fill", "orange")
.attr("width", 50)
.attr("height", 20)
.attr("x", width - 60)
.attr("y", 10)
//let this button listen for a click
.on("click", (event, datum) => {
//when the button is clicked...
//generate a sequence of numbers of random length
//generate fake data for each item in the sequence
const newFakeData = d3.range(maximumDots * Math.random()).map((d) => {
return { x: Math.random(), y: Math.random(), z: Math.random() };
});

//look for existing dots and compare them with the new data
//sometimes we will have more data in the array, sometimes less
//so we need to add elements and remove elements, but also update existing dots to their new values
const match = svg.selectAll(".fakeDots").data(newFakeData);

//for new data without a matching dot we use .enter()
//this code is almost identical to how we built the first generation of circles
//the new circles are drawn with 0 radius, and then grow
match
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("r", 0)
.attr("fill", (d) => d3.interpolateMagma(d.z))
.attr("stroke", "white")
.attr("stroke-width", 0)
.attr("class", "fakeDots")
.transition()
.duration(1000)
.delay((d) => Math.random() * 1000)
.ease(d3.easeBounce)
.attr("r", (d) => 5);

//for SVG elements without data to support it, we use .exit()
//these circles will shrink to 0 radius and then get removed from SVG
match
.exit()
.transition()
.duration(1000)
.delay((d) => Math.random() * 1000)
.ease(d3.easeExpOut)
.attr("r", 0)
.remove();

//for data that had matching SVG elements, we just update the position and color
//but we don't need to call .update()... since it's the default behavior
match
.transition()
.duration(1000)
.delay((d) => Math.random() * 1000)
.ease(d3.easeBackOut.overshoot(1.7))
.attr("fill", (d) => d3.interpolateMagma(d.z))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y));
});

//label for the button
g.append("text")
.attr("fill", "white")
.attr("x", width - 58)
.attr("y", 22)
.text("Shuffle!")
.style("font-family", "courier")
.style("font-size", 10)
//so the text doesn't get in the way of our button seeing the mouse click...
.attr("pointer-events", "none");

return svg.node();
}
Insert cell
Insert cell
Insert cell
events = {
//svg variables
const height = 300;
const margin = 50;
const spacing = 300 / 4;

//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
let bg = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#002");

//clickable circle
svg
.append("circle")
.attr("cx", 100)
.attr("cy", height / 4)
.attr("r", 25)
.attr("fill", "#0ff")
.on("click", (event) => {
d3.select(event.currentTarget).attr("fill", "#f00");
});

//hoverable circle
svg
.append("circle")
.attr("cx", 100)
.attr("cy", (2 * height) / 4)
.attr("r", 25)
.attr("fill", "#0ff")
.on("mouseover", (event) => {
d3.select(event.target).attr("fill", "#ff0");
})
.on("mouseout", (event) => {
d3.select(event.currentTarget).attr("fill", "#0ff");
});

//double-clickable circle
svg
.append("circle")
.attr("cx", 100)
.attr("cy", (3 * height) / 4)
.attr("r", 25)
.attr("fill", "#0ff")
.on("dblclick", (event) => {
const currentColor = d3.select(event.target).attr("fill");
const newColor =
currentColor == "rgb(0,255,255)" ? "rgb(0,255,0)" : "rgb(0,255,255)";

d3.select(event.currentTarget).attr("fill", newColor);
});

//text labels
svg
.append("text")
.attr("x", 140)
.attr("y", height / 4)
.attr("fill", "white")
.text("click me once!");
svg
.append("text")
.attr("x", 140)
.attr("y", (2 * height) / 4)
.attr("fill", "white")
.text("hover on me!");
svg
.append("text")
.attr("x", 140)
.attr("y", (3 * height) / 4)
.attr("fill", "white")
.text("double-click me many times!");

return svg.node();
}
Insert cell
Insert cell
Insert cell
smallDetails = {
const usPopulation = [
{ year: 1960, usPop: 180671000 },
{ year: 1970, usPop: 205052000 },
{ year: 1980, usPop: 227225000 },
{ year: 1990, usPop: 249623000 },
{ year: 2000, usPop: 282162411 },
{ year: 2010, usPop: 309321666 },
{ year: 2020, usPop: 326687501 }
];

//svg variables
let height = 400;
let margin = 75;

//create SVG artboard
let svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "blue");

//create text in the corner
svg
.append("text")
.attr("x", width - margin / 2)
.attr("y", margin / 2)
.attr("fill", "white")
.style("text-anchor", "end")
.attr("font-size", "12")
.attr("font-family", "courier")
.attr("id", "infoText")
.text("Click on a red bar!");

//visualization utilities
const barWidth = (height - margin * 2) / usPopulation.length;
const popExtents = d3.extent(usPopulation, (d) => d.usPop);
const popScale = d3
.scaleLinear()
.domain(popExtents)
.range([100, width - margin * 2]);

//draw bars
svg
.selectAll(".yearBars")
.data(usPopulation)
.enter()
.append("rect")
.attr("x", margin)
.attr("y", (d, i) => i * barWidth + margin)
.attr("width", (d) => popScale(d.usPop))
.attr("height", barWidth)
.attr("fill", "red")
.on("mouseover", (e) => {
d3.select(e.currentTarget).attr("stroke", "white").attr("stroke-width", "2");
})
.on("mouseout", (e) => {
d3.select(e.currentTarget).attr("stroke", "none").attr("stroke-width", "0");
})
.on("click", (e, d, i) => {
d3.select("#infoText").text(
"In the year " +
d.year +
", item " +
i +
" of dataset, the US had " +
//toLocaleString() takes a long number and adds commas/periods to separate thousands
d.usPop.toLocaleString() +
" people."
);
});

//draw labels
svg
.selectAll(".yearLabels")
.data(usPopulation)
.enter()
.append("text")
.attr("x", margin - 5)
.attr("y", (d, i) => i * barWidth + margin + barWidth / 1.5)
.attr("fill", "white")
.style("text-anchor", "end")
.text((d) => d.year)
.attr("font-family", "courier")
.on("mouseover", (e, d, i) => {
d3.select(e.currentTarget).attr("stroke", "white").attr("stroke-width", "1");
})
.on("mouseout", (e, d, i) => {
d3.select(this).attr("stroke", "none").attr("stroke-width", "0");
});

return svg.node();
}
Insert cell
Insert cell
{
//svg variables
const height = 400;

//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#002");

//create circle visualization
svg
.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", 50)
//take the line below out to see what happens if a property isn't set before animation
.attr("stroke-width", 0)
.attr("stroke", "blue")
.attr("fill", "cyan")
.on("mouseover", (e) => {
d3.select(e.currentTarget)
.transition()
.duration(2000)
.ease(d3.easeBounceOut)
.attr("fill", "magenta")
.attr("stroke", "white")
.attr("stroke-width", 10);
})
.on("mouseout", (e) => {
d3.select(e.currentTarget)
.transition()
.delay(500)
.duration(1500)
.attr("r", 50)
.attr("stroke-width", "0")
.attr("fill", "cyan");
});

return svg.node();
}
Insert cell
Insert cell
{
//svg variables
let height = 400;
const margin = 50;

//create SVG artboard
let svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#002");

//simple dataset
const colorData = [
{ startColor: "magenta", endColor: "cyan", travelTime: 2000 },
{ startColor: "cyan", endColor: "yellow", travelTime: 1500 },
{ startColor: "yellow", endColor: "magenta", travelTime: 1000 }
];

//data binding to circles
svg
.selectAll(".colorCircles")
.data(colorData)
.enter()
.append("circle")
.attr("cx", margin * 2)
.attr("cy", (d, i) => ((i + 1) * height) / 4)
.attr("r", margin - 10)
.attr("fill", (d) => d.startColor)
.attr("class", "colorCircles");

//create svg button and attach event listener
const button = svg
.append("rect")
.attr("fill", "orange")
.attr("width", 50)
.attr("height", 20)
.attr("x", width - 60)
.attr("y", 10)
.on("click", () => {
d3.selectAll(".colorCircles")
.transition()
.duration((d) => d.travelTime)
.delay((d, i) => i * 500)
.ease(d3.easeBounce)
.attr("cx", width - margin)
.attr("fill", (d) => d.endColor);
});

//label for the button
svg
.append("text")
.attr("fill", "white")
.attr("x", width - 58)
.attr("y", 22)
.text("Animate!")
.style("font-family", "courier")
.style("font-size", 10)
//so the text doesn't get in the way of our button seeing the mouse click...
.attr("pointer-events", "none");

return svg.node();
}
Insert cell
Insert cell
{
//svg variables
const height = 200;

//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#002");

svg
.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", 50)
.attr("stroke", "white")
.attr("stroke-width", "0")
.attr("fill", "cyan")
.on("mouseover", (e) => {
d3.select(e.currentTarget)
//first transition - change color
.transition()
.duration(750)
.attr("fill", "magenta")
//second transtition - grow stroke and change color
.transition()
.duration(1500)
.attr("stroke-width", "10")
.attr("fill", "yellow")
//third transition - grow radius and color and remove stroke
.transition()
.duration(500)
.attr("r", "75")
.attr("fill", "orange")
.attr("stroke-width", "0");
})
.on("mouseout", (e) => {
d3.select(e.currentTarget)
.transition()
.delay(200)
.duration(750)
.attr("r", 50)
.attr("stroke-width", "0")
.attr("fill", "cyan");
});

return svg.node();
}
Insert cell
Insert cell
usAnimated = {
//dataset
const usPopulation = [
{ year: 1960, usPop: 180671000 },
{ year: 1970, usPop: 205052000 },
{ year: 1980, usPop: 227225000 },
{ year: 1990, usPop: 249623000 },
{ year: 2000, usPop: 282162411 },
{ year: 2010, usPop: 309321666 },
{ year: 2020, usPop: 326687501 }
];

//svg variables
const height = 400;
const margin = 75;

//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//create svg background color
const bg = svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "blue");

//better visualization utilities
const barScale = d3
.scaleBand()
.domain(usPopulation.map((d) => d.year))
.range([margin, height - margin])
.paddingInner(0.1);

const popExtents = d3.extent(usPopulation, (d) => d.usPop);

const popScale = d3
.scaleLinear()
.domain(popExtents)
.range([100, width - margin * 2]);

//text in the upper right for filling in by event data
const infoText = svg
.append("text")
.attr("x", width - margin / 2)
.attr("y", margin / 2)
.attr("fill", "white")
.style("text-anchor", "end")
.attr("font-size", "12")
.attr("font-family", "courier")
.attr("opacity", 1)
.attr("id", "infoBlock")
.text("Click on a red bar!");

//data binding
svg
.selectAll(".yearBars")
.data(usPopulation)
.enter()
.append("rect")
.attr("x", margin)
.attr("y", (d, i) => barScale(d.year))
.attr("width", (d) => popScale(d.usPop))
.attr("height", barScale.bandwidth)
.attr("fill", "red")
.attr("stroke", "white")
.attr("stroke-width", "1")
.on("mouseover", (e) => {
d3.select(e.currentTarget)
.raise() //this line of code brings the hovered bar to the top of the SVG layers to prevent overlapping
.transition()
.duration(750)
.attr("stroke-width", "10");
})
.on("mouseout", (e) => {
d3.select(e.currentTarget)
.transition()
.duration(750)
.attr("stroke-width", "1");
})
.on("click", (e, d) => {
d3.select("#infoBlock")
.transition()
.duration(500)
.attr("opacity", 0)
.transition()
.duration(1)
.text(
"In the year " +
d.year +
", the US had " +
//toLocaleString() takes a long number and adds commas/periods to separate thousands
d.usPop.toLocaleString() +
" people."
)
.transition()
.duration(500)
.attr("opacity", 1);
});

//simple y axis
const axis = svg
.append("g")
.attr("transform", "translate(" + margin + ",0)")
.call(d3.axisLeft(barScale).tickSize(width - margin.left - margin.right));

axis.select(".domain").remove();
axis.selectAll(".tick text").attr("x", -10).attr("fill", "white");

return svg.node();
}
Insert cell
Insert cell
Insert cell
gameViz = {
const height = 800;
const simpleData = {
x: Math.random() * width,
y: Math.random() * height,
z: Math.random()
};

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

svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#0ab");

const target = [{ x: 200, y: 200, z: 0.25, size: 20, health: 10 }];

svg
.selectAll(".target")
.data(target)
.enter()
.append("circle")
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", (d) => d.size)
.attr("fill", (d) => d3.interpolatePlasma(d.z))
.attr("stroke-width", (d) => d.health)
.attr("stroke", "white")
.attr("id", "target")
.on("mouseover", (e, d) => {
//subtract 1 from health
d.health--;

//update data every time the fish is caught
d.x = Math.random() * width;
d.y = Math.random() * height;
d.z = Math.random();
d.size = Math.random() * 20 + 5;

//check for win!
if (d.health == 0) {
//show winning message
svg
.append("text")
.text("you win!")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("font-family", "impact")
.attr("fill", "white");

//remove the fish target from game
d3.select("#target").remove();
}

//update after each catch
d3.select(e.currentTarget)
.data(target)
.transition()
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", (d) => d.size)
.attr("fill", (d) => d3.interpolatePlasma(d.z))
.attr("stroke-width", (d) => d.health);
});

return svg.node();
}
Insert cell
Insert cell
CSAs = await d3.json(
"https://api.census.gov/data/2021/acs/acs1?get=NAME,B01001_001E&for=combined%20statistical%20area:*"
)
Insert cell
Insert cell
filteredCSAs = await d3.json(
"https://api.census.gov/data/2021/acs/acs1?get=NAME,B01001_001E&for=combined%20statistical%20area:176,408"
)
Insert cell
Insert cell
csaData = {
//.slice(1) cuts off the zeroeth item of the array
return CSAs.slice(1).map((csa) => {
return {
name: csa[0],
shortName: csa[0].split("-")[0],
population: parseInt(csa[1]),
csaID: parseInt(csa[2])
};
});
//.slice(10, 30);
//you can cut your resulting array with a from and to index if you just want to grab an alphabetical list of cities
}
Insert cell
Insert cell
Insert cell
Insert cell
//scalePoint() uses a domain full of strings. Here, we use the shortened name of each CSA.
yScale = d3
.scalePoint()
.domain(csaData.map((d) => d.shortName))
.range([20, 100])
Insert cell
//we can then ask the scale where any city would fall in the range based on its name, in array order
yScale(csaData[12].shortName)
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