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

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