5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 1// Series color assignment and the ramps used by the field views.
2// Series slots follow the solver identity in fixed order (registry solvers
3// first, then any loaded solver ids alphabetically); a filter or rerun
4// never repaints a surviving series.
6import { SOLVERS } from "../solvers";
8const SERIES_VARS = [
9 "--series-1",
10 "--series-2",
11 "--series-3",
12 "--series-4",
13 "--series-5",
14];
16export function solverColorVar(solverId: string, extraIds: string[]): string {
17 const known = SOLVERS.map((s) => s.id);
18 const extras = [...new Set(extraIds.filter((id) => !known.includes(id)))].sort();
19 const order = [...known, ...extras];
20 const idx = order.indexOf(solverId);
21 return `var(${SERIES_VARS[Math.max(0, idx) % SERIES_VARS.length]})`;
22}
24// Ramps for canvas rendering (canvas cannot read CSS variables per pixel).
25// Values are the reference palette's sequential blue steps and diverging
26// blue/red pair, in light- and dark-mode steppings.
28const SEQ_LIGHT = ["#cde2fb", "#9ec5f4", "#6da7ec", "#3987e5", "#256abf", "#184f95", "#0d366b"];
29const SEQ_DARK = ["#0d366b", "#184f95", "#256abf", "#3987e5", "#6da7ec", "#9ec5f4", "#cde2fb"];
31const DIV_LIGHT = { neg: "#2a78d6", mid: "#f0efec", pos: "#e34948" };
32const DIV_DARK = { neg: "#3987e5", mid: "#383835", pos: "#e66767" };
34function hexToRgb(hex: string): [number, number, number] {
35 return [
36 parseInt(hex.slice(1, 3), 16),
37 parseInt(hex.slice(3, 5), 16),
38 parseInt(hex.slice(5, 7), 16),
39 ];
40}
42function lerp(a: number, b: number, t: number): number {
43 return a + (b - a) * t;
44}
46function interpStops(stops: string[], t: number): [number, number, number] {
47 const x = Math.min(1, Math.max(0, t)) * (stops.length - 1);
48 const i = Math.min(stops.length - 2, Math.floor(x));
49 const f = x - i;
50 const c0 = hexToRgb(stops[i]);
51 const c1 = hexToRgb(stops[i + 1]);
52 return [lerp(c0[0], c1[0], f), lerp(c0[1], c1[1], f), lerp(c0[2], c1[2], f)];
53}
55export function isDarkMode(): boolean {
56 return window.matchMedia("(prefers-color-scheme: dark)").matches;
57}
59/** Sequential ramp (magnitude), t in [0, 1], light means small. */
60export function sequentialColor(t: number, dark: boolean): [number, number, number] {
61 return interpStops(dark ? SEQ_DARK : SEQ_LIGHT, t);
62}
64/** Diverging ramp (polarity), t in [-1, 1], gray at 0. */
65export function divergingColor(t: number, dark: boolean): [number, number, number] {
66 const d = dark ? DIV_DARK : DIV_LIGHT;
67 const tt = Math.min(1, Math.max(-1, t));
68 if (tt < 0) return interpStops([d.neg, d.mid], 1 + tt);
69 return interpStops([d.mid, d.pos], tt);
70}
72export function cssColor(rgb: [number, number, number]): string {
73 return `rgb(${Math.round(rgb[0])}, ${Math.round(rgb[1])}, ${Math.round(rgb[2])})`;
74}