Published
Edited
Oct 27, 2020
4 forks
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
years = // TODO: compute min and max years
Insert cell
dataInitial = // TODO: filter to initial year
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
x = d3.scaleLinear()
// TODO: create scale for x axis
Insert cell
y = d3.scaleLinear()
// TODO: create scale for y axis
Insert cell
Insert cell
color = d3.scaleOrdinal()
// TODO: create color encoding
Insert cell
Insert cell
size = d3.scaleSqrt()
// TODO: create size encoding
Insert cell
Insert cell
{
// create the container SVG element
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// TODO: position and populate the x-axis

// TODO: position and populate the y-axis

// TODO: add circle elements for each country
// use scales to set fill color, x, y, and radius

// return the SVG DOM element for display
return svg.node();
}
Insert cell
Insert cell
{
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

svg.append('g')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x))
// TODO: Add x-axis title 'text' element.

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
// TODO: Add y-axis title 'text' element.
// TODO: Add a background label for the current year.

const countries = svg
.selectAll('circle')
.data(dataInitial)
.join('circle')
// TODO: sort so smaller circles are drawn last
.attr('opacity', 0.75)
.attr('fill', d => color(d.cluster))
.attr('cx', d => x(d.fertility))
.attr('cy', d => y(d.life_expect))
.attr('r', d => size(d.pop));
// TODO: add a tooltip
// TODO: Add mouse hover interactions, using D3 to update attributes directly.

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof yearFilter = Scrubber(
// TODO: construct scrubber
)
Insert cell
{
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

svg.append('g')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x))
.append('text')
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.attr('x', width - margin.right)
.attr('y', -10)
.text('Fertility');

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
.append('text')
.attr('transform', `translate(20, ${margin.top}) rotate(-90)`)
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.text('Life Expectancy');
const yearLabel = svg.append('text')
.attr('class', 'year')
.attr('x', 40)
.attr('y', height - margin.bottom - 20)
.attr('fill', '#ccc')
.attr('font-family', 'Helvetica Neue, Arial')
.attr('font-weight', 500)
.attr('font-size', 80)
.text(years[0]); // <-- TODO: update to use yearFilter

const countries = svg
.selectAll('circle')
.data(gapminder.filter(d => d.year === years[0])) // <-- TODO: update to use yearFilter
.join('circle')
.sort((a, b) => b.pop - a.pop)
.attr('class', 'country')
.attr('opacity', 0.75)
.attr('fill', d => color(d.cluster))
.attr('cx', d => x(d.fertility))
.attr('cy', d => y(d.life_expect))
.attr('r', d => size(d.pop));
countries
.append('title')
.text(d => d.country);
countries
.on('mouseover', function() {
d3.select(this).attr('stroke', '#333').attr('stroke-width', 2);
})
.on('mouseout', function() {
d3.select(this).attr('stroke', null);
});

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
viewof yearAnimate = Scrubber(
d3.range(years[0], years[1] + 1, 5),
{ autoplay: false, delay: 1000, loop: false }
)
Insert cell
chartAnimate = {
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

svg.append('g')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x))
.append('text')
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.attr('x', width - margin.right)
.attr('y', -10)
.text('Fertility');

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
.append('text')
.attr('transform', `translate(20, ${margin.top}) rotate(-90)`)
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.text('Life Expectancy');
const yearLabel = svg.append('text')
.attr('class', 'year')
.attr('x', 40)
.attr('y', height - margin.bottom - 20)
.attr('fill', '#ccc')
.attr('font-family', 'Helvetica Neue, Arial')
.attr('font-weight', 500)
.attr('font-size', 80)
.text(years[0]); // <-- simply use the minimum year, as updates occur elsewhere

const countries = svg
.selectAll('circle.country')
// TODO: add key function
.join('circle')
.sort((a, b) => b.pop - a.pop)
.attr('class', 'country')
.attr('opacity', 0.75)
.attr('fill', d => color(d.cluster))
.attr('cx', d => x(d.fertility))
.attr('cy', d => y(d.life_expect))
.attr('r', d => size(d.pop));
countries
.append('title')
.text(d => d.country);

countries
.on('mouseover', function() {
d3.select(this).attr('stroke', '#333').attr('stroke-width', 2);
})
.on('mouseout', function() {
d3.select(this).attr('stroke', null);
});

