Skip to content

Commit

Permalink
Semi
Browse files Browse the repository at this point in the history
  • Loading branch information
sheepzh committed Aug 5, 2024
1 parent 9a12883 commit 9ac79fc
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 137 deletions.
96 changes: 96 additions & 0 deletions src/app/components/Habit/components/Period/Average/Wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2024 Hengyang Zhang
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/

import { getCompareColor } from "@app/util/echarts"
import { EchartsWrapper } from "@hooks"
import { averageByDay } from "@util/period"
import { getPrimaryTextColor } from "@util/style"
import { BarSeriesOption, ComposeOption, GridComponentOption, TitleComponentOption, TooltipComponentOption } from "echarts"

type EcOption = ComposeOption<
| BarSeriesOption
| GridComponentOption
| TitleComponentOption
| TooltipComponentOption
>

export type BizOption = {
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 milliseconds = row.milliseconds
return {
name: idx,
value: milliseconds,
row,
} as unknown as BarSeriesOption['data'][number]
}


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 textColor = getPrimaryTextColor()

return {
tooltip: {
formatter: (params: any) => ''
},
grid: {
top: 60,
bottom: 30,
left: 100,
right: 80,
},
xAxis: {
type: 'time',
axisLabel: { formatter: '{HH}:{mm}', color: textColor },
axisLine: { show: false },
min: 0,
max: currData.length,
},
yAxis: {
type: 'value',
axisLabel: { show: false },
axisTick: { show: false },
splitLine: { show: false },
},
series: [
{
type: "bar",
stack: 'one',
large: true,
data: currData,
barCategoryGap: '50%',
color: CURR_COLOR,
itemStyle: { borderRadius: [5, 5, 0, 0] },
}, {
type: "bar",
stack: 'one',
large: true,
data: prevData,
color: PREV_COLOR,
itemStyle: { borderRadius: [0, 0, 5, 5] },
}
],
}
}

