Skip to content

Commit 7b71fff

Browse files
committed
Refactor
1 parent 872935a commit 7b71fff

File tree

5 files changed

+271
-221
lines changed

5 files changed

+271
-221
lines changed

src/app/components/dashboard/components/week-on-week.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
* This software is released under the MIT License.
55
* https://opensource.org/licenses/MIT
66
*/
7+
import type { Ref } from "vue"
8+
import type { TimerQueryParam } from "@service/timer-service"
79
import type { ECharts } from "echarts/core"
10+
811
import { init, use, ComposeOption } from "echarts/core"
912
import { CandlestickChart, CandlestickSeriesOption } from "echarts/charts"
1013
import {
@@ -18,8 +21,6 @@ use([CandlestickChart, GridComponent, TitleComponent, TooltipComponent])
1821
import { formatPeriodCommon, MILL_PER_DAY } from "@util/time"
1922
import { ElLoading } from "element-plus"
2023
import { defineComponent, h, onMounted, ref } from "vue"
21-
import type { Ref } from "vue"
22-
import type { TimerQueryParam } from "@service/timer-service"
2324
import timerService from "@service/timer-service"
2425
import DataItem from "@entity/dto/data-item"
2526
import { groupBy, sum } from "@util/array"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright (c) 2021 Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
8+
import type { ComputedRef, Ref } from "vue"
9+
import type { TimerQueryParam } from "@service/timer-service"
10+
11+
import { computed, defineComponent, h, onMounted, ref, watch } from "vue"
12+
import timerService, { SortDirect } from "@service/timer-service"
13+
import HostOptionInfo from "../../host-option-info"
14+
import DataItem from "@entity/dto/data-item"
15+
import ChartWrapper from "./wrapper"
16+
17+
const _default = defineComponent({
18+
name: "TrendChart",
19+
setup(_, ctx) {
20+
const elRef: Ref<HTMLDivElement> = ref()
21+
const host: Ref<HostOptionInfo> = ref(HostOptionInfo.empty())
22+
const dateRange: Ref<Array<Date>> = ref([])
23+
const chartWrapper: ChartWrapper = new ChartWrapper()
24+
25+
const queryParam: ComputedRef<TimerQueryParam> = computed(() => ({
26+
// If the host is empty, no result will be queried with this param.
27+
host: host.value.host === '' ? '___foo_bar' : host.value.host,
28+
mergeHost: host.value.merged,
29+
fullHost: true,
30+
sort: 'date',
31+
sortOrder: SortDirect.ASC
32+
}))
33+
34+
async function queryAndRender() {
35+
const row: DataItem[] = await timerService.select(queryParam.value)
36+
chartWrapper.render(host.value, dateRange.value, row)
37+
}
38+
39+
watch(host, () => queryAndRender())
40+
watch(dateRange, () => queryAndRender())
41+
42+
ctx.expose({
43+
setDomain: (key: string) => host.value = HostOptionInfo.from(key),
44+
setDateRange: (newVal: Date[]) => dateRange.value = newVal
45+
})
46+
47+
onMounted(() => {
48+
chartWrapper.init(elRef.value)
49+
queryAndRender()
50+
})
51+
52+
return () => h('div', { class: 'chart-container', ref: elRef })
53+
}
54+
})
55+
56+
export default _default
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* Copyright (c) 2022 Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
8+
import type { ECharts, ComposeOption } from "echarts/core"
9+
import type { LineSeriesOption } from "echarts/charts"
10+
import type {
11+
GraphicComponentOption,
12+
LegendComponentOption,
13+
TitleComponentOption,
14+
TooltipComponentOption,
15+
ToolboxComponentOption,
16+
} from "echarts/components"
17+
18+
import { init, use } from "echarts/core"
19+
import { LineChart } from "echarts/charts"
20+
import { GridComponent, LegendComponent, TitleComponent, TooltipComponent, ToolboxComponent } from "echarts/components"
21+
import { CanvasRenderer } from "echarts/renderers"
22+
import { t } from "@app/locale"
23+
import { formatPeriodCommon, formatTime, MILL_PER_DAY } from "@util/time"
24+
import HostOptionInfo from "../../host-option-info"
25+
import DataItem from "@entity/dto/data-item"
26+
import hostAliasService from "@service/host-alias-service"
27+
import HostAlias from "@entity/dao/host-alias"
28+
29+
use([
30+
LineChart,
31+
GridComponent,
32+
LegendComponent,
33+
TitleComponent,
34+
ToolboxComponent,
35+
TooltipComponent,
36+
CanvasRenderer,
37+
])
38+
39+
type EcOption = ComposeOption<
40+
| LineSeriesOption
41+
| GraphicComponentOption
42+
| LegendComponentOption
43+
| TitleComponentOption
44+
| ToolboxComponentOption
45+
| TooltipComponentOption
46+
>
47+
48+
const TITLE = t(msg => msg.trend.history.title)
49+
const DEFAULT_SUB_TITLE = t(msg => msg.trend.defaultSubTitle)
50+
const SAVE_AS_IMAGE = t(msg => msg.trend.saveAsImageTitle)
51+
52+
const TIME_UNIT = t(msg => msg.trend.history.timeUnit)
53+
const NUMBER_UNIT = t(msg => msg.trend.history.numberUnit)
54+
55+
function formatTimeOfEchart(params: any): string {
56+
const format = params instanceof Array ? params[0] : params
57+
const { seriesName, name, value } = format
58+
return `${seriesName}<br/>${name}&ensp;-&ensp;${formatPeriodCommon((typeof value === 'number' ? value : 0) * 1000)}`
59+
}
60+
61+
const mill2Second = (mill: number) => Math.floor((mill || 0) / 1000)
62+
63+
function optionOf(
64+
xAxisData: string[],
65+
subtext: string,
66+
[focusData, totalData, timeData]: number[][]
67+
) {
68+
const option: EcOption = {
69+
backgroundColor: 'rgba(0,0,0,0)',
70+
grid: { top: '100' },
71+
title: { text: TITLE, subtext, left: 'center' },
72+
tooltip: { trigger: 'item' },
73+
toolbox: {
74+
feature: {
75+
saveAsImage: {
76+
show: true,
77+
title: SAVE_AS_IMAGE,
78+
excludeComponents: ['toolbox'],
79+
pixelRatio: 1,
80+
backgroundColor: '#fff'
81+
}
82+
}
83+
},
84+
xAxis: { type: 'category', data: xAxisData },
85+
yAxis: [
86+
{ name: TIME_UNIT, type: 'value' },
87+
{ name: NUMBER_UNIT, type: 'value' }
88+
],
89+
legend: {
90+
left: 'left',
91+
data: [t(msg => msg.item.total), t(msg => msg.item.focus), t(msg => msg.item.time)]
92+
},
93+
series: [{
94+
// run time
95+
name: t(msg => msg.item.total),
96+
data: totalData,
97+
yAxisIndex: 0,
98+
type: 'line',
99+
smooth: true,
100+
tooltip: { formatter: formatTimeOfEchart }
101+
}, {
102+
name: t(msg => msg.item.focus),
103+
data: focusData,
104+
yAxisIndex: 0,
105+
type: 'line',
106+
smooth: true,
107+
tooltip: { formatter: formatTimeOfEchart }
108+
}, {
109+
name: t(msg => msg.item.time),
110+
data: timeData,
111+
yAxisIndex: 1,
112+
type: 'line',
113+
smooth: true,
114+
tooltip: {
115+
formatter: (params: any) => {
116+
const format = params instanceof Array ? params[0] : params
117+
const { seriesName, name, value } = format
118+
return `${seriesName}<br/>${name}&emsp;-&emsp;${value}`
119+
}
120+
}
121+
}]
122+
}
123+
return option
124+
}
125+
126+
127+
// Get the timestamp of one timestamp of date
128+
const timestampOf = (d: Date) => d.getTime()
129+
130+
/**
131+
* Get the x-axis of date
132+
*/
133+
function getAxias(format: string, dateRange: Date[] | undefined): string[] {
134+
if (!dateRange || !dateRange.length) {
135+
// @since 0.0.9
136+
// The dateRange is cleared, return empty data
137+
return []
138+
}
139+
const xAxisData = []
140+
const startTime = timestampOf(dateRange[0])
141+
const endTime = timestampOf(dateRange[1])
142+
for (let time = startTime; time <= endTime; time += MILL_PER_DAY) {
143+
xAxisData.push(formatTime(time, format))
144+
}
145+
return xAxisData
146+
}
147+
148+
async function processSubtitle(host: HostOptionInfo) {
149+
let subtitle = host.toString()
150+
if (!subtitle) {
151+
return DEFAULT_SUB_TITLE
152+
}
153+
if (!host.merged) {
154+
// If not merged, append the site name to the original subtitle
155+
// @since 0.9.0
156+
const hostAlias: HostAlias = await hostAliasService.get(host.host)
157+
const siteName = hostAlias?.name
158+
siteName && (subtitle += ` / ${siteName}`)
159+
}
160+
return subtitle
161+
}
162+
163+
class ChartWrapper {
164+
instance: ECharts
165+
166+
init(container: HTMLDivElement) {
167+
this.instance = init(container)
168+
}
169+
170+
async render(host: HostOptionInfo, dateRange: Date[], row: DataItem[]) {
171+
// 1. x-axis data
172+
let xAxisData: string[], allDates: string[]
173+
if (!host || !dateRange || dateRange.length !== 2) {
174+
xAxisData = []
175+
allDates = []
176+
} else {
177+
xAxisData = getAxias('{m}/{d}', dateRange)
178+
allDates = getAxias('{y}{m}{d}', dateRange)
179+
}
180+
181+
// 2. subtitle
182+
const subtitle = await processSubtitle(host)
183+
184+
// 3. series data
185+
const focusData = []
186+
const totalData = []
187+
const timeData = []
188+
189+
const dateInfoMap = {}
190+
row.forEach(row => dateInfoMap[row.date] = row)
191+
192+
allDates.forEach(date => {
193+
const row = dateInfoMap[date] || {}
194+
focusData.push(mill2Second(row.focus))
195+
totalData.push(mill2Second(row.total))
196+
timeData.push(row.time || 0)
197+
})
198+
199+
const option: EcOption = optionOf(xAxisData, subtitle, [focusData, totalData, timeData])
200+
201+
this.instance?.setOption(option)
202+
}
203+
}
204+
205+
export default ChartWrapper

0 commit comments

Comments
 (0)