diff --git a/src/app/components/Habit/components/HabitFilter.tsx b/src/app/components/Habit/components/HabitFilter.tsx
index 60ce63e8..d33a0a45 100644
--- a/src/app/components/Habit/components/HabitFilter.tsx
+++ b/src/app/components/Habit/components/HabitFilter.tsx
@@ -11,9 +11,13 @@ import { t } from "@app/locale"
import { ElementDatePickerShortcut } from "@src/element-ui/date"
import DateRangeFilterItem from "@app/components/common/DateRangeFilterItem"
import TimeFormatFilterItem from "@app/components/common/TimeFormatFilterItem"
-import { FilterOption } from "../type"
import { useState } from "@hooks"
+export type FilterOption = {
+ timeFormat: timer.app.TimeFormat
+ dateRange: [Date, Date]
+}
+
type ShortCutProp = [label: string, dayAgo: number]
const shortcutProps: ShortCutProp[] = [
diff --git a/src/app/components/Habit/components/Period/Average/Wrapper.tsx b/src/app/components/Habit/components/Period/Average/Wrapper.tsx
index f6c3248c..cb686b79 100644
--- a/src/app/components/Habit/components/Period/Average/Wrapper.tsx
+++ b/src/app/components/Habit/components/Period/Average/Wrapper.tsx
@@ -5,11 +5,20 @@
* https://opensource.org/licenses/MIT
*/
-import { getCompareColor } from "@app/util/echarts"
+import { t } from "@app/locale"
+import { getCompareColor, tooltipDot } from "@app/util/echarts"
import { EchartsWrapper } from "@hooks"
-import { averageByDay } from "@util/period"
+import { averageByDay, MINUTE_PER_PERIOD } from "@util/period"
import { getPrimaryTextColor } from "@util/style"
+import { formatPeriodCommon } from "@util/time"
import { BarSeriesOption, ComposeOption, GridComponentOption, TitleComponentOption, TooltipComponentOption } from "echarts"
+import { BarChart } from "echarts/charts"
+import { GridComponent, TitleComponent, TooltipComponent } from "echarts/components"
+import { use } from "echarts/core"
+import { SVGRenderer } from "echarts/renderers"
+import { TopLevelFormatterParams } from "echarts/types/dist/shared"
+
+use([SVGRenderer, BarChart, GridComponent, TitleComponent, , TooltipComponent])
type EcOption = ComposeOption<
| BarSeriesOption
@@ -19,6 +28,8 @@ type EcOption = ComposeOption<
>
export type BizOption = {
+ currRange: timer.period.KeyRange
+ prevRange: timer.period.KeyRange
curr: timer.period.Row[]
prev: timer.period.Row[]
periodSize: number
@@ -26,43 +37,107 @@ export type BizOption = {
const [CURR_COLOR, PREV_COLOR] = getCompareColor()
-const cvt2Item = (row: timer.period.Row, idx: number): BarSeriesOption['data'][number] => {
+const cvt2Item = (row: timer.period.Row): BarSeriesOption['data'][number] => {
const milliseconds = row.milliseconds
return {
- name: idx,
value: milliseconds,
row,
} as unknown as BarSeriesOption['data'][number]
}
+const formatXAxis = (idx: number, periodSize: number) => {
+ let min = idx * periodSize * MINUTE_PER_PERIOD
+ const hour = Math.floor(min / 60)
+ min = min - hour * 60
+ return hour.toString().padStart(2, '0') + ':' + min.toString().padStart(2, '0')
+}
+
+const key2Str = (key: timer.period.Key) => {
+ const { month, date } = key
+ return `${month?.toString?.()?.padStart(2, '0')}/${date?.toString?.()?.padStart(2, '0')}`
+}
+
+const range2Str = (keyRange: timer.period.KeyRange) => {
+ const [start, end] = keyRange
+ return `${key2Str(start)}-${key2Str(end)}`
+}
+
+const formatValueLine = (mill: number, range: timer.period.KeyRange, color: string): string => {
+ return formatFlexLine(
+ `${tooltipDot(color)} ${formatPeriodCommon(mill ?? 0)}`,
+ range2Str(range),
+ )
+}
+
+const formatFlexLine = (left: string, right: string): string => {
+ return `
+
+
+ ${left}
+
+
+ ${right}
+
+
+ `
+}
+
+const formatTooltip = (params: TopLevelFormatterParams, biz: BizOption): string => {
+ const { periodSize, prevRange, currRange } = biz
+ console.log(params)
+ if (!Array.isArray(params)) return ''
+ const [curr, prev] = params || []
+
+ const idx = curr.dataIndex
+ const start = formatXAxis(idx, periodSize)
+ const end = formatXAxis(idx + 1, periodSize)
+ const timeLine = formatFlexLine(
+ `${start}-${end}`,
+ t(msg => msg.habit.period.chartType.average),
+ )
+ const spaceLine = ``
+
+ const currLine = formatValueLine(curr.value as number, currRange, CURR_COLOR)
+ const prevLine = formatValueLine(-(prev.value as number), prevRange, PREV_COLOR)
+
+ return `${timeLine}${spaceLine}${currLine}${prevLine}`
+}
const generateOption = (biz: BizOption): EcOption => {
let { curr, prev, periodSize } = biz
curr = averageByDay(curr, periodSize)
prev = averageByDay(prev, periodSize)
- const currData = curr.map((r, idx) => cvt2Item(r, idx))
- const prevData = prev.map((r, idx) => cvt2Item(({ ...r, milliseconds: -r.milliseconds }), idx))
+ const currData = curr.map(r => cvt2Item(r))
+ const prevData = prev.map(r => cvt2Item(({ ...r, milliseconds: -r.milliseconds })))
const textColor = getPrimaryTextColor()
+ const borderRadius = 5 * periodSize
return {
tooltip: {
- formatter: (params: any) => ''
+ trigger: 'axis',
+ formatter: (params: TopLevelFormatterParams) => formatTooltip(params, biz),
},
grid: {
- top: 60,
- bottom: 30,
- left: 100,
- right: 80,
+ top: 30,
+ bottom: 0,
+ left: 40,
+ right: 20,
},
xAxis: {
- type: 'time',
- axisLabel: { formatter: '{HH}:{mm}', color: textColor },
+ type: 'category',
+ axisLabel: {
+ color: textColor,
+ interval: (16 / periodSize - 1),
+ formatter: (_, index) => formatXAxis(index, periodSize),
+ },
axisLine: { show: false },
+ axisTick: { show: false },
min: 0,
max: currData.length,
+ offset: -borderRadius * 2,
},
yAxis: {
type: 'value',
@@ -78,14 +153,14 @@ const generateOption = (biz: BizOption): EcOption => {
data: currData,
barCategoryGap: '50%',
color: CURR_COLOR,
- itemStyle: { borderRadius: [5, 5, 0, 0] },
+ itemStyle: { borderRadius: [borderRadius, borderRadius, 0, 0] },
}, {
type: "bar",
stack: 'one',
large: true,
data: prevData,
color: PREV_COLOR,
- itemStyle: { borderRadius: [0, 0, 5, 5] },
+ itemStyle: { borderRadius: [0, 0, borderRadius, borderRadius] },
}
],
}
diff --git a/src/app/components/Habit/components/Period/Average/index.tsx b/src/app/components/Habit/components/Period/Average/index.tsx
index 23742fa5..8371a4b7 100644
--- a/src/app/components/Habit/components/Period/Average/index.tsx
+++ b/src/app/components/Habit/components/Period/Average/index.tsx
@@ -8,7 +8,7 @@
import type { StyleValue } from "vue"
import BarWrapper, { BizOption } from "./Wrapper"
import { computed, defineComponent } from "vue"
-import { usePeriodFilter, usePeriodValue } from "../context"
+import { usePeriodFilter, usePeriodRange, usePeriodValue } from "../context"
import { useEcharts } from "@hooks"
const CONTAINER_STYLE: StyleValue = {
@@ -19,9 +19,16 @@ const CONTAINER_STYLE: StyleValue = {
const _default = defineComponent(() => {
const value = usePeriodValue()
const filter = usePeriodFilter()
- const bizOption = computed(() => {
- const { periodSize, average } = filter.value || {}
- return { ...value.value || {}, averageByDate: average, periodSize } as BizOption
+ const periodRange = usePeriodRange()
+ const bizOption = computed(() => {
+ const { periodSize } = filter.value || {}
+ return {
+ curr: value.value?.curr,
+ prev: value.value?.prev,
+ periodSize,
+ currRange: periodRange.value?.curr,
+ prevRange: periodRange.value?.prev,
+ }
})
const { elRef } = useEcharts(BarWrapper, bizOption, { manual: true })
return () =>
diff --git a/src/app/components/Habit/components/Period/Filter.tsx b/src/app/components/Habit/components/Period/Filter.tsx
index d647c2aa..d9d5b623 100644
--- a/src/app/components/Habit/components/Period/Filter.tsx
+++ b/src/app/components/Habit/components/Period/Filter.tsx
@@ -6,9 +6,10 @@
*/
import SelectFilterItem from '@app/components/common/SelectFilterItem'
-import SwitchFilterItem from '@app/components/common/SwitchFilterItem'
import { t } from '@app/locale'
+import { useCached } from '@hooks'
import { HabitMessage } from '@i18n/message/app/habit'
+import { ElRadioButton, ElRadioGroup } from 'element-plus'
import { PropType, defineComponent, ref, watch } from 'vue'
// [value, label]
@@ -28,9 +29,17 @@ function allOptions(): Record {
return allOptions
}
+const ALL_CHARTS = ['average', 'trend', 'stack'] as const
+export type ChartType = typeof ALL_CHARTS[number]
+const CHART_CONFIG: { [type in ChartType]: string } = {
+ average: t(msg => msg.habit.period.chartType.average),
+ trend: t(msg => msg.habit.period.chartType.trend),
+ stack: t(msg => msg.habit.period.chartType.stack),
+}
+
export type FilterOption = {
periodSize: number
- average: boolean
+ chartType: ChartType
}
const _default = defineComponent({
@@ -42,31 +51,43 @@ const _default = defineComponent({
},
setup(prop, ctx) {
const periodSize = ref(prop.defaultValue?.periodSize || 1)
- const average = ref(prop.defaultValue?.average || false)
- watch([periodSize, average], () =>
+ const { data: chartType, setter: setChartType } = useCached('habit-period-chart-type', prop.defaultValue?.chartType)
+ watch([periodSize, chartType], () =>
ctx.emit('change', {
periodSize: periodSize.value,
- average: average.value,
+ chartType: chartType.value,
})
)
- return () => <>
- {
- const newPeriodSize = parseInt(val)
- if (isNaN(newPeriodSize)) return
- periodSize.value = newPeriodSize
+ return () => (
+
- msg.habit.period.averageLabel)}
- defaultValue={average.value}
- onChange={(val: boolean) => average.value = val}
- />
- >
+ >
+ {
+ const newPeriodSize = parseInt(val)
+ if (isNaN(newPeriodSize)) return
+ periodSize.value = newPeriodSize
+ }}
+ />
+ val && setChartType(val as ChartType)}
+ >
+ {Object.entries(CHART_CONFIG).map(([type, name]) => (
+
+ {name}
+
+ ))}
+
+
+ )
},
})
diff --git a/src/app/components/Habit/components/Period/Trend/Wrapper.ts b/src/app/components/Habit/components/Period/Trend/Wrapper.ts
index 6202cd41..079a586e 100644
--- a/src/app/components/Habit/components/Period/Trend/Wrapper.ts
+++ b/src/app/components/Habit/components/Period/Trend/Wrapper.ts
@@ -32,7 +32,6 @@ export type BizOption = {
}
function formatXAxis(params) {
- console.log(params)
const ts = params as number
const date = new Date(ts)
if (date.getHours() === 0 && date.getMinutes() === 0) {
diff --git a/src/app/components/Habit/components/Period/context.ts b/src/app/components/Habit/components/Period/context.ts
index 8b4c5319..f63d1f0f 100644
--- a/src/app/components/Habit/components/Period/context.ts
+++ b/src/app/components/Habit/components/Period/context.ts
@@ -14,9 +14,15 @@ type Value = {
prev: timer.period.Row[]
}
+export type PeriodRange = {
+ curr: timer.period.KeyRange
+ prev: timer.period.KeyRange
+}
+
type Context = {
value: Ref
filter: Ref
+ periodRange: Ref
}
const NAMESPACE = 'habitPeriod'
@@ -24,8 +30,11 @@ const NAMESPACE = 'habitPeriod'
export const initProvider = (
value: Ref,
filter: Ref,
-) => useProvide(NAMESPACE, { value, filter })
+ periodRange: Ref,
+) => useProvide(NAMESPACE, { value, filter, periodRange })
export const usePeriodValue = (): Ref => useProvider(NAMESPACE, "value").value
export const usePeriodFilter = (): Ref => useProvider(NAMESPACE, "filter").filter
+
+export const usePeriodRange = (): Ref => useProvider(NAMESPACE, "periodRange").periodRange
\ No newline at end of file
diff --git a/src/app/components/Habit/components/Period/index.tsx b/src/app/components/Habit/components/Period/index.tsx
index c7d8c732..01097a7d 100644
--- a/src/app/components/Habit/components/Period/index.tsx
+++ b/src/app/components/Habit/components/Period/index.tsx
@@ -17,11 +17,11 @@ import periodService from "@service/period-service"
import { useHabitFilter } from "../context"
import { getDayLength, MILL_PER_DAY } from "@util/time"
import { MAX_PERIOD_ORDER, keyOf } from "@util/period"
-import { initProvider } from "./context"
+import { initProvider, PeriodRange } from "./context"
import { useRequest } from "@src/hooks/useRequest"
import Average from "./Average"
-const computeRange = (filterDateRange: [Date, Date]): { curr: timer.period.KeyRange, prev: timer.period.KeyRange } => {
+const computeRange = (filterDateRange: [Date, Date]): PeriodRange => {
const [startDate, endDate] = filterDateRange || []
const dateLength = getDayLength(startDate, endDate)
const prevStartDate = new Date(startDate.getTime() - MILL_PER_DAY * dateLength)
@@ -46,7 +46,7 @@ const _default = defineComponent({
setup: () => {
const globalFilter = useHabitFilter()
const periodRange = computed(() => computeRange(globalFilter.value?.dateRange))
- const filter = ref({ periodSize: 1, average: false })
+ const filter = ref({ periodSize: 1, chartType: 'average' })
const { data } = useRequest(async () => {
const { curr: currRange, prev: prevRange } = periodRange.value || {}
@@ -58,7 +58,7 @@ const _default = defineComponent({
return { curr, prev }
}, { deps: [periodRange, filter], defaultValue: { curr: [], prev: [] } })
- initProvider(data, filter)
+ initProvider(data, filter, periodRange)
return () => msg.habit.period.title)}
@@ -70,7 +70,7 @@ const _default = defineComponent({
{
- filter.value?.average
+ filter.value?.chartType === 'average'
?
:
}
diff --git a/src/app/components/Habit/components/Site/Summary.tsx b/src/app/components/Habit/components/Site/Summary.tsx
index 50668ecf..1764b71d 100644
--- a/src/app/components/Habit/components/Site/Summary.tsx
+++ b/src/app/components/Habit/components/Site/Summary.tsx
@@ -4,9 +4,9 @@ import { periodFormatter } from "@app/util/time"
import { computed, defineComponent } from "vue"
import { useHabitFilter } from "../context"
import { useRows } from "./context"
-import { FilterOption } from "../../type"
import { computeAverageLen } from "./common"
import { sum } from "@util/array"
+import { FilterOption } from "../HabitFilter"
type Result = {
focus: {
diff --git a/src/app/components/Habit/components/context.ts b/src/app/components/Habit/components/context.ts
index 42840ea2..2341214d 100644
--- a/src/app/components/Habit/components/context.ts
+++ b/src/app/components/Habit/components/context.ts
@@ -6,8 +6,8 @@
*/
import { Ref } from "vue"
-import { FilterOption } from "../type"
import { useProvide, useProvider } from "@hooks"
+import { FilterOption } from "./HabitFilter"
type Context = {
filter: Ref
diff --git a/src/app/components/Habit/index.tsx b/src/app/components/Habit/index.tsx
index 28a846f1..797eb2f6 100644
--- a/src/app/components/Habit/index.tsx
+++ b/src/app/components/Habit/index.tsx
@@ -4,12 +4,10 @@
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
-import type { FilterOption } from "./type"
-
import { defineComponent } from "vue"
import { daysAgo } from "@util/time"
import ContentContainer from "@app/components/common/ContentContainer"
-import HabitFilter from "./components/HabitFilter"
+import HabitFilter, { FilterOption } from "./components/HabitFilter"
import Site from "./components/Site"
import Period from "./components/Period"
import { initProvider } from "./components/context"
diff --git a/src/app/components/Habit/type.d.ts b/src/app/components/Habit/type.d.ts
deleted file mode 100644
index 15df48fb..00000000
--- a/src/app/components/Habit/type.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export type FilterOption = {
- timeFormat: timer.app.TimeFormat
- dateRange: [Date, Date]
-}
\ No newline at end of file
diff --git a/src/i18n/message/app/habit-resource.json b/src/i18n/message/app/habit-resource.json
index d149db14..5fd0d47c 100644
--- a/src/i18n/message/app/habit-resource.json
+++ b/src/i18n/message/app/habit-resource.json
@@ -7,7 +7,11 @@
"title": "不同时段的访问习惯",
"busiest": "每天最繁忙时段",
"idle": "最长空闲时段",
- "averageLabel": "平均每天",
+ "chartType": {
+ "average": "日均",
+ "trend": "趋势",
+ "stack": "累积"
+ },
"sizes": {
"fifteen": "每十五分钟统计一次",
"halfHour": "每半小时统计一次",
@@ -35,7 +39,6 @@
},
"zh_TW": {
"period": {
- "averageLabel": "平均每天",
"sizes": {
"fifteen": "按十五分鐘統計",
"halfHour": "按半小時統計",
@@ -65,7 +68,11 @@
"title": "Habit of time periods",
"busiest": "Busiest time of day",
"idle": "Longest idle period",
- "averageLabel": "Daily average",
+ "chartType": {
+ "average": "Daily average",
+ "trend": "Trend",
+ "stack": "Stack"
+ },
"sizes": {
"fifteen": "Per 15 minutes",
"halfHour": "Per half hour",
@@ -93,7 +100,6 @@
},
"ja": {
"period": {
- "averageLabel": "1日平均",
"sizes": {
"fifteen": "15分で統計",
"halfHour": "30分で統計",
@@ -117,7 +123,6 @@
},
"pt_PT": {
"period": {
- "averageLabel": "Média diária",
"sizes": {
"fifteen": "Por 15 minutos",
"halfHour": "Por meia hora",
@@ -147,7 +152,6 @@
"hour": "Кожну годину",
"twoHour": "Кожні 2 години"
},
- "averageLabel": "Середнє за день",
"title": "Проміжки часу",
"busiest": "Найзавантаженіший час доби",
"idle": "Найдовший період бездіяльності"
@@ -171,7 +175,6 @@
"title": "Hábito de períodos de tiempo",
"busiest": "Hora más ocupada del día",
"idle": "Periodo inactivo más largo",
- "averageLabel": "Promedio diario",
"sizes": {
"fifteen": "Por 15 minutos",
"halfHour": "Por media hora",
@@ -195,7 +198,6 @@
"title": "Gewohnheiten jeden Augenblick",
"busiest": "Die geschäftigste Zeit des Tages",
"idle": "Längste Leerlaufzeit",
- "averageLabel": "Täglicher Durchschnitt",
"sizes": {
"fifteen": "Pro 15 Minuten",
"halfHour": "Pro halbe Stunde",
@@ -219,7 +221,6 @@
"title": "Habitude des périodes de temps",
"busiest": "Période de la journée la plus chargée",
"idle": "La plus longue période d'inactivité",
- "averageLabel": "Moyenne quotidienne",
"sizes": {
"fifteen": "Par tranche de 15 minutes",
"halfHour": "Par demi-heure",
diff --git a/src/i18n/message/app/habit.ts b/src/i18n/message/app/habit.ts
index 2d893125..25282083 100644
--- a/src/i18n/message/app/habit.ts
+++ b/src/i18n/message/app/habit.ts
@@ -15,7 +15,11 @@ export type HabitMessage = {
title: string
busiest: string
idle: string
- averageLabel: string
+ chartType: {
+ average: string
+ trend: string
+ stack: string
+ }
sizes: {
fifteen: string
halfHour: string