Skip to content

Commit

Permalink
feat: add all time overview
Browse files Browse the repository at this point in the history
closes #34
  • Loading branch information
simonwep committed Apr 21, 2024
1 parent e40f405 commit 4a0f770
Show file tree
Hide file tree
Showing 16 changed files with 493 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/app/components/charts/line-chart/LineChart.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface StackedLineChartSeries {
export interface LineChartConfig {
series: StackedLineChartSeries[];
labels: string[];
valueFormatter?: (value: number) => string;
}
22 changes: 16 additions & 6 deletions src/app/components/charts/line-chart/LineChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
</template>

<script lang="ts" setup>
import { GridComponentOption, LegendComponentOption, LineSeriesOption } from 'echarts';
import { LineChart } from 'echarts/charts';
import { GridComponent, LegendComponent } from 'echarts/components';
import { GridComponentOption, LegendComponentOption, LineSeriesOption, TooltipComponentOption } from 'echarts';
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components';
import * as echarts from 'echarts/core';
import { SVGRenderer } from 'echarts/renderers';
import { computed } from 'vue';
import EChart from '@components/charts/echart/EChart.vue';
import { LineChartConfig } from '@components/charts/line-chart/LineChart.types';
import { ClassNames } from '@utils';
echarts.use([LineChart, SVGRenderer, LegendComponent, GridComponent]);
echarts.use([SVGRenderer, LegendComponent, GridComponent, TooltipComponent]);
type EChartsOption = echarts.ComposeOption<LineSeriesOption | GridComponentOption | LegendComponentOption>;
type EChartsOption = echarts.ComposeOption<
LineSeriesOption | TooltipComponentOption | GridComponentOption | LegendComponentOption
>;
const props = defineProps<{
class?: ClassNames;
Expand Down Expand Up @@ -53,7 +54,10 @@ const options = computed(
type: 'value',
axisTick: { lineStyle: { color: 'var(--chart-line-color)' } },
axisLine: { lineStyle: { color: 'var(--chart-line-color)' } },
axisLabel: { color: 'var(--chart-label)' },
axisLabel: {
color: 'var(--chart-label)',
formatter: props.data.valueFormatter
},
splitLine: { lineStyle: { color: 'var(--chart-line-color)' } }
},
series: props.data.series.map((v) => ({
Expand All @@ -72,4 +76,10 @@ const options = computed(
width: 100%;
height: 100%;
}
.chartTooltip {
font-family: var(--font-family);
color: var(--c-primary-text);
background: var(--c-primary);
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface StackedLineChartSeries {
name: string;
trendName: string;
data: number[];
color?: string;
}

export interface StackedLineChartConfig {
series: StackedLineChartSeries[];
labels: string[];
valueFormatter?: (value: number) => string;
}
150 changes: 150 additions & 0 deletions src/app/components/charts/stacked-line-chart/StackedLineChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<template>
<EChart :class="[$style.stackedLineChart, classes]" :options="options" />
</template>

<script lang="ts" setup>
import { GridComponentOption, LegendComponentOption, LineSeriesOption, TooltipComponentOption } from 'echarts';
import { LineChart } from 'echarts/charts';
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components';
import * as echarts from 'echarts/core';
import { SVGRenderer } from 'echarts/renderers';
import { computed } from 'vue';
import EChart from '@components/charts/echart/EChart.vue';
import { ClassNames, rollingAverage } from '@utils';
import { StackedLineChartConfig } from './StackedLineChart.types';
echarts.use([LineChart, SVGRenderer, LegendComponent, GridComponent, TooltipComponent]);
type EChartsOption = echarts.ComposeOption<
LineSeriesOption | TooltipComponentOption | GridComponentOption | LegendComponentOption
>;
const props = defineProps<{
class?: ClassNames;
data: StackedLineChartConfig;
}>();
const classes = computed(() => props.class);
/* eslint-disable @typescript-eslint/no-explicit-any */
const options = computed(
(): EChartsOption => ({
animation: false,
legend: {
data: props.data.series.flatMap((v) => [v.name, v.trendName]),
textStyle: { color: 'var(--chart-label)' },
lineStyle: { width: 2, cap: 'round' },
itemStyle: { opacity: 0 }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '35px',
containLabel: true
},
tooltip: {
trigger: 'axis',
transitionDuration: 0,
backgroundColor: 'var(--chart-tooltip-background-color)',
borderColor: 'var(--chart-tooltip-border-color)',
textStyle: {
color: 'var(--chart-tooltip-color)',
fontFamily: 'var(--font-family)',
fontSize: 12
},
valueFormatter: (v) => props.data.valueFormatter?.(v as number) ?? String(v),
axisPointer: {
type: 'cross',
label: {
color: 'var(--c-primary-text)',
backgroundColor: 'var(--chart-tooltip-axis-label-background)',
formatter: (params) => {
if (params.axisDimension === 'x' || !props.data.valueFormatter) {
return ` ${params.value}`;
} else {
return props.data.valueFormatter(params.value as number);
}
}
},
lineStyle: {
color: 'var(--chart-tooltip-cross-color)'
},
crossStyle: {
color: 'var(--chart-tooltip-cross-color)'
}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: props.data.labels,
axisTick: { lineStyle: { color: 'var(--chart-line-color)' } },
axisLine: { lineStyle: { color: 'var(--chart-line-color)' } },
axisLabel: { color: 'var(--chart-label)' }
},
yAxis: {
type: 'value',
axisTick: { lineStyle: { color: 'var(--chart-line-color)' } },
axisLine: { lineStyle: { color: 'var(--chart-line-color)' } },
axisLabel: {
color: 'var(--chart-label)',
formatter: props.data.valueFormatter
},
splitLine: { lineStyle: { color: 'var(--chart-line-color)' } }
},
series: props.data.series.flatMap((s, seriesIndex, series) => {
const areaGraph: EChartsOption['series'] = {
name: s.name,
type: 'line',
data: s.data.map(
(d, index) =>
d - (seriesIndex ? series.slice(0, seriesIndex).reduce((acc, curr) => acc + curr.data[index], 0) : 0)
),
color: s.color,
silent: true,
stack: 'Total',
areaStyle: {
color: s.color,
opacity: 0.15
},
emphasis: {
disabled: true
}
};
const trendLine: EChartsOption['series'] = {
name: s.trendName,
type: 'line',
data: rollingAverage(s.data, 6),
color: s.color,
showSymbol: false,
silent: true,
tooltip: {
show: false
},
lineStyle: {
width: 1,
type: 'dashed',
opacity: 0.25
}
};
return [areaGraph, trendLine];
})
})
);
</script>

<style lang="scss" module>
.stackedLineChart {
width: 100%;
height: 100%;
}
.chartTooltip {
font-family: var(--font-family);
color: var(--c-primary-text);
background: var(--c-primary);
}
</style>
2 changes: 2 additions & 0 deletions src/app/pages/Frame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
<ToolsButton :class="$style.btn" />
<AdminButton v-if="user?.admin" :class="$style.btn" />
<ChangeYearButton :class="$style.btn" />

<template v-if="media !== 'mobile'">
<ChangeLanguageButton :class="$style.btn" />
<ChangeCurrencyButton :class="$style.btn" />
<InfoButton :class="$style.btn" />
</template>

<div :class="$style.divider" />
<CloudButton :class="$style.btn" />
</div>
Expand Down
55 changes: 43 additions & 12 deletions src/app/pages/dashboard/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -1,57 +1,81 @@
<template>
<Pane :class="$style.dashboard">
<template v-if="state.years.length > 1" #beforeTitle>
<Button :icon="RiArrowLeftSLine" rounded @click="rotateYear(-1)" />
<Button :icon="RiArrowRightSLine" rounded @click="rotateYear(1)" />
</template>
<template #title>
<span>
<i18n-t keypath="page.dashboard.header" scope="global">
<template v-if="view === AllTime">
<RiCalendar2Line size="18" />
<span>
{{ t('page.dashboard.allTimeFromTo', { from: state.years[0].year, to: state.years.at(-1)?.year }) }}
</span>
</template>
<template v-else>
<template v-if="state.years.length > 1">
<Button :icon="RiArrowLeftSLine" rounded @click="rotateYear(-1)" />
<Button :icon="RiArrowRightSLine" rounded @click="rotateYear(1)" />
</template>
<i18n-t tag="span" keypath="page.dashboard.header" scope="global">
<template #year>
<TextWheel :values="allYears" :value="state.activeYear" />
</template>
</i18n-t>
</span>
</template>
</template>
<template #header>
<div :class="$style.viewButtons">
<Button
textual
size="l"
:icon="RiPieChartLine"
:icon="RiDashboardLine"
:tooltip="t('page.dashboard.title')"
:color="view === Overview ? 'primary' : 'dimmed'"
@click="view = Overview"
/>
<Button
textual
size="l"
:icon="RiGridLine"
:icon="RiTableLine"
:tooltip="t('page.dashboard.tables')"
:color="view === Summary ? 'primary' : 'dimmed'"
@click="view = Summary"
/>
<span :class="$style.divider" />
<Button
textual
size="l"
:icon="RiEarthLine"
:tooltip="t('page.dashboard.allTime')"
:color="view === AllTime ? 'primary' : 'dimmed'"
@click="view = AllTime"
/>
</div>
</template>
<ComponentTransition :is="view" />
</Pane>
</template>

<script lang="ts" setup>
import { RiArrowLeftSLine, RiArrowRightSLine, RiGridLine, RiPieChartLine } from '@remixicon/vue';
import {
RiArrowLeftSLine,
RiArrowRightSLine,
RiCalendar2Line,
RiDashboardLine,
RiEarthLine,
RiTableLine
} from '@remixicon/vue';
import { computed, shallowRef } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '@components/base/button/Button.vue';
import TextWheel from '@components/base/text-wheel/TextWheel.vue';
import ComponentTransition from '@components/misc/component-transition/ComponentTransition.vue';
import { useDataStore } from '@store/state';
import Pane from '../shared/Pane.vue';
import AllTime from './all-time/AllTime.vue';
import Overview from './overview/Overview.vue';
import Summary from './summary/Summary.vue';
import type { Component } from 'vue';
const { t } = useI18n();
const { state, changeYear } = useDataStore();
const view = shallowRef(Overview);
const view = shallowRef<Component>(Overview);
const allYears = computed(() => state.years.map((v) => v.year));
Expand All @@ -73,7 +97,14 @@ const rotateYear = (dir: -1 | 1) => {
.viewButtons {
display: flex;
gap: 4px;
align-items: center;
gap: 6px;
.divider {
width: 1px;
height: 80%;
background-color: var(--app-border);
}
}
.version {
Expand Down
Loading

0 comments on commit 4a0f770

Please sign in to comment.