Published
Edited
Sep 17, 2019
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require('d3@5')
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
margin = ({
top: 50,
right: 50,
bottom: 50,
left: 100
})
Insert cell
Insert cell
mutable container = d3.create('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);

/** In your notebook (without mutable):

container = d3.create('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
*/
Insert cell
Insert cell
Insert cell
mutable chart = (mutable container).append('g')
.attr('id', 'chart')
.attr('transform', `translate(${margin.left}, ${margin.top})`);

/** In your notebook (without mutable):

chart = container.append('g')
.attr('id', 'chart')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
*/
Insert cell
Insert cell
container.node()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
__selections_n__.container
.insert('rect', ':first-child')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', '#ff96ca');

/** In your notebook (without namespace):
** Same container as before!
** (In this notebook, if don't use the namespace here,
** we'll also modify the render of the previous stage.
** Try it if you are curious!)
container
.insert('rect', ':first-child')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', '#ff96ca');
*/
Insert cell
Insert cell
__selections_n__.chart
.append('rect')
.attr('width', width)
.attr('height', height)
.attr('fill', '#42adf4');

/** In your notebook (without namespace):
** Same chart as before!
** (In this notebook, if don't use the namespace here,
** we'll also modify the render of the previous stage.
** Try it if you are curious!)
chart
.append('rect')
.attr('width', width)
.attr('height', height)
.attr('fill', '#42adf4');
*/

Insert cell
__selections_n__.container.node()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
ageDomain = unique(census.map((row) => { return row.age_group }));
Insert cell
Insert cell
peopleDomain = [0, d3.max(census, row => row.people)];
Insert cell
Insert cell
sexDomain = [1, 2];
Insert cell
Insert cell
Insert cell
Insert cell
x = d3.scaleBand().rangeRound([0, width])
.padding(0.1)
.domain(ageDomain);
Insert cell
Insert cell
y = d3.scaleLinear()
.range([height, 0])
.domain(peopleDomain);
Insert cell
Insert cell
color = {
const maleColor = '#42adf4';
const femaleColor = '#ff96ca';

// create a scale function that maps 1 to the male color and 2 to the female color
return d3.scaleOrdinal()
.range([maleColor, femaleColor])
.domain(sexDomain);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable xaxis = __axes_n__.chart
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x));

/** In your notebook (without mutable and namespace):

xaxis = chart
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(x));

*/
Insert cell
mutable yaxis = __axes_n__.chart
.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y));

/** In your notebook (without mutable and namespace):

yaxis = chart
.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y));

*/
Insert cell
Insert cell
__axes_n__.container.node()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// make the font consistent
__titles_n__.container.selectAll('text').style("font-family", "sans-serif");

