main
vue 204 lines 5.6 KB
Raw
1 <template>
2 <n-card class="box-border w-full overflow-hidden" title="Top 8 indices size & health" segmented>
3 <n-spin class="h-100 overflow-hidden" :show="loading">
4 <div class="h-100 max-w-full overflow-hidden">
5 <VChart ref="chartRef" class="h-full w-full" autoresize :option="chartOption" />
6 </div>
7 </n-spin>
8 </n-card>
9 </template>
10
11 <script setup lang="ts">
12 import type { PieSeriesOption } from "echarts/charts"
13 import type { GridComponentOption, LegendComponentOption, TooltipComponentOption } from "echarts/components"
14 import type { ComposeOption } from "echarts/core"
15 import type { IndexStats } from "@/types/indices.d"
16 import bytes from "bytes"
17 import { PieChart } from "echarts/charts"
18 import { GridComponent, LegendComponent, TooltipComponent } from "echarts/components"
19 import { use } from "echarts/core"
20 import { CanvasRenderer } from "echarts/renderers"
21 import _ from "lodash"
22 import { NCard, NSpin } from "naive-ui"
23 import { computed, ref, toRefs } from "vue"
24 import VChart from "vue-echarts"
25 import {
26 buildChartTooltipGlassBase,
27 CHART_COLORS,
28 chartTooltipThemeFromStyle,
29 formatChartTooltipWithMarker
30 } from "@/components/common/charts"
31 import { useThemeStore } from "@/stores/theme"
32 import { IndexHealth } from "@/types/indices.d"
33
34 const props = defineProps<{
35 indices: IndexStats[] | null
36 }>()
37
38 use([CanvasRenderer, PieChart, TooltipComponent, LegendComponent, GridComponent])
39
40 type ChartOption = ComposeOption<TooltipComponentOption | LegendComponentOption | GridComponentOption | PieSeriesOption>
41
42 const { indices } = toRefs(props)
43
44 const style = computed(() => useThemeStore().style)
45 const loading = computed(() => !indices.value)
46 const chartRef = ref<InstanceType<typeof VChart> | null>(null)
47
48 const labelFontSize = computed(() => (window.innerWidth > 1000 ? 13 : 11))
49
50 const chartOption = computed((): ChartOption => {
51 const list = indices.value ?? []
52
53 const data = _.chain(list)
54 .map(i => {
55 const item = { ...i }
56 if (typeof item.store_size === "string") {
57 item.store_size_value = bytes(item.store_size) || undefined
58 } else {
59 item.store_size_value = item.store_size
60 }
61 return item
62 })
63 .orderBy(["store_size"], ["desc"])
64 .slice(0, 8)
65 .value()
66
67 const green = list.filter(i => i.health === IndexHealth.GREEN).length
68 const yellow = list.filter(i => i.health === IndexHealth.YELLOW).length
69 const red = list.filter(i => i.health === IndexHealth.RED).length
70
71 const sizeData = data.map(i => ({
72 value: i.store_size_value || 0,
73 name: i.index
74 }))
75
76 const fg = style.value["fg-default-color"]
77 const bg = style.value["bg-default-color"]
78
79 const tooltipBase = buildChartTooltipGlassBase(chartTooltipThemeFromStyle(style.value))
80
81 return {
82 tooltip: {
83 ...tooltipBase,
84 formatter: params => {
85 if (!params || Array.isArray(params)) return ""
86 const value = typeof params.value === "number" ? params.value : 0
87 const percent = typeof params.percent === "number" ? params.percent : 0
88 return formatChartTooltipWithMarker({
89 marker: params.marker,
90 color: params.color,
91 title: params.seriesName ?? "",
92 lines: [`${params.name}: <strong>${value}</strong> (${percent}%)`]
93 })
94 }
95 },
96 legend: {
97 data: sizeData.map(i => i.name),
98 left: "left",
99 type: "scroll",
100 textStyle: { color: fg },
101 pageTextStyle: { color: fg }
102 },
103 grid: {
104 top: "0%",
105 bottom: "0%",
106 height: "80%"
107 },
108 series: [
109 {
110 name: "Indices Health",
111 top: "-30%",
112 zlevel: 1,
113 type: "pie",
114 radius: [0, "20%"],
115 label: { show: false },
116 itemStyle: {
117 borderColor: bg,
118 borderWidth: 2
119 },
120 data: [
121 { value: green, name: "Green", itemStyle: { color: style.value["success-color"] } },
122 { value: yellow, name: "Yellow", itemStyle: { color: style.value["warning-color"] } },
123 { value: red, name: "Red", itemStyle: { color: style.value["error-color"] } }
124 ]
125 },
126 {
127 name: "Indices Size",
128 type: "pie",
129 top: "-20%",
130 bottom: "10%",
131 color: [...CHART_COLORS],
132 tooltip: {
133 ...tooltipBase,
134 formatter: params => {
135 if (!params || Array.isArray(params)) return ""
136 const value = typeof params.value === "number" ? params.value : 0
137 const percent = typeof params.percent === "number" ? params.percent : 0
138 return formatChartTooltipWithMarker({
139 marker: params.marker,
140 color: params.color,
141 title: params.seriesName ?? "",
142 lines: [`${params.name ?? ""}`, `<strong>${bytes(value)}</strong> (${percent}%)`]
143 })
144 }
145 },
146
147 avoidLabelOverlap: true,
148 radius: ["24%", "35%"],
149 minAngle: 5,
150 zlevel: 2,
151 labelLine: {
152 length: 25,
153 length2: 5,
154 showAbove: true,
155 lineStyle: {
156 width: 1.5,
157 type: "dashed"
158 }
159 },
160 label: {
161 formatter: "{name|{b}}\n{per|{d}%}",
162 minMargin: 10,
163 edgeDistance: 10,
164 lineHeight: 15,
165 rich: {
166 name: {
167 color: fg,
168 fontSize: labelFontSize.value
169 },
170 per: {
171 fontSize: labelFontSize.value,
172 fontWeight: "bold",
173 color: fg
174 }
175 }
176 },
177 labelLayout(params) {
178 const chartWidth = chartRef.value?.getWidth() ?? 0
179 const points = params.labelLinePoints
180 const labelRect = params.labelRect
181 if (!points || !labelRect) {
182 return {}
183 }
184 const isLeft = chartWidth > 0 ? labelRect.x < chartWidth / 2 : false
185 if (points[2]) {
186 points[2][0] = isLeft ? labelRect.x : labelRect.x + labelRect.width
187 }
188 return {
189 labelLinePoints: points,
190 hideOverlap: false,
191 moverOverlap: "shiftX",
192 draggable: true
193 }
194 },
195 itemStyle: {
196 borderColor: bg,
197 borderWidth: 1
198 },
199 data: sizeData
200 }
201 ]
202 }
203 })
204 </script>