@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.
- package/README.md +1 -1
- package/dist/component/FluxStatisticsAreaChart.vue.d.ts +11 -4
- package/dist/component/FluxStatisticsBarChart.vue.d.ts +11 -4
- package/dist/component/FluxStatisticsBase.vue.d.ts +1 -0
- package/dist/component/FluxStatisticsBoxPlotChart.vue.d.ts +15 -0
- package/dist/component/FluxStatisticsBubbleChart.vue.d.ts +14 -0
- package/dist/component/FluxStatisticsCandlestickChart.vue.d.ts +15 -0
- package/dist/component/FluxStatisticsChart.vue.d.ts +7 -5
- package/dist/component/FluxStatisticsChartPane.vue.d.ts +1 -0
- package/dist/component/FluxStatisticsComparison.vue.d.ts +16 -0
- package/dist/component/FluxStatisticsDonutChart.vue.d.ts +8 -4
- package/dist/component/FluxStatisticsEmpty.vue.d.ts +19 -0
- package/dist/component/FluxStatisticsHeatmapChart.vue.d.ts +15 -0
- package/dist/component/FluxStatisticsLegendItem.vue.d.ts +1 -0
- package/dist/component/FluxStatisticsLineChart.vue.d.ts +11 -4
- package/dist/component/FluxStatisticsMixedChart.vue.d.ts +17 -0
- package/dist/component/FluxStatisticsPieChart.vue.d.ts +8 -4
- package/dist/component/FluxStatisticsPolarAreaChart.vue.d.ts +14 -0
- package/dist/component/FluxStatisticsRadarChart.vue.d.ts +12 -0
- package/dist/component/FluxStatisticsRadialBar.vue.d.ts +11 -0
- package/dist/component/FluxStatisticsScatterChart.vue.d.ts +14 -0
- package/dist/component/FluxStatisticsSparkline.vue.d.ts +13 -0
- package/dist/component/FluxStatisticsTreemapChart.vue.d.ts +11 -0
- package/dist/component/index.d.ts +13 -0
- package/dist/composable/index.d.ts +10 -0
- package/dist/composable/useChartHoverSync.d.ts +9 -0
- package/dist/composable/useChartLegend.d.ts +14 -0
- package/dist/composable/useChartSeriesSetup.d.ts +23 -0
- package/dist/composable/useECharts.d.ts +9 -0
- package/dist/composable/usePieSlicesSetup.d.ts +14 -0
- package/dist/echarts.d.ts +1 -0
- package/dist/index.css +230 -37
- package/dist/index.d.ts +5 -2
- package/dist/index.js +10919 -9041
- package/dist/index.js.map +1 -1
- package/dist/util/baseOptions.d.ts +15 -0
- package/dist/util/colors.d.ts +4 -0
- package/dist/util/convert.d.ts +22 -0
- package/dist/util/defaultOptions.d.ts +76 -0
- package/dist/util/iconSvg.d.ts +2 -0
- package/dist/util/index.d.ts +7 -0
- package/dist/util/seriesDefaults.d.ts +15 -0
- package/dist/util/sparklineOptions.d.ts +7 -0
- package/package.json +14 -15
- package/src/component/FluxStatisticsAreaChart.vue +38 -41
- package/src/component/FluxStatisticsBarChart.vue +38 -33
- package/src/component/FluxStatisticsBase.vue +14 -1
- package/src/component/FluxStatisticsBoxPlotChart.vue +69 -0
- package/src/component/FluxStatisticsBubbleChart.vue +56 -0
- package/src/component/FluxStatisticsCandlestickChart.vue +81 -0
- package/src/component/FluxStatisticsChart.vue +19 -169
- package/src/component/FluxStatisticsChartPane.vue +23 -11
- package/src/component/FluxStatisticsComparison.vue +113 -0
- package/src/component/FluxStatisticsDonutChart.vue +39 -18
- package/src/component/FluxStatisticsEmpty.vue +44 -0
- package/src/component/FluxStatisticsHeatmapChart.vue +80 -0
- package/src/component/FluxStatisticsLegend.vue +33 -1
- package/src/component/FluxStatisticsLegendItem.vue +3 -1
- package/src/component/FluxStatisticsLineChart.vue +38 -41
- package/src/component/FluxStatisticsMixedChart.vue +55 -0
- package/src/component/FluxStatisticsPieChart.vue +39 -18
- package/src/component/FluxStatisticsPolarAreaChart.vue +53 -0
- package/src/component/FluxStatisticsRadarChart.vue +108 -0
- package/src/component/FluxStatisticsRadialBar.vue +48 -0
- package/src/component/FluxStatisticsScatterChart.vue +56 -0
- package/src/component/FluxStatisticsSparkline.vue +67 -0
- package/src/component/FluxStatisticsTreemapChart.vue +39 -0
- package/src/component/index.ts +13 -0
- package/src/composable/index.ts +10 -0
- package/src/composable/useChartHoverSync.ts +92 -0
- package/src/composable/useChartLegend.ts +23 -0
- package/src/composable/useChartSeriesSetup.ts +75 -0
- package/src/composable/useECharts.ts +55 -0
- package/src/composable/usePieSlicesSetup.ts +58 -0
- package/src/css/Base.module.scss +28 -1
- package/src/css/Chart.module.scss +66 -32
- package/src/css/ChartPane.module.scss +24 -9
- package/src/css/Comparison.module.scss +52 -0
- package/src/css/Empty.module.scss +39 -0
- package/src/css/Grid.module.scss +1 -0
- package/src/css/Legend.module.scss +11 -1
- package/src/css/Metric.module.scss +6 -0
- package/src/css/Sparkline.module.scss +13 -0
- package/src/echarts.ts +47 -0
- package/src/index.ts +7 -3
- package/src/util/baseOptions.ts +74 -0
- package/src/util/colors.ts +86 -0
- package/src/util/convert.ts +360 -0
- package/src/util/defaultOptions.ts +398 -0
- package/src/util/iconSvg.ts +20 -0
- package/src/util/index.ts +7 -0
- package/src/util/seriesDefaults.ts +210 -0
- 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,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
|
+
}
|