Scales and axes
The scale
scale on the Chart sets the range of values it shows, as [min, max].
Every block in every Plot of the chart uses it.
<Chart scale={[0, 100]}>…</Chart>
<Chart scale={[-40, 40]}>…</Chart> // negative values run the other way
<Chart scale={[0, Math.max(...values())]}>…</Chart> // a scale that follows the data
When the scale changes, everything on it moves together.
A Bar that runs past the end of the scale is cut off there.
A value Label stays at the end of the scale, still showing its value, so a number past the end can’t widen the page.
A Dot, a Tick or a Place keeps the place its value gives it, even when that is outside the chart.
To keep those inside, give the slat’s own element overflow: clip in its CSS.
The axis
The Chart draws an axis on its own: grid lines with numbers.
tickspicks the lines: a list ([0, 25, 50, 75, 100]), a rough count (5), orfalsefor none.formatturns a value into its text:(v) => v + "%".
import { Chart, Plot, Bar, Label } from "@bezda/rhp";
export default function Battery() {
return (
<Chart
scale={[0, 100]}
ticks={[0, 25, 50, 75, 100]}
format={(v) => v + "%"}
>
<Plot device={["Phone", "Laptop", "Watch"]} charge={[64, 38, 91]}>
{(d) => (
<div>
<Label edge="start">{d.device}</Label>
<Bar to={d.charge} />
<Label at={d.charge}>{d.charge}%</Label>
</div>
)}
</Plot>
</Chart>
);
}Drawing the scale yourself
For a scale that looks different (dashed lines, numbers in a pill, bands between ticks), put a Scale in the Chart.
A Scale is a Plot of ticks: its slat is drawn once per tick, and gets d.at, the tick’s value.
import { createSignal } from "solid-js";
import {
Chart,
Scale,
Plot,
Bar,
Tick,
Label,
slat,
every,
} from "@bezda/rhp";
// The scale is a slat too: a dashed line and a number per tick.
const Mark = slat(
{
room: { after: 22 },
css: `
.line {
--rhp-tick-width: 1px;
background: none;
border-left: 1px dashed var(--rhp-grid);
}
.number {
top: calc(100% + 6px);
translate: -50% 0;
--rhp-label-gap: 0px;
color: var(--rhp-muted);
font-size: 11px;
}
`,
},
(t) => (
<div>
<Tick at={t.at} thick={1} class="line" />
<Label at={t.at} class="number">
{t.at}
</Label>
</div>
),
);
export default function Rainfall() {
const [max, setMax] = createSignal(60);
return (
<>
<label class="demo-controls">
Scale up to {max()} mm
<input
type="range"
min="40"
max="120"
step="5"
value={max()}
onInput={(e) => setMax(+e.target.value)}
/>
</label>
<Chart scale={[0, max()]}>
<Scale ticks={every(10)}>{Mark}</Scale>
<Plot month={["May", "June", "July"]} rain={[38, 24, 11]}>
{(d) => (
<div>
<Label edge="start">{d.month}</Label>
<Bar to={d.rain} />
</div>
)}
</Plot>
</Chart>
</>
);
}A Scale slat also gets:
d.next, the next tick’s value, to fill the space between two ticks,d.firstandd.last, true at the two ends,d.toEnd, how far the tick is from the end of the scale in pixels, measured in the browser.
A number near the end of the scale can collide with the last one.
CSS alone can hide it, and the same rule works when the chart is drawn on a server.
rhp gives every Label the room it has left, from its own value to the end of the scale, as calc((1 - var(--rhp-p)) * 100%).
Put that in max-width (max-height in a vertical chart) and a number with less than 30px of room is given no size at all:
.num { max-width: calc(((1 - var(--rhp-p)) * 100% - 30px) * 1000); overflow: hidden; }
every(10) gives a tick at each multiple of 10.
every(10, { ends: true }) adds the scale’s two ends as ticks.
A Chart with a Scale in it draws no axis of its own.
A second axis
cross on the Chart adds a second scale, [min, max], that runs across the slats: the y of a scatter plot or a line chart.
In a horizontal chart it runs from bottom to top, and in a vertical chart from left to right.
An overlap Plot’s slats all share the whole plot, so a Dot or a Label with cross sits at its at on the scale and at its cross on the second axis: a scatter plot.
import { Chart, Plot, Dot, Label } from "@bezda/rhp";
// Six cafés: the price on the scale, the rating on a second axis (cross).
export default function Cafes() {
return (
<Chart
scale={[2, 5.5]}
ticks={[2, 3, 4, 5]}
format={(p) => "€" + p}
cross={[3, 5]}
crossTicks={[3, 4, 5]}
crossFormat={(r) => r + "★"}
height={220}
>
<Plot
overlap
cafe={[
"Corner",
"Roastery",
"Kiosk",
"Book & Bean",
"Mill St",
"Harbour",
]}
price={[3.2, 4.5, 2.4, 3.8, 2.9, 4.1]}
rating={[4.1, 4.8, 3.2, 4.4, 3.8, 3.5]}
>
{(d) => (
<div>
<Dot at={d.price} cross={d.rating} size="10px" />
<Label
at={d.price}
cross={d.rating}
style={{ "--rhp-label-gap": "9px" }}
>
{d.cafe}
</Label>
</div>
)}
</Plot>
</Chart>
);
}A Line puts its points’ y on the second axis: a line chart.
fill fills under the line, down to base (0 by default).
import { Chart, Plot, Dot, Line } from "@bezda/rhp";
// Lisbon's average high by month: a Line on the second axis, and a dot each month.
const month = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const high = [15, 16, 19, 20, 23, 26, 28, 29, 27, 23, 18, 15];
export default function Lisbon() {
return (
<Chart
scale={[0.5, 12.5]}
ticks={month}
format={(m) => "JFMAMJJASOND"[m - 1]}
cross={[10, 30]}
crossTicks={[10, 20, 30]}
crossFormat={(t) => t + "°"}
height={200}
>
<Plot overlap points={[month.map((m, i) => [m, high[i]])]}>
{(d) => <Line points={d.points} fill base={10} />}
</Plot>
<Plot overlap month={month} high={high}>
{(d) => <Dot at={d.month} cross={d.high} size="8px" />}
</Plot>
</Chart>
);
}crossTicks and crossFormat set the second axis, like ticks and format for the first.
A horizontal chart with a second axis is 240px tall unless height says otherwise.
cross is optional.
A chart without it draws no second axis, and the CSS for one only reaches the page once some chart on it asks for a second axis.