export default class Wrapper extends EchartsWrapper<BizOption, EcOption> {
generateOption = generateOption
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/**
* Copyright (c) 2021 Hengyang Zhang
* Copyright (c) 2024 Hengyang Zhang
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/

import type { StyleValue } from "vue"
import BarWrapper, { BizOption } from "./BarWrapper"
import BarWrapper, { BizOption } from "./Wrapper"
import { computed, defineComponent } from "vue"
import { usePeriodFilter, usePeriodValue } from "./context"
import { usePeriodFilter, usePeriodValue } from "../context"
import { useEcharts } from "@hooks"

const CONTAINER_STYLE: StyleValue = {
Expand Down
107 changes: 107 additions & 0 deletions src/app/components/Habit/components/Period/Summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { computed, defineComponent } from "vue"
import { usePeriodFilter, usePeriodValue } from "./context"
import { KanbanIndicatorCell } from "@app/components/common/kanban"
import { periodFormatter } from "@app/util/time"
import { t } from "@app/locale"
import { averageByDay } from "@util/period"
import { formatTime } from "@util/time"
import { useHabitFilter } from "../context"

type Result = {
favorite: {
period: string
average: number
}
longestIdle: {
length: string
period: string
}
}

const renderIndicator = (summary: Result, format: timer.app.TimeFormat) => {
const {
favorite: { period: favoritePeriod = null, average = null },
longestIdle: { period: idlePeriod, length: idleLength },
} = summary || {}
return <>
<div class="indicator-wrapper">
<KanbanIndicatorCell
mainName={t(msg => msg.habit.period.busiest)}
mainValue={favoritePeriod}
subTips={msg => msg.habit.common.focusAverage}
subValue={periodFormatter(average, { format })}
/>
</div>
<div class="indicator-wrapper">
<KanbanIndicatorCell
mainName={t(msg => msg.habit.period.idle)}
mainValue={idleLength}
subTips={() => idlePeriod}
/>
</div>
</>
}

const computeSummary = (rows: timer.period.Row[], periodSize: number): Result => {
const averaged = averageByDay(rows, periodSize)
const favoriteRow = averaged.sort((b, a) => a.milliseconds - b.milliseconds)[0]
let favoritePeriod = '-'
if (favoriteRow) {
const start = favoriteRow.startTime
const end = favoriteRow.endTime
favoritePeriod = `${formatTime(start, "{h}:{i}")}-${formatTime(end, "{h}:{i}")}`
}

let maxIdle: [timer.period.Row, timer.period.Row, number] = [, , 0]

let idleStart: timer.period.Row = null, idleEnd: timer.period.Row = null
rows.forEach(r => {
if (r.milliseconds) {
if (!idleStart) return
const newEmptyTs = idleEnd.endTime.getTime() - idleStart.endTime.getTime()
if (newEmptyTs > maxIdle[2]) {
maxIdle = [idleStart, idleEnd, newEmptyTs]
}
idleStart = idleEnd = null
} else {
idleEnd = r
!idleStart && (idleStart = idleEnd)
}
})

const [start, end] = maxIdle

let idleLength = '-'
let idlePeriod = null
if (start && end) {
idleLength = periodFormatter(end.endTime.getTime() - start.startTime.getTime(), { format: 'hour' })
const format = t(msg => msg.calendar.simpleTimeFormat)
const startTime = formatTime(start.startTime, format)
const endTime = formatTime(end.endTime, format)
idlePeriod = startTime + '-' + endTime
}

return {
favorite: {
period: favoritePeriod,
average: favoriteRow?.milliseconds,
},
longestIdle: {
length: idleLength,
period: idlePeriod,
}
}
}

const _default = defineComponent({
setup() {
const data = usePeriodValue()
const filter = usePeriodFilter()
const globalFilter = useHabitFilter()
const summary = computed(() => computeSummary(data.value?.curr, filter.value?.periodSize))

return () => renderIndicator(summary.value, globalFilter.value?.timeFormat)
}
})

export default _default
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { TooltipComponent, GridComponent } from "echarts/components"
import { formatPeriodCommon, formatTime } from "@util/time"
import { getPrimaryTextColor } from "@util/style"
import { EchartsWrapper } from "@hooks"
import { averageByDay } from "@util/period"
import { getCompareColor } from "@app/util/echarts"

use([BarChart, SVGRenderer, TooltipComponent, GridComponent])
Expand All @@ -28,9 +27,7 @@ type EcOption = ComposeOption<
>

export type BizOption = {
curr: timer.period.Row[]
prev: timer.period.Row[]
averageByDate: boolean
data: timer.period.Row[]
periodSize: number
}

Expand All @@ -45,12 +42,12 @@ function formatXAxis(params) {
}
}

function formatTimeOfEcharts(params: any, averageByDate: boolean): string {
function formatTimeOfEcharts(params: any): string {
const format = params instanceof Array ? params[0] : params
const { value, data } = format
const milliseconds = data?.[4] || 0
// If average, don't show the date
const start = formatTime(value[2], averageByDate ? '{h}:{i}' : '{m}-{d} {h}:{i}')
const start = formatTime(value[2], '{m}-{d} {h}:{i}')
const end = formatTime(value[3], '{h}:{i}')
return `${formatPeriodCommon(Math.floor(milliseconds))}<br/>${start}-${end}`
}
Expand All @@ -71,21 +68,26 @@ const cvt2Item = (row: timer.period.Row, idx: number, periodSize: number): BarIt
return [idx, getYAxisValue(milliseconds, periodSize), startTime, endTime, milliseconds]
}

function generateOption({ curr, prev, averageByDate, periodSize }: BizOption): EcOption {
if (averageByDate) {
curr = averageByDay(curr, periodSize)
prev = averageByDay(prev, periodSize)
}
function generateOption({ data, periodSize }: BizOption): EcOption {

const currData: BarItem[] = curr.map((r, idx) => cvt2Item(r, idx, periodSize))
const prevData: BarItem[] = prev.map((r, idx) => cvt2Item(({ ...r, milliseconds: -r.milliseconds }), idx, periodSize))
const xAxisAxisLabelFormatter = averageByDate ? '{HH}:{mm}' : formatXAxis
const textColor = getPrimaryTextColor()
const [currColor, prevColor] = getCompareColor()

const currData: BarItem[] = data.map((r, idx) => cvt2Item(r, idx, periodSize))
const series: EcOption['series'] = [{
type: "bar",
stack: 'one',
large: true,
data: currData,
barCategoryGap: 0,
itemStyle: { borderRadius: [5, 5, 0, 0] },
animationDelay: idx => idx * 10,
}]

const textColor = getPrimaryTextColor()

return {
tooltip: {
formatter: (params: any) => formatTimeOfEcharts(params, averageByDate)
formatter: (params: any) => formatTimeOfEcharts(params)
},
grid: {
top: 60,
Expand All @@ -95,7 +97,7 @@ function generateOption({ curr, prev, averageByDate, periodSize }: BizOption): E
},
xAxis: {
type: 'time',
axisLabel: { formatter: xAxisAxisLabelFormatter, color: textColor },
axisLabel: { formatter: formatXAxis, color: textColor },
axisLine: { show: false },
min: 0,
max: currData.length,
Expand All @@ -106,22 +108,7 @@ function generateOption({ curr, prev, averageByDate, periodSize }: BizOption): E
axisTick: { show: false },
splitLine: { show: false },
},
series: [{
type: "bar",
stack: 'one',
large: true,
data: currData,
// barGap: '50%',
barCategoryGap: '50%',
color: currColor,
itemStyle: { borderRadius: [5, 5, 0, 0] },
}, {
type: "bar",
stack: 'one',
large: true,
data: prevData,
color: prevColor,
}]
series,
}
}
export default class ChartWrapper extends EchartsWrapper<BizOption, EcOption> {
Expand Down
30 changes: 30 additions & 0 deletions src/app/components/Habit/components/Period/Trend/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 Hengyang Zhang
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/

import type { StyleValue } from "vue"
import BarWrapper, { BizOption } from "./Wrapper"
import { computed, defineComponent } from "vue"
import { usePeriodFilter, usePeriodValue } from "../context"
import { useEcharts } from "@hooks"

const CONTAINER_STYLE: StyleValue = {
width: "100%",
height: "100%",
}

const _default = defineComponent(() => {
const value = usePeriodValue()
const filter = usePeriodFilter()
const bizOption = computed(() => {
const { periodSize } = filter.value || {}
return { data: value.value?.curr, periodSize } as BizOption
})
const { elRef } = useEcharts(BarWrapper, bizOption, { manual: true })
return () => <div style={CONTAINER_STYLE} ref={elRef} />
})

export default _default
Loading

0 comments on commit 9ac79fc

Please sign in to comment.