Draw charts from one categorical palette, --chart-1…8

The one-hue ramp told lines apart by lightness alone. --chart-1 is now
--primary and slots 2-8 are distinct hues (orange, violet, green, orchid,
ochre, teal, magenta) that clear 3:1 on --card and stay apart under
protanopia and deuteranopia; red and sage are left out as the status
colours. A chart takes the slots in order instead of spreading a ramp,
and a dashboard's own palette continues after its primary and listed
colours with slots 2-8 rather than tints of its scheme. Line charts stay
at five series. Tokens mirror index's, as design-check requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
This commit is contained in:
2026-10-06 07:05:09 +02:00
co-authored by Claude Opus 5.5
parent d7ca933478
commit 4f4de1fd9d
12 changed files with 151 additions and 149 deletions
+13 -36
View File
@@ -15,52 +15,31 @@ import { si } from "@/lib/utils"
/**
* How many lines one chart carries.
*
* The bound is the palette's: `--chart-1…5` is one designed ramp, and a sixth
* line would either repeat a step or invent a colour outside the system.
* Past five a legend is a puzzle, and five is as far as the palette keeps any
* two lines apart for a colour-blind reader too, not only neighbouring ones.
*/
export const MAX_SERIES = 5
/**
* The ramp's slots, as a palette names them.
* The palette's slots, as a palette names them: `--chart-1…8`.
*
* The whole set one may draw from — five, for the same reason `MAX_SERIES` is
* five, because they are the same five.
* One categorical palette, taken in order. The first slot is the brand
* primary and the rest are hues picked to stand apart from it and from each
* other, so a line is told by its hue rather than by a step of lightness.
*/
export const CHART_SLOTS = Array.from({ length: MAX_SERIES }, (_, index) =>
export const CHART_SLOTS = Array.from({ length: 8 }, (_, index) =>
String(index + 1),
)
/** No palette named; a chart left with this spreads itself. */
/** No palette named; a chart left with this takes the slots in order. */
export const NO_PALETTE: string[] = []
/**
* What a chart of *n* lines draws with when no palette was named: the ramp
* spread, rather than its first *n* steps.
*
* Three lines used to take slots 1, 2 and 3, which are adjacent steps of a
* ramp that carries identity by lightness alone — at a 2px stroke they are
* close to one picture. The ends and the middle are as far apart as five slots
* allow, and the same reasoning gives every other count.
*
* This moved after the palette shipped, and moving it was safe *because* of
* how the palette is stored: a named palette is honoured exactly as written,
* so no stored document means anything different than it did. Only the
* unwritten default moved, and any dashboard that dislikes where it moved to
* can now name the old one. That inertness is the property worth keeping —
* not this table, which is a design decision and may move again.
* The slots a chart draws with: a named palette exactly as written, so no
* stored document changes meaning, and otherwise the shared one in order.
*/
const SPREADS: Record<number, string[]> = {
1: ["1"],
2: ["1", "5"],
3: ["1", "3", "5"],
4: ["1", "2", "4", "5"],
5: ["1", "2", "3", "4", "5"],
}
/** The slots a chart of `count` lines draws with, named palette or not. */
export function slotsFor(count: number, palette?: string[]): string[] {
if (palette?.length) return palette
return SPREADS[Math.min(Math.max(Math.trunc(count) || 1, 1), MAX_SERIES)]
export function slotsFor(palette?: string[]): string[] {
return palette?.length ? palette : CHART_SLOTS
}
/**
@@ -334,9 +313,7 @@ export function UplotChart({
let told: number | null = null
/** Whether the click about to arrive is the end of a drag. */
let dragging = false
// Resolved once for the whole chart: how many lines there are is part of
// which slots they take, when nothing named them.
const slots = slotsFor(labels.length, palette)
const slots = slotsFor(palette)
// One builder for the whole chart: it is a factory, and the series only
// need the function it returns.
const spline = smooth ? { paths: uPlot.paths.spline?.() } : {}