@flux-ui/statistics 3.0.0-next.66 → 3.0.0-next.68

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.
Files changed (101) hide show
  1. package/README.md +1 -1
  2. package/dist/component/FluxStatisticsAreaChart.vue.d.ts +13 -5
  3. package/dist/component/FluxStatisticsBarChart.vue.d.ts +13 -5
  4. package/dist/component/FluxStatisticsBase.vue.d.ts +8 -15
  5. package/dist/component/FluxStatisticsBoxPlotChart.vue.d.ts +15 -0
  6. package/dist/component/FluxStatisticsBubbleChart.vue.d.ts +14 -0
  7. package/dist/component/FluxStatisticsCandlestickChart.vue.d.ts +15 -0
  8. package/dist/component/FluxStatisticsChange.vue.d.ts +2 -1
  9. package/dist/component/FluxStatisticsChart.vue.d.ts +8 -19
  10. package/dist/component/FluxStatisticsChartPane.vue.d.ts +9 -17
  11. package/dist/component/FluxStatisticsComparison.vue.d.ts +16 -0
  12. package/dist/component/FluxStatisticsDetailsTable.vue.d.ts +6 -13
  13. package/dist/component/FluxStatisticsDetailsTableRow.vue.d.ts +2 -1
  14. package/dist/component/FluxStatisticsDonutChart.vue.d.ts +10 -5
  15. package/dist/component/FluxStatisticsEmpty.vue.d.ts +19 -0
  16. package/dist/component/FluxStatisticsGrid.vue.d.ts +7 -11
  17. package/dist/component/FluxStatisticsHeatmapChart.vue.d.ts +15 -0
  18. package/dist/component/FluxStatisticsKpi.vue.d.ts +2 -1
  19. package/dist/component/FluxStatisticsLegend.vue.d.ts +7 -11
  20. package/dist/component/FluxStatisticsLegendItem.vue.d.ts +3 -1
  21. package/dist/component/FluxStatisticsLineChart.vue.d.ts +13 -5
  22. package/dist/component/FluxStatisticsMeter.vue.d.ts +2 -1
  23. package/dist/component/FluxStatisticsMetric.vue.d.ts +6 -13
  24. package/dist/component/FluxStatisticsMixedChart.vue.d.ts +17 -0
  25. package/dist/component/FluxStatisticsPieChart.vue.d.ts +10 -5
  26. package/dist/component/FluxStatisticsPolarAreaChart.vue.d.ts +14 -0
  27. package/dist/component/FluxStatisticsRadarChart.vue.d.ts +12 -0
  28. package/dist/component/FluxStatisticsRadialBar.vue.d.ts +11 -0
  29. package/dist/component/FluxStatisticsScatterChart.vue.d.ts +14 -0
  30. package/dist/component/FluxStatisticsSparkline.vue.d.ts +13 -0
  31. package/dist/component/FluxStatisticsTreemapChart.vue.d.ts +11 -0
  32. package/dist/component/index.d.ts +13 -0
  33. package/dist/composable/index.d.ts +10 -0
  34. package/dist/composable/useChartHoverSync.d.ts +9 -0
  35. package/dist/composable/useChartLegend.d.ts +14 -0
  36. package/dist/composable/useChartSeriesSetup.d.ts +23 -0
  37. package/dist/composable/useECharts.d.ts +9 -0
  38. package/dist/composable/usePieSlicesSetup.d.ts +14 -0
  39. package/dist/echarts.d.ts +1 -0
  40. package/dist/index.css +230 -37
  41. package/dist/index.d.ts +5 -2
  42. package/dist/index.js +10924 -9047
  43. package/dist/index.js.map +1 -1
  44. package/dist/util/baseOptions.d.ts +15 -0
  45. package/dist/util/colors.d.ts +4 -0
  46. package/dist/util/convert.d.ts +22 -0
  47. package/dist/util/defaultOptions.d.ts +76 -0
  48. package/dist/util/iconSvg.d.ts +2 -0
  49. package/dist/util/index.d.ts +7 -0
  50. package/dist/util/seriesDefaults.d.ts +15 -0
  51. package/dist/util/sparklineOptions.d.ts +7 -0
  52. package/package.json +14 -15
  53. package/src/component/FluxStatisticsAreaChart.vue +38 -41
  54. package/src/component/FluxStatisticsBarChart.vue +38 -33
  55. package/src/component/FluxStatisticsBase.vue +14 -1
  56. package/src/component/FluxStatisticsBoxPlotChart.vue +69 -0
  57. package/src/component/FluxStatisticsBubbleChart.vue +56 -0
  58. package/src/component/FluxStatisticsCandlestickChart.vue +81 -0
  59. package/src/component/FluxStatisticsChart.vue +19 -169
  60. package/src/component/FluxStatisticsChartPane.vue +23 -11
  61. package/src/component/FluxStatisticsComparison.vue +113 -0
  62. package/src/component/FluxStatisticsDonutChart.vue +39 -18
  63. package/src/component/FluxStatisticsEmpty.vue +44 -0
  64. package/src/component/FluxStatisticsHeatmapChart.vue +80 -0
  65. package/src/component/FluxStatisticsLegend.vue +33 -1
  66. package/src/component/FluxStatisticsLegendItem.vue +3 -1
  67. package/src/component/FluxStatisticsLineChart.vue +38 -41
  68. package/src/component/FluxStatisticsMixedChart.vue +55 -0
  69. package/src/component/FluxStatisticsPieChart.vue +39 -18
  70. package/src/component/FluxStatisticsPolarAreaChart.vue +53 -0
  71. package/src/component/FluxStatisticsRadarChart.vue +108 -0
  72. package/src/component/FluxStatisticsRadialBar.vue +48 -0
  73. package/src/component/FluxStatisticsScatterChart.vue +56 -0
  74. package/src/component/FluxStatisticsSparkline.vue +67 -0
  75. package/src/component/FluxStatisticsTreemapChart.vue +39 -0
  76. package/src/component/index.ts +13 -0
  77. package/src/composable/index.ts +10 -0
  78. package/src/composable/useChartHoverSync.ts +92 -0
  79. package/src/composable/useChartLegend.ts +23 -0
  80. package/src/composable/useChartSeriesSetup.ts +75 -0
  81. package/src/composable/useECharts.ts +55 -0
  82. package/src/composable/usePieSlicesSetup.ts +58 -0
  83. package/src/css/Base.module.scss +28 -1
  84. package/src/css/Chart.module.scss +66 -32
  85. package/src/css/ChartPane.module.scss +24 -9
  86. package/src/css/Comparison.module.scss +52 -0
  87. package/src/css/Empty.module.scss +39 -0
  88. package/src/css/Grid.module.scss +1 -0
  89. package/src/css/Legend.module.scss +11 -1
  90. package/src/css/Metric.module.scss +6 -0
  91. package/src/css/Sparkline.module.scss +13 -0
  92. package/src/echarts.ts +47 -0
  93. package/src/index.ts +7 -3
  94. package/src/util/baseOptions.ts +74 -0
  95. package/src/util/colors.ts +86 -0
  96. package/src/util/convert.ts +360 -0
  97. package/src/util/defaultOptions.ts +398 -0
  98. package/src/util/iconSvg.ts +20 -0
  99. package/src/util/index.ts +7 -0
  100. package/src/util/seriesDefaults.ts +210 -0
  101. package/src/util/sparklineOptions.ts +67 -0