// TODO: update function
}
// TODO: extend SVG node
}
Insert cell
chartAnimate.setYear(yearAnimate),
md`Average Fertility & Life Expectancy by Country in ${yearAnimate}`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
regions = [
{ index: 0, label: 'South Asia' },
{ index: 1, label: 'Europe & Central Asia' },
{ index: 2, label: 'Sub-Saharan Africa' },
{ index: 3, label: 'America' },
{ index: 4, label: 'East Asia & Pacific' },
{ index: 5, label: 'Middle East & North Africa' }
];
Insert cell
Insert cell
function colorLegend(container) {
const titlePadding = 14; // padding between title and entries
const entrySpacing = 16; // spacing between legend entries
const entryRadius = 5; // radius of legend entry marks
const labelOffset = 4; // additional horizontal offset of text labels
const baselineOffset = 4; // text baseline offset, depends on radius and font size

const title = container.append('text')
// TODO: create legend's title

const entries = container.selectAll('g')
// TODO: create and entry for each label

const symbols = entries.append('circle')
// TODO: add color indication for each label

const labels = entries.append('text')
// TODO: add text labels
}
Insert cell
Insert cell
Insert cell
{
const svg = d3.create('svg')
.attr('width', 200)
.attr('height', 110);
const legend = svg.append('g')
.attr('transform', 'translate(0, 10)')
.call(colorLegend); // <-- our legend helper is invoked just like an axis generator

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// container is a d3 selection for the container group (<g>) element
// selmodel is a selection model instance for tracking selected legend entries
function legend(container, selmodel) {
const titlePadding = 14;
const entrySpacing = 16;
const entryRadius = 5;
const labelOffset = 4;
const baselineOffset = 4;
const title = container.append('text')
// TODO: create legend's title
// The "on" method registers event listeners
// We update the selection model in response
const entries = container.selectAll('g')
// TODO: create an entry for each label + handle interaction
const symbols = entries.append('circle')
// TODO: add color indication for each label
const labels = entries.append('text')
// TODO: add text label

// Listen to selection model, update symbol and labels upon changes
selmodel.on('change.legend', () => {
// TODO: add effects from legend's interaction
});
}
Insert cell
Insert cell
Insert cell
Insert cell
chartLegend = {
const selmodel = SelectionModel(); // <-- Instantiate a selection model
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

svg.append('g')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x))
.append('text')
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.attr('x', width - margin.right)
.attr('y', -10)
.text('Fertility');

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
.append('text')
.attr('transform', `translate(20, ${margin.top}) rotate(-90)`)
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.text('Life Expectancy');
const yearLabel = svg.append('text')
.attr('class', 'year')
.attr('x', 40)
.attr('y', height - margin.bottom - 20)
.attr('fill', '#ccc')
.attr('font-family', 'Helvetica Neue, Arial')
.attr('font-weight', 500)
.attr('font-size', 80)
.text(1955);
// Add and position the legend; place in the upper right corner.
svg.append('g')
// TODO: add legend
const countries = svg
// TODO: add data points (circles)
countries
.append('title')
.text(d => d.country);

countries
.on('mouseover', function() {
d3.select(this).attr('stroke', '#333').attr('stroke-width', 2);
})
.on('mouseout', function() {
d3.select(this).attr('stroke', null);
});
function setYear(year) {
yearLabel.text(year);
countries
// TODO: update data points to the set year.
}

// Add a selection model listener that updates country colors
// We name the event 'change.chart' as this is the chart's own internal listener.
// We do not want the name to collide with the listener defined in the legend component.
selmodel.on('change.chart', () => {
// TODO: add effects from legend's interaction
});

return Object.assign(svg.node(), { setYear });
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chartDynamic = {
const selmodel = SelectionModel();
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

svg.append('g')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x))
.append('text')
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.attr('x', width - margin.right)
.attr('y', -10)
.text('Fertility');

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
.append('text')
.attr('transform', `translate(20, ${margin.top}) rotate(-90)`)
.attr('text-anchor', 'end')
.attr('fill', 'black')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.text('Life Expectancy');
const yearLabel = svg.append('text')
.attr('class', 'year')
.attr('x', 40)
.attr('y', height - margin.bottom - 20)
.attr('fill', '#ccc')
.attr('font-family', 'Helvetica Neue, Arial')
.attr('font-weight', 500)
.attr('font-size', 80)
.text(years[0]);
svg.append('g')
.attr('transform', `translate(${width - margin.right - 150}, 10)`)
.call(container => legend(container, selmodel));
let countries = svg
.selectAll('circle.country')
.data(dataDynamic.filter(d => d.year === years[0]), d => d.country)
.join('circle')
.attr('class', 'country')
.sort((a, b) => b.pop - a.pop)
.attr('opacity', 0.75)
.attr('fill', d => color(d.cluster))
.attr('cx', d => x(d.fertility))
.attr('cy', d => y(d.life_expect))
.attr('r', d => size(d.pop));
countries
.append('title')
.text(d => d.country);

countries
.on('mouseover', function() {
d3.select(this).attr('stroke', '#333').attr('stroke-width', 2);
})
.on('mouseout', function() {
d3.select(this).attr('stroke', null);
});
function setYear(year) {
yearLabel.text(year);

countries = countries
.data(dataDynamic.filter(d => d.year === year), d => d.country)
.join(
// Add code to customize how countries enter the scene.
// Idea: fade in from transparent and grow from zero size
// Make sure new elements have their properties properly initialized!
enter => enter.append('circle')
.attr('class', 'country'),
update => update,
// Add code to customize how countries exit the scene.
// Idea: fade out to transparent and shrink to zero size before removal
exit => exit.remove()
);
// Animate enter + update countries to current position and size
// Hint: If you modify opacity above, you probably want to update it here!
countries.transition()
.duration(1000)
.attr('cx', d => x(d.fertility))
.attr('cy', d => y(d.life_expect))
.attr('r', d => size(d.pop));
}

selmodel.on('change.chart', () => {
countries.attr('fill', d => selmodel.has(d.cluster) ? color(d.cluster) : '#ccc');
});

return Object.assign(svg.node(), { setYear });
}
Insert cell
Insert cell
Insert cell
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