/** In your notebook (without namespace).

container.selectAll('text').style("font-family", "sans-serif");

*/
Insert cell
mutable title = __titles_n__.container.append("text")
.attr("transform", `translate(${(width + margin.left + margin.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Census Age Group and Population by Sex");


/** In your notebook (without mutable and namespace):

title = container.append("text")
.attr("transform", `translate(${(width + margin.left + margin.right)/2},20)`)
.style("text-anchor", "middle")
.style("font-weight", 700)
.text("Census Age Group and Population by Sex");

*/
Insert cell
// text label for the y axis
mutable ytitle = __titles_n__.chart.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Population");

/** You know the drill. In your own notebook you can ignore mutable and __titles__.
** We'll stop reminding you at this point to save space. */
Insert cell
// text label for the x axis
mutable xtitle = __titles_n__.chart.append("text")
.attr("transform", `translate(${(width/2)}, ${(height + margin.top - 10)})`)
.style("text-anchor", "middle")
.text("Age Group");
Insert cell
__titles_n__.container.node()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3Legend = require('d3-svg-legend')
Insert cell
legend_auto = d3Legend
.legendColor()
.scale(color)
.labels(["Male", "Female"])
Insert cell
Insert cell
__auto_legend_n__.container.append("g")
.attr("class", "legend_auto")
.style('font-size', 12)
.style('font-family', 'sans-serif')
.attr("transform", "translate(650, 100)")
.call(legend_auto)
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
mutable legend = __manual_legend_n__.chart
.selectAll(".legend") // step 1
.data(color.domain()) // step 2
.enter() // step 3
.append("g") // step 4
.attr("class", "legend") // step 5
.attr("transform", function(d, i) { // step 5
// i is the index
return `translate(0, ${i * 20})`;
})
.style('font-family', 'sans-serif');
Insert cell
Insert cell
mutable legend.append("rect")
.attr('class', 'legend-rect')
.attr("x", width + margin.right-12)
.attr("y", 65)
.attr("width", 12)
.attr("height", 12)
.style("fill", color);
Insert cell
Insert cell
mutable legend.append("text")
.attr('class', 'legend-text')
.attr("x", width + margin.right-22)
.attr("y", 70)
.style('font-size', "12px")
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d === 1 ? 'Male' : 'Female'; });
Insert cell
__manual_legend_n__.container.node()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
state = ({year: 1900, sex: 2})
Insert cell
Insert cell
Insert cell
function isYearAndSex(row, year, sex) {
return row.year === year && row.sex === sex;
}
Insert cell
filteredData = census.filter(row => isYearAndSex(row, state.year, state.sex));
Insert cell
Insert cell
Insert cell
bars = __bars_enter_namespace__.chart.selectAll('.bar').data(filteredData);
Insert cell
Insert cell
mutable enterbars = bars
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', (d) => x(d.age_group))
.attr('y', (d) => y(d.people))
.attr('width', x.bandwidth())
.attr('height', (d) => height - y(d.people))
.attr('fill', (d) => color(d.sex));
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function updateNaive(sex, step) {
// Step 1
state.year += step;
state.sex = sex;
const newData = census.filter(row => isYearAndSex(row, state.year, state.sex));
// Step 2
const bars = __update_naive_n__.chart.selectAll('.bar')
.data(newData);
// Step 3
bars
.transition('update')
.duration(500)
.attr('x', d => x(d.age_group))
.attr('y', d => y(d.people))
.attr('height', d => height - y(d.people))
.attr('fill', d => color(d.sex));
document.getElementById('curr-year-naive').textContent = state.year;
}
Insert cell
Insert cell
viewof timeButtonsNaive = {
const decrement = html`<button id="decrement">&lt;&lt;</button>`;
decrement.onclick = () => {
if (state.year > 1900) {
updateNaive(state.sex, -10);
}
};
const increment = html`<button id="increment">&gt;&gt;</button>`;
increment.onclick = () => {
if (state.year < 2000) {
updateNaive(state.sex, 10);
}
}
const switchSexButton = html`<button>switch sex</button>`;
switchSexButton.onclick = () => updateNaive(state.sex === 2 ? 1 : 2, 0);
const view = html`
<div style="width: 800px; text-align: center; font-family: sans-serif">
${decrement}
<span id="curr-year-naive">
${state.year}
</span>
${increment}
<br/>
${switchSexButton}
</div>
`
return view;
}

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function updateBetter(sex, step) {
// Step 1. Data.
state.sex = sex;
state.year += step;
const newData = census.filter(row => isYearAndSex(row, state.year, state.sex));
// Step 2. Join.
const bars = __update_better_n__.chart.selectAll('.bar')
.data(newData, (d) => {
if (d.year === state.year) {
// the age for the current year should match the age - step for the previous year.
return d.age_group - step;
} else {
return d.age_group;
}
});
// Step 3. Enter.
bars.enter().append('rect')
.attr('class', 'bar')
.attr('x', d => x(d.age_group))
.attr('y', d => y(0))
.attr('width', x.bandwidth())
.attr('height', 0)
.attr('fill', d => color(d.sex))
.transition('enter-transition')
.duration(500)
.attr('y', d => y(d.people))
.attr('height', d => height - y(d.people))
// Step 4. Update.
bars
.transition('update-transition')
.duration(500)
.attr('x', d => x(d.age_group))
.attr('y', d => y(d.people))
.attr('height', d => height - y(d.people))
.attr('fill', d => color(d.sex));
// Step 5. Exit.
bars.exit()
.transition('exit-transition')
.duration(500)
.attr('height', 0)
.attr('y', y(0))
.remove();
// update the year text
document.getElementById('curr-year-better').textContent = state.year;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3Tip = require("d3-tip")
Insert cell
Insert cell
tip = d3Tip()
.attr('class', "d3-tip")
.style("color", "white")
.style("background-color", "black")
.style("padding", "6px")
.style("border-radius", "4px")
.style("font-size", "12px")
.offset([-10, 0])
.html(function(d) { return `<strong>${d3.format(',')(d.people)}</strong> people`; })
Insert cell
Insert cell
Insert cell
__tooltip_n__.container.call(tip);
Insert cell
Insert cell
__tooltip_n__.chart
.selectAll('.bar')
.on('mouseover', function(d) {
// show the tooltip on mouse over
tip.show(d, this);
// when the bar is mouse-overed, we slightly decrease opacity of the bar.
d3.select(this).style('opacity', 0.7);
})
.on('mouseout', function(d) {
// hide the tooltip on mouse out
tip.hide();
d3.select(this).style('opacity', 1)
});
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
__dynamic_legend_n__.legend
.selectAll('.legend-rect')
.style('opacity', d => d === state.sex ? 1 : 0.5);
Insert cell
Insert cell
__dynamic_legend_n__.legend
.selectAll('.legend-text')
.style('opacity', d => d === state.sex ? 1 : 0.5)
.style('font-weight', d => d === state.sex ? 700 : 400);
Insert cell
Insert cell
Insert cell
Insert cell
__dynamic_legend_n__.legend
.on('click', d => updateLegendV2Chart(d, 0))
Insert cell
Insert cell
__dynamic_legend_n__.legend
.style('cursor', 'pointer');
Insert cell
Insert cell
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