@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
@@ -1,193 +1,43 @@
1
1
  <template>
2
- <div :class="$style.statisticsChart">
3
- <ApexCharts
4
- ref="chart"
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 { toRaw, unref, useTemplateRef } from 'vue';
121
- import { useI18n } from 'vue-i18n';
122
- import ApexCharts from 'vue3-apexcharts';
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?: ApexOptions;
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
- useResizeObserver(chart as any, () => {
136
- const _chart = unref(chart);
24
+ const defaults = buildDefaultOptions();
137
25
 
138
- if (!_chart) {
139
- return;
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
- let labels: string[];
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
- return `${labels && dataPointIndex !== null && labels[dataPointIndex] ? `
158
- <div class="${$style.statisticsChartTooltipTitle}">
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
- if (Number.isInteger(value)) {
188
- return formatNumber(value);
189
- }
37
+ return deepResolveCssVars(merged);
38
+ });
39
+
40
+ const {chartInstance} = useECharts(chart, mergedOptions);
190
41
 
191
- return value;
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
- #content>
12
- <div :class="$style.statisticsChartPaneBody">
13
- <div :class="$style.statisticsChartPaneContainer">
14
- <slot/>
15
- </div>
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
- <slot name="legend"/>
18
- </div>
23
+ <slot name="legend"/>
24
+ </div>
19
25
 
20
- <FluxToolbar v-if="slots.toolbar">
21
- <slot name="toolbar"/>
22
- </FluxToolbar>
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
- :aspectRatio="aspectRatio"
4
- :options="merge({
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 { ApexOptions } from 'apexcharts';
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
- options = {}
19
+ advancedOptions = {},
20
+ slices,
21
+ title,
22
+ tooltip = false,
23
+ tooltipValueFormatter
27
24
  } = defineProps<{
28
- readonly aspectRatio?: number;
29
- readonly options?: ApexOptions;
30
- readonly series: ApexOptions['series'];
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
  }>();