Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 75e9dae007999d929ef08583252b1a3e24703db9
parent e4bc59181ffa629a0c7c6d4fb36383af208a3398
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 12:30:49 -0400

charts: ChartView takes its colours from seriesColor(i), so the sixth series reaches --chart-6

ChartView cycled its own `var(--chart-${(i % 5) + 1})` for series and pie
slices, so the sixth series repeated --chart-1 and the slot added in release
11 was never used outside the hub chart. Series 1–5 are unchanged; the sixth
is --chart-6; past six, the hub chart's golden-angle hues rather than a
repeat. ChartView.test.ts renders it and reads the ChartContainer's <style>
declarations (fails on the old code: s5 = var(--chart-1)).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Acommon/components/charts/ChartView.test.ts | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/components/charts/ChartView.tsx | 12+++++++++---
2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/common/components/charts/ChartView.test.ts b/common/components/charts/ChartView.test.ts @@ -0,0 +1,73 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import * as React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { ChartView } from "./ChartView"; +import { newChart, type ChartConfig } from "../../lib/chartConfig"; +import type { ChartData } from "../../lib/chartAggregate"; +import { seriesColor } from "../../lib/homepageChart"; + +// ChartView's colours (release 13, slice W2): a series' colour — and a pie +// slice's — is seriesColor(i), so the sixth reaches --chart-6 instead of +// wrapping back to --chart-1 (the old `i % 5`). What a server render can see +// is the ChartContainer's <style>: one `--color-<id>: <colour>;` per series, +// which every Line/Area/Bar then paints with. The marks themselves need a +// measured container, which a static render does not have. + +// common's tsconfig has `jsx: "preserve"` (Next compiles it), so tsx falls +// back to the classic transform — `React.createElement` on a free `React`. +(globalThis as { React?: typeof React }).React = React; + +function data(n: number): ChartData { + const categories = ["2026-01", "2026-02"]; + return { + categories, + series: Array.from({ length: n }, (_, i) => ({ + key: `channel ${i + 1}`, + points: categories.map((x, j) => ({ x, y: i + j + 1 })), + })), + }; +} + +function render(d: ChartData, config: ChartConfig): string { + return renderToStaticMarkup(React.createElement(ChartView, { data: d, config })); +} + +// The declarations of the light-theme block (ChartStyle writes the same list +// once per theme), as [id, colour] pairs. +function colourDecls(html: string): [string, string][] { + const block = html.match(/\[data-chart=[^\]]+\] \{([^}]*)\}/); + assert.ok(block, "the chart's <style> block is rendered"); + return [...block[1].matchAll(/--color-([\w-]+): ([^;]+);/g)].map((m) => [m[1], m[2]]); +} + +test("a grouped chart's sixth series is --chart-6, and the first five are unchanged", () => { + for (const type of ["line", "area", "bar", "stackedBar"] as const) { + const html = render(data(7), newChart({ type, groupBy: "channel" })); + assert.deepEqual(colourDecls(html), [ + ["s0", "var(--chart-1)"], + ["s1", "var(--chart-2)"], + ["s2", "var(--chart-3)"], + ["s3", "var(--chart-4)"], + ["s4", "var(--chart-5)"], + ["s5", "var(--chart-6)"], + // Past the six validated slots: the hub chart's golden-angle hue, not + // a repeat of --chart-2. + ["s6", seriesColor(6)], + ], type); + assert.notEqual(seriesColor(6), "var(--chart-2)"); + } +}); + +test("a pie's sixth slice is --chart-6", () => { + const d: ChartData = { + categories: ["a", "b", "c", "d", "e", "f", "g"], + series: [{ key: "all", points: "abcdefg".split("").map((x, i) => ({ x, y: i + 1 })) }], + }; + const html = render(d, newChart({ type: "pie" })); + assert.deepEqual( + colourDecls(html).map(([id, c]) => `${id}=${c}`), + Array.from({ length: 7 }, (_, i) => `p${i}=${seriesColor(i)}`), + ); + assert.ok(html.includes("--color-p5: var(--chart-6);")); +}); diff --git a/common/components/charts/ChartView.tsx b/common/components/charts/ChartView.tsx @@ -26,6 +26,7 @@ import { import { useMediaQuery } from "../../lib/useMediaQuery"; import type { ChartData } from "../../lib/chartAggregate"; import { xAxisLabel, yAxisLabel, type ChartConfig } from "../../lib/chartConfig"; +import { seriesColor } from "../../lib/homepageChart"; const AXIS_LABEL_STYLE = { fill: "var(--muted-foreground)", fontSize: 11 }; @@ -33,12 +34,17 @@ const AXIS_LABEL_STYLE = { fill: "var(--muted-foreground)", fontSize: 11 }; // can't appear in `--color-<key>`), while keeping the human label for legends. // An ungrouped chart has a single "all" series — label it by its Y metric // (e.g. "Total views") rather than the meaningless internal key. +// +// A series' colour (and a pie slice's) is seriesColor(i), the one the hub's +// cross-site chart uses: the six validated slots --chart-1..6, then hues +// spread by the golden angle. This chart used to cycle its own `i % 5`, so +// the sixth series repeated the first and --chart-6 was never reached. function seriesMeta(data: ChartData, config: ChartConfig) { const single = config.groupBy === "none"; return data.series.map((s, i) => ({ id: `s${i}`, label: single ? yAxisLabel(config) : s.key, - color: `var(--chart-${(i % 5) + 1})`, + color: seriesColor(i), })); } @@ -89,7 +95,7 @@ export function ChartView({ })); const pieConfig: ShadcnChartConfig = {}; data.categories.forEach((label, i) => { - pieConfig[`p${i}`] = { label, color: `var(--chart-${(i % 5) + 1})` }; + pieConfig[`p${i}`] = { label, color: seriesColor(i) }; }); return ( <ChartContainer config={pieConfig} className="h-[240px] sm:h-[320px] w-full"> @@ -97,7 +103,7 @@ export function ChartView({ <ChartTooltip content={<ChartTooltipContent />} /> <Pie data={pieData} dataKey="value" nameKey="name" innerRadius={40}> {pieData.map((_, i) => ( - <Cell key={i} fill={`var(--chart-${(i % 5) + 1})`} /> + <Cell key={i} fill={seriesColor(i)} /> ))} </Pie> </PieChart>