Published
Edited
May 7, 2021
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
Insert cell
datasets = require('vega-datasets')
Insert cell
gapminder = datasets['gapminder.json']()
Insert cell
Insert cell
md`
* select()
* selectAll()
* join()`
Insert cell
// "Visualize" a subset of the data as an ordered list
// extract the 10 most populous countries in 2005
listData = gapminder
.filter(d => d.year == 2005)
.sort((a, b) => b.pop - a.pop)
.slice(0, 10)
Insert cell
list = {
const fmt = d3.format(',');
const ol = d3.create('ol');

ol.selectAll('li')
.data(listData)
.join('li') // creates as many list items as needed
.text(d => `${d.country} : ${fmt(d.pop)}`) // insert the text into the empty list items

return ol.node();
}
Insert cell
Insert cell
// The longer version
list_long_version = {
const fmt = d3.format(',');
const ol = d3.create('ol');

ol.selectAll('li')
.data(listData)
.join(
enter => enter.append('li'), // append li element for each data
update => update, // do nothing with data that match with existing element
exit => exit.remove() // removie li elemeents whose backing data is gone
)
.text(d => `${d.country} : ${fmt(d.pop)}`) // insert the text into the empty list items

return ol.node();
}
Insert cell
Insert cell
// compute average life expectancy per region ('cluster')
// output values as 'key' (cluster) and 'value' (life_expect)
barData = aq.from(gapminder)
.filter(d => d.year === 2005)
.groupby('cluster')
.rollup({ mean: d => op.floor(op.mean(d.life_expect)) })
.orderby('cluster')
.select({ cluster: 'key', mean: 'value' })
.objects()
Insert cell
data = [59, 78, 49, 73, 76, 71]
Insert cell
{
const container = d3.create('div');
const barChart = container.selectAll('div')
barChart
.data(barData)
.join("div")
.style('background', 'steelblue')
.style('border', '1px solid white')
.style('font-size', 'small')
.style('color', 'white')
.style('text-align', 'right')
.style('padding', '3px')
.style("width", d => `${d.value}px`)
.text(d => d.value)
return container.node();
}
Insert cell
md`## Scale Functions`
Insert cell
xScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, width])
Insert cell
{
const container = d3.create('div');
const barChart = container.selectAll('div')
barChart
.data(barData)
.join("div")
.style('background', 'steelblue')
.style('border', '1px solid white')
.style('font-size', 'small')
.style('color', 'white')
.style('text-align', 'right')
.style('padding', '3px')
.style("width", d => `${xScale(d.value)}px`) // Use x scale
.text(d => d.value)
return container.node();
}
Insert cell
color = d3.scaleOrdinal(d3.schemeTableau10)
.domain(data)
Insert cell
{
const container = d3.create('div');
const barChart = container.selectAll('div')
barChart
.data(barData)
.join("div")
.style('background', d => color(d.key)) // Use colored background
.style('border', '1px solid white')
.style('font-size', 'small')
.style('color', 'white')
.style('text-align', 'right')
.style('padding', '3px')
.style("width", d => `${xScale(d.value)}px`)
.text(d => d.value)
return container.node();
}
Insert cell
Insert cell
{
const barHeight = 25;
const height = data.length * barHeight; // height of the svg (which is hard-coded but can be scaled)

const container = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Create the rect for each data
container.selectAll('rect')
.data(barData)
.join("rect")
.attr("x", 0)
.attr("y", (d, i) => i * barHeight)
.attr("height", barHeight)
.attr("width", d => xScale(d.value))
.attr("fill", d => color(d.key))
.attr("stroke", "white");

// Create the text labels
container.selectAll("text")
.data(barData)
.join("text")
.attr("x", d => xScale(d.value))
.attr("y", (d, i) => i * barHeight)
.attr("dx", -20)
.attr("dy", "1.25em")
.attr("fill", "white")
.style("font-size","small")
.text(d => d.value)

// return SVG DOM element
return container.node()
}
Insert cell
height = 150
Insert cell
yScale = d3.scaleBand()
.domain(barData.map(d => d.key))
.range([0, height])
Insert cell
Insert cell
// horizontal bar charts
{
const container = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Create the rect for each data
container.selectAll('rect')
.data(barData)
.join("rect")
.attr("x", 0)
.attr("y", d => yScale(d.key))
.attr("height", yScale.bandwidth())
.attr("width", d => xScale(d.value))
.attr("fill", d => color(d.key))
.attr("stroke", "white");

// Create the text labels
container.selectAll("text")
.data(barData)
.join("text")
.attr("x", d => xScale(d.value))
.attr("y", d => yScale(d.key))
.attr("dx", -20)
.attr("dy", "1.25em")
.attr("fill", "white")
.style("font-size","small")
.text(d => d.value)

// return SVG DOM element
return container.node()
}
Insert cell
Insert cell
// vertical bar charts
{
const width = 400;
const height = 200;

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

// x Scale
const xScale2 = d3.scaleBand()
.domain(d3.range(barData.length))
.range([0, width])

const yScale2 = d3.scaleLinear()
.domain([0, d3.max(barData, d => d.value)])
.range([height, 0]) // changed this!

container.selectAll("rect")
.data(barData)
.join("rect")
.attr("x", d => xScale2(d.key))
.attr("y", d => yScale2(d.value))
.attr("height", d => yScale2(0) - yScale2(d.value)) // changed this!
.attr("width", xScale2.bandwidth())
.attr("fill", d => color(d.key))
.attr("stroke", "white")

container.selectAll("text")
.data(barData)
.join("text")
.attr("x", d => xScale2(d.key))
.attr("y", d => yScale2(d.value))
.attr("dx", 30)
.attr("dy", 15)
.attr("fill", "white")
.style("font-size","small")
.style("text-anchor", "middle")
.text(d => d.value)

return container.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
// Created this dataset to try out the difference between join() and enter() in the example below
barData2 = [
{key: 0, value: 98},
{key: 1, value: 50},
{key: 2, value: 46},
{key: 3, value: 76},
{key: 4, value: 90},
{key: 5, value: 84},
]
Insert cell
{
const height = 200
const width = 500

const xScale3 = d3.scaleLinear()
.domain([0, d3.max(barData2, d => d.value)])
.range([0, width])

const yScale3 = d3.scaleBand()
.domain(barData2.map(d => d.key))
.range([0, height])
const container = d3.create("svg")
.attr("width", width)
.attr("height", height)

const g = container.selectAll("g")
.data(barData2)
.enter() // use enter() but not join() so that we can later append rect and text only for new data
.append("g")
.attr("transform", d => `translate(0, ${yScale3(d.key)})`);

g.append("rect")
.attr("width", d => xScale3(d.value))
.attr("height", d => yScale3.bandwidth())
.style("fill", d => color(d.key))
.style("stroke", "white")

g.append("text")
.attr("x", d => xScale3(d.value))
.attr("dx", -20)
.attr("dy", '1.5em')
.style("fill", "white")
.style("font-size", "small")
.text(d => d.value)
return container.node()
}
Insert cell
Insert cell
Insert cell
{
const height = 200
const width = 500
const margin = ({top: 10, right: 10, bottom: 20, left: 20})

// the old scales
const xScale3 = d3.scaleLinear()
.domain([0, d3.max(barData, d => d.value)])
.range([0, width])

const yScale3 = d3.scaleBand()
.domain(barData.map(d => d.key))
.range([0, height])

// Copy the scales and change the range
const xScaleWithMargin = xScale3.copy()
.range([margin.left, width - margin.right])

const yScaleWithMargin = yScale3.copy()
.range([margin.top, height - margin.bottom])

// Create the container
const container = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style('border', '1px dotted #999'); // dotted borders to visualize the margin

const g = container.selectAll("g")
.data(barData)
.enter()
.append("g")
.attr("transform", d => `translate(${margin.left}, ${yScaleWithMargin(d.key)})`); // changed! the lower value shoul be the margin.left

g.append("rect")
.attr("width", d => xScaleWithMargin(d.value) - xScaleWithMargin(0)) // changed! need to minus the margin
.attr("height", d => yScaleWithMargin.bandwidth())
.style("fill", d => color(d.key))
.style("stroke", "white")

g.append("text")
.attr("x", d => xScaleWithMargin(d.value) - xScaleWithMargin(0)) // changed! need to minus the margin
.attr("dx", -20)
.attr("dy", '1.5em')
.style("fill", "white")
.style("font-size", "small")
.text(d => d.value)

// Draw the axes
container.append("g")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(xScaleWithMargin))

container.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(yScaleWithMargin))
return container.node()
}
Insert cell
md`## Other imports`
Insert cell
import {aq, op} from '@uwdata/arquero'
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