Multiple
23 of 23Four in ten use AI to write
Two plots in one chart that work together. Pick a generation in the bars and the pie changes to show how often its people use AI to write, a breakdown the bars alone don't give.
- two Plots in one Chart
- overlap
- shares and stackUp
- a slat you can pick
- keyboard
The simple version
A pie Plot and a bar Plot in one Chart, where a click on a bar changes the pie. The same structure with almost no styling: a good place to start your own.
How it’s built
Two Plots share one Chart, and picking in one changes what the other shows.
One Chart, two Plots. A Chart draws its Plots over each other in one box, so each decides which part of it to use:
<Chart scale={[0, 100]} ticks={false} style={{ "--pitch": "10.5cqw", "--bars": "calc(var(--pitch) * 4)" }}>
<Plot overlap …>{PartSlat}</Plot>
<Plot keyboard style={{ "justify-self": "end" }} …>{GenSlat}</Plot>
</Chart>
rhp sets a Plot’s own width and height, so the tubes are sized by their slats instead.
thickness is the room one slat gets, and in cqw it is a share of the chart’s width, so the tubes and the pie keep their proportions at any size.
justify-self: end puts the tubes at the end of the box, and the pie’s CSS draws in the square that’s left.
The pie says more than the tubes. A tube is how many of a generation use AI to write. The pie splits those by how often, and keeps the rest:
const split = (using, daily) => [daily, using - daily, 100 - using];
With nothing picked, the pie is everyone: each generation’s split, weighed by how many people are in it. So the two views are the same numbers at two depths, rather than two charts that happen to sit together.
Picking.
rhp keeps no selection, so a signal holds the picked generation, and both Plots read it.
A Plot’s props are its data, so a click handler can’t be passed in as one.
Instead the slat marks itself with data-gen, and the poster listens:
const pick = (e) => {
const gen = e.target.closest("[data-gen]");
setPicked((was) => (gen && was !== +gen.dataset.gen ? +gen.dataset.gen : -1));
};
A tube’s slat runs the chart’s whole height, so it lets the pointer through, and only the tube and its share and name take a click.
A click on another tube picks it, and a click on the same one or anywhere else goes back to everyone.
keyboard on the Plot adds Tab and the arrow keys, Enter or Space picks, and Escape lets go.
The pie is built like the gallery’s pie.
Each edge is pushed aside so a gap is one width all the way in, and a stroke in the wedge’s own color rounds its corners.
A path can’t be transitioned without flattening its rim, so animated moves the two ends of each span and the wedge is drawn again every frame.
A wedge too narrow for its edges to reach the middle ends where they cross, so its gap isn’t squeezed.
Each wedge’s svg casts a short shadow evenly all round; one cast downward would make the gaps that run across look thinner than the ones that run down.
A label that doesn’t fit goes to a corner. The pie is .86 of a square, which leaves the square’s corners free. A part under a sixth of the pie sends its label to a corner, and the corners are shared out so the labels end up as near their wedges as they can all be. A dotted line then runs straight across or straight down to the nearest part of the wedge it can reach that way, and if there is none, out to the square’s edge and along it, so it never crosses the pie. The label measures itself, so the line stops at its edge, and the line fades in once the label has arrived. The part slats aren’t size containers, because a container is a layer of its own, and the last wedge would cover the labels of the parts before it as they move; without one, every label sits over every wedge.
Each tube has a track of its own.
The Chart’s height is the pie’s, which is too tall for a tube, so the slat’s CSS gives the fill and the outline a shorter track in place of rhp’s 100%:
.slat:vertical :is(.fill, .tube) {
bottom: calc(var(--low) + var(--rhp-lo) * var(--tall));
height: calc((var(--rhp-hi) - var(--rhp-lo)) * var(--tall));
}
It runs from just over the share and name to just under the paragraph, whose height is measured and given to the Chart as --side.
The tubes are drawn only once the paragraph is measured, so they start at their height instead of shrinking into it.
Each tube has its own color, checked to stay apart from its neighbours for colorblind readers too, and named under it.
In the JS version rhp moves every number a Plot is given, so each Plot names the ones rhp draws:
<Plot overlap animate={p.js && ["from", "to"]} …>
<Plot keyboard animate={p.js && ["value"]} …>
The pie and its labels move on their own clock, and picked has to jump to the new tube rather than pass through the others.
On a narrow poster a container query puts the tubes under the pie, across the whole width. Flat, the pie is one stacked bar at the top and the tubes lie down under it.