Insert cell
Insert cell
Insert cell
axisExample = {
//create SVG arboard
const svg = d3.create("svg").attr("width", width).attr("height", 70);

//create a scale from pi to 2*pi, converting to the pixel x-dimension
const simpleScale = d3
.scaleLinear()
.domain([Math.PI, Math.PI * 2])
.range([50, width - 50]);
//.nice();
//Uncomment the line above and remove the ; in the .range() line...
//...scales can be made 'nice', which means they round up or down to cleaner values

//Make a group ('g') in the SVG
svg
.append("g")
//Move the group down. Axes are drawn at (0,0), and must be positioned with transform
.attr("transform", "translate(0,35)")
//Axes are 'call'ed, which draws them on the screen. This means they can be configured separately from when they are drawn.
.call(
d3
.axisBottom(simpleScale) //connect axis to the scale created above
//.axisTop(simpleScale) //try this one instead of line above!
.ticks(20) //guide to d3 on how many positions to mark on the axis. D3 will do its best to match this number, but might not
.tickPadding(10) //space from tick mark to text label
.tickSizeInner(8) //tick mark length for inside tick marks, negative values will flip the tick to the other side of the axis
.tickSizeOuter(16) //tick mark length for first and last tick marks
.tickFormat(d3.format(".2f")) //guidelines for how D3 should format the text labels. This format means to show two decimal places.
//check out other label formats, like percentages, currencies, and totally custom labels here: https://github.com/d3/d3-format
);

//after the axis is drawn, we can change other visual styles
svg
.selectAll(".tick text") //find all the tick labels
.attr("font-size", "13px") //change font size
.attr("fill", "#566270") //change font color
.attr("font-family", "georgia"); //change font

//inner tick marks styles
svg.selectAll("g line").attr("stroke", "#00aaff").attr("stroke-width", 3);

//axis line and outer tick styles
svg
.select("g path")
.attr("stroke", "#ff00aa")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "8,2"); //dashed line - the first number is the dash length, and the second number is the gap
return svg.node();
}
Insert cell
Insert cell
Insert cell
{
//start data binding
d3.selectAll(".visualizationObjects")
.data(dataset)
.enter()
//end data binding, it's short!

//once data is bound, we can do cool things!
.append("element")
.attr("attribute", (d) => d.property)
.attr("attribute", (state) => state.property)
.attr("attribute", (d) => {
const value = d.propertyName * d.otherProperty;
return value / d.anotherProperty;
})
.attr("attributeName", (d, i) => {
let value = d.propertyName * i;
return value;
})
.attr("class", "visualizationObjects");
}
Insert cell
Insert cell
//dataset
emojiData = [
{ value: 50, emoji: "🐨" },
{ value: 89, emoji: "🐸" },
{ value: 40, emoji: "🐧" },
{ value: 7, emoji: "🦆" },
{ value: 40, emoji: "🐥" },
{ value: 52, emoji: "🐞" },
{ value: 12, emoji: "🐙" },
{ value: 32, emoji: "🐡" },
{ value: 17, emoji: "🐪" },
{ value: 78, emoji: "🦗" }
]
Insert cell
emojiHeight = 250
Insert cell
emojiMargin = 50
Insert cell
dataBinding = {
//SVG setup

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

//background / outline
svg
.append("rect")
.attr("width", width)
.attr("height", emojiHeight)
.attr("stroke", "orange")
.attr("stroke-width", 10)
.attr("fill", "#eff");

//scales
const xScale = d3
.scaleLinear()
.domain([0, emojiData.length - 1])
.range([emojiMargin, width - emojiMargin]);

const yScale = d3
.scaleLinear()
.domain([0, 100])
.range([emojiHeight - emojiMargin, emojiMargin]);

//data binding!
svg
.selectAll(".emojiAnimals")
.data(emojiData)
.enter()
.append("text")
.attr("x", (d, i) => xScale(i))
.attr("y", (d) => yScale(d.value))
.text((d) => d.emoji)
.attr("font-size", 48)
.attr("text-anchor", "middle");

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const height = 200;
const svg = d3.create("svg").attr("width", width).attr("height", height);

//this is another alternative to a for loop - used when we don't want to bind data to element (usually because we don't have data)
//we'll focus on this more next week, don't worry about it now!
//here, we loop 10 times to make 10 circles
const circles = d3.range(10).map((d) => {
svg
.append("circle")
.attr("cx", Math.random() * width)
.attr("cy", Math.random() * height)
.attr("r", 5);
});

//search for all circles elements inside of the svg
const selection = svg.selectAll("circle");
const emptySelection = svg.selectAll("something-we-did-not-make");

//show the selection in observable
return selection;

//comment the return above and uncomment the line below if you want to see an empty selection
//return emptySelection

//comment the return above and uncomment the line below if you want to see the visualization
//return svg.node();
}
Insert cell
Insert cell
{
const height = 200;
const svg = d3.create("svg").attr("width", width).attr("height", height);

//change these!!! make one bigger than the other, or make them the same number
const numberOfCircles = 10;
const numberOfDataValues = 5;

const circles = d3.range(numberOfCircles).map((d) => {
svg
.append("circle")
.attr("cx", Math.random() * width)
.attr("cy", Math.random() * height)
.attr("r", 5);
});

const fakeData = d3.range(numberOfDataValues).map((d) => Math.random());

//search for all circles elements inside of the svg
const comparedData = svg.selectAll("circle").data(fakeData);

//show the selection in observable
return comparedData;

//comment the return above and uncomment the line below if you want to see the visualization
//return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
//B18102_006E male,5-17 years old
//B18102_025E female,5-17 years old

//B18103_007E male,5-17,vision difficulty
//B18103_026E female,5-17,vision difficulty

//B18102_007E male,5-17,hearing difficulty
//B18102_026E female,5-17,hearing difficulty

disabilityData = await d3.json(
"https://api.census.gov/data/2019/acs/acs1?get=NAME,B18102_006E,B18102_025E,B18103_007E,B18103_026E,B18102_007E,B18102_026E&for=state:*"
)
Insert cell
dData = await d3.json(
"https://api.census.gov/data/2019/acs/acs1?get=NAME,B18102_006E,B18102_025E,B18103_007E,B18103_026E,B18102_007E,B18102_026E&for=region:*"
)
Insert cell
Insert cell
Insert cell
disabilityStates = {
const statesData = [];

for (let i = 1; i < disabilityData.length; i++) {
//keep each state in a variable while we're working on it
const state = disabilityData[i];

//combine data points to create meaningful sums and percentages
const totalYoungPopulation = parseInt(state[1]) + parseInt(state[2]);
const visionDifficultyPercentage =
(parseInt(state[3]) + parseInt(state[4])) / totalYoungPopulation;
const hearingDifficultyPercentage =
(parseInt(state[5]) + parseInt(state[6])) / totalYoungPopulation;
const maleDifficultyPercentage =
(parseInt(state[3]) + parseInt(state[5])) / parseInt(state[1]);
const femaleDifficultyPercentage =
(parseInt(state[4]) + parseInt(state[6])) / parseInt(state[2]);
const totalYoungDifficultyPercentage =
femaleDifficultyPercentage + maleDifficultyPercentage;

//this is a fun single line! It looks through the state labels array and finds the abbreviation that matches each state fips code
const abbreviation = stateLabels.find(
(label) => label.fips == parseInt(state[7])
).abbreviation;

//create new object for each state
//note that you can just include a variable in an object, and the key will be automatically generated from the variable name
statesData.push({
name: state[0],
totalYoungPopulation,
visionDifficultyPercentage,
hearingDifficultyPercentage,
maleDifficultyPercentage,
femaleDifficultyPercentage,
totalYoungDifficultyPercentage,
abbreviation,
fipsCode: state[7]
});
}

//sort states alphabetically
statesData.sort((a, b) => d3.ascending(a.name, b.name));

return statesData;
}
Insert cell
Insert cell
viewof selectedStates = Inputs.table(disabilityStates)
Insert cell
Insert cell
selectedStates
Insert cell
visionDisabilityExtents = d3.extent(
selectedStates,
(state) => state.totalYoungDifficultyPercentage
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
simpleViz = {
//Create SVG artboard
//Note that we did not make a `width` variable. Instead, Observable automatically
//creates a `width` variable that is equal to the width of the Observable page column
const svg = d3.create("svg").attr("width", width).attr("height", height);

//Create SVG rect for background color
svg
.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", bgColor);

//add axes defined below
svg
.append("g")
.attr("transform", "translate(0," + (height - margin) + ")")
.call(xAxis)
.call((g) => {
g.selectAll("line");
});

svg
.append("g")
.attr("transform", "translate(" + margin + ")")
.call(yAxis)
.call((g) => {
g.select(".domain").remove();
g.selectAll("line").attr("opacity", ".2");
});

//style axes
svg
.selectAll(".tick text")
.attr("font-size", "10px")
.attr("fill", "#566270")
.attr("font-family", typeface);

svg.selectAll("line").attr("stroke", "#566270");

//data binding
svg
.selectAll(".disabilityDots") //find all the visualizations elements (there are none!)
.data(disabilityStates) //compare with dataset
.enter() //for loop replacement. each data point that is *not* matched in our SVG code is passed forward
.append("circle") //add a circle for each one...
.attr("cx", (d) => abbreviationToPixelsX(d.abbreviation))
.attr("cy", (d) => percentToPixelsY(d[simpleTopic]))
.attr("r", 5)
.attr("fill", circleColor)
.attr("class", "disabilityDots");

//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
//x-scale
//it is a point scale, not a linear scale, which takes in categorical data like names for its domain
//https://observablehq.com/@d3/d3-scalepoint
abbreviationToPixelsX = d3
.scalePoint()
.domain(disabilityStates.map((d) => d.abbreviation))
.range([margin, width - margin])
.padding(1)
Insert cell
//y-scale
percentToPixelsY = d3
.scaleLinear()
.domain([0, 0.05])
.range([height - margin, margin])
Insert cell
xAxis = d3
.axisBottom(abbreviationToPixelsX)
.tickPadding(10)
.tickSizeInner(5)
//.tickSizeInner(-height + margin * 2) //try this one too to see vertical grid lines!
.tickSizeOuter(0)
Insert cell
yAxis = d3
.axisLeft(percentToPixelsY)
.ticks(5)
.tickSize(-width + margin * 2)
.tickPadding(10)
.tickFormat(d3.format(".0%"))
Insert cell
Insert cell
Insert cell
vizTopic
Insert cell
Insert cell
Insert cell
Insert cell
viz = {
//create SVG artboard
const svg = d3.create("svg").attr("width", width).attr("height", height);

//this is fancy. we are going to use the dropdown above to determine which two data keys from the census we should show
const visualizationKeys =
vizTopic == "Females vs Males"
? ["femaleDifficultyPercentage", "maleDifficultyPercentage"]
: ["hearingDifficultyPercentage", "visionDifficultyPercentage"];

//so, we can now use visualizationKeys[0] to get either "maleDifficultyPercentage" or "hearingDifficultyPercentage",
//depending on user choice from the dropdown.

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

//add axes defined above
svg
.append("g")
.attr("transform", "translate(0," + (height - margin) + ")")
.call(xAxis)
.call((g) => {
g.selectAll("line");
});

svg
.append("g")
.attr("transform", "translate(" + margin + ")")
.call(yAxis)
.call((g) => {
g.select(".domain").remove();
g.selectAll("line").attr("stroke-dasharray", "2,4");
});

//style axes
svg
.selectAll(".tick text")
.attr("font-size", "10px")
.attr("fill", "#566270")
.attr("font-family", "menlo");

svg.selectAll("line").attr("stroke", "#566270");

//create line *behind* the circles
svg
.selectAll(".dumbell")
.data(disabilityStates, (d) => d.fips)
.enter()
.append("line")
.attr("x1", (d) => abbreviationToPixelsX(d.abbreviation))
.attr("y1", (d) => percentToPixelsY(d[visualizationKeys[0]]))
.attr("x2", (d) => abbreviationToPixelsX(d.abbreviation))
.attr("y2", (d) => percentToPixelsY(d[visualizationKeys[1]]))
.attr("alt", "red")
.attr("stroke", (d) =>
//check which key is larger, and use the appropriate color for the line. ternary!!!
d[visualizationKeys[0]] > d[visualizationKeys[1]]
? colorFemaleHearing
: colorMaleVision
)
.attr("stroke-width", 2)
.attr("class", "dumbell");

//standard data binding - no for loop!!!
svg
.selectAll(".circlesCategoryA")
.data(disabilityStates, (d) => d.fips)
.enter()
.append("circle")
.attr("cx", (d) => abbreviationToPixelsX(d.abbreviation))
.attr("cy", (d) => percentToPixelsY(d[visualizationKeys[0]]))
.attr("r", 5)
.attr("fill", colorFemaleHearing)
.attr("class", "circlesCategoryA")
//disregard the next two lines for interactivity -- that's next week's focus!
.on("mouseover", (event, datum) => tooltipShow(event, datum, 0))
.on("mouseout", (event, datum) => tooltipHide(event, datum));

svg
.selectAll(".circlesCategoryB")
.data(disabilityStates, (d) => d.fips)
.enter()
.append("circle")
.attr("cx", (d, i) => abbreviationToPixelsX(d.abbreviation))
.attr("cy", (d) => percentToPixelsY(d[visualizationKeys[1]]))
.attr("r", 5)
.attr("fill", colorMaleVision)
.attr("class", "circlesCategoryB")
//disregard the next two lines for interactivity -- that's next week's focus!
.on("mouseover", (e, d) => tooltipShow(e, d, 1))
.on("mouseout", (event, datum) => tooltipHide(event, datum));

svg
.append("text")
.text("")
.attr("y", margin / 2)
.attr("id", "tooltip")
.attr("opacity", 0)
.attr("font-family", "menlo")
.attr("font-size", "10px")
.attr("text-anchor", "middle");

//show visualization in Observable
return svg.node();
}
Insert cell
Insert cell
Insert cell
tooltipHide = (e, d) => {
d3.select(e.target).transition().attr("r", 5);
d3.select("#tooltip").transition().delay(500).attr("opacity", 0);
}
Insert cell
tooltipShow = (event, data, dotCategory) => {
const visualizationKeys =
vizTopic == "Females vs Males"
? ["femaleDifficultyPercentage", "maleDifficultyPercentage"]
: ["hearingDifficultyPercentage", "visionDifficultyPercentage"];

d3.select(event.target).transition().attr("r", 8);
d3.select("#tooltip")
.attr("x", abbreviationToPixelsX(data.abbreviation))
.text(
data.name +
" : " +
d3.format(".2%")(data[visualizationKeys[dotCategory]]) +
" " +
visualizationKeys[dotCategory]
)
.transition()
.attr("opacity", 1);
}
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