@squadbase/vantage 0.6.2-dev1.d594c56 → 0.6.2-dev1.e85faec
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/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-6OTTGAPO.js +44 -0
- package/dist/chunk-6OTTGAPO.js.map +1 -0
- 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/components/index.d.ts +11 -247
- package/dist/components/index.js +5 -2
- package/dist/delta-text-CvwBGJ95.d.ts +15 -0
- package/dist/doc/index.d.ts +215 -2
- package/dist/doc/index.js +300 -3
- package/dist/doc/index.js.map +1 -1
- package/dist/format-U0cQl069.d.ts +243 -0
- package/dist/slide/index.d.ts +272 -1
- package/dist/slide/index.js +966 -1
- 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/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/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/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/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-slide/SKILL.md +54 -36
- package/templates/AGENTS.md +2 -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-HKSVYMYJ.js.map +0 -1
- package/dist/chunk-XPQSTZUU.js.map +0 -1
|
@@ -16,7 +16,7 @@ import { forwardRef, useMemo, type ForwardedRef, type ReactElement, type Ref } f
|
|
|
16
16
|
import { ChartFrame } from "@squadbase/vantage/components"
|
|
17
17
|
import { EChart } from "@squadbase/vantage/components"
|
|
18
18
|
import type { ChartComparison, ChartForecast, NumericKeys } from "@squadbase/vantage/components"
|
|
19
|
-
import { createContext, useCallback, useEffect, useRef, useState } from "react"
|
|
19
|
+
import { createContext, useCallback, useEffect, useRef, useState, type RefObject } from "react"
|
|
20
20
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
21
21
|
import { DEFAULT_LOCALE, EMPTY_VALUE, formatValue, toValueFormatter, type ValueFormat } from "@squadbase/vantage/components"
|
|
22
22
|
import type { ChartColor, ChartColors, ChartSeriesColor, ValueFormatter } from "@squadbase/vantage/components"
|
|
@@ -49,6 +49,8 @@ const SERIES_TOKENS = ["--chart-1", "--chart-2", "--chart-3", "--chart-4", "--ch
|
|
|
49
49
|
interface ChartTokens {
|
|
50
50
|
/** Resolved `--chart-1..5`, in order. */
|
|
51
51
|
series: string[]
|
|
52
|
+
/** Computed `font-family` of the container, so canvas text matches the DOM. Optional. */
|
|
53
|
+
fontFamily?: string
|
|
52
54
|
/** Body text: titles and the default `textStyle`. */
|
|
53
55
|
foreground: string | null
|
|
54
56
|
/** Secondary text: axis labels, legend, subtitles. */
|
|
@@ -57,10 +59,23 @@ interface ChartTokens {
|
|
|
57
59
|
border: string | null
|
|
58
60
|
/** Tooltip surface. */
|
|
59
61
|
popover: string | null
|
|
62
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
63
|
+
colors?: Record<string, string>
|
|
64
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
65
|
+
surface?: string | null
|
|
60
66
|
/** Tooltip text. */
|
|
61
67
|
popoverForeground: string | null
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
71
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
72
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
73
|
+
const OPTION_COLOR_TOKENS = [
|
|
74
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
75
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
76
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
77
|
+
]
|
|
78
|
+
|
|
64
79
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
65
80
|
|
|
66
81
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -116,14 +131,19 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
116
131
|
const color = read(token)
|
|
117
132
|
if (color) series.push(color)
|
|
118
133
|
}
|
|
119
|
-
|
|
134
|
+
const tokens: ChartTokens = {
|
|
120
135
|
series,
|
|
136
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
137
|
+
surface: readChartSurface(target),
|
|
138
|
+
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
121
139
|
foreground: read("--foreground"),
|
|
122
140
|
mutedForeground: read("--muted-foreground"),
|
|
123
141
|
border: read("--border"),
|
|
124
142
|
popover: read("--popover"),
|
|
125
143
|
popoverForeground: read("--popover-foreground"),
|
|
126
144
|
}
|
|
145
|
+
tokenScopes.set(tokens, target)
|
|
146
|
+
return tokens
|
|
127
147
|
}
|
|
128
148
|
|
|
129
149
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -131,8 +151,13 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
131
151
|
if (a === b) return true
|
|
132
152
|
if (!a || !b) return false
|
|
133
153
|
return (
|
|
154
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
155
|
+
a.surface === b.surface &&
|
|
156
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
157
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
134
158
|
a.series.length === b.series.length &&
|
|
135
159
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
160
|
+
a.fontFamily === b.fontFamily &&
|
|
136
161
|
a.foreground === b.foreground &&
|
|
137
162
|
a.mutedForeground === b.mutedForeground &&
|
|
138
163
|
a.border === b.border &&
|
|
@@ -155,7 +180,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
155
180
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
156
181
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
157
182
|
*/
|
|
158
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
183
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
159
184
|
if (typeof document === "undefined") return () => {}
|
|
160
185
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
161
186
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -170,8 +195,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
170
195
|
|
|
171
196
|
const themeObserver = new MutationObserver(schedule)
|
|
172
197
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
173
|
-
|
|
174
|
-
|
|
198
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
199
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
200
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
201
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
202
|
+
}
|
|
175
203
|
|
|
176
204
|
const styleObserver = new MutationObserver(schedule)
|
|
177
205
|
if (document.head) {
|
|
@@ -227,7 +255,7 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
227
255
|
// Let the page background show through: charts sit on cards, not on a canvas.
|
|
228
256
|
backgroundColor: "transparent",
|
|
229
257
|
...(tokens.series.length > 0 ? { color: tokens.series } : {}),
|
|
230
|
-
textStyle: { color: text },
|
|
258
|
+
textStyle: { color: text, ...(tokens.fontFamily ? { fontFamily: tokens.fontFamily } : {}) },
|
|
231
259
|
title: { textStyle: { color: text }, subtextStyle: { color: muted } },
|
|
232
260
|
legend: { textStyle: { color: muted } },
|
|
233
261
|
categoryAxis: axisTheme(tokens, false),
|
|
@@ -243,7 +271,11 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
243
271
|
padding: [8, 12],
|
|
244
272
|
// Rounded card with a soft shadow; in dark mode the shadow barely shows, so the border carries the edge.
|
|
245
273
|
extraCssText: "border-radius:8px;box-shadow:var(--shadow-card, 0 4px 12px rgb(0 0 0 / 0.08));",
|
|
246
|
-
textStyle: {
|
|
274
|
+
textStyle: {
|
|
275
|
+
color: tokens.popoverForeground ?? undefined,
|
|
276
|
+
fontSize: 12,
|
|
277
|
+
...(tokens.fontFamily ? { fontFamily: tokens.fontFamily } : {}),
|
|
278
|
+
},
|
|
247
279
|
axisPointer: { lineStyle: { color: line }, crossStyle: { color: line } },
|
|
248
280
|
},
|
|
249
281
|
}
|
|
@@ -255,22 +287,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
255
287
|
* components that build an ECharts option from token colours themselves (the
|
|
256
288
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
257
289
|
*/
|
|
258
|
-
function useChartTokens(): ChartTokens | null {
|
|
290
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
259
291
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
260
292
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
261
293
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
262
294
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
263
295
|
const tokensRef = useRef(tokens)
|
|
264
296
|
useEffect(() => {
|
|
297
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
265
298
|
const sync = () => {
|
|
266
|
-
const next = readChartTokens()
|
|
299
|
+
const next = readChartTokens(target)
|
|
267
300
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
268
301
|
tokensRef.current = next
|
|
269
302
|
setTokens(next)
|
|
270
303
|
}
|
|
271
304
|
sync()
|
|
272
|
-
return watchChartTokens(sync)
|
|
273
|
-
}, [])
|
|
305
|
+
return watchChartTokens(sync, target)
|
|
306
|
+
}, [scope])
|
|
274
307
|
return tokens
|
|
275
308
|
}
|
|
276
309
|
|
|
@@ -282,7 +315,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
282
315
|
/**
|
|
283
316
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
284
317
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
285
|
-
* variants `"positive-fill"`, …) are read
|
|
318
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
286
319
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
287
320
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
288
321
|
*/
|
|
@@ -293,12 +326,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
293
326
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
294
327
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
295
328
|
if (typeof document === "undefined") return ""
|
|
296
|
-
|
|
329
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
330
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
297
331
|
}
|
|
298
332
|
if (typeof document === "undefined") return color
|
|
299
333
|
let value = color.trim()
|
|
300
334
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
301
|
-
const styles = getComputedStyle(document.documentElement)
|
|
335
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
302
336
|
if (cssVar) {
|
|
303
337
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
304
338
|
} else if (value.includes("var(")) {
|
|
@@ -344,7 +378,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
344
378
|
if (color) return color
|
|
345
379
|
}
|
|
346
380
|
}
|
|
347
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
381
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
348
382
|
}
|
|
349
383
|
|
|
350
384
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -358,9 +392,10 @@ const WIDTH_BUCKET = 40
|
|
|
358
392
|
*/
|
|
359
393
|
function useChartSurface<E extends Element>(
|
|
360
394
|
forwarded: ForwardedRef<E>,
|
|
361
|
-
tokens
|
|
362
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
395
|
+
tokens?: ChartTokens | null,
|
|
396
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
363
397
|
const [node, setNode] = useState<E | null>(null)
|
|
398
|
+
const scopedTokens = useChartTokens(node)
|
|
364
399
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
365
400
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
366
401
|
const ref = useCallback(
|
|
@@ -374,7 +409,7 @@ function useChartSurface<E extends Element>(
|
|
|
374
409
|
useEffect(() => {
|
|
375
410
|
if (!node) return
|
|
376
411
|
setSurface(readChartSurface(node) ?? undefined)
|
|
377
|
-
}, [node, tokens])
|
|
412
|
+
}, [node, tokens, scopedTokens])
|
|
378
413
|
useEffect(() => {
|
|
379
414
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
380
415
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -383,7 +418,7 @@ function useChartSurface<E extends Element>(
|
|
|
383
418
|
observer.observe(node)
|
|
384
419
|
return () => observer.disconnect()
|
|
385
420
|
}, [node])
|
|
386
|
-
return { ref, surface, width }
|
|
421
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
387
422
|
}
|
|
388
423
|
|
|
389
424
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-utils.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -1537,6 +1572,10 @@ const toDate = (t: ParsedTime) => new Date(t.year, t.month - 1, t.day)
|
|
|
1537
1572
|
|
|
1538
1573
|
/** How many x labels a time axis shows at most (the step is picked so the labels never crowd). */
|
|
1539
1574
|
const TIME_AXIS_MAX_LABELS = 6
|
|
1575
|
+
/** Under this plot width (px) the axis, legend and labels switch to their compact form. */
|
|
1576
|
+
const NARROW_PLOT_WIDTH = 400
|
|
1577
|
+
/** The most x labels a narrow plot shows (a 262px plot reads 4 at most). */
|
|
1578
|
+
const NARROW_TIME_AXIS_MAX_LABELS = 4
|
|
1540
1579
|
|
|
1541
1580
|
/** Calendar-friendly label steps per granularity (every 3 months, every week …). */
|
|
1542
1581
|
const TIME_AXIS_STEPS: Record<TimeGranularity, readonly number[]> = {
|
|
@@ -1982,7 +2021,9 @@ const LEGEND_LINE_BUDGET = 320
|
|
|
1982
2021
|
/** How many lines the plain legend takes, estimated from its labels (12px text + 8px dot + gaps). */
|
|
1983
2022
|
function legendLines(items: readonly string[], plotWidth?: number, reserve = 0): number {
|
|
1984
2023
|
const width = items.reduce((sum, name) => sum + [...name].length * 12 + 8 + 5 + 16, 0)
|
|
1985
|
-
|
|
2024
|
+
// A narrow plot has no room for the PNG buttons beside the legend: they move off its line, so no reserve.
|
|
2025
|
+
const narrow = plotWidth !== undefined && plotWidth < NARROW_PLOT_WIDTH
|
|
2026
|
+
const budget = Math.max(80, (plotWidth ? (narrow ? plotWidth : Math.min(LEGEND_LINE_BUDGET, plotWidth)) : LEGEND_LINE_BUDGET) - (narrow ? 0 : reserve))
|
|
1986
2027
|
return Math.min(3, Math.max(1, Math.ceil(width / budget)))
|
|
1987
2028
|
}
|
|
1988
2029
|
|
|
@@ -2237,7 +2278,15 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2237
2278
|
? null
|
|
2238
2279
|
: timeAxisLabelStep(
|
|
2239
2280
|
rawIndex,
|
|
2240
|
-
plotWidth
|
|
2281
|
+
plotWidth
|
|
2282
|
+
? Math.max(
|
|
2283
|
+
2,
|
|
2284
|
+
Math.min(
|
|
2285
|
+
plotWidth < NARROW_PLOT_WIDTH ? NARROW_TIME_AXIS_MAX_LABELS : TIME_AXIS_MAX_LABELS,
|
|
2286
|
+
Math.floor(plotWidth / TIME_LABEL_WIDTH),
|
|
2287
|
+
),
|
|
2288
|
+
)
|
|
2289
|
+
: undefined,
|
|
2241
2290
|
)
|
|
2242
2291
|
// Plain (non-date) x labels of a line chart: thinned to every n-th so they never touch, never rotated.
|
|
2243
2292
|
const plainStep = (() => {
|
|
@@ -2283,8 +2332,12 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2283
2332
|
: []
|
|
2284
2333
|
// On a narrow plot the max and the latest label would print over each other: keep the latest.
|
|
2285
2334
|
const pointGap = plotWidth && labels.length > 1 ? plotWidth / (kind === "bar" ? labels.length : labels.length - 1) : null
|
|
2335
|
+
// By default only the latest value is labelled; an explicit `highlight` keeps its max + latest.
|
|
2336
|
+
const latestOnly = cfg.highlight === undefined && !shareHighlight && picked.length === 2
|
|
2286
2337
|
const highlightPoints =
|
|
2287
|
-
|
|
2338
|
+
latestOnly
|
|
2339
|
+
? [picked[1]!]
|
|
2340
|
+
: picked.length === 2 && pointGap !== null && Math.abs(picked[1]!.index - picked[0]!.index) * pointGap < HIGHLIGHT_LABEL_WIDTH
|
|
2288
2341
|
? [picked[1]!]
|
|
2289
2342
|
: picked
|
|
2290
2343
|
|
|
@@ -2495,6 +2548,8 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2495
2548
|
? { width: 0, color, opacity: 0 }
|
|
2496
2549
|
: { width: 2, color, opacity: dimSeries ? DIMMED : 1 },
|
|
2497
2550
|
itemStyle: { color, opacity: dimSeries ? DIMMED : 1 },
|
|
2551
|
+
// Hovering a series fades the others (not for a stack, where the bands read together).
|
|
2552
|
+
emphasis: stacked ? undefined : { focus: "series" as const },
|
|
2498
2553
|
areaStyle: areaStyle && dimSeries ? { ...areaStyle, opacity: DIMMED * 0.3 } : areaStyle,
|
|
2499
2554
|
markPoint: markPointFor(c, color),
|
|
2500
2555
|
markLine: undefined,
|
|
@@ -2941,7 +2996,10 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
2941
2996
|
legend: {
|
|
2942
2997
|
show: showLegend,
|
|
2943
2998
|
top: 0,
|
|
2944
|
-
|
|
2999
|
+
// Narrow: left-aligned and wrapping above the plot, so nothing is cut off at the right edge.
|
|
3000
|
+
...(plotWidth !== undefined && plotWidth < NARROW_PLOT_WIDTH
|
|
3001
|
+
? { left: 0, right: 0 }
|
|
3002
|
+
: { right: reserveActions ? ACTIONS_RESERVE : 0 }),
|
|
2945
3003
|
// Plain wraps onto a second line at narrow widths instead of hiding items behind arrows.
|
|
2946
3004
|
type: "plain",
|
|
2947
3005
|
icon: "circle",
|
|
@@ -3002,7 +3060,12 @@ function buildCartesianOption(cfg: CartesianChartConfig): EChartsOption {
|
|
|
3002
3060
|
}
|
|
3003
3061
|
}
|
|
3004
3062
|
const shares = percent ? cartesianSeriesValues(i <= lastActual ? [row] : [], categories, "percent") : null
|
|
3005
|
-
|
|
3063
|
+
// Rows follow the legend order (categories first), not ECharts' own series order.
|
|
3064
|
+
const legendRank = (p: AxisTooltipParam) => {
|
|
3065
|
+
const at = categories.indexOf(p.seriesName ?? "")
|
|
3066
|
+
return at === -1 ? categories.length : at
|
|
3067
|
+
}
|
|
3068
|
+
for (const p of [...list].sort((a, b) => legendRank(a) - legendRank(b))) {
|
|
3006
3069
|
const color = typeof p.color === "string" ? p.color : "currentColor"
|
|
3007
3070
|
if (p.seriesId === FORECAST_SERIES_ID) {
|
|
3008
3071
|
// At and before the boundary the forecast repeats the actual value.
|
|
@@ -3518,9 +3581,8 @@ function LineChartImpl<T extends Record<string, unknown>>(
|
|
|
3518
3581
|
}: LineChartProps<T>,
|
|
3519
3582
|
ref: ForwardedRef<HTMLDivElement>,
|
|
3520
3583
|
) {
|
|
3521
|
-
const tokens = useChartTokens()
|
|
3522
3584
|
// Highlight labels are backed by the surface the chart sits on (a card, the page).
|
|
3523
|
-
const { ref: frameRef, surface, width: plotWidth } = useChartSurface(ref
|
|
3585
|
+
const { ref: frameRef, surface, width: plotWidth, tokens } = useChartSurface(ref)
|
|
3524
3586
|
// Ticks and on-plot labels read short (¥1億); the tooltip reads the value as `format` says.
|
|
3525
3587
|
const tooltipFormatter = useMemo(() => resolveValueFormatter(valueFormatter, format), [valueFormatter, format])
|
|
3526
3588
|
const axisFormatter = useMemo(() => resolveAxisFormatter(valueFormatter, format), [valueFormatter, format])
|
|
@@ -60,7 +60,7 @@ export const PageShellHeader = forwardRef<HTMLDivElement, PageShellHeaderProps>(
|
|
|
60
60
|
<div
|
|
61
61
|
ref={ref}
|
|
62
62
|
data-slot="page-shell-header"
|
|
63
|
-
className={cn("flex flex-wrap items-start gap-x-4 gap-y-
|
|
63
|
+
className={cn("flex flex-wrap items-start gap-x-4 gap-y-4 px-6 pt-8", className)}
|
|
64
64
|
{...props}
|
|
65
65
|
>
|
|
66
66
|
{children}
|
|
@@ -72,12 +72,12 @@ export const PageShellHeader = forwardRef<HTMLDivElement, PageShellHeaderProps>(
|
|
|
72
72
|
{hasChips ? (
|
|
73
73
|
<div data-slot="page-shell-chips" className="order-1 flex basis-full flex-wrap gap-1.5">
|
|
74
74
|
{source ? (
|
|
75
|
-
<Badge variant="outline" className="h-auto max-w-full whitespace-normal">
|
|
75
|
+
<Badge variant="outline" className="h-auto max-w-full whitespace-normal text-muted-foreground">
|
|
76
76
|
出典:{source}
|
|
77
77
|
</Badge>
|
|
78
78
|
) : null}
|
|
79
79
|
{periodText && periodText !== EMPTY_VALUE ? (
|
|
80
|
-
<Badge variant="outline" className="tabular-nums">
|
|
80
|
+
<Badge variant="outline" className="tabular-nums text-muted-foreground">
|
|
81
81
|
{periodText}
|
|
82
82
|
</Badge>
|
|
83
83
|
) : null}
|
|
@@ -115,7 +115,7 @@ export const PageShellTitle = forwardRef<HTMLHeadingElement, PageShellTitleProps
|
|
|
115
115
|
<h1
|
|
116
116
|
ref={ref}
|
|
117
117
|
data-slot="page-shell-title"
|
|
118
|
-
className={cn("text-2xl font-
|
|
118
|
+
className={cn("text-2xl font-semibold tracking-tight text-foreground", className)}
|
|
119
119
|
{...props}
|
|
120
120
|
/>
|
|
121
121
|
),
|
|
@@ -204,11 +204,11 @@ PageShellSummary.displayName = "PageShellSummary"
|
|
|
204
204
|
* emits classes it can see literally in the source.
|
|
205
205
|
*/
|
|
206
206
|
const summaryCardTones = {
|
|
207
|
-
positive: "border-positive/
|
|
208
|
-
negative: "border-negative/
|
|
209
|
-
warning: "border-warning/
|
|
210
|
-
info: "border-info/
|
|
211
|
-
neutral: "border-neutral/
|
|
207
|
+
positive: "border-positive/20 dark:border-positive/25 bg-positive-subtle [&>svg]:text-positive",
|
|
208
|
+
negative: "border-negative/20 dark:border-negative/25 bg-negative-subtle [&>svg]:text-negative",
|
|
209
|
+
warning: "border-warning/20 dark:border-warning/25 bg-warning-subtle [&>svg]:text-warning",
|
|
210
|
+
info: "border-info/20 dark:border-info/25 bg-info-subtle [&>svg]:text-info",
|
|
211
|
+
neutral: "border-neutral/20 dark:border-neutral/25 bg-neutral-subtle [&>svg]:text-neutral",
|
|
212
212
|
violet: "border-chart-4/30 bg-chart-4/5 [&>svg]:text-chart-4",
|
|
213
213
|
pink: "border-chart-5/30 bg-chart-5/5 [&>svg]:text-chart-5",
|
|
214
214
|
} as const
|