Skip to content

Commit

Permalink
Semi
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepzh committed Aug 6, 2024
1 parent 9ac79fc commit b696315
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 68 deletions.
6 changes: 5 additions & 1 deletion src/app/components/Habit/components/HabitFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand Down
105 changes: 90 additions & 15 deletions src/app/components/Habit/components/Period/Average/Wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,50 +28,116 @@ type EcOption = ComposeOption<
>

export type BizOption = {
currRange: timer.period.KeyRange
prevRange: timer.period.KeyRange
curr: timer.period.Row[]
prev: timer.period.Row[]
periodSize: number
}

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)}&emsp;${formatPeriodCommon(mill ?? 0)}`,
range2Str(range),
)
}

const formatFlexLine = (left: string, right: string): string => {
return `
<div style="display: flex; justify-content: space-between; margin: 0px;">
<span>
${left}
</span>
<span style="margin-left: 20px">
${right}
</span>
</div>
`
}

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(
`<b>${start}-${end}</b>`,
t(msg => msg.habit.period.chartType.average),
)
const spaceLine = `<div style="width: 100%; height: 4px; background-color: transparent"></div>`

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',
Expand All @@ -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] },
}
],
}
Expand Down
15 changes: 11 additions & 4 deletions src/app/components/Habit/components/Period/Average/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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<BizOption>(() => {
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 () => <div style={CONTAINER_STYLE} ref={elRef} />
Expand Down
65 changes: 43 additions & 22 deletions src/app/components/Habit/components/Period/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -28,9 +29,17 @@ function allOptions(): Record<number, string> {
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({
Expand All @@ -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<ChartType>('habit-period-chart-type', prop.defaultValue?.chartType)
watch([periodSize, chartType], () =>
ctx.emit('change', {
periodSize: periodSize.value,
average: average.value,
chartType: chartType.value,
})
)
return () => <>
<SelectFilterItem
historyName='periodSize'
defaultValue={periodSize.value?.toString?.()}
options={allOptions()}
onSelect={(val: string) => {
const newPeriodSize = parseInt(val)
if (isNaN(newPeriodSize)) return
periodSize.value = newPeriodSize
return () => (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'self-start',
}}
/>
<SwitchFilterItem
historyName='average'
label={t(msg => msg.habit.period.averageLabel)}
defaultValue={average.value}
onChange={(val: boolean) => average.value = val}
/>
</>
>
<SelectFilterItem
historyName='periodSize'
defaultValue={periodSize.value?.toString?.()}
options={allOptions()}
onSelect={(val: string) => {
const newPeriodSize = parseInt(val)
if (isNaN(newPeriodSize)) return
periodSize.value = newPeriodSize
}}
/>
<ElRadioGroup
modelValue={chartType.value}
onChange={val => val && setChartType(val as ChartType)}
>
{Object.entries(CHART_CONFIG).map(([type, name]) => (
<ElRadioButton value={type}>
{name}
</ElRadioButton>
))}
</ElRadioGroup>
</div>
)
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion src/app/components/Habit/components/Period/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,27 @@ type Value = {
prev: timer.period.Row[]
}

export type PeriodRange = {
curr: timer.period.KeyRange
prev: timer.period.KeyRange
}

type Context = {
value: Ref<Value>
filter: Ref<FilterOption>
periodRange: Ref<PeriodRange>
}

const NAMESPACE = 'habitPeriod'

export const initProvider = (
value: Ref<Value>,
filter: Ref<FilterOption>,
) => useProvide<Context>(NAMESPACE, { value, filter })
periodRange: Ref<PeriodRange>,
) => useProvide<Context>(NAMESPACE, { value, filter, periodRange })

export const usePeriodValue = (): Ref<Value> => useProvider<Context>(NAMESPACE, "value").value

export const usePeriodFilter = (): Ref<FilterOption> => useProvider<Context>(NAMESPACE, "filter").filter

export const usePeriodRange = (): Ref<PeriodRange> => useProvider<Context>(NAMESPACE, "periodRange").periodRange
10 changes: 5 additions & 5 deletions src/app/components/Habit/components/Period/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -46,7 +46,7 @@ const _default = defineComponent({
setup: () => {
const globalFilter = useHabitFilter()
const periodRange = computed(() => computeRange(globalFilter.value?.dateRange))
const filter = ref<FilterOption>({ periodSize: 1, average: false })
const filter = ref<FilterOption>({ periodSize: 1, chartType: 'average' })

const { data } = useRequest(async () => {
const { curr: currRange, prev: prevRange } = periodRange.value || {}
Expand All @@ -58,7 +58,7 @@ const _default = defineComponent({
return { curr, prev }
}, { deps: [periodRange, filter], defaultValue: { curr: [], prev: [] } })

initProvider(data, filter)
initProvider(data, filter, periodRange)

return () => <KanbanCard
title={t(msg => msg.habit.period.title)}
Expand All @@ -70,7 +70,7 @@ const _default = defineComponent({
</div>
<div class="col1">
{
filter.value?.average
filter.value?.chartType === 'average'
? <Average />
: <Trend />
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/Habit/components/Site/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading

0 comments on commit b696315

Please sign in to comment.