Dashboard-level chart palette
A dashboard names the data colours its charts draw with: an ordered, distinct
subset of the `--chart-1…5` ramp, stored on the settings channel that already
carries `theme` and `locked`. No backend or client change — `SettingDef.value`
is free-form and a name this build does not wire up is left alone rather than
refused.
Naming none is the whole ramp, which resolves to the identical token per
series as the `--chart-${(index % 5) + 1}` charts drew with before, so every
existing dashboard is unaffected. `palette.check.ts` asserts that equivalence
rather than trusting it.
Distinct slots, not free assignment with repeats: only slot 1 against slot 5
clears 3:1 within the ramp, so two traces on one slot could not be told apart.
Status colour is deliberately outside the palette — a fault is `--destructive`
because of what it means, not because of where it sits.
The provider is mounted by the editor as well as the view, so picking a
palette repaints the charts beside the panel instead of describing them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZeGnqVsf5VHQqvz4HdUhN
This commit is contained in:
@@ -4,6 +4,7 @@ import { useState } from "react"
|
||||
|
||||
import type { MessageInfo, SettingDef, WidgetDef } from "@/client"
|
||||
import { DEFAULT_RANGE, RANGES } from "@/components/Common/RangePicker"
|
||||
import { CHART_SLOTS, paletteOf } from "@/components/Common/UplotChart"
|
||||
import {
|
||||
PANEL_SECTION,
|
||||
PanelTitle,
|
||||
@@ -1071,6 +1072,8 @@ export function DashboardPanel({
|
||||
const canvas = canvasOf(dashboard)
|
||||
const theme = settingOf(dashboard, "theme")
|
||||
const locked = settingOf(dashboard, "locked")
|
||||
const paletteSetting = settingOf(dashboard, "palette")
|
||||
const palette = paletteOf(paletteSetting.value)
|
||||
|
||||
/** Settings are a map, so one of them changing rewrites the whole of it. */
|
||||
const setSetting = (name: SettingName, setting: SettingDef) =>
|
||||
@@ -1225,6 +1228,57 @@ export function DashboardPanel({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<span className={PANEL_SECTION}>Palette</span>
|
||||
<div
|
||||
className="flex flex-wrap items-center gap-2"
|
||||
data-testid="dashboard-palette"
|
||||
>
|
||||
{CHART_SLOTS.map((slot) => {
|
||||
const picked = palette.includes(slot)
|
||||
return (
|
||||
<button
|
||||
key={slot}
|
||||
type="button"
|
||||
aria-pressed={picked}
|
||||
aria-label={`Colour ${slot}`}
|
||||
// The last one cannot go. An empty palette falls back to
|
||||
// the whole ramp, so the row would then contradict what
|
||||
// the charts beside it are drawing.
|
||||
disabled={picked && palette.length === 1}
|
||||
onClick={() =>
|
||||
setSetting("palette", {
|
||||
...paletteSetting,
|
||||
value: picked
|
||||
? palette.filter((other) => other !== slot)
|
||||
: [...palette, slot],
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"size-11 rounded-full transition-shadow md:size-8",
|
||||
picked &&
|
||||
"ring-2 ring-ring ring-offset-2 ring-offset-card",
|
||||
)}
|
||||
style={{
|
||||
// A token alpha rather than `opacity`, which would fade
|
||||
// the ring with the swatch.
|
||||
background: picked
|
||||
? `var(--chart-${slot})`
|
||||
: `color-mix(in srgb, var(--chart-${slot}) 30%, transparent)`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
The colours this dashboard's charts draw with, taken in the order
|
||||
you pick them — a chart with more lines than colours starts over
|
||||
at the first. Each is offered once: two lines sharing a colour
|
||||
could not be told apart, and neighbouring ones are close enough
|
||||
already.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<span className={PANEL_SECTION}>Lock</span>
|
||||
<div className="flex items-center justify-between gap-2 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user