@squadbase/vantage 0.6.2-dev1.d594c56 → 0.6.2-dev1.e333781
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-X5NVCQKT.js → build-YOPOGPQB.js} +7 -7
- package/dist/{build-X5NVCQKT.js.map → build-YOPOGPQB.js.map} +1 -1
- package/dist/{check-ZBIH422T.js → check-FBHT6W6J.js} +5 -5
- package/dist/{check-ZBIH422T.js.map → check-FBHT6W6J.js.map} +1 -1
- package/dist/chunk-2IAPIOPS.js +152 -0
- package/dist/chunk-2IAPIOPS.js.map +1 -0
- package/dist/chunk-57GHR5LN.js +163 -0
- package/dist/chunk-57GHR5LN.js.map +1 -0
- package/dist/{chunk-7DZGJQ3P.js → chunk-5C2SEDPH.js} +21 -4
- package/dist/chunk-5C2SEDPH.js.map +1 -0
- package/dist/chunk-6OTTGAPO.js +44 -0
- package/dist/chunk-6OTTGAPO.js.map +1 -0
- package/dist/{chunk-2ICNHUG6.js → chunk-7652CGJJ.js} +10 -3
- package/dist/{chunk-2ICNHUG6.js.map → chunk-7652CGJJ.js.map} +1 -1
- package/dist/{chunk-XPQSTZUU.js → chunk-7S52M2SL.js} +4 -3
- package/dist/chunk-7S52M2SL.js.map +1 -0
- package/dist/chunk-JXFCSF3K.js +673 -0
- package/dist/chunk-JXFCSF3K.js.map +1 -0
- package/dist/{chunk-HKSVYMYJ.js → chunk-QOXXOFGL.js} +61 -929
- package/dist/chunk-QOXXOFGL.js.map +1 -0
- package/dist/{chunk-WAMZWZJB.js → chunk-R6MTJAHX.js} +6 -6
- package/dist/chunk-R6MTJAHX.js.map +1 -0
- package/dist/{chunk-CEXAQQ5W.js → chunk-S5MDTHH7.js} +180 -18
- package/dist/chunk-S5MDTHH7.js.map +1 -0
- package/dist/{chunk-JKLLFVEU.js → chunk-UWSQUHX6.js} +4 -99
- package/dist/chunk-UWSQUHX6.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/components/index.d.ts +11 -247
- package/dist/components/index.js +5 -2
- package/dist/delta-text-CvwBGJ95.d.ts +15 -0
- package/dist/{dev-4XEDTELV.js → dev-GUKQT6U6.js} +7 -7
- package/dist/{dev-4XEDTELV.js.map → dev-GUKQT6U6.js.map} +1 -1
- package/dist/doc/index.d.ts +215 -2
- package/dist/doc/index.js +307 -3
- package/dist/doc/index.js.map +1 -1
- package/dist/{doctor-5E2VEZPK.js → doctor-B2O3GJDU.js} +5 -5
- package/dist/{doctor-5E2VEZPK.js.map → doctor-B2O3GJDU.js.map} +1 -1
- package/dist/format-U0cQl069.d.ts +243 -0
- package/dist/{routes-3SGAFDMC.js → routes-PVKNT777.js} +4 -4
- package/dist/{routes-3SGAFDMC.js.map → routes-PVKNT777.js.map} +1 -1
- package/dist/slide/index.d.ts +272 -1
- package/dist/slide/index.js +972 -2
- package/dist/slide/index.js.map +1 -1
- package/dist/table/index.js +5 -2
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +4 -1
- package/dist/ui/index.js +3 -3
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-JHXZBGQS.js → upgrade-7ZXMCTTR.js} +4 -4
- package/dist/{upgrade-JHXZBGQS.js.map → upgrade-7ZXMCTTR.js.map} +1 -1
- package/dist/vite/index.js +3 -3
- package/docs/en/changelog.md +2 -0
- package/docs/en/components.md +13 -2
- package/docs/en/doc/delta-text.md +40 -0
- package/docs/en/doc/doc-action-items.md +49 -0
- package/docs/en/doc/doc-callout.md +38 -0
- package/docs/en/doc/doc-data-table.md +60 -0
- package/docs/en/doc/doc-executive-summary.md +64 -0
- package/docs/en/doc/doc-figure.md +56 -0
- package/docs/en/doc/doc-key-findings.md +61 -0
- package/docs/en/doc/doc-kpi-scorecard.md +80 -0
- package/docs/en/doc/doc-notes-and-sources.md +43 -0
- package/docs/en/doc/doc-report-header.md +46 -0
- package/docs/en/pages-and-metadata.md +15 -0
- package/docs/en/parts/area-chart.md +99 -0
- package/docs/en/parts/bar-chart.md +91 -0
- package/docs/en/parts/bar-list.md +50 -0
- package/docs/en/parts/category-bar.md +37 -0
- package/docs/en/parts/chart-frame.md +39 -0
- package/docs/en/parts/delta.md +4 -3
- package/docs/en/parts/donut-chart.md +72 -0
- package/docs/en/parts/funnel-steps.md +2 -1
- package/docs/en/parts/heatmap.md +53 -0
- package/docs/en/parts/kpi-card.md +2 -1
- package/docs/en/parts/legend.md +35 -0
- package/docs/en/parts/line-chart.md +94 -0
- package/docs/en/parts/progress-circle.md +33 -0
- package/docs/en/parts/sample-data.md +39 -0
- package/docs/en/parts/scatter-chart.md +56 -0
- package/docs/en/parts/tracker.md +36 -0
- package/docs/en/parts/updated-at.md +30 -0
- package/docs/en/parts/waterfall-chart.md +56 -0
- package/docs/en/slide/slide-chart-full.md +48 -0
- package/docs/en/slide/slide-chart-insight.md +52 -0
- package/docs/en/slide/slide-columns.md +46 -0
- package/docs/en/slide/slide-comparison.md +68 -0
- package/docs/en/slide/slide-cover.md +39 -0
- package/docs/en/slide/slide-exec-summary.md +47 -0
- package/docs/en/slide/slide-heading.md +38 -0
- package/docs/en/slide/slide-kpi-grid.md +55 -0
- package/docs/en/slide/slide-kpi-hero.md +58 -0
- package/docs/en/slide/slide-next-actions.md +49 -0
- package/docs/en/slide/slide-scorecard-table.md +54 -0
- package/docs/en/slide/slide-section.md +44 -0
- package/docs/en/slide/slide-source-line.md +34 -0
- package/docs/en/ui/alert-dialog.md +42 -0
- package/docs/en/ui/avatar.md +31 -0
- package/docs/en/ui/combobox.md +74 -0
- package/docs/en/ui/drawer.md +41 -0
- package/docs/en/ui/empty.md +1 -1
- package/docs/en/ui/error-state.md +4 -3
- package/docs/en/ui/field.md +42 -0
- package/docs/en/ui/hover-card.md +21 -0
- package/docs/en/ui/kbd.md +17 -0
- package/docs/en/ui/loading.md +1 -1
- package/docs/en/ui/pagination.md +36 -0
- package/docs/en/ui/radio-group.md +21 -0
- package/docs/en/ui/toast.md +44 -0
- package/docs/en/ui/toggle-group.md +4 -3
- package/docs/index.json +857 -77
- package/docs/ja/changelog.md +2 -0
- package/docs/ja/components.md +12 -1
- package/docs/ja/doc/delta-text.md +40 -0
- package/docs/ja/doc/doc-action-items.md +49 -0
- package/docs/ja/doc/doc-callout.md +38 -0
- package/docs/ja/doc/doc-data-table.md +60 -0
- package/docs/ja/doc/doc-executive-summary.md +64 -0
- package/docs/ja/doc/doc-figure.md +56 -0
- package/docs/ja/doc/doc-key-findings.md +61 -0
- package/docs/ja/doc/doc-kpi-scorecard.md +80 -0
- package/docs/ja/doc/doc-notes-and-sources.md +43 -0
- package/docs/ja/doc/doc-report-header.md +46 -0
- package/docs/ja/page-types/slide.md +1 -0
- package/docs/ja/pages-and-metadata.md +15 -0
- package/docs/ja/parts/area-chart.md +49 -25
- package/docs/ja/parts/bar-chart.md +50 -24
- package/docs/ja/parts/bar-list.md +31 -8
- package/docs/ja/parts/chart-frame.md +36 -0
- package/docs/ja/parts/delta.md +5 -4
- package/docs/ja/parts/donut-chart.md +45 -26
- package/docs/ja/parts/funnel-steps.md +27 -8
- package/docs/ja/parts/heatmap.md +45 -0
- package/docs/ja/parts/kpi-card.md +2 -1
- package/docs/ja/parts/legend.md +36 -0
- package/docs/ja/parts/line-chart.md +48 -22
- package/docs/ja/parts/sample-data.md +61 -0
- package/docs/ja/parts/scatter-chart.md +69 -0
- package/docs/ja/parts/updated-at.md +27 -0
- package/docs/ja/parts/waterfall-chart.md +61 -0
- package/docs/ja/slide/slide-chart-full.md +48 -0
- package/docs/ja/slide/slide-chart-insight.md +52 -0
- package/docs/ja/slide/slide-columns.md +46 -0
- package/docs/ja/slide/slide-comparison.md +68 -0
- package/docs/ja/slide/slide-cover.md +39 -0
- package/docs/ja/slide/slide-exec-summary.md +47 -0
- package/docs/ja/slide/slide-heading.md +38 -0
- package/docs/ja/slide/slide-kpi-grid.md +55 -0
- package/docs/ja/slide/slide-kpi-hero.md +58 -0
- package/docs/ja/slide/slide-next-actions.md +49 -0
- package/docs/ja/slide/slide-scorecard-table.md +54 -0
- package/docs/ja/slide/slide-section.md +44 -0
- package/docs/ja/slide/slide-source-line.md +34 -0
- package/docs/ja/ui/empty.md +1 -1
- package/docs/ja/ui/error-state.md +4 -3
- package/docs/ja/ui/loading.md +1 -1
- package/docs/ja/ui/toggle-group.md +4 -3
- package/package.json +2 -2
- package/registry/ui/area-chart.tsx +88 -26
- package/registry/ui/attention-list.tsx +303 -0
- package/registry/ui/bar-chart.tsx +88 -26
- package/registry/ui/bar-list.tsx +87 -24
- package/registry/ui/box-plot-chart.tsx +3906 -0
- package/registry/ui/category-bar.tsx +54 -19
- package/registry/ui/chart-frame.tsx +56 -21
- package/registry/ui/dashboard-card.tsx +2 -2
- package/registry/ui/delta.tsx +11 -8
- package/registry/ui/donut-chart.tsx +89 -26
- package/registry/ui/echart.tsx +194 -44
- package/registry/ui/funnel-steps.tsx +33 -5
- package/registry/ui/heatmap.tsx +55 -21
- package/registry/ui/histogram-chart.tsx +3986 -0
- package/registry/ui/kpi-card.tsx +41 -14
- package/registry/ui/legend.tsx +87 -24
- package/registry/ui/line-chart.tsx +88 -26
- package/registry/ui/page-shell.tsx +9 -9
- package/registry/ui/paired-chart.tsx +3827 -0
- package/registry/ui/sample-data.tsx +1 -1
- package/registry/ui/scatter-chart.tsx +88 -26
- package/registry/ui/section-header.tsx +2 -2
- package/registry/ui/slider.tsx +1 -1
- package/registry/ui/status-badge.tsx +8 -8
- package/registry/ui/tooltip.tsx +6 -2
- package/registry/ui/tracker.tsx +9 -3
- package/registry/ui/trend-indicator.tsx +2 -2
- package/registry/ui/waterfall-chart.tsx +88 -26
- package/skills/vantage-design/SKILL.md +27 -25
- package/skills/vantage-design/references/extract.md +16 -14
- package/skills/vantage-design/references/format.md +6 -5
- package/skills/vantage-design/references/presets.md +60 -110
- package/skills/vantage-pitfalls/SKILL.md +5 -0
- package/skills/vantage-slide/SKILL.md +54 -36
- package/templates/AGENTS.md +4 -2
- package/templates/agents/doc.md +48 -0
- package/templates/agents/page.md +22 -1
- package/templates/agents/slide.md +70 -2
- package/theme.css +16 -15
- package/dist/chunk-7DZGJQ3P.js.map +0 -1
- package/dist/chunk-CEXAQQ5W.js.map +0 -1
- package/dist/chunk-HKSVYMYJ.js.map +0 -1
- package/dist/chunk-JKLLFVEU.js.map +0 -1
- package/dist/chunk-WAMZWZJB.js.map +0 -1
- package/dist/chunk-XPQSTZUU.js.map +0 -1
|
@@ -22,7 +22,7 @@ import { forwardRef, useEffect, useMemo, useState, type ForwardedRef, type React
|
|
|
22
22
|
import { ChartFrame } from "@squadbase/vantage/components"
|
|
23
23
|
import { EChart } from "@squadbase/vantage/components"
|
|
24
24
|
import type { ChartCommonProps, ChartEventPayload, ChartSeriesColor } from "@squadbase/vantage/components"
|
|
25
|
-
import { createContext, useCallback, useRef } from "react"
|
|
25
|
+
import { createContext, useCallback, useRef, type RefObject } from "react"
|
|
26
26
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
27
27
|
import { DEFAULT_LOCALE, EMPTY_VALUE, formatValue, toValueFormatter, type ValueFormat } from "@squadbase/vantage/components"
|
|
28
28
|
import type { ChartColor, ChartColors, ValueFormatter } from "@squadbase/vantage/components"
|
|
@@ -54,6 +54,8 @@ const SERIES_TOKENS = ["--chart-1", "--chart-2", "--chart-3", "--chart-4", "--ch
|
|
|
54
54
|
interface ChartTokens {
|
|
55
55
|
/** Resolved `--chart-1..5`, in order. */
|
|
56
56
|
series: string[]
|
|
57
|
+
/** Computed `font-family` of the container, so canvas text matches the DOM. Optional. */
|
|
58
|
+
fontFamily?: string
|
|
57
59
|
/** Body text: titles and the default `textStyle`. */
|
|
58
60
|
foreground: string | null
|
|
59
61
|
/** Secondary text: axis labels, legend, subtitles. */
|
|
@@ -62,10 +64,23 @@ interface ChartTokens {
|
|
|
62
64
|
border: string | null
|
|
63
65
|
/** Tooltip surface. */
|
|
64
66
|
popover: string | null
|
|
67
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
68
|
+
colors?: Record<string, string>
|
|
69
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
70
|
+
surface?: string | null
|
|
65
71
|
/** Tooltip text. */
|
|
66
72
|
popoverForeground: string | null
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
76
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
77
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
78
|
+
const OPTION_COLOR_TOKENS = [
|
|
79
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
80
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
81
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
82
|
+
]
|
|
83
|
+
|
|
69
84
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
70
85
|
|
|
71
86
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -121,14 +136,19 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
121
136
|
const color = read(token)
|
|
122
137
|
if (color) series.push(color)
|
|
123
138
|
}
|
|
124
|
-
|
|
139
|
+
const tokens: ChartTokens = {
|
|
125
140
|
series,
|
|
141
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
142
|
+
surface: readChartSurface(target),
|
|
143
|
+
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
126
144
|
foreground: read("--foreground"),
|
|
127
145
|
mutedForeground: read("--muted-foreground"),
|
|
128
146
|
border: read("--border"),
|
|
129
147
|
popover: read("--popover"),
|
|
130
148
|
popoverForeground: read("--popover-foreground"),
|
|
131
149
|
}
|
|
150
|
+
tokenScopes.set(tokens, target)
|
|
151
|
+
return tokens
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -136,8 +156,13 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
136
156
|
if (a === b) return true
|
|
137
157
|
if (!a || !b) return false
|
|
138
158
|
return (
|
|
159
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
160
|
+
a.surface === b.surface &&
|
|
161
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
162
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
139
163
|
a.series.length === b.series.length &&
|
|
140
164
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
165
|
+
a.fontFamily === b.fontFamily &&
|
|
141
166
|
a.foreground === b.foreground &&
|
|
142
167
|
a.mutedForeground === b.mutedForeground &&
|
|
143
168
|
a.border === b.border &&
|
|
@@ -160,7 +185,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
160
185
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
161
186
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
162
187
|
*/
|
|
163
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
188
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
164
189
|
if (typeof document === "undefined") return () => {}
|
|
165
190
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
166
191
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -175,8 +200,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
175
200
|
|
|
176
201
|
const themeObserver = new MutationObserver(schedule)
|
|
177
202
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
178
|
-
|
|
179
|
-
|
|
203
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
204
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
205
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
206
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
207
|
+
}
|
|
180
208
|
|
|
181
209
|
const styleObserver = new MutationObserver(schedule)
|
|
182
210
|
if (document.head) {
|
|
@@ -232,7 +260,7 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
232
260
|
// Let the page background show through: charts sit on cards, not on a canvas.
|
|
233
261
|
backgroundColor: "transparent",
|
|
234
262
|
...(tokens.series.length > 0 ? { color: tokens.series } : {}),
|
|
235
|
-
textStyle: { color: text },
|
|
263
|
+
textStyle: { color: text, ...(tokens.fontFamily ? { fontFamily: tokens.fontFamily } : {}) },
|
|
236
264
|
title: { textStyle: { color: text }, subtextStyle: { color: muted } },
|
|
237
265
|
legend: { textStyle: { color: muted } },
|
|
238
266
|
categoryAxis: axisTheme(tokens, false),
|
|
@@ -248,7 +276,11 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
248
276
|
padding: [8, 12],
|
|
249
277
|
// Rounded card with a soft shadow; in dark mode the shadow barely shows, so the border carries the edge.
|
|
250
278
|
extraCssText: "border-radius:8px;box-shadow:var(--shadow-card, 0 4px 12px rgb(0 0 0 / 0.08));",
|
|
251
|
-
textStyle: {
|
|
279
|
+
textStyle: {
|
|
280
|
+
color: tokens.popoverForeground ?? undefined,
|
|
281
|
+
fontSize: 12,
|
|
282
|
+
...(tokens.fontFamily ? { fontFamily: tokens.fontFamily } : {}),
|
|
283
|
+
},
|
|
252
284
|
axisPointer: { lineStyle: { color: line }, crossStyle: { color: line } },
|
|
253
285
|
},
|
|
254
286
|
}
|
|
@@ -260,22 +292,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
260
292
|
* components that build an ECharts option from token colours themselves (the
|
|
261
293
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
262
294
|
*/
|
|
263
|
-
function useChartTokens(): ChartTokens | null {
|
|
295
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
264
296
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
265
297
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
266
298
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
267
299
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
268
300
|
const tokensRef = useRef(tokens)
|
|
269
301
|
useEffect(() => {
|
|
302
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
270
303
|
const sync = () => {
|
|
271
|
-
const next = readChartTokens()
|
|
304
|
+
const next = readChartTokens(target)
|
|
272
305
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
273
306
|
tokensRef.current = next
|
|
274
307
|
setTokens(next)
|
|
275
308
|
}
|
|
276
309
|
sync()
|
|
277
|
-
return watchChartTokens(sync)
|
|
278
|
-
}, [])
|
|
310
|
+
return watchChartTokens(sync, target)
|
|
311
|
+
}, [scope])
|
|
279
312
|
return tokens
|
|
280
313
|
}
|
|
281
314
|
|
|
@@ -287,7 +320,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
287
320
|
/**
|
|
288
321
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
289
322
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
290
|
-
* variants `"positive-fill"`, …) are read
|
|
323
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
291
324
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
292
325
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
293
326
|
*/
|
|
@@ -298,12 +331,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
298
331
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
299
332
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
300
333
|
if (typeof document === "undefined") return ""
|
|
301
|
-
|
|
334
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
335
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
302
336
|
}
|
|
303
337
|
if (typeof document === "undefined") return color
|
|
304
338
|
let value = color.trim()
|
|
305
339
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
306
|
-
const styles = getComputedStyle(document.documentElement)
|
|
340
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
307
341
|
if (cssVar) {
|
|
308
342
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
309
343
|
} else if (value.includes("var(")) {
|
|
@@ -349,7 +383,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
349
383
|
if (color) return color
|
|
350
384
|
}
|
|
351
385
|
}
|
|
352
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
386
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
353
387
|
}
|
|
354
388
|
|
|
355
389
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -363,9 +397,10 @@ const WIDTH_BUCKET = 40
|
|
|
363
397
|
*/
|
|
364
398
|
function useChartSurface<E extends Element>(
|
|
365
399
|
forwarded: ForwardedRef<E>,
|
|
366
|
-
tokens
|
|
367
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
400
|
+
tokens?: ChartTokens | null,
|
|
401
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
368
402
|
const [node, setNode] = useState<E | null>(null)
|
|
403
|
+
const scopedTokens = useChartTokens(node)
|
|
369
404
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
370
405
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
371
406
|
const ref = useCallback(
|
|
@@ -379,7 +414,7 @@ function useChartSurface<E extends Element>(
|
|
|
379
414
|
useEffect(() => {
|
|
380
415
|
if (!node) return
|
|
381
416
|
setSurface(readChartSurface(node) ?? undefined)
|
|
382
|
-
}, [node, tokens])
|
|
417
|
+
}, [node, tokens, scopedTokens])
|
|
383
418
|
useEffect(() => {
|
|
384
419
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
385
420
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -388,7 +423,7 @@ function useChartSurface<E extends Element>(
|
|
|
388
423
|
observer.observe(node)
|
|
389
424
|
return () => observer.disconnect()
|
|
390
425
|
}, [node])
|
|
391
|
-
return { ref, surface, width }
|
|
426
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
392
427
|
}
|
|
393
428
|
|
|
394
429
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-utils.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -1542,6 +1577,10 @@ const toDate = (t: ParsedTime) => new Date(t.year, t.month - 1, t.day)
|
|
|
1542
1577
|
|
|
1543
1578
|
/** How many x labels a time axis shows at most (the step is picked so the labels never crowd). */
|
|
1544
1579
|
const TIME_AXIS_MAX_LABELS = 6
|
|
1580
|
+
/** Under this plot width (px) the axis, legend and labels switch to their compact form. */
|
|
1581
|
+
const NARROW_PLOT_WIDTH = 400
|
|
1582
|
+
/** The most x labels a narrow plot shows (a 262px plot reads 4 at most). */
|
|
1583
|
+
const NARROW_TIME_AXIS_MAX_LABELS = 4
|
|
1545
1584
|
|
|
1546
1585
|
/** Calendar-friendly label steps per granularity (every 3 months, every week …). */
|
|
1547
1586
|
const TIME_AXIS_STEPS: Record<TimeGranularity, readonly number[]> = {
|
|
@@ -1987,7 +2026,9 @@ const LEGEND_LINE_BUDGET = 320
|
|
|
1987
2026
|
/** How many lines the plain legend takes, estimated from its labels (12px text + 8px dot + gaps). */
|
|
1988
2027
|
function legendLines(items: readonly string[], plotWidth?: number, reserve = 0): number {
|
|
1989
2028
|
const width = items.reduce((sum, name) => sum + [...name].length * 12 + 8 + 5 + 16, 0)
|
|
1990
|
-
|
|
2029
|
+
// A narrow plot has no room for the PNG buttons beside the legend: they move off its line, so no reserve.
|
|
2030
|
+
const narrow = plotWidth !== undefined && plotWidth < NARROW_PLOT_WIDTH
|
|
2031
|
+
const budget = Math.max(80, (plotWidth ? (narrow ? plotWidth : Math.min(LEGEND_LINE_BUDGET, plotWidth)) : LEGEND_LINE_BUDGET) - (narrow ? 0 : reserve))
|
|
1991
2032
|
return Math.min(3, Math.max(1, Math.ceil(width / budget)))
|
|
1992
2033
|
}
|
|
1993
2034
|
|
|
@@ -2242,7 +2283,15 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2242
2283
|
? null
|
|
2243
2284
|
: timeAxisLabelStep(
|
|
2244
2285
|
rawIndex,
|
|
2245
|
-
plotWidth
|
|
2286
|
+
plotWidth
|
|
2287
|
+
? Math.max(
|
|
2288
|
+
2,
|
|
2289
|
+
Math.min(
|
|
2290
|
+
plotWidth < NARROW_PLOT_WIDTH ? NARROW_TIME_AXIS_MAX_LABELS : TIME_AXIS_MAX_LABELS,
|
|
2291
|
+
Math.floor(plotWidth / TIME_LABEL_WIDTH),
|
|
2292
|
+
),
|
|
2293
|
+
)
|
|
2294
|
+
: undefined,
|
|
2246
2295
|
)
|
|
2247
2296
|
// Plain (non-date) x labels of a line chart: thinned to every n-th so they never touch, never rotated.
|
|
2248
2297
|
const plainStep = (() => {
|
|
@@ -2288,8 +2337,12 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2288
2337
|
: []
|
|
2289
2338
|
// On a narrow plot the max and the latest label would print over each other: keep the latest.
|
|
2290
2339
|
const pointGap = plotWidth && labels.length > 1 ? plotWidth / (kind === "bar" ? labels.length : labels.length - 1) : null
|
|
2340
|
+
// By default only the latest value is labelled; an explicit `highlight` keeps its max + latest.
|
|
2341
|
+
const latestOnly = cfg.highlight === undefined && !shareHighlight && picked.length === 2
|
|
2291
2342
|
const highlightPoints =
|
|
2292
|
-
|
|
2343
|
+
latestOnly
|
|
2344
|
+
? [picked[1]!]
|
|
2345
|
+
: picked.length === 2 && pointGap !== null && Math.abs(picked[1]!.index - picked[0]!.index) * pointGap < HIGHLIGHT_LABEL_WIDTH
|
|
2293
2346
|
? [picked[1]!]
|
|
2294
2347
|
: picked
|
|
2295
2348
|
|
|
@@ -2500,6 +2553,8 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2500
2553
|
? { width: 0, color, opacity: 0 }
|
|
2501
2554
|
: { width: 2, color, opacity: dimSeries ? DIMMED : 1 },
|
|
2502
2555
|
itemStyle: { color, opacity: dimSeries ? DIMMED : 1 },
|
|
2556
|
+
// Hovering a series fades the others (not for a stack, where the bands read together).
|
|
2557
|
+
emphasis: stacked ? undefined : { focus: "series" as const },
|
|
2503
2558
|
areaStyle: areaStyle && dimSeries ? { ...areaStyle, opacity: DIMMED * 0.3 } : areaStyle,
|
|
2504
2559
|
markPoint: markPointFor(c, color),
|
|
2505
2560
|
markLine: undefined,
|
|
@@ -2946,7 +3001,10 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2946
3001
|
legend: {
|
|
2947
3002
|
show: showLegend,
|
|
2948
3003
|
top: 0,
|
|
2949
|
-
|
|
3004
|
+
// Narrow: left-aligned and wrapping above the plot, so nothing is cut off at the right edge.
|
|
3005
|
+
...(plotWidth !== undefined && plotWidth < NARROW_PLOT_WIDTH
|
|
3006
|
+
? { left: 0, right: 0 }
|
|
3007
|
+
: { right: reserveActions ? ACTIONS_RESERVE : 0 }),
|
|
2950
3008
|
// Plain wraps onto a second line at narrow widths instead of hiding items behind arrows.
|
|
2951
3009
|
type: "plain",
|
|
2952
3010
|
icon: "circle",
|
|
@@ -3007,7 +3065,12 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
3007
3065
|
}
|
|
3008
3066
|
}
|
|
3009
3067
|
const shares = percent ? cartesianSeriesValues(i <= lastActual ? [row] : [], categories, "percent") : null
|
|
3010
|
-
|
|
3068
|
+
// Rows follow the legend order (categories first), not ECharts' own series order.
|
|
3069
|
+
const legendRank = (p: AxisTooltipParam) => {
|
|
3070
|
+
const at = categories.indexOf(p.seriesName ?? "")
|
|
3071
|
+
return at === -1 ? categories.length : at
|
|
3072
|
+
}
|
|
3073
|
+
for (const p of [...list].sort((a, b) => legendRank(a) - legendRank(b))) {
|
|
3011
3074
|
const color = typeof p.color === "string" ? p.color : "currentColor"
|
|
3012
3075
|
if (p.seriesId === FORECAST_SERIES_ID) {
|
|
3013
3076
|
// At and before the boundary the forecast repeats the actual value.
|
|
@@ -3543,7 +3606,7 @@ function DonutChartImpl<T extends Record<string, unknown>>(
|
|
|
3543
3606
|
const showLegend = donutOptions?.showLegend ?? true
|
|
3544
3607
|
const valueFormatter = useMemo(() => resolveValueFormatter(valueFormatterProp, format), [valueFormatterProp, format])
|
|
3545
3608
|
|
|
3546
|
-
const tokens =
|
|
3609
|
+
const { ref: frameRef, tokens } = useChartSurface(ref)
|
|
3547
3610
|
const slices = useMemo(
|
|
3548
3611
|
() => donutSlices(data, { index: labelKey, value: valueKey, colors, maxSlices, otherColor: CHART_OTHER_COLOR }),
|
|
3549
3612
|
[data, labelKey, valueKey, colors, maxSlices],
|
|
@@ -3625,7 +3688,7 @@ function DonutChartImpl<T extends Record<string, unknown>>(
|
|
|
3625
3688
|
return (
|
|
3626
3689
|
<ChartFrame
|
|
3627
3690
|
{...chartRootProps(rest)}
|
|
3628
|
-
ref={
|
|
3691
|
+
ref={frameRef}
|
|
3629
3692
|
title={title}
|
|
3630
3693
|
summary={resolvedSummary}
|
|
3631
3694
|
isLoading={isLoading ?? loading ?? false}
|