@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
|
@@ -1,193 +1,43 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
:class="$style.statisticsChartElement"
|
|
6
|
-
width="100%"
|
|
7
|
-
height="100%"
|
|
8
|
-
:options="merge({
|
|
9
|
-
chart: {
|
|
10
|
-
height: '100%',
|
|
11
|
-
width: '100%',
|
|
12
|
-
offsetX: 0,
|
|
13
|
-
offsetY: 0,
|
|
14
|
-
parentHeightOffset: 0,
|
|
15
|
-
redrawOnParentResize: false,
|
|
16
|
-
redrawOnWindowResize: false,
|
|
17
|
-
animations: {
|
|
18
|
-
enabled: true,
|
|
19
|
-
speed: 300,
|
|
20
|
-
animateGradually: {
|
|
21
|
-
enabled: false,
|
|
22
|
-
delay: 150
|
|
23
|
-
},
|
|
24
|
-
dynamicAnimation: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
speed: 150
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
toolbar: {
|
|
30
|
-
show: false
|
|
31
|
-
},
|
|
32
|
-
zoom: {
|
|
33
|
-
enabled: false
|
|
34
|
-
},
|
|
35
|
-
events: {
|
|
36
|
-
mounted: (context: any) => {
|
|
37
|
-
const svg = context.el.querySelector('svg');
|
|
38
|
-
svg?.querySelectorAll('title').forEach((t: HTMLTitleElement) => t.remove());
|
|
39
|
-
},
|
|
40
|
-
updated: (context: any) => {
|
|
41
|
-
const svg = context.el.querySelector('svg');
|
|
42
|
-
svg?.querySelectorAll('title').forEach((t: HTMLTitleElement) => t.remove());
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
colors: [
|
|
47
|
-
'var(--chart-1)',
|
|
48
|
-
'var(--chart-2)',
|
|
49
|
-
'var(--chart-3)',
|
|
50
|
-
'var(--chart-4)'
|
|
51
|
-
],
|
|
52
|
-
dataLabels: {
|
|
53
|
-
enabled: false
|
|
54
|
-
},
|
|
55
|
-
grid: {
|
|
56
|
-
borderColor: 'var(--stroke)',
|
|
57
|
-
padding: {
|
|
58
|
-
top: 0,
|
|
59
|
-
left: -3,
|
|
60
|
-
right: -3,
|
|
61
|
-
bottom: 0
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
legend: {
|
|
65
|
-
show: false
|
|
66
|
-
},
|
|
67
|
-
plotOptions: {
|
|
68
|
-
bar: {
|
|
69
|
-
borderRadius: 6,
|
|
70
|
-
columnWidth: '75%'
|
|
71
|
-
},
|
|
72
|
-
donut: {
|
|
73
|
-
expandOnClick: false
|
|
74
|
-
},
|
|
75
|
-
pie: {
|
|
76
|
-
expandOnClick: false
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
states: {
|
|
80
|
-
hover: {
|
|
81
|
-
filter: {
|
|
82
|
-
type: 'none'
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
title: {
|
|
87
|
-
text: undefined
|
|
88
|
-
},
|
|
89
|
-
tooltip: {
|
|
90
|
-
custom: generateTooltip,
|
|
91
|
-
followCursor: false,
|
|
92
|
-
hideEmptySeries: true,
|
|
93
|
-
onDatasetHover: {
|
|
94
|
-
highlightDataSeries: true
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
xaxis: {
|
|
98
|
-
labels: { show: false },
|
|
99
|
-
axisBorder: { show: false },
|
|
100
|
-
axisTicks: { show: false },
|
|
101
|
-
crosshairs: { show: true },
|
|
102
|
-
tooltip: { enabled: false }
|
|
103
|
-
},
|
|
104
|
-
yaxis: {
|
|
105
|
-
show: false,
|
|
106
|
-
labels: { show: false }
|
|
107
|
-
}
|
|
108
|
-
}, options, {series})"
|
|
109
|
-
:series="series"/>
|
|
110
|
-
</div>
|
|
2
|
+
<div
|
|
3
|
+
ref="chart"
|
|
4
|
+
:class="$style.statisticsChart"/>
|
|
111
5
|
</template>
|
|
112
6
|
|
|
113
7
|
<script
|
|
114
8
|
lang="ts"
|
|
115
9
|
setup>
|
|
116
|
-
import { useResizeObserver } from '@basmilius/common';
|
|
117
|
-
import { formatNumber } from '@basmilius/utils';
|
|
118
|
-
import type { ApexOptions } from 'apexcharts';
|
|
119
10
|
import { merge } from 'lodash-es';
|
|
120
|
-
import {
|
|
121
|
-
import {
|
|
122
|
-
import
|
|
11
|
+
import { computed, useTemplateRef } from 'vue';
|
|
12
|
+
import { type EChartsOption, useECharts } from '~flux/statistics/composable';
|
|
13
|
+
import { buildDefaultOptions, deepResolveCssVars } from '~flux/statistics/util';
|
|
123
14
|
import $style from '~flux/statistics/css/Chart.module.scss';
|
|
124
15
|
|
|
125
16
|
const {
|
|
126
17
|
options = {}
|
|
127
18
|
} = defineProps<{
|
|
128
|
-
readonly options?:
|
|
129
|
-
readonly series: ApexOptions['series'];
|
|
19
|
+
readonly options?: EChartsOption;
|
|
130
20
|
}>();
|
|
131
21
|
|
|
132
22
|
const chart = useTemplateRef('chart');
|
|
133
|
-
const {t} = useI18n({useScope: 'parent'});
|
|
134
23
|
|
|
135
|
-
|
|
136
|
-
const _chart = unref(chart);
|
|
24
|
+
const defaults = buildDefaultOptions();
|
|
137
25
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// @ts-ignore Update exists.
|
|
143
|
-
_chart.chart?.update();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
function generateTooltip({dataPointIndex, w}: { dataPointIndex: number | null; w: any; }): string {
|
|
147
|
-
w = toRaw(w);
|
|
26
|
+
const mergedOptions = computed<EChartsOption>(() => {
|
|
27
|
+
const merged = merge({}, defaults, options) as EChartsOption & { color?: unknown; series?: unknown };
|
|
148
28
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (w.globals.categoryLabels && w.globals.categoryLabels.length > 0) {
|
|
152
|
-
labels = w.globals.categoryLabels;
|
|
153
|
-
} else if (w.globals.labels && w.globals.labels.length > 0) {
|
|
154
|
-
labels = w.globals.labels;
|
|
29
|
+
if (options && (options as { color?: unknown }).color !== undefined) {
|
|
30
|
+
merged.color = (options as { color: unknown }).color;
|
|
155
31
|
}
|
|
156
32
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
${t(labels[dataPointIndex])}
|
|
160
|
-
</div>
|
|
161
|
-
` : ''}
|
|
162
|
-
|
|
163
|
-
<div class="${$style.statisticsChartTooltipBody}">
|
|
164
|
-
${w.globals.series.map((_: any, index: number) => `
|
|
165
|
-
<div class="${$style.statisticsChartTooltipSeriesColor}" style="background: ${w.globals.seriesColors[index] ?? w.globals.colors[index]}"></div>
|
|
166
|
-
|
|
167
|
-
<div class="${$style.statisticsChartTooltipSeriesName}">
|
|
168
|
-
${w.globals.seriesNames[index]}
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
<div class="${$style.statisticsChartTooltipSeriesValue}">
|
|
172
|
-
${formatSeriesValue(w, dataPointIndex, index)}
|
|
173
|
-
</div>
|
|
174
|
-
`).join('')}
|
|
175
|
-
</div>`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function formatSeriesValue(w: any, dataPointIndex: number | null, seriesIndex: number): string {
|
|
179
|
-
const value = dataPointIndex !== null
|
|
180
|
-
? w.globals.series[seriesIndex][dataPointIndex]
|
|
181
|
-
: w.globals.series[seriesIndex];
|
|
182
|
-
|
|
183
|
-
if (w.config.tooltip.y.formatter) {
|
|
184
|
-
return w.config.tooltip.y.formatter(value, {dataPointIndex, seriesIndex});
|
|
33
|
+
if (options && (options as { series?: unknown }).series !== undefined) {
|
|
34
|
+
merged.series = (options as { series: unknown }).series;
|
|
185
35
|
}
|
|
186
36
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
37
|
+
return deepResolveCssVars(merged);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const {chartInstance} = useECharts(chart, mergedOptions);
|
|
190
41
|
|
|
191
|
-
|
|
192
|
-
}
|
|
42
|
+
defineExpose({chartInstance});
|
|
193
43
|
</script>
|
|
@@ -7,19 +7,26 @@
|
|
|
7
7
|
'--aspect-ratio': aspectRatio,
|
|
8
8
|
'--max-height': maxHeight && `${maxHeight}px`,
|
|
9
9
|
'--min-height': minHeight && `${minHeight}px`
|
|
10
|
-
}"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
}">
|
|
11
|
+
<template
|
|
12
|
+
v-if="slots.info"
|
|
13
|
+
#info>
|
|
14
|
+
<slot name="info"/>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<template #content>
|
|
18
|
+
<div :class="$style.statisticsChartPaneBody">
|
|
19
|
+
<div :class="$style.statisticsChartPaneContainer">
|
|
20
|
+
<slot/>
|
|
21
|
+
</div>
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
<slot name="legend"/>
|
|
24
|
+
</div>
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
<FluxToolbar v-if="slots.toolbar">
|
|
27
|
+
<slot name="toolbar"/>
|
|
28
|
+
</FluxToolbar>
|
|
29
|
+
</template>
|
|
23
30
|
</Base>
|
|
24
31
|
</template>
|
|
25
32
|
|
|
@@ -28,6 +35,8 @@
|
|
|
28
35
|
setup>
|
|
29
36
|
import { FluxToolbar } from '@flux-ui/components';
|
|
30
37
|
import type { FluxIconName } from '@flux-ui/types';
|
|
38
|
+
import { provide } from 'vue';
|
|
39
|
+
import { createChartLegendContext, FluxStatisticsChartLegendInjectionKey } from '~flux/statistics/composable';
|
|
31
40
|
import Base from './FluxStatisticsBase.vue';
|
|
32
41
|
import $style from '~flux/statistics/css/ChartPane.module.scss';
|
|
33
42
|
|
|
@@ -41,7 +50,10 @@
|
|
|
41
50
|
|
|
42
51
|
const slots = defineSlots<{
|
|
43
52
|
default(): any;
|
|
53
|
+
info?(): any;
|
|
44
54
|
legend?(): any;
|
|
45
55
|
toolbar?(): any;
|
|
46
56
|
}>();
|
|
57
|
+
|
|
58
|
+
provide(FluxStatisticsChartLegendInjectionKey, createChartLegendContext());
|
|
47
59
|
</script>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Base
|
|
3
|
+
is-small
|
|
4
|
+
:icon="icon"
|
|
5
|
+
:title="title">
|
|
6
|
+
<div :class="$style.statisticsComparison">
|
|
7
|
+
<div :class="$style.statisticsComparisonItem">
|
|
8
|
+
<div :class="$style.statisticsComparisonItemLabel">
|
|
9
|
+
{{ currentLabel ?? 'Current' }}
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div :class="$style.statisticsComparisonItemValue">
|
|
13
|
+
{{ formattedCurrent }}
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div :class="$style.statisticsComparisonDivider"/>
|
|
18
|
+
|
|
19
|
+
<div :class="$style.statisticsComparisonItem">
|
|
20
|
+
<div :class="$style.statisticsComparisonItemLabel">
|
|
21
|
+
{{ previousLabel ?? 'Previous' }}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div :class="$style.statisticsComparisonItemValueMuted">
|
|
25
|
+
{{ formattedPrevious }}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div
|
|
31
|
+
v-if="showDelta"
|
|
32
|
+
:class="$style.statisticsComparisonBottom">
|
|
33
|
+
<Change
|
|
34
|
+
:color="deltaColor"
|
|
35
|
+
:icon="deltaIcon"
|
|
36
|
+
:value="formattedDelta"/>
|
|
37
|
+
|
|
38
|
+
<span
|
|
39
|
+
v-if="footer"
|
|
40
|
+
:class="$style.statisticsComparisonFooter">
|
|
41
|
+
{{ footer }}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
</Base>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script
|
|
48
|
+
lang="ts"
|
|
49
|
+
setup>
|
|
50
|
+
import type { FluxColor, FluxIconName } from '@flux-ui/types';
|
|
51
|
+
import { computed } from 'vue';
|
|
52
|
+
import Base from './FluxStatisticsBase.vue';
|
|
53
|
+
import Change from './FluxStatisticsChange.vue';
|
|
54
|
+
import $style from '~flux/statistics/css/Comparison.module.scss';
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
current,
|
|
58
|
+
format,
|
|
59
|
+
previous,
|
|
60
|
+
showDelta = true
|
|
61
|
+
} = defineProps<{
|
|
62
|
+
readonly current: number;
|
|
63
|
+
readonly currentLabel?: string;
|
|
64
|
+
readonly footer?: string;
|
|
65
|
+
readonly format?: (value: number) => string;
|
|
66
|
+
readonly icon?: FluxIconName;
|
|
67
|
+
readonly previous: number;
|
|
68
|
+
readonly previousLabel?: string;
|
|
69
|
+
readonly showDelta?: boolean;
|
|
70
|
+
readonly title: string;
|
|
71
|
+
}>();
|
|
72
|
+
|
|
73
|
+
const formattedCurrent = computed(() => format ? format(current) : current);
|
|
74
|
+
const formattedPrevious = computed(() => format ? format(previous) : previous);
|
|
75
|
+
|
|
76
|
+
const deltaValue = computed(() => {
|
|
77
|
+
if (previous === 0) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return ((current - previous) / Math.abs(previous)) * 100;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const deltaColor = computed<FluxColor>(() => {
|
|
85
|
+
if (deltaValue.value > 0) {
|
|
86
|
+
return 'success';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (deltaValue.value < 0) {
|
|
90
|
+
return 'danger';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return 'gray';
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const deltaIcon = computed<FluxIconName | undefined>(() => {
|
|
97
|
+
if (deltaValue.value > 0) {
|
|
98
|
+
return 'arrow-trend-up';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (deltaValue.value < 0) {
|
|
102
|
+
return 'arrow-trend-down';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return undefined;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const formattedDelta = computed(() => {
|
|
109
|
+
const sign = deltaValue.value > 0 ? '+' : '';
|
|
110
|
+
|
|
111
|
+
return `${sign}${deltaValue.value.toFixed(1)}%`;
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
@@ -1,32 +1,53 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Chart
|
|
3
|
-
|
|
4
|
-
:options="
|
|
5
|
-
chart: {
|
|
6
|
-
type: 'donut'
|
|
7
|
-
},
|
|
8
|
-
legend: {
|
|
9
|
-
show: false
|
|
10
|
-
},
|
|
11
|
-
tooltip: {
|
|
12
|
-
enabled: false
|
|
13
|
-
}
|
|
14
|
-
}, options)"
|
|
15
|
-
:series="series"/>
|
|
3
|
+
ref="chartRef"
|
|
4
|
+
:options="mergedOptions"/>
|
|
16
5
|
</template>
|
|
17
6
|
|
|
18
7
|
<script
|
|
19
8
|
lang="ts"
|
|
20
9
|
setup>
|
|
21
|
-
import type {
|
|
10
|
+
import type { FluxStatisticsChartPieSlice } from '@flux-ui/types';
|
|
22
11
|
import { merge } from 'lodash-es';
|
|
12
|
+
import { computed } from 'vue';
|
|
13
|
+
import { usePieSlicesSetup, type EChartsOption } from '~flux/statistics/composable';
|
|
14
|
+
import { buildSharedItemTooltipFormatter, type ChartTooltipValueFormatter, POLAR_BASE_OPTIONS, toDonutSeries } from '~flux/statistics/util';
|
|
23
15
|
import Chart from './FluxStatisticsChart.vue';
|
|
16
|
+
import $style from '~flux/statistics/css/Chart.module.scss';
|
|
24
17
|
|
|
25
18
|
const {
|
|
26
|
-
|
|
19
|
+
advancedOptions = {},
|
|
20
|
+
slices,
|
|
21
|
+
title,
|
|
22
|
+
tooltip = false,
|
|
23
|
+
tooltipValueFormatter
|
|
27
24
|
} = defineProps<{
|
|
28
|
-
readonly
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
25
|
+
readonly advancedOptions?: EChartsOption;
|
|
26
|
+
readonly slices: readonly FluxStatisticsChartPieSlice[];
|
|
27
|
+
readonly title?: string;
|
|
28
|
+
readonly tooltip?: boolean;
|
|
29
|
+
readonly tooltipValueFormatter?: ChartTooltipValueFormatter;
|
|
31
30
|
}>();
|
|
31
|
+
|
|
32
|
+
const { t, palette, tooltipItems } = usePieSlicesSetup(() => slices);
|
|
33
|
+
|
|
34
|
+
const echartsSeries = computed(() => [toDonutSeries(slices, palette.value)]);
|
|
35
|
+
|
|
36
|
+
const tooltipOptions = computed<EChartsOption>(() => {
|
|
37
|
+
if (!tooltip) {
|
|
38
|
+
return { tooltip: { show: false } };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
tooltip: {
|
|
43
|
+
show: true,
|
|
44
|
+
trigger: 'item',
|
|
45
|
+
formatter: buildSharedItemTooltipFormatter(t, $style as never, () => tooltipItems.value, () => title, tooltipValueFormatter) as never
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const mergedOptions = computed<EChartsOption>(() =>
|
|
51
|
+
merge({}, POLAR_BASE_OPTIONS, tooltipOptions.value, advancedOptions, { series: echartsSeries.value, color: palette.value })
|
|
52
|
+
);
|
|
32
53
|
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.statisticsEmpty">
|
|
3
|
+
<FluxIcon
|
|
4
|
+
v-if="icon"
|
|
5
|
+
:class="$style.statisticsEmptyIcon"
|
|
6
|
+
:name="icon"/>
|
|
7
|
+
|
|
8
|
+
<div
|
|
9
|
+
v-if="title"
|
|
10
|
+
:class="$style.statisticsEmptyTitle">
|
|
11
|
+
{{ title }}
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
v-if="description"
|
|
16
|
+
:class="$style.statisticsEmptyDescription">
|
|
17
|
+
{{ description }}
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div
|
|
21
|
+
v-if="slots.default"
|
|
22
|
+
:class="$style.statisticsEmptyActions">
|
|
23
|
+
<slot/>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script
|
|
29
|
+
lang="ts"
|
|
30
|
+
setup>
|
|
31
|
+
import { FluxIcon } from '@flux-ui/components';
|
|
32
|
+
import type { FluxIconName } from '@flux-ui/types';
|
|
33
|
+
import $style from '~flux/statistics/css/Empty.module.scss';
|
|
34
|
+
|
|
35
|
+
defineProps<{
|
|
36
|
+
readonly description?: string;
|
|
37
|
+
readonly icon?: FluxIconName;
|
|
38
|
+
readonly title?: string;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
const slots = defineSlots<{
|
|
42
|
+
default?(): any;
|
|
43
|
+
}>();
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Chart
|
|
3
|
+
:options="mergedOptions"/>
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script
|
|
7
|
+
lang="ts"
|
|
8
|
+
setup>
|
|
9
|
+
import { blue100, blue300, blue500, blue700 } from '@flux-ui/internals';
|
|
10
|
+
import type { FluxStatisticsChartHeatmapSeries } from '@flux-ui/types';
|
|
11
|
+
import { merge } from 'lodash-es';
|
|
12
|
+
import { computed } from 'vue';
|
|
13
|
+
import { useI18n } from 'vue-i18n';
|
|
14
|
+
import type { EChartsOption } from '~flux/statistics/composable';
|
|
15
|
+
import { buildCartesianBaseOptions, buildHeatmapTooltipOptions, toHeatmapSeries } from '~flux/statistics/util';
|
|
16
|
+
import Chart from './FluxStatisticsChart.vue';
|
|
17
|
+
import $style from '~flux/statistics/css/Chart.module.scss';
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
advancedOptions = {},
|
|
21
|
+
series,
|
|
22
|
+
tooltip = false,
|
|
23
|
+
xAxisLabels = false,
|
|
24
|
+
xLabels = [],
|
|
25
|
+
yAxisLabels = false,
|
|
26
|
+
yLabels = []
|
|
27
|
+
} = defineProps<{
|
|
28
|
+
readonly advancedOptions?: EChartsOption;
|
|
29
|
+
readonly series: readonly FluxStatisticsChartHeatmapSeries[];
|
|
30
|
+
readonly tooltip?: boolean;
|
|
31
|
+
readonly xAxisLabels?: boolean;
|
|
32
|
+
readonly xLabels?: readonly string[];
|
|
33
|
+
readonly yAxisLabels?: boolean;
|
|
34
|
+
readonly yLabels?: readonly string[];
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
const {t} = useI18n({useScope: 'parent'});
|
|
38
|
+
|
|
39
|
+
const translatedXLabels = computed(() => xLabels.map(label => t(String(label))));
|
|
40
|
+
const translatedYLabels = computed(() => yLabels.map(label => t(String(label))));
|
|
41
|
+
|
|
42
|
+
const echartsSeries = computed(() => series.map(s =>
|
|
43
|
+
toHeatmapSeries({ ...s, name: s.name ? t(String(s.name)) : undefined }, xLabels, yLabels)
|
|
44
|
+
));
|
|
45
|
+
|
|
46
|
+
const mergedOptions = computed<EChartsOption>(() => {
|
|
47
|
+
const base: EChartsOption = {
|
|
48
|
+
...buildCartesianBaseOptions({ yAxisType: 'category', xAxisLabels, yAxisLabels }),
|
|
49
|
+
color: [blue500],
|
|
50
|
+
visualMap: {
|
|
51
|
+
show: false,
|
|
52
|
+
min: 0,
|
|
53
|
+
max: 100,
|
|
54
|
+
inRange: { color: [blue100, blue300, blue500, blue700] }
|
|
55
|
+
},
|
|
56
|
+
xAxis: {
|
|
57
|
+
type: 'category',
|
|
58
|
+
data: translatedXLabels.value as string[],
|
|
59
|
+
axisLabel: { show: xAxisLabels, color: 'var(--foreground-secondary)' },
|
|
60
|
+
axisLine: { show: false },
|
|
61
|
+
axisTick: { show: false },
|
|
62
|
+
splitLine: { show: false }
|
|
63
|
+
},
|
|
64
|
+
yAxis: {
|
|
65
|
+
type: 'category',
|
|
66
|
+
data: translatedYLabels.value as string[],
|
|
67
|
+
axisLabel: { show: yAxisLabels, color: 'var(--foreground-secondary)' },
|
|
68
|
+
axisLine: { show: false },
|
|
69
|
+
axisTick: { show: false },
|
|
70
|
+
splitLine: { show: false }
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const tooltipOptions: EChartsOption = tooltip
|
|
75
|
+
? buildHeatmapTooltipOptions(t, $style as never, () => series)
|
|
76
|
+
: { tooltip: { show: false } };
|
|
77
|
+
|
|
78
|
+
return merge({}, base, tooltipOptions, advancedOptions, { series: echartsSeries.value });
|
|
79
|
+
});
|
|
80
|
+
</script>
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="$style.statisticsLegend">
|
|
3
|
-
<slot/>
|
|
3
|
+
<slot v-if="hasSlot"/>
|
|
4
|
+
<FluxStatisticsLegendItem
|
|
5
|
+
v-else
|
|
6
|
+
v-for="(item, index) in autoItems"
|
|
7
|
+
:key="item.label"
|
|
8
|
+
:color="item.color as `#${string}` | undefined"
|
|
9
|
+
:icon="item.icon"
|
|
10
|
+
:is-hovered="legendContext?.hoveredIndex.value === index"
|
|
11
|
+
:label="item.label"
|
|
12
|
+
:value="item.value"
|
|
13
|
+
@mouseenter="onItemMouseEnter(index)"
|
|
14
|
+
@mouseleave="onItemMouseLeave"/>
|
|
4
15
|
</div>
|
|
5
16
|
</template>
|
|
6
17
|
|
|
7
18
|
<script
|
|
8
19
|
lang="ts"
|
|
9
20
|
setup>
|
|
21
|
+
import { computed, inject, useSlots } from 'vue';
|
|
22
|
+
import { FluxStatisticsChartLegendInjectionKey } from '~flux/statistics/composable';
|
|
23
|
+
import FluxStatisticsLegendItem from './FluxStatisticsLegendItem.vue';
|
|
10
24
|
import $style from '~flux/statistics/css/Legend.module.scss';
|
|
25
|
+
|
|
26
|
+
const slots = useSlots();
|
|
27
|
+
const legendContext = inject(FluxStatisticsChartLegendInjectionKey, null);
|
|
28
|
+
|
|
29
|
+
const hasSlot = computed(() => !!slots.default);
|
|
30
|
+
const autoItems = computed(() => legendContext?.items.value ?? []);
|
|
31
|
+
|
|
32
|
+
function onItemMouseEnter(index: number): void {
|
|
33
|
+
if (legendContext) {
|
|
34
|
+
legendContext.hoveredIndex.value = index;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function onItemMouseLeave(): void {
|
|
39
|
+
if (legendContext) {
|
|
40
|
+
legendContext.hoveredIndex.value = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
11
43
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
:class="$style.statisticsLegendItem"
|
|
3
|
+
:class="clsx($style.statisticsLegendItem, isHovered && $style.isHovered)"
|
|
4
4
|
:style="{
|
|
5
5
|
'--color': colorValue
|
|
6
6
|
}">
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
setup>
|
|
34
34
|
import { FluxIcon } from '@flux-ui/components';
|
|
35
35
|
import type { FluxColor, FluxIconName } from '@flux-ui/types';
|
|
36
|
+
import { clsx } from 'clsx';
|
|
36
37
|
import { computed } from 'vue';
|
|
37
38
|
import $style from '~flux/statistics/css/Legend.module.scss';
|
|
38
39
|
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
} = defineProps<{
|
|
42
43
|
readonly color?: FluxColor | `#${string}`;
|
|
43
44
|
readonly icon?: FluxIconName;
|
|
45
|
+
readonly isHovered?: boolean;
|
|
44
46
|
readonly label: string;
|
|
45
47
|
readonly value?: string | number;
|
|
46
48
|
}>();
|