@@ -0,0 +1,13 @@
1
+ .statisticsSparkline {
2
+ position: relative;
3
+ display: block;
4
+ width: 100%;
5
+ height: 40px;
6
+ min-width: 60px;
7
+ }
8
+
9
+ .statisticsSparklineChart {
10
+ position: absolute;
11
+ display: block;
12
+ inset: 0;
13
+ }
package/src/echarts.ts ADDED
@@ -0,0 +1,47 @@
1
+ import {
2
+ BarChart,
3
+ BoxplotChart,
4
+ CandlestickChart,
5
+ GaugeChart,
6
+ HeatmapChart,
7
+ LineChart,
8
+ PieChart,
9
+ RadarChart,
10
+ ScatterChart,
11
+ TreemapChart
12
+ } from 'echarts/charts';
13
+ import {
14
+ AxisPointerComponent,
15
+ GridComponent,
16
+ LegendComponent,
17
+ RadarComponent,
18
+ TitleComponent,
19
+ TooltipComponent,
20
+ VisualMapComponent
21
+ } from 'echarts/components';
22
+ import { use } from 'echarts/core';
23
+ import { LabelLayout, LegacyGridContainLabel } from 'echarts/features';
24
+ import { CanvasRenderer } from 'echarts/renderers';
25
+
26
+ use([
27
+ BarChart,
28
+ BoxplotChart,
29
+ CandlestickChart,
30
+ GaugeChart,
31
+ HeatmapChart,
32
+ LineChart,
33
+ PieChart,
34
+ RadarChart,
35
+ ScatterChart,
36
+ TreemapChart,
37
+ AxisPointerComponent,
38
+ GridComponent,
39
+ LegendComponent,
40
+ RadarComponent,
41
+ TitleComponent,
42
+ TooltipComponent,
43
+ VisualMapComponent,
44
+ LabelLayout,
45
+ LegacyGridContainLabel,
46
+ CanvasRenderer
47
+ ]);
package/src/index.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import { amber500, blue500, cyan500, emerald500, fuchsia500, green500, indigo500, lime500, orange500, pink500, purple500, red500, rose50, sky500, teal500, violet500, yellow500 } from '@flux-ui/internals';
2
+ import type { FluxStatisticsChartColor } from '@flux-ui/types';
2
3
 
