main
vue 163 lines 4.13 KB
Raw
1 <template>
2 <VChart
3 ref="chartRef"
4 class="w-full"
5 :style="{ height, width: '100%' }"
6 :autoresize="{ onResize: updatePlotWidth }"
7 :option="chartOption"
8 @finished="updatePlotWidth"
9 @click="onChartClick"
10 />
11 </template>
12
13 <script setup lang="ts">
14 import type { BarSeriesOption } from "echarts/charts"
15 import type { GridComponentOption, TitleComponentOption, TooltipComponentOption } from "echarts/components"
16 import type { ComposeOption } from "echarts/core"
17 import { BarChart } from "echarts/charts"
18 import { GridComponent, TitleComponent, TooltipComponent } from "echarts/components"
19 import { use } from "echarts/core"
20 import { CanvasRenderer } from "echarts/renderers"
21 import { computed, ref } from "vue"
22 import VChart from "vue-echarts"
23 import { useSettingsStore } from "@/stores/settings"
24 import { useThemeStore } from "@/stores/theme"
25 import dayjs from "@/utils/dayjs"
26 import {
27 buildChartTooltipGlassBase,
28 CHART_COLORS,
29 CHART_GRID_CONTAIN_AXIS_LABELS,
30 chartTooltipThemeFromStyle,
31 formatChartTooltipAxisFirst
32 } from "."
33
34 const props = withDefaults(
35 defineProps<{
36 labels?: string[]
37 data?: number[]
38 height?: string
39 monochrome?: boolean
40 labelsDatetime?: boolean
41 }>(),
42 {
43 labels: () => [],
44 data: () => [],
45 height: "100%"
46 }
47 )
48
49 const emit = defineEmits<{
50 itemClick: [item: { name: string }]
51 }>()
52
53 use([CanvasRenderer, BarChart, TitleComponent, TooltipComponent, GridComponent])
54
55 type ChartOption = ComposeOption<TitleComponentOption | TooltipComponentOption | GridComponentOption | BarSeriesOption>
56
57 const GRID_HORIZONTAL_PADDING = 48
58
59 const themeStore = useThemeStore()
60 const dFormats = useSettingsStore().dateFormat
61 const chartRef = ref<InstanceType<typeof VChart> | null>(null)
62 const plotWidth = ref(0)
63
64 function updatePlotWidth() {
65 const chartWidth = chartRef.value?.getWidth() ?? 0
66 plotWidth.value = Math.max(0, chartWidth - GRID_HORIZONTAL_PADDING)
67 }
68
69 const categoryLabels = computed(() =>
70 (props.labels || []).map(label =>
71 props.labelsDatetime ? `${dayjs(label).format(dFormats.date)}\n${dayjs(label).format(dFormats.time)}` : label
72 )
73 )
74
75 const showXAxisLabels = computed(() => plotWidth.value === 0 || plotWidth.value >= 500)
76
77 const chartOption = computed((): ChartOption => {
78 const style = themeStore.style
79 const fg = style["fg-default-color"]
80 const bc = style["border-color"]
81 const ff = style["font-family"]
82 const palette = props.monochrome ? [CHART_COLORS[0]] : CHART_COLORS
83 const hasData = props.labels?.length > 0
84
85 if (!hasData) {
86 return {
87 backgroundColor: "transparent",
88 title: {
89 text: "No data",
90 left: "center",
91 top: "center",
92 textStyle: { color: fg, fontSize: 16, fontFamily: ff }
93 }
94 }
95 }
96
97 const barData = (props.labels || []).map((_, i) => ({
98 value: Number(props.data[i] ?? 0),
99 itemStyle: {
100 color: palette[i % palette.length],
101 borderRadius: [4, 4, 0, 0]
102 }
103 }))
104
105 return {
106 backgroundColor: "transparent",
107 grid: {
108 left: 8,
109 right: 8,
110 top: 8,
111 bottom: showXAxisLabels.value ? 56 : 16,
112 ...CHART_GRID_CONTAIN_AXIS_LABELS
113 },
114 tooltip: {
115 ...buildChartTooltipGlassBase(chartTooltipThemeFromStyle(style), { trigger: "axis" }),
116 axisPointer: { type: "shadow" },
117 formatter: params =>
118 formatChartTooltipAxisFirst(params, {
119 resolveColor: p => {
120 const idx = p.dataIndex ?? 0
121 return palette[idx % palette.length]
122 }
123 })
124 },
125 xAxis: {
126 type: "category",
127 data: categoryLabels.value,
128 axisLine: { show: false },
129 axisTick: { show: false },
130 axisLabel: {
131 show: showXAxisLabels.value,
132 color: fg,
133 fontSize: 11,
134 interval: "auto",
135 hideOverlap: true
136 }
137 },
138 yAxis: {
139 type: "value",
140 axisLine: { show: false },
141 axisTick: { show: false },
142 axisLabel: { color: fg, fontSize: 10 },
143 splitLine: { lineStyle: { color: bc } }
144 },
145 series: [
146 {
147 name: "value",
148 type: "bar",
149 barWidth: "60%",
150 data: barData,
151 emphasis: { focus: "series" }
152 }
153 ]
154 }
155 })
156
157 function onChartClick(params: unknown) {
158 const p = params as { componentType?: string; dataIndex?: number }
159 if (p.componentType !== "series" || p.dataIndex == null) return
160 const name = (props.labels || [])[p.dataIndex]
161 if (name) emit("itemClick", { name })
162 }
163 </script>