Published
Edited
Oct 13, 2019
Insert cell
md`# Line chart with Tooltip: weekly earnings
Data: [bls.gov 2019 population survey](https://www.bls.gov/news.release/wkyeng.t01.htm)`
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height))
.style('-webkit-tap-hightlight-color', 'transparent')
.style('overflow', 'visible')
svg.append('g')
.call(xAxis)
svg.append('g')
.call(yAxis)
svg.append('path')
.datum(dataYearValue)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('stroke-width', 1.5)
.attr('stroke-linejoin', 'round')
.attr('stroke-linecap', 'round')
.attr('d', line)
const tooltip = svg.append('g')
svg.on('touchmove mousemove', function() {
const {date, value, quarter} = bisect(d3.mouse(this)[0])
tooltip
.attr('transform', `translate(${x(date)}, ${y(value)})`)
.call(callout, `${value.toLocaleString(undefined, {style: 'currency', currency: 'USD'})}
Q${quarter} ${date.toLocaleString(undefined, {year: 'numeric'})}`)
})
svg.on('touchend mouseleave', () => tooltip.call(callout, null))
return svg.node()
}
Insert cell
height = 500
Insert cell
margin = ({
top: 20,
right: 30,
bottom: 30,
left: 40
})
Insert cell
xAxis = g => g
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
Insert cell
yAxis = g => g
.attr('transform', `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y))
.call(g => g.select('.domain').remove())
.call(g => g.select('.tick:last-of-type text').clone()
.attr('x', 3)
.attr('text-anchor', 'start')
.attr('font-weight', 'bold')
.text(dataYearValue.y)
)
Insert cell
x = d3.scaleUtc()
.domain(d3.extent(dataYearValue, d => d.date))
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear()
.domain([600, d3.max(dataYearValue, d => d.value)]).nice()
.range([height - margin.bottom, margin.top])
Insert cell
line = d3.line()
.curve(d3.curveStep)
.defined(d => !isNaN(d.value))
.x(d => x(d.date))
.y(d => y(d.value))
Insert cell
md`# Dependencies`

Insert cell
callout = (g, value) => {
if (!value) return g.style('display', 'none')
g
.style('display', null)
.style('pointer-events', 'none')
.style('font', '10px sans-serif')
const path = g.selectAll('path')
.data([null])
.join('path')
.attr('fill', 'white')
.attr('stroke', 'black')
const text = g.selectAll('text')
.data([null])
.join('text')
.call(text => text
.selectAll('tspan')
.data((value + '').split(/\n/))
.join('tspan')
.attr('x', 0)
.attr('y', (d, i) => `${i * 1.1}em`)
.style('font-weight', (_, i) => i ? null : 'bold')
.text(d => d)

)
const {x, y, width: w, height: h} = text.node().getBBox()
text.attr('transform', `translate(${-w / 2},${15 - y})`)
path.attr('d', `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`)
}
Insert cell
bisect = {
const bisect = d3.bisector(d => d.date).left
return mx => {
const date = x.invert(mx)
const index = bisect(dataYearValue, date, 1)
const a = dataYearValue[index - 1]
const b = dataYearValue[index]
return date - a.date > b.date - date ? b : a
}
}
Insert cell
data = (await d3.csv('https://raw.githubusercontent.com/adg29/datasets/master/bls.gov/weekly-earnings/2019-median-usual-total-population.csv', d3.autoType))
Insert cell
dataTransformed = data.reduce((quarterly, d) => {
let yearlyQuarters = [1,2,3,4].map(quarter => {
let date = getQuarterRange(d.Year, quarter)
return {
date: new Date(date.end.format('Y-MM-DD')),
value: d[`Qtr${quarter}`],
quarter
}
}).filter(d => d.value)
return quarterly.concat(yearlyQuarters)
}, [])
Insert cell
dataYearValue = Object.assign(dataTransformed, {y: '$ Weekly Earnings'})
Insert cell
function getQuarterRange(year, quarter) {

const start = moment().year(year).quarter(quarter).startOf('quarter');

const end = moment().year(year).quarter(quarter).endOf('quarter');

return {start, end};
}

Insert cell
d3 = require('https://d3js.org/d3.v5.js')
Insert cell
moment = require('moment')
Insert cell
md`# Sources:
https://www.bls.gov/news.release/wkyeng.t01.htm
https://observablehq.com/@d3/line-chart-with-tooltip
https://stackoverflow.com/a/40412192/367495
`
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