3
4
  export * from './component';
5
+ export * from './composable';
4
6
 
5
- export const CHART_COLORS = [
7
+ export type { ChartTooltipValueFormatter, SharedTooltipItem, Translator } from './util';
8
+
9
+ export const CHART_COLORS: readonly FluxStatisticsChartColor[] = [
6
10
  'var(--chart-1)',
7
11
  'var(--chart-2)',
8
12
  'var(--chart-3)',
9
13
  'var(--chart-4)'
10
14
  ];
11
15
 
12
- export const CHART_COLORFUL_COLORS = [
16
+ export const CHART_COLORFUL_COLORS: readonly FluxStatisticsChartColor[] = [
13
17
  red500,
14
18
  orange500,
15
19
  amber500,
@@ -27,4 +31,4 @@ export const CHART_COLORFUL_COLORS = [
27
31
  fuchsia500,
28
32
  pink500,
29
33
  rose50
30
- ];
34
+ ] as FluxStatisticsChartColor[];
@@ -0,0 +1,74 @@
1
+ import type { EChartsOption } from '~flux/statistics/composable';
2
+
3
+ const FOREGROUND_LABEL = { show: true, color: 'var(--foreground-secondary)' } as const;
4
+ const HIDDEN_AXIS = { show: false } as const;
5
+ const DASHED_SPLIT_LINE = { show: true, lineStyle: { type: 'dashed' as const, color: 'var(--gray-200)' } } as const;
6
+
7
+ interface CartesianBaseConfig {
8
+ readonly xAxisType?: 'category' | 'value';
9
+ readonly yAxisType?: 'value' | 'category';
10
+ readonly scale?: boolean;
11
+ readonly tooltipTrigger?: 'axis' | 'item';
12
+ readonly xAxisLabels?: boolean;
13
+ readonly yAxisLabels?: boolean;
14
+ readonly splitLines?: boolean;
15
+ }
16
+
17
+ export function buildCartesianGrid(xAxisLabels: boolean, yAxisLabels: boolean): EChartsOption['grid'] {
18
+ if (!xAxisLabels && !yAxisLabels) {
19
+ return { left: 0, right: 0, top: 0, bottom: 0, containLabel: false };
20
+ }
21
+
22
+ return {
23
+ left: yAxisLabels ? 9 : 0,
24
+ right: 9,
25
+ top: 9,
26
+ bottom: xAxisLabels ? 9 : 0,
27
+ containLabel: true
28
+ };
29
+ }
30
+
31
+ export function buildCartesianBaseOptions(config: CartesianBaseConfig = {}): EChartsOption {
32
+ const xAxisLabels = config.xAxisLabels ?? false;
33
+ const yAxisLabels = config.yAxisLabels ?? false;
34
+ const showSplitLines = config.splitLines ?? false;
35
+
36
+ const xSplitLine = showSplitLines && config.xAxisType === 'value' ? DASHED_SPLIT_LINE : HIDDEN_AXIS;
37
+ const ySplitLine = showSplitLines && config.yAxisType !== 'category' ? DASHED_SPLIT_LINE : HIDDEN_AXIS;
38
+
39
+ return {
40
+ grid: buildCartesianGrid(xAxisLabels, yAxisLabels),
41
+ tooltip: { trigger: config.tooltipTrigger ?? 'item' },
42
+ xAxis: {
43
+ type: config.xAxisType ?? 'category',
44
+ show: true,
45
+ scale: config.scale && config.xAxisType === 'value',
46
+ splitLine: xSplitLine,
47
+ axisLabel: xAxisLabels ? FOREGROUND_LABEL : HIDDEN_AXIS,
48
+ axisLine: HIDDEN_AXIS,
49
+ axisTick: HIDDEN_AXIS
50
+ },
51
+ yAxis: {
52
+ type: config.yAxisType ?? 'value',
53
+ show: true,
54
+ scale: config.scale && config.yAxisType !== 'category',
55
+ splitLine: ySplitLine,
56
+ axisLabel: yAxisLabels ? FOREGROUND_LABEL : HIDDEN_AXIS,
57
+ axisLine: HIDDEN_AXIS,
58
+ axisTick: HIDDEN_AXIS
59
+ }
60
+ };
61
+ }
62
+
63
+ export const POLAR_BASE_OPTIONS: EChartsOption = {
64
+ tooltip: { show: false },
65
+ xAxis: { show: false },
66
+ yAxis: { show: false },
67
+ grid: { show: false }
68
+ };
69
+
70
+ export const SPARKLINE_AXIS_BASE: EChartsOption = {
71
+ tooltip: { trigger: 'axis' },
72
+ xAxis: { show: false, boundaryGap: false },
73
+ yAxis: { show: false, min: 'dataMin', max: 'dataMax' }
74
+ };
@@ -0,0 +1,86 @@
1
+ import { ref } from 'vue';
2
+
3
+ const CSS_VAR_PATTERN = /var\((--[^,)]+)(?:,\s*([^)]+))?\)/g;
4
+
5
+ const themeVersion = ref(0);
6
+
7
+ if (typeof document !== 'undefined') {
8
+ new MutationObserver(() => themeVersion.value++).observe(
9
+ document.documentElement,
10
+ { attributes: true, attributeFilter: ['class', 'data-theme'] }
11
+ );
12
+ }
13
+
14
+ export function useCssVarVersion() {
15
+ return themeVersion;
16
+ }
17
+
18
+ export function resolveCssVar(input: string, root?: HTMLElement): string {
19
+ if (typeof document === 'undefined' || !input.includes('var(')) {
20
+ return input;
21
+ }
22
+
23
+ const target = root ?? document.documentElement;
24
+
25
+ return input.replace(CSS_VAR_PATTERN, (match, name: string, fallback?: string) => {
26
+ const value = getComputedStyle(target).getPropertyValue(name).trim();
27
+
28
+ if (value) {
29
+ return value;
30
+ }
31
+
32
+ return fallback ? resolveCssVar(fallback.trim(), target) : match;
33
+ });
34
+ }
35
+
36
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
37
+ if (value === null || typeof value !== 'object') {
38
+ return false;
39
+ }
40
+
41
+ const proto = Object.getPrototypeOf(value);
42
+ return proto === Object.prototype || proto === null;
43
+ }
44
+
45
+ export function deepResolveCssVars<T>(value: T, root?: HTMLElement): T {
46
+ if (typeof value === 'string') {
47
+ return (value.includes('var(') ? resolveCssVar(value, root) : value) as T;
48
+ }
49
+
50
+ if (Array.isArray(value)) {
51
+ let changed = false;
52
+ const out: unknown[] = new Array(value.length);
53
+
54
+ for (let i = 0; i < value.length; i++) {
55
+ const resolved = deepResolveCssVars(value[i], root);
56
+
57
+ if (resolved !== value[i]) {
58
+ changed = true;
59
+ }
60
+
61
+ out[i] = resolved;
62
+ }
63
+
64
+ return (changed ? out : value) as T;
65
+ }
66
+
67
+ if (!isPlainObject(value)) {
68
+ return value;
69
+ }
70
+
71
+ let changed = false;
72
+ const out: Record<string, unknown> = {};
73
+
74
+ for (const key of Object.keys(value)) {
75
+ const original = value[key];
76
+ const resolved = deepResolveCssVars(original, root);
77
+
78
+ if (resolved !== original) {
79
+ changed = true;
80
+ }
81
+
82
+ out[key] = resolved;
83
+ }
84
+
85
+ return (changed ? out : value) as T;
86
+ }
@@ -0,0 +1,360 @@
1
+ import type {
2
+ FluxStatisticsChartAreaSeries,
3
+ FluxStatisticsChartBarSeries,
4
+ FluxStatisticsChartBoxPlotSeries,
5
+ FluxStatisticsChartBubbleSeries,
6
+ FluxStatisticsChartCandlestickSeries,
7
+ FluxStatisticsChartCartesianSeries,
8
+ FluxStatisticsChartCategoryPoint,
9
+ FluxStatisticsChartColor,
10
+ FluxStatisticsChartGaugeSeries,
11
+ FluxStatisticsChartHeatmapSeries,
12
+ FluxStatisticsChartLineSeries,
13
+ FluxStatisticsChartMixedSeries,
14
+ FluxStatisticsChartPieSlice,
15
+ FluxStatisticsChartRadarSeries,
16
+ FluxStatisticsChartScatterSeries,
17
+ FluxStatisticsChartTreemapNode
18
+ } from '@flux-ui/types';
19
+ import type {
20
+ BarSeriesOption,
21
+ BoxplotSeriesOption,
22
+ CandlestickSeriesOption,
23
+ GaugeSeriesOption,
24
+ HeatmapSeriesOption,
25
+ LineSeriesOption,
26
+ PieSeriesOption,
27
+ RadarSeriesOption,
28
+ ScatterSeriesOption,
29
+ TreemapSeriesOption
30
+ } from 'echarts/charts';
31
+ import {
32
+ AREA_SERIES_DEFAULTS,
33
+ BAR_SERIES_DEFAULTS,
34
+ BOXPLOT_SERIES_DEFAULTS,
35
+ BUBBLE_SERIES_DEFAULTS,
36
+ CANDLESTICK_SERIES_DEFAULTS,
37
+ DONUT_SERIES_DEFAULTS,
38
+ GAUGE_SERIES_DEFAULTS,
39
+ HEATMAP_SERIES_DEFAULTS,
40
+ LINE_SERIES_DEFAULTS,
41
+ PIE_SERIES_DEFAULTS,
42
+ POLAR_AREA_SERIES_DEFAULTS,
43
+ RADAR_SERIES_DEFAULTS,
44
+ SCATTER_SERIES_DEFAULTS,
45
+ TREEMAP_SERIES_DEFAULTS
46
+ } from './seriesDefaults';
47
+
48
+ export function resolveChartColor(color?: FluxStatisticsChartColor): string | undefined {
49
+ if (!color) {
50
+ return undefined;
51
+ }
52
+
53
+ if (color.startsWith('#') || color.startsWith('var(')) {
54
+ return color;
55
+ }
56
+
57
+ return `var(--${color}-600)`;
58
+ }
59
+
60
+ export function extractLabels(
61
+ series: readonly { readonly data: readonly (number | FluxStatisticsChartCategoryPoint)[] }[]
62
+ ): readonly string[] | undefined {
63
+ for (const s of series) {
64
+ const labels: string[] = [];
65
+ let hasAny = false;
66
+
67
+ for (const point of s.data) {
68
+ if (typeof point === 'object' && point !== null && typeof point.label === 'string') {
69
+ labels.push(point.label);
70
+ hasAny = true;
71
+ } else {
72
+ labels.push('');
73
+ }
74
+ }
75
+
76
+ if (hasAny) {
77
+ return labels;
78
+ }
79
+ }
80
+
81
+ return undefined;
82
+ }
83
+
84
+ function extractValues(data: readonly (number | FluxStatisticsChartCategoryPoint)[]): number[] {
85
+ return data.map(point => typeof point === 'number' ? point : point.value);
86
+ }
87
+
88
+ export function toLineSeries(s: FluxStatisticsChartLineSeries, fallbackColor: string): LineSeriesOption {
89
+ return {
90
+ ...LINE_SERIES_DEFAULTS,
91
+ name: s.name,
92
+ data: extractValues(s.data),
93
+ color: resolveChartColor(s.color) ?? fallbackColor
94
+ };
95
+ }
96
+
97
+ export function toAreaSeries(s: FluxStatisticsChartAreaSeries, fallbackColor: string): LineSeriesOption {
98
+ return {
99
+ ...AREA_SERIES_DEFAULTS,
100
+ name: s.name,
101
+ data: extractValues(s.data),
102
+ color: resolveChartColor(s.color) ?? fallbackColor
103
+ };
104
+ }
105
+
106
+ export function toBarSeries(s: FluxStatisticsChartBarSeries, fallbackColor: string): BarSeriesOption {
107
+ return {
108
+ ...BAR_SERIES_DEFAULTS,
109
+ name: s.name,
110
+ data: extractValues(s.data),
111
+ color: resolveChartColor(s.color) ?? fallbackColor
112
+ };
113
+ }
114
+
115
+ export function toMixedSeries(s: FluxStatisticsChartMixedSeries, fallbackColor: string): LineSeriesOption | BarSeriesOption {
116
+ const color = resolveChartColor(s.color) ?? fallbackColor;
117
+ const values = extractValues(s.data);
118
+
119
+ if (s.type === 'bar') {
120
+ return {
121
+ ...BAR_SERIES_DEFAULTS,
122
+ barCategoryGap: '55%',
123
+ name: s.name,
124
+ data: values,
125
+ color
126
+ };
127
+ }
128
+
129
+ if (s.type === 'area') {
130
+ return {
131
+ ...AREA_SERIES_DEFAULTS,
132
+ name: s.name,
133
+ data: values,
134
+ color
135
+ };
136
+ }
137
+
138
+ return {
139
+ ...LINE_SERIES_DEFAULTS,
140
+ lineStyle: { width: 2 },
141
+ name: s.name,
142
+ data: values,
143
+ color
144
+ };
145
+ }
146
+
147
+ interface PieDataItem {
148
+ readonly name: string;
149
+ readonly value: number;
150
+ readonly itemStyle?: { readonly color: string };
151
+ }
152
+
153
+ function buildPieData(slices: readonly FluxStatisticsChartPieSlice[], palette: readonly string[]): PieDataItem[] {
154
+ return slices.map((slice, idx) => {
155
+ const color = resolveChartColor(slice.color) ?? palette[idx % palette.length];
156
+
157
+ return {
158
+ name: slice.label,
159
+ value: slice.value,
160
+ itemStyle: { color }
161
+ };
162
+ });
163
+ }
164
+
165
+ export function toPieSeries(slices: readonly FluxStatisticsChartPieSlice[], palette: readonly string[]): PieSeriesOption {
166
+ return {
167
+ ...PIE_SERIES_DEFAULTS,
168
+ data: buildPieData(slices, palette)
169
+ };
170
+ }
171
+
172
+ export function toDonutSeries(slices: readonly FluxStatisticsChartPieSlice[], palette: readonly string[]): PieSeriesOption {
173
+ return {
174
+ ...DONUT_SERIES_DEFAULTS,
175
+ data: buildPieData(slices, palette)
176
+ };
177
+ }
178
+
179
+ export function toPolarAreaSeries(slices: readonly FluxStatisticsChartPieSlice[], palette: readonly string[]): PieSeriesOption {
180
+ return {
181
+ ...POLAR_AREA_SERIES_DEFAULTS,
182
+ data: buildPieData(slices, palette)
183
+ };
184
+ }
185
+
186
+ interface RadarRing {
187
+ readonly value: readonly number[];
188
+ readonly name?: string;
189
+ }
190
+
191
+ export function toRadarSeries(series: readonly FluxStatisticsChartRadarSeries[], palette: readonly string[]): RadarSeriesOption {
192
+ const data: RadarRing[] = series.map(ring => ({
193
+ value: ring.values,
194
+ name: ring.name
195
+ }));
196
+
197
+ return {
198
+ ...RADAR_SERIES_DEFAULTS,
199
+ data,
200
+ color: palette as string[]
201
+ } as RadarSeriesOption;
202
+ }
203
+
204
+ export function toScatterSeries(s: FluxStatisticsChartScatterSeries, fallbackColor: string): ScatterSeriesOption {
205
+ return {
206
+ ...SCATTER_SERIES_DEFAULTS,
207
+ name: s.name,
208
+ data: s.data.map(point => [point.x, point.y]),
209
+ color: resolveChartColor(s.color) ?? fallbackColor
210
+ };
211
+ }
212
+
213
+ export function toBubbleSeries(s: FluxStatisticsChartBubbleSeries, fallbackColor: string): ScatterSeriesOption {
214
+ const sizes = s.data.map(p => p.size);
215
+ const maxSize = Math.max(1, ...sizes);
216
+
217
+ return {
218
+ ...BUBBLE_SERIES_DEFAULTS,
219
+ name: s.name,
220
+ data: s.data.map(point => [point.x, point.y, point.size]),
221
+ symbolSize: (val: unknown) => {
222
+ const size = Array.isArray(val) ? (val[2] as number) : 0;
223
+ return 14 + (size / maxSize) * 30;
224
+ },
225
+ color: resolveChartColor(s.color) ?? fallbackColor
226
+ };
227
+ }
228
+
229
+ export function toCandlestickSeries(s: FluxStatisticsChartCandlestickSeries): CandlestickSeriesOption {
230
+ const positive = resolveChartColor(s.positiveColor) ?? 'var(--success-500)';
231
+ const negative = resolveChartColor(s.negativeColor) ?? 'var(--danger-500)';
232
+
233
+ return {
234
+ ...CANDLESTICK_SERIES_DEFAULTS,
235
+ name: s.name,
236
+ data: s.data.map(point => [point.open, point.close, point.low, point.high]),
237
+ itemStyle: {
238
+ color: positive,
239
+ color0: negative,
240
+ borderColor: positive,
241
+ borderColor0: negative
242
+ }
243
+ };
244
+ }
245
+
246
+ export function toBoxPlotSeries(s: FluxStatisticsChartBoxPlotSeries, fallbackColor: string): BoxplotSeriesOption {
247
+ const color = resolveChartColor(s.color) ?? fallbackColor;
248
+
249
+ return {
250
+ ...BOXPLOT_SERIES_DEFAULTS,
251
+ name: s.name,
252
+ data: s.data.map(point => [point.min, point.q1, point.median, point.q3, point.max]),
253
+ itemStyle: {
254
+ color,
255
+ borderColor: 'var(--foreground-prominent)',
256
+ borderWidth: 1
257
+ }
258
+ };
259
+ }
260
+
261
+ export function toHeatmapSeries(
262
+ s: FluxStatisticsChartHeatmapSeries,
263
+ xLabels: readonly (string | number)[],
264
+ yLabels: readonly (string | number)[]
265
+ ): HeatmapSeriesOption {
266
+ const data = s.data.map(point => {
267
+ const xIdx = typeof point.x === 'number' ? point.x : xLabels.indexOf(point.x);
268
+ const yIdx = typeof point.y === 'number' ? point.y : yLabels.indexOf(point.y);
269
+
270
+ return [xIdx, yIdx, point.value];
271
+ });
272
+
273
+ return {
274
+ ...HEATMAP_SERIES_DEFAULTS,
275
+ name: s.name,
276
+ data
277
+ };
278
+ }
279
+
280
+ interface TreemapDataItem {
281
+ readonly name: string;
282
+ readonly value?: number;
283
+ readonly itemStyle?: { readonly color: string };
284
+ readonly children?: TreemapDataItem[];
285
+ }
286
+
287
+ function mapTreemapNode(node: FluxStatisticsChartTreemapNode, palette: readonly string[], index: number): TreemapDataItem {
288
+ const resolved = resolveChartColor(node.color);
289
+ const color = resolved ?? palette[index % palette.length];
290
+
291
+ return {
292
+ name: node.name,
293
+ value: node.value,
294
+ itemStyle: { color },
295
+ children: node.children?.map((child, idx) => mapTreemapNode(child, palette, idx))
296
+ };
297
+ }
298
+
299
+ export function toTreemapSeries(
300
+ nodes: readonly FluxStatisticsChartTreemapNode[],
301
+ palette: readonly string[]
302
+ ): TreemapSeriesOption {
303
+ return {
304
+ ...TREEMAP_SERIES_DEFAULTS,
305
+ data: nodes.map((node, idx) => mapTreemapNode(node, palette, idx)),
306
+ levels: [{
307
+ itemStyle: { borderColor: 'var(--surface)', borderWidth: 3, gapWidth: 0 }
308
+ }, {
309
+ itemStyle: { gapWidth: 1 }
310
+ }, {
311
+ itemStyle: { gapWidth: 1 }
312
+ }]
313
+ } as TreemapSeriesOption;
314
+ }
315
+
316
+ export function toGaugeSeries(
317
+ s: FluxStatisticsChartGaugeSeries,
318
+ fallbackColor: string,
319
+ index: number,
320
+ total: number
321
+ ): GaugeSeriesOption {
322
+ const color = resolveChartColor(s.color) ?? fallbackColor;
323
+ const ringStep = total > 1 ? 30 : 0;
324
+ const baseRadius = s.radius ?? (90 - index * ringStep);
325
+ const labelOffset = (total - 1 - index) * 22 - 30;
326
+
327
+ return {
328
+ ...GAUGE_SERIES_DEFAULTS,
329
+ radius: `${baseRadius}%`,
330
+ data: [{ value: s.value, name: s.name, itemStyle: { color } }],
331
+ progress: {
332
+ show: true,
333
+ width: 14,
334
+ roundCap: true,
335
+ itemStyle: { color }
336
+ },
337
+ title: {
338
+ show: true,
339
+ color: 'var(--foreground-secondary)',
340
+ fontSize: 12,
341
+ fontWeight: 500,
342
+ fontFamily: 'inherit',
343
+ offsetCenter: [0, `${labelOffset}%`]
344
+ },
345
+ detail: {
346
+ show: total === 1,
347
+ color: 'var(--foreground-prominent)',
348
+ fontSize: 28,
349
+ fontWeight: 800,
350
+ fontFamily: 'inherit',
351
+ offsetCenter: [0, '10%'],
352
+ formatter: '{value}%'
353
+ }
354
+ } as GaugeSeriesOption;
355
+ }
356
+
357
+ export function cartesianFallbackLabels(series: readonly FluxStatisticsChartCartesianSeries[]): string[] {
358
+ const longest = series.reduce((max, s) => Math.max(max, s.data.length), 0);
359
+ return Array.from({ length: longest }, (_, i) => String(i + 1));
360
+ }