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