stateChart = (
data,
chartType = 'deaths',
chartWidth = width / 3,
chartHeight = width * 0.618 / 3,
) => {
const dotWidth = chartWidth * 0.03
const lineWidth = chartWidth * 0.03
const margin = { left: 0, top: dotWidth * 2, bottom: dotWidth * 1.5, right: 0 }
const bottomAxisOffset = 10
const innerWidth = chartWidth - margin.left - margin.right
const innerHeight = chartHeight - margin.top - margin.bottom
const dataAsPairs = Object.entries(data[chartType]).filter((([key, value]) => key !== '_meta'))
const minRatio = d3.quantile(
dataAsPairs,
0.5,
([key, details]) => details.percent > 0 ? details.percent : null
)
const perN = reasonableDenominator(minRatio)
const perNFormat = n => {
let returnValue = d3.format(',d')(n * perN)
if (returnValue === '0' && n > 0) returnValue = d3.format('.1r')(n * perN)
return returnValue
}
const yMax = Math.max(
d3.max(dataAsPairs, ([eth, d]) => d.percentTopErr),
1 / 100000
)
const yScale = d3.scaleLinear()
.domain([yMax, 0])
.range([0, innerHeight])
.nice()
const xScale = d3.scalePoint()
.domain(dataAsPairs.map(([eth, d]) => eth))
.range([0, innerWidth])
.padding(1)
// Chart
const chart = d3.select(DOM.svg(chartWidth, chartHeight))
// Axes and Borders
const yAxis = d3.axisLeft(yScale)
.ticks(5)
.tickFormat(perNFormat)
.tickSize(-innerWidth)
chart.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.call(yAxis)
.call(g => g.select('.domain').remove())
.call(g => g.selectAll(".tick line").attr('color', '#E5E5E5'))
.call(
// move ticks above the grid lines
g => g.selectAll(".tick text")
.attr('x', 3)
.attr('text-anchor', 'start')
.attr('dy', -5)
.attr('fill', '#666')
)
// Borders
chart.append('rect')
.attr('x', 0.5)
.attr('y', 0.5)
.attr('width', chartWidth - 1)
.attr('height', chartHeight - 1)
.attr('stroke', '#E5E5E5')
.attr('fill', 'none')
// Inner Chart
const innerChart = chart.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const dataSelection = innerChart.selectAll()
.data(dataAsPairs)
.enter()
// Margin of Error
dataSelection.append('line')
.attr('x1', ([eth, d]) => xScale(eth))
.attr('x2', ([eth, d]) => xScale(eth))
.attr('y1', ([eth, d]) => yScale(d.percentTopErr))
.attr('y2', ([eth, d]) => yScale(d.percentBottomErr))
.attr('stroke', ([eth, d]) => RACES[eth].color)
.attr('stroke-width', lineWidth)
.attr('stroke-linecap', 'round')
.attr('opacity', 0.15)
// Data Point
dataSelection.append('circle')
.attr('cx', ([eth, d]) => xScale(eth))
.attr('cy', ([eth, d]) => yScale(d.percent))
.attr('r', dotWidth)
.attr('fill', ([eth, d]) => RACES[eth].color)
.attr('stroke', '#FFF')
.attr('stroke-width', 1.5)
.append('title').text(([eth, d]) => RACES[eth].label)
// Data Point Number Label
dataSelection.append('text')
.attr('x', ([eth, d]) => xScale(eth) + dotWidth * 1.5)
.attr('y', ([eth, d]) => yScale(d.percent) + dotWidth * 0.5)
.attr('font-size', 12)
.attr('font-family', 'sans-serif')
.attr('fill', '#777')
.attr('cursor', 'default')
.text(([eth, d]) => perNFormat(d.percent))
// Data Point Label
dataSelection.append('text')
.attr('x', ([eth, d]) => xScale(eth))
.attr('y', ([eth, d]) => yScale(d.percent) + dotWidth * 0.4)
.attr('text-anchor', 'middle')
.attr('text-align', 'center')
.attr('font-size', dotWidth)
.attr('font-family', 'sans-serif')
.attr('fill', '#FFF')
.attr('cursor', 'default')
.text(([eth, d]) => RACES[eth].abbr)
.append('title').text(([eth, d]) => RACES[eth].label)
const unknownPct = data[chartType]._meta.unknownRace / data[chartType]._meta.total
const unknownColor = R.cond([
[R.gt(0.2), R.always('#777')],
[R.gt(0.5), R.always('#980')],
[R.T, R.always('#800')],
])(unknownPct)
return html`
<div class="state-chart">
<div class="state-chart__details">
<div class="state-chart__title">${US_STATES[data.state]}</div>
<div class="state-chart__notes">
${capitalize(chartType)} per ${d3.format(',d')(perN)} •
<strong>Unknown Race:</strong>
<span style="color: ${unknownColor}">
${d3.format('.0%')(unknownPct)}
</span>
<br />
<strong>Pop:</strong> ${d3.format(',d')(data.totalPopulation)} •
<strong>${capitalize(chartType)}:</strong> ${d3.format(',d')(data[chartType]._meta.total)}
</div>
</div>
<div class="state-chart__chart">
${chart.node()}
${stateTable(data, chartType)}
</div>
</div>`
}