|
import { useRef, useEffect } from 'react' |
|
import * as Plot from '@observablehq/plot' |
|
|
|
const SpeakerPlot = ({ data }) => { |
|
const containerRef = useRef() |
|
const allSpeakers = data.language_table.reduce( |
|
(sum, curr) => sum + curr.speakers, |
|
0 |
|
) |
|
const languages = data.language_table |
|
.sort((a, b) => b.speakers - a.speakers) |
|
.slice(0, 100) |
|
.reduce((acc, d) => { |
|
acc.push({ |
|
...d, |
|
rank: acc.length + 1, |
|
cumSpeakers: |
|
acc.reduce((sum, curr) => sum + curr.speakers, 0) + d.speakers, |
|
cumSpeakersPercent: |
|
(acc.reduce((sum, curr) => sum + curr.speakers, 0) + d.speakers) / |
|
allSpeakers |
|
}) |
|
return acc |
|
}, []) |
|
|
|
useEffect(() => { |
|
const plot = Plot.plot({ |
|
width: 750, |
|
height: 500, |
|
subtitle: 'Number of languages vs speakers covered', |
|
x: { |
|
label: 'Languages', |
|
ticks: [] |
|
}, |
|
y: { |
|
label: 'Number of Speakers (millions)' |
|
}, |
|
color: { |
|
legend: true, |
|
domain: ['Speakers', 'Cumulative Speakers'], |
|
range: ['green', 'lightgrey'] |
|
}, |
|
marks: [ |
|
Plot.barY(languages, { |
|
x: 'rank', |
|
y: d => d.cumSpeakers / 1e6, |
|
fill: d => 'Cumulative Speakers', |
|
sort: { x: 'y' }, |
|
title: d => |
|
`The ${ |
|
d.rank |
|
} most spoken languages cover\n${d.cumSpeakersPercent.toLocaleString( |
|
'en-US', |
|
{ style: 'percent' } |
|
)} of all speakers`, |
|
tip: true |
|
}), |
|
Plot.barY(languages, { |
|
x: 'rank', |
|
y: d => d.speakers / 1e6, |
|
title: d => |
|
`${d.language_name}\n(${d.speakers.toLocaleString('en-US', { |
|
notation: 'compact', |
|
compactDisplay: 'long' |
|
})} speakers)`, |
|
tip: true, |
|
fill: d => 'Speakers', |
|
sort: { x: '-y' } |
|
}), |
|
Plot.crosshairX(languages, { |
|
x: 'rank', |
|
y: d => d.cumSpeakers / 1e6, |
|
textStrokeOpacity: 0, |
|
textFillOpacity: 0 |
|
}), |
|
Plot.tip(['The 41 most spoken languages cover 80% of all speakers.'], { |
|
x: 41, |
|
y: languages[40].cumSpeakers / 1e6 |
|
}) |
|
] |
|
}) |
|
containerRef.current.append(plot) |
|
return () => plot.remove() |
|
}, []) |
|
|
|
return ( |
|
<div |
|
ref={containerRef} |
|
style={{ |
|
width: '100%', |
|
height: '100%', |
|
display: 'flex', |
|
alignItems: 'center', |
|
justifyContent: 'center' |
|
}} |
|
/> |
|
) |
|
} |
|
|
|
export default SpeakerPlot |
|
|