@flux-ui/statistics 3.0.0-next.67 → 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 (93) hide show
  1. package/README.md +1 -1
  2. package/dist/component/FluxStatisticsAreaChart.vue.d.ts +11 -4
  3. package/dist/component/FluxStatisticsBarChart.vue.d.ts +11 -4
  4. package/dist/component/FluxStatisticsBase.vue.d.ts +1 -0
  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/FluxStatisticsChart.vue.d.ts +7 -5
  9. package/dist/component/FluxStatisticsChartPane.vue.d.ts +1 -0
  10. package/dist/component/FluxStatisticsComparison.vue.d.ts +16 -0
  11. package/dist/component/FluxStatisticsDonutChart.vue.d.ts +8 -4
  12. package/dist/component/FluxStatisticsEmpty.vue.d.ts +19 -0
  13. package/dist/component/FluxStatisticsHeatmapChart.vue.d.ts +15 -0
  14. package/dist/component/FluxStatisticsLegendItem.vue.d.ts +1 -0
  15. package/dist/component/FluxStatisticsLineChart.vue.d.ts +11 -4
  16. package/dist/component/FluxStatisticsMixedChart.vue.d.ts +17 -0
  17. package/dist/component/FluxStatisticsPieChart.vue.d.ts +8 -4
  18. package/dist/component/FluxStatisticsPolarAreaChart.vue.d.ts +14 -0
  19. package/dist/component/FluxStatisticsRadarChart.vue.d.ts +12 -0
  20. package/dist/component/FluxStatisticsRadialBar.vue.d.ts +11 -0
  21. package/dist/component/FluxStatisticsScatterChart.vue.d.ts +14 -0
  22. package/dist/component/FluxStatisticsSparkline.vue.d.ts +13 -0
  23. package/dist/component/FluxStatisticsTreemapChart.vue.d.ts +11 -0
  24. package/dist/component/index.d.ts +13 -0
  25. package/dist/composable/index.d.ts +10 -0
  26. package/dist/composable/useChartHoverSync.d.ts +9 -0
  27. package/dist/composable/useChartLegend.d.ts +14 -0
  28. package/dist/composable/useChartSeriesSetup.d.ts +23 -0
  29. package/dist/composable/useECharts.d.ts +9 -0
  30. package/dist/composable/usePieSlicesSetup.d.ts +14 -0
  31. package/dist/echarts.d.ts +1 -0
  32. package/dist/index.css +230 -37
  33. package/dist/index.d.ts +5 -2
  34. package/dist/index.js +10919 -9041
  35. package/dist/index.js.map +1 -1
  36. package/dist/util/baseOptions.d.ts +15 -0
  37. package/dist/util/colors.d.ts +4 -0
  38. package/dist/util/convert.d.ts +22 -0
  39. package/dist/util/defaultOptions.d.ts +76 -0
  40. package/dist/util/iconSvg.d.ts +2 -0
  41. package/dist/util/index.d.ts +7 -0
  42. package/dist/util/seriesDefaults.d.ts +15 -0
  43. package/dist/util/sparklineOptions.d.ts +7 -0
  44. package/package.json +14 -15
  45. package/src/component/FluxStatisticsAreaChart.vue +38 -41
  46. package/src/component/FluxStatisticsBarChart.vue +38 -33
  47. package/src/component/FluxStatisticsBase.vue +14 -1
  48. package/src/component/FluxStatisticsBoxPlotChart.vue +69 -0
  49. package/src/component/FluxStatisticsBubbleChart.vue +56 -0
  50. package/src/component/FluxStatisticsCandlestickChart.vue +81 -0
  51. package/src/component/FluxStatisticsChart.vue +19 -169
  52. package/src/component/FluxStatisticsChartPane.vue +23 -11
  53. package/src/component/FluxStatisticsComparison.vue +113 -0
  54. package/src/component/FluxStatisticsDonutChart.vue +39 -18
  55. package/src/component/FluxStatisticsEmpty.vue +44 -0
  56. package/src/component/FluxStatisticsHeatmapChart.vue +80 -0
  57. package/src/component/FluxStatisticsLegend.vue +33 -1
  58. package/src/component/FluxStatisticsLegendItem.vue +3 -1
  59. package/src/component/FluxStatisticsLineChart.vue +38 -41
  60. package/src/component/FluxStatisticsMixedChart.vue +55 -0
  61. package/src/component/FluxStatisticsPieChart.vue +39 -18
  62. package/src/component/FluxStatisticsPolarAreaChart.vue +53 -0
  63. package/src/component/FluxStatisticsRadarChart.vue +108 -0
  64. package/src/component/FluxStatisticsRadialBar.vue +48 -0
  65. package/src/component/FluxStatisticsScatterChart.vue +56 -0
  66. package/src/component/FluxStatisticsSparkline.vue +67 -0
  67. package/src/component/FluxStatisticsTreemapChart.vue +39 -0
  68. package/src/component/index.ts +13 -0
  69. package/src/composable/index.ts +10 -0
  70. package/src/composable/useChartHoverSync.ts +92 -0
  71. package/src/composable/useChartLegend.ts +23 -0
  72. package/src/composable/useChartSeriesSetup.ts +75 -0
  73. package/src/composable/useECharts.ts +55 -0
  74. package/src/composable/usePieSlicesSetup.ts +58 -0
  75. package/src/css/Base.module.scss +28 -1
  76. package/src/css/Chart.module.scss +66 -32
  77. package/src/css/ChartPane.module.scss +24 -9
  78. package/src/css/Comparison.module.scss +52 -0
  79. package/src/css/Empty.module.scss +39 -0
  80. package/src/css/Grid.module.scss +1 -0
  81. package/src/css/Legend.module.scss +11 -1
  82. package/src/css/Metric.module.scss +6 -0
  83. package/src/css/Sparkline.module.scss +13 -0
  84. package/src/echarts.ts +47 -0
  85. package/src/index.ts +7 -3
  86. package/src/util/baseOptions.ts +74 -0
  87. package/src/util/colors.ts +86 -0
  88. package/src/util/convert.ts +360 -0
  89. package/src/util/defaultOptions.ts +398 -0
  90. package/src/util/iconSvg.ts +20 -0
  91. package/src/util/index.ts +7 -0
  92. package/src/util/seriesDefaults.ts +210 -0
  93. package/src/util/sparklineOptions.ts +67 -0
@@ -0,0 +1,398 @@
1
+ import { formatNumber } from '@basmilius/utils';
2
+ import type { FluxIconName } from '@flux-ui/types';
3
+ import type { EChartsOption } from '~flux/statistics/composable';
4
+ import { renderIconSvg } from './iconSvg';
5
+
6
+ export interface TooltipParam {
7
+ readonly seriesName?: string;
8
+ readonly seriesIndex: number;
9
+ readonly color: string;
10
+ readonly value: number | string | (number | string)[];
11
+ readonly name: string;
12
+ readonly dataIndex?: number;
13
+ readonly axisValue?: string;
14
+ readonly axisValueLabel?: string;
15
+ readonly data?: unknown;
16
+ readonly marker?: string;
17
+ }
18
+
19
+ export type TooltipStyleClasses = {
20
+ readonly statisticsChartTooltipTitle: string;
21
+ readonly statisticsChartTooltipBody: string;
22
+ readonly statisticsChartTooltipSection: string;
23
+ readonly statisticsChartTooltipSectionTitle: string;
24
+ readonly statisticsChartTooltipSectionBody: string;
25
+ readonly statisticsChartTooltipSeriesColor: string;
26
+ readonly statisticsChartTooltipSeriesIcon: string;
27
+ readonly statisticsChartTooltipSeriesName: string;
28
+ readonly statisticsChartTooltipSeriesValue: string;
29
+ readonly isActive: string;
30
+ readonly isDimmed: string;
31
+ };
32
+
33
+ export type Translator = (key: string) => string;
34
+
35
+ export interface SharedTooltipItem {
36
+ readonly name: string;
37
+ readonly value: number | string;
38
+ readonly color: string;
39
+ readonly icon?: FluxIconName;
40
+ readonly seriesIndex?: number;
41
+ readonly dataIndex?: number;
42
+ }
43
+
44
+ export type ChartTooltipValueFormatter = (value: number | string, item: SharedTooltipItem) => string;
45
+
46
+ export const CHART_DEFAULT_COLORS = [
47
+ 'var(--chart-1)',
48
+ 'var(--chart-2)',
49
+ 'var(--chart-3)',
50
+ 'var(--chart-4)'
51
+ ] as const;
52
+
53
+ export function buildTooltipFormatter(
54
+ t: Translator,
55
+ styles: TooltipStyleClasses,
56
+ getSeriesIcons?: () => readonly (FluxIconName | undefined)[],
57
+ valueFormatter?: ChartTooltipValueFormatter
58
+ ) {
59
+ return (params: TooltipParam | TooltipParam[]): string => {
60
+ const items = Array.isArray(params) ? params : [params];
61
+
62
+ if (items.length === 0) {
63
+ return '';
64
+ }
65
+
66
+ const rawTitle = items[0].axisValueLabel ?? items[0].axisValue ?? items[0].name ?? '';
67
+ const title = rawTitle ? t(String(rawTitle)) : '';
68
+ const icons = getSeriesIcons?.();
69
+
70
+ return renderTooltip(t, styles, title, items.map(param => ({
71
+ name: param.seriesName ?? '',
72
+ value: extractValue(param.value),
73
+ color: param.color,
74
+ icon: icons?.[param.seriesIndex],
75
+ seriesIndex: param.seriesIndex,
76
+ dataIndex: param.dataIndex
77
+ })), -1, valueFormatter);
78
+ };
79
+ }
80
+
81
+ export function buildCartesianTooltipOptions(
82
+ t: Translator,
83
+ styles: TooltipStyleClasses,
84
+ getSeriesIcons: () => readonly (FluxIconName | undefined)[],
85
+ valueFormatter?: ChartTooltipValueFormatter
86
+ ): EChartsOption {
87
+ return {
88
+ tooltip: {
89
+ show: true,
90
+ trigger: 'axis',
91
+ formatter: buildTooltipFormatter(t, styles, getSeriesIcons, valueFormatter) as never
92
+ }
93
+ };
94
+ }
95
+
96
+ export function buildSharedItemTooltipFormatter(
97
+ t: Translator,
98
+ styles: TooltipStyleClasses,
99
+ getItems: () => readonly SharedTooltipItem[],
100
+ getTitle?: () => string | undefined,
101
+ valueFormatter?: ChartTooltipValueFormatter
102
+ ) {
103
+ return (params: TooltipParam | TooltipParam[]): string => {
104
+ const param = Array.isArray(params) ? params[0] : params;
105
+
106
+ if (!param) {
107
+ return '';
108
+ }
109
+
110
+ const items = getItems();
111
+ const activeIndex = param.dataIndex ?? -1;
112
+ const rawTitle = getTitle?.() ?? '';
113
+ const title = rawTitle ? t(String(rawTitle)) : '';
114
+
115
+ return renderTooltip(t, styles, title, items, activeIndex, valueFormatter);
116
+ };
117
+ }
118
+
119
+ function renderTooltip(
120
+ t: Translator,
121
+ styles: TooltipStyleClasses,
122
+ title: string,
123
+ items: readonly SharedTooltipItem[],
124
+ activeIndex: number = -1,
125
+ valueFormatter?: ChartTooltipValueFormatter
126
+ ): string {
127
+ if (items.length === 0) {
128
+ return '';
129
+ }
130
+
131
+ const titleHtml = title
132
+ ? `<div class="${styles.statisticsChartTooltipTitle}">${title}</div>`
133
+ : '';
134
+
135
+ const hasActive = activeIndex !== -1;
136
+
137
+ const body = items.map((item, idx) => {
138
+ const isActive = !hasActive || idx === activeIndex;
139
+ const activeClass = isActive ? ` ${styles.isActive}` : '';
140
+ const translatedName = item.name ? t(String(item.name)) : '';
141
+
142
+ const marker = item.icon
143
+ ? `<div class="${styles.statisticsChartTooltipSeriesIcon}${activeClass}" style="color: ${item.color}">${renderIconSvg(item.icon, item.color, 14)}</div>`
144
+ : `<div class="${styles.statisticsChartTooltipSeriesColor}${activeClass}" style="background: ${item.color}"></div>`;
145
+
146
+ const display = valueFormatter ? valueFormatter(item.value, item) : formatValue(item.value);
147
+
148
+ return `
149
+ ${marker}
150
+ <div class="${styles.statisticsChartTooltipSeriesName}${activeClass}">${translatedName}</div>
151
+ <div class="${styles.statisticsChartTooltipSeriesValue}${activeClass}">${display}</div>
152
+ `;
153
+ }).join('');
154
+
155
+ return `${titleHtml}<div class="${styles.statisticsChartTooltipBody}">${body}</div>`;
156
+ }
157
+
158
+ function extractValue(value: TooltipParam['value']): number | string {
159
+ if (Array.isArray(value)) {
160
+ const last = value[value.length - 1];
161
+ return typeof last === 'number' || typeof last === 'string' ? last : '';
162
+ }
163
+
164
+ return value ?? '';
165
+ }
166
+
167
+ function formatValue(value: TooltipParam['value']): string {
168
+ if (Array.isArray(value)) {
169
+ const last = value[value.length - 1];
170
+ return formatValue(last);
171
+ }
172
+
173
+ if (typeof value === 'number') {
174
+ return Number.isInteger(value)
175
+ ? formatNumber(value)
176
+ : value.toString();
177
+ }
178
+
179
+ return String(value ?? '');
180
+ }
181
+
182
+ export interface BoxPlotTooltipPoint {
183
+ readonly label?: string;
184
+ readonly min: number;
185
+ readonly q1: number;
186
+ readonly median: number;
187
+ readonly q3: number;
188
+ readonly max: number;
189
+ }
190
+
191
+ export function buildBoxPlotTooltipOptions(
192
+ t: Translator,
193
+ styles: TooltipStyleClasses,
194
+ getSeries: () => readonly { readonly name?: string; readonly icon?: FluxIconName; readonly data: readonly BoxPlotTooltipPoint[] }[],
195
+ getPalette: () => readonly string[]
196
+ ): EChartsOption {
197
+ const formatter = (params: TooltipParam | TooltipParam[]): string => {
198
+ const param = Array.isArray(params) ? params[0] : params;
199
+
200
+ if (!param) {
201
+ return '';
202
+ }
203
+
204
+ const series = getSeries();
205
+ const palette = getPalette();
206
+ const seriesIndex = param.seriesIndex ?? 0;
207
+ const dataIndex = param.dataIndex ?? 0;
208
+ const s = series[seriesIndex];
209
+ const point = s?.data[dataIndex];
210
+
211
+ if (!s || !point) {
212
+ return '';
213
+ }
214
+
215
+ const color = palette[seriesIndex % palette.length];
216
+ const seriesName = s.name ? t(String(s.name)) : '';
217
+ const pointLabel = point.label ? t(String(point.label)) : '';
218
+ const title = [seriesName, pointLabel].filter(Boolean).join(' — ');
219
+
220
+ const items: SharedTooltipItem[] = [
221
+ { name: 'Min', value: point.min, color },
222
+ { name: 'Q1', value: point.q1, color },
223
+ { name: 'Median', value: point.median, color },
224
+ { name: 'Q3', value: point.q3, color },
225
+ { name: 'Max', value: point.max, color }
226
+ ];
227
+
228
+ return renderTooltip(t, styles, title, items);
229
+ };
230
+
231
+ return {
232
+ tooltip: {
233
+ show: true,
234
+ trigger: 'item',
235
+ formatter: formatter as never
236
+ }
237
+ };
238
+ }
239
+
240
+ export interface HeatmapTooltipPoint {
241
+ readonly x: string | number;
242
+ readonly y: string | number;
243
+ readonly value: number;
244
+ readonly formatted?: string;
245
+ }
246
+
247
+ export function buildHeatmapTooltipOptions(
248
+ t: Translator,
249
+ styles: TooltipStyleClasses,
250
+ getSeries: () => readonly { readonly name?: string; readonly data: readonly HeatmapTooltipPoint[] }[]
251
+ ): EChartsOption {
252
+ const formatter = (params: TooltipParam | TooltipParam[]): string => {
253
+ const param = Array.isArray(params) ? params[0] : params;
254
+
255
+ if (!param) {
256
+ return '';
257
+ }
258
+
259
+ const series = getSeries();
260
+ const seriesIndex = param.seriesIndex ?? 0;
261
+ const dataIndex = param.dataIndex ?? 0;
262
+ const s = series[seriesIndex];
263
+ const point = s?.data[dataIndex];
264
+
265
+ if (!s || !point) {
266
+ return '';
267
+ }
268
+
269
+ const seriesName = s.name ? t(String(s.name)) : '';
270
+ const xLabel = t(String(point.x));
271
+ const yLabel = t(String(point.y));
272
+ const title = [seriesName, `${xLabel} · ${yLabel}`].filter(Boolean).join(' — ');
273
+
274
+ const items: SharedTooltipItem[] = [
275
+ { name: '', value: point.formatted ?? point.value, color: 'var(--primary-600)' }
276
+ ];
277
+
278
+ return renderTooltip(t, styles, title, items);
279
+ };
280
+
281
+ return {
282
+ tooltip: {
283
+ show: true,
284
+ trigger: 'item',
285
+ formatter: formatter as never
286
+ }
287
+ };
288
+ }
289
+
290
+ export interface TreemapTooltipNode {
291
+ readonly name: string;
292
+ readonly value?: number;
293
+ readonly color?: string;
294
+ }
295
+
296
+ export function buildTreemapTooltipOptions(
297
+ t: Translator,
298
+ styles: TooltipStyleClasses
299
+ ): EChartsOption {
300
+ const formatter = (params: TooltipParam | TooltipParam[]): string => {
301
+ const param = Array.isArray(params) ? params[0] : params;
302
+
303
+ if (!param) {
304
+ return '';
305
+ }
306
+
307
+ const data = param.data as TreemapTooltipNode | undefined;
308
+
309
+ if (!data) {
310
+ return '';
311
+ }
312
+
313
+ const color = data.color ?? 'var(--primary-600)';
314
+ const title = data.name ? t(String(data.name)) : '';
315
+
316
+ const items: SharedTooltipItem[] = [
317
+ { name: '', value: data.value ?? '', color }
318
+ ];
319
+
320
+ return renderTooltip(t, styles, title, items);
321
+ };
322
+
323
+ return {
324
+ tooltip: {
325
+ show: true,
326
+ trigger: 'item',
327
+ formatter: formatter as never
328
+ }
329
+ };
330
+ }
331
+
332
+ export function buildGaugeTooltipOptions(
333
+ t: Translator,
334
+ styles: TooltipStyleClasses,
335
+ getSeries: () => readonly { readonly name?: string; readonly value: number; readonly icon?: FluxIconName }[],
336
+ getPalette: () => readonly string[]
337
+ ): EChartsOption {
338
+ const formatter = (params: TooltipParam | TooltipParam[]): string => {
339
+ const param = Array.isArray(params) ? params[0] : params;
340
+
341
+ if (!param) {
342
+ return '';
343
+ }
344
+
345
+ const series = getSeries();
346
+ const palette = getPalette();
347
+ const seriesIndex = param.seriesIndex ?? 0;
348
+ const s = series[seriesIndex];
349
+
350
+ if (!s) {
351
+ return '';
352
+ }
353
+
354
+ const color = palette[seriesIndex % palette.length];
355
+ const title = s.name ? t(String(s.name)) : '';
356
+
357
+ const items: SharedTooltipItem[] = [
358
+ { name: '', value: s.value, color, icon: s.icon }
359
+ ];
360
+
361
+ return renderTooltip(t, styles, title, items);
362
+ };
363
+
364
+ return {
365
+ tooltip: {
366
+ show: true,
367
+ trigger: 'item',
368
+ formatter: formatter as never
369
+ }
370
+ };
371
+ }
372
+
373
+ export function buildDefaultOptions(): EChartsOption {
374
+ return {
375
+ color: [
376
+ 'var(--chart-1)',
377
+ 'var(--chart-2)',
378
+ 'var(--chart-3)',
379
+ 'var(--chart-4)'
380
+ ],
381
+ animation: true,
382
+ animationDuration: 1000,
383
+ animationDurationUpdate: 400,
384
+ animationEasing: 'cubicOut',
385
+ animationEasingUpdate: 'cubicInOut',
386
+ tooltip: {
387
+ show: false,
388
+ backgroundColor: 'transparent',
389
+ borderWidth: 0,
390
+ padding: 0,
391
+ extraCssText: 'box-shadow: none;',
392
+ className: 'flux-statistics-tooltip'
393
+ },
394
+ legend: {
395
+ show: false
396
+ }
397
+ };
398
+ }
@@ -0,0 +1,20 @@
1
+ import { iconRegistry } from '@flux-ui/components';
2
+ import type { FluxIconName } from '@flux-ui/types';
3
+
4
+ export function renderIconSvg(name: FluxIconName | undefined, color: string, size: number = 14): string {
5
+ if (!name) {
6
+ return '';
7
+ }
8
+
9
+ const icon = iconRegistry[name];
10
+
11
+ if (!icon) {
12
+ return '';
13
+ }
14
+
15
+ const [width, height, , , pathData] = icon;
16
+ const paths = Array.isArray(pathData) ? pathData : [pathData];
17
+ const pathElements = paths.map(d => `<path d="${d}" fill="${color}"/>`).join('');
18
+
19
+ return `<svg viewBox="0 0 ${width} ${height}" width="${size}" height="${size}" focusable="false" aria-hidden="true">${pathElements}</svg>`;
20
+ }
@@ -0,0 +1,7 @@
1
+ export * from './baseOptions';
2
+ export * from './colors';
3
+ export * from './convert';
4
+ export * from './defaultOptions';
5
+ export * from './iconSvg';
6
+ export * from './seriesDefaults';
7
+ export * from './sparklineOptions';
@@ -0,0 +1,210 @@
1
+ import type {
2
+ BarSeriesOption,
3
+ BoxplotSeriesOption,
4
+ CandlestickSeriesOption,
5
+ GaugeSeriesOption,
6
+ HeatmapSeriesOption,
7
+ LineSeriesOption,
8
+ PieSeriesOption,
9
+ RadarSeriesOption,
10
+ ScatterSeriesOption,
11
+ TreemapSeriesOption
12
+ } from 'echarts/charts';
13
+
14
+ const SOFT_EMPHASIS = { focus: 'none' } as const;
15
+ const PIE_EMPHASIS = { focus: 'none', scale: true, scaleSize: 6 } as const;
16
+ const NO_EMPHASIS = { disabled: true } as const;
17
+ const PIE_SLICE_BORDER = { borderColor: 'var(--surface)', borderWidth: 2 };
18
+
19
+ const stagger = (step: number) => (idx: number) => idx * step;
20
+
21
+ export const LINE_SERIES_DEFAULTS: Partial<LineSeriesOption> = {
22
+ type: 'line',
23
+ smooth: true,
24
+ showSymbol: false,
25
+ emphasis: SOFT_EMPHASIS
26
+ };
27
+
28
+ export const AREA_SERIES_DEFAULTS: Partial<LineSeriesOption> = {
29
+ type: 'line',
30
+ smooth: true,
31
+ showSymbol: false,
32
+ areaStyle: { opacity: 0.25 },
33
+ lineStyle: { width: 2 },
34
+ emphasis: SOFT_EMPHASIS
35
+ };
36
+
37
+ export const BAR_SERIES_DEFAULTS: Partial<BarSeriesOption> = {
38
+ type: 'bar',
39
+ itemStyle: { borderRadius: 6 },
40
+ barCategoryGap: '25%',
41
+ emphasis: SOFT_EMPHASIS,
42
+ animationDelay: stagger(40),
43
+ animationDelayUpdate: stagger(20)
44
+ };
45
+
46
+ export const PIE_SERIES_DEFAULTS: Partial<PieSeriesOption> = {
47
+ type: 'pie',
48
+ radius: '75%',
49
+ label: { show: false },
50
+ itemStyle: { ...PIE_SLICE_BORDER, borderRadius: 6 },
51
+ selectedMode: false,
52
+ emphasis: PIE_EMPHASIS
53
+ };
54
+
55
+ export const DONUT_SERIES_DEFAULTS: Partial<PieSeriesOption> = {
56
+ type: 'pie',
57
+ radius: ['55%', '75%'],
58
+ padAngle: 1,
59
+ label: { show: false },
60
+ itemStyle: { ...PIE_SLICE_BORDER, borderRadius: 6 },
61
+ selectedMode: false,
62
+ emphasis: PIE_EMPHASIS
63
+ };
64
+
65
+ export const POLAR_AREA_SERIES_DEFAULTS: Partial<PieSeriesOption> = {
66
+ type: 'pie',
67
+ roseType: 'area',
68
+ radius: ['10%', '75%'],
69
+ padAngle: 1,
70
+ label: { show: false },
71
+ itemStyle: { ...PIE_SLICE_BORDER, opacity: 0.85, borderRadius: 6 },
72
+ selectedMode: false,
73
+ emphasis: PIE_EMPHASIS
74
+ };
75
+
76
+ export const RADAR_SERIES_DEFAULTS: Partial<RadarSeriesOption> = {
77
+ type: 'radar',
78
+ areaStyle: { opacity: 0.25 },
79
+ lineStyle: { width: 2.5 },
80
+ symbolSize: 6,
81
+ emphasis: SOFT_EMPHASIS
82
+ };
83
+
84
+ export const HEATMAP_SERIES_DEFAULTS: Partial<HeatmapSeriesOption> = {
85
+ type: 'heatmap',
86
+ itemStyle: {
87
+ borderColor: 'var(--surface)',
88
+ borderWidth: 2,
89
+ borderRadius: 6
90
+ },
91
+ label: { show: false },
92
+ emphasis: SOFT_EMPHASIS,
93
+ animationDelay: stagger(8),
94
+ animationDelayUpdate: stagger(4)
95
+ };
96
+
97
+ export const SCATTER_SERIES_DEFAULTS: Partial<ScatterSeriesOption> = {
98
+ type: 'scatter',
99
+ symbolSize: 14,
100
+ itemStyle: {
101
+ borderColor: 'var(--surface)',
102
+ borderWidth: 2,
103
+ opacity: 0.9
104
+ },
105
+ emphasis: SOFT_EMPHASIS,
106
+ animationDelay: stagger(25),
107
+ animationDelayUpdate: stagger(15)
108
+ };
109
+
110
+ export const BUBBLE_SERIES_DEFAULTS: Partial<ScatterSeriesOption> = {
111
+ type: 'scatter',
112
+ itemStyle: {
113
+ borderColor: 'var(--surface)',
114
+ borderWidth: 2,
115
+ opacity: 0.65
116
+ },
117
+ emphasis: SOFT_EMPHASIS,
118
+ animationDelay: stagger(25),
119
+ animationDelayUpdate: stagger(15)
120
+ };
121
+
122
+ export const BOXPLOT_SERIES_DEFAULTS: Partial<BoxplotSeriesOption> = {
123
+ type: 'boxplot',
124
+ itemStyle: {
125
+ color: 'var(--chart-1)',
126
+ borderColor: 'var(--foreground-prominent)',
127
+ borderWidth: 1
128
+ },
129
+ emphasis: SOFT_EMPHASIS,
130
+ animationDelay: stagger(60),
131
+ animationDelayUpdate: stagger(30)
132
+ };
133
+
134
+ export const CANDLESTICK_SERIES_DEFAULTS: Partial<CandlestickSeriesOption> = {
135
+ type: 'candlestick',
136
+ itemStyle: {
137
+ color: 'var(--success-500)',
138
+ color0: 'var(--danger-500)',
139
+ borderColor: 'var(--success-500)',
140
+ borderColor0: 'var(--danger-500)'
141
+ },
142
+ emphasis: SOFT_EMPHASIS,
143
+ animationDelay: stagger(30),
144
+ animationDelayUpdate: stagger(15)
145
+ };
146
+
147
+ export const TREEMAP_SERIES_DEFAULTS: Partial<TreemapSeriesOption> = {
148
+ type: 'treemap',
149
+ roam: false,
150
+ nodeClick: false,
151
+ breadcrumb: { show: false },
152
+ itemStyle: {
153
+ borderColor: 'var(--surface)',
154
+ borderWidth: 3,
155
+ borderRadius: 6,
156
+ gapWidth: 0
157
+ },
158
+ label: {
159
+ show: true,
160
+ color: 'var(--white)',
161
+ fontSize: 13,
162
+ fontWeight: 600,
163
+ fontFamily: 'inherit'
164
+ },
165
+ emphasis: SOFT_EMPHASIS
166
+ };
167
+
168
+ export const GAUGE_SERIES_DEFAULTS: Partial<GaugeSeriesOption> = {
169
+ type: 'gauge',
170
+ startAngle: 225,
171
+ endAngle: -45,
172
+ min: 0,
173
+ max: 100,
174
+ radius: '90%',
175
+ emphasis: NO_EMPHASIS,
176
+ progress: {
177
+ show: true,
178
+ width: 14,
179
+ roundCap: true
180
+ },
181
+ axisLine: {
182
+ lineStyle: {
183
+ width: 14,
184
+ color: [[1, 'var(--gray-100)']]
185
+ },
186
+ roundCap: true
187
+ },
188
+ axisTick: { show: false },
189
+ splitLine: { show: false },
190
+ axisLabel: { show: false },
191
+ pointer: { show: false },
192
+ anchor: { show: false },
193
+ title: {
194
+ show: true,
195
+ color: 'var(--foreground-secondary)',
196
+ fontSize: 12,
197
+ fontWeight: 500,
198
+ fontFamily: 'inherit',
199
+ offsetCenter: [0, '-30%']
200
+ },
201
+ detail: {
202
+ show: true,
203
+ color: 'var(--foreground-prominent)',
204
+ fontSize: 28,
205
+ fontWeight: 800,
206
+ fontFamily: 'inherit',
207
+ offsetCenter: [0, '10%'],
208
+ formatter: '{value}%'
209
+ }
210
+ };
@@ -0,0 +1,67 @@
1
+ import type { EChartsOption } from '~flux/statistics/composable';
2
+
3
+ export type SparklineVariant = 'line' | 'bar' | 'area';
4
+
5
+ export interface SparklineSeriesItem {
6
+ readonly name?: string;
7
+ readonly data: (number | string | null)[];
8
+ }
9
+
10
+ export function buildSparklineOptions(
11
+ variant: SparklineVariant,
12
+ color: string,
13
+ series: readonly SparklineSeriesItem[]
14
+ ): EChartsOption {
15
+ const data = series[0]?.data ?? [];
16
+
17
+ const base: EChartsOption = {
18
+ animation: false,
19
+ grid: {
20
+ left: 0,
21
+ right: 0,
22
+ top: 0,
23
+ bottom: 0,
24
+ containLabel: false
25
+ },
26
+ tooltip: { show: false },
27
+ xAxis: {
28
+ type: 'category',
29
+ show: false,
30
+ boundaryGap: variant === 'bar'
31
+ },
32
+ yAxis: {
33
+ type: 'value',
34
+ show: false,
35
+ scale: true
36
+ },
37
+ color: [color]
38
+ };
39
+
40
+ if (variant === 'bar') {
41
+ return {
42
+ ...base,
43
+ series: [{
44
+ type: 'bar',
45
+ data: data as never,
46
+ itemStyle: { borderRadius: 2 },
47
+ barCategoryGap: '40%',
48
+ emphasis: { disabled: true },
49
+ silent: true
50
+ }]
51
+ };
52
+ }
53
+
54
+ return {
55
+ ...base,
56
+ series: [{
57
+ type: 'line',
58
+ data: data as never,
59
+ smooth: true,
60
+ showSymbol: false,
61
+ lineStyle: { width: 2 },
62
+ areaStyle: variant === 'area' ? { opacity: 0.25 } : undefined,
63
+ emphasis: { disabled: true },
64
+ silent: true
65
+ }]
66
+ };
67
+ }