Skip to content

Commit a8b6f9b

Browse files
committed
feat: highlight sankey chart when hovering dashboard tiles
closes #68
1 parent c43426c commit a8b6f9b

File tree

9 files changed

+53
-20
lines changed

9 files changed

+53
-20
lines changed

src/app/pages/dashboard/overview/Overview.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<template>
22
<div :class="$style.overview">
3-
<SummaryPanels />
3+
<SummaryPanels @hovered-panel="highlight = $event" />
44
<AsyncComponent
5+
:properties="{ highlight }"
56
:show="media !== 'mobile'"
67
:class="$style.chart"
78
:import="() => import('./widgets/charts/DistributionChart.vue')"
@@ -11,10 +12,12 @@
1112

1213
<script lang="ts" setup>
1314
import AsyncComponent from '@components/misc/async-component/AsyncComponent.vue';
14-
import { useMediaQuery } from '../../../../composables/useMediaQuery';
15+
import { useMediaQuery } from '@composables';
1516
import SummaryPanels from './widgets/header-panels/SummaryPanels.vue';
17+
import { ref } from 'vue';
1618
1719
const media = useMediaQuery();
20+
const highlight = ref<string>();
1821
</script>
1922

2023
<style lang="scss" module>

src/app/pages/dashboard/overview/widgets/charts/DistributionChart.vue

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SankeyChart from './sankey-chart/SankeyChart.vue';
1515
1616
const props = defineProps<{
1717
class?: ClassNames;
18+
highlight?: 'income' | 'expenses';
1819
}>();
1920
2021
const classes = computed(() => props.class);
@@ -54,13 +55,15 @@ const data = computed((): SankeyChartConfig => {
5455
labels.push({
5556
id: group.id,
5657
name: `${group.name} (${format(total)})`,
57-
color: color(60 + 60 * (total / totalIncome))
58+
color: color(60 + 60 * (total / totalIncome)),
59+
muted: props.highlight === 'expenses'
5860
});
5961
6062
links.push({
6163
target: income.id,
6264
source: group.id,
63-
value: total
65+
value: total,
66+
muted: props.highlight === 'expenses'
6467
});
6568
6669
for (let i = 0; i < group.budgets.length; i++) {
@@ -71,13 +74,15 @@ const data = computed((): SankeyChartConfig => {
7174
labels.push({
7275
id: budget.id,
7376
name: `${budget.name} (${format(total)})`,
74-
color: color(60 + 60 * (total / totalIncome))
77+
color: color(60 + 60 * (total / totalIncome)),
78+
muted: props.highlight === 'expenses'
7579
});
7680
7781
links.push({
7882
target: group.id,
7983
source: budget.id,
80-
value: total
84+
value: total,
85+
muted: props.highlight === 'expenses'
8186
});
8287
}
8388
}
@@ -93,13 +98,15 @@ const data = computed((): SankeyChartConfig => {
9398
labels.push({
9499
id: group.id,
95100
name: `${group.name} (${format(total)})`,
96-
color: color(60 * (1 - total / totalExpenses))
101+
color: color(60 * (1 - total / totalExpenses)),
102+
muted: props.highlight === 'income'
97103
});
98104
99105
links.push({
100106
target: group.id,
101107
source: income.id,
102-
value: total
108+
value: total,
109+
muted: props.highlight === 'income'
103110
});
104111
105112
for (let i = 0; i < group.budgets.length; i++) {
@@ -111,13 +118,15 @@ const data = computed((): SankeyChartConfig => {
111118
id: budget.id,
112119
name: `${budget.name} (${format(total)})`,
113120
color: color(60 * (1 - total / totalExpenses)),
114-
align: 'left'
121+
align: 'left',
122+
muted: props.highlight === 'income'
115123
});
116124
117125
links.push({
118126
target: budget.id,
119127
source: group.id,
120-
value: total
128+
value: total,
129+
muted: props.highlight === 'income'
121130
});
122131
}
123132
}

src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ export interface SankeyChartLink {
22
source: string;
33
target: string;
44
value: number;
5+
muted?: boolean;
56
}
67

78
export interface SankeyChartLabel {
89
id: string;
910
name: string;
1011
color: string;
12+
muted?: boolean;
1113
align?: 'left' | 'right';
1214
}
1315

src/app/pages/dashboard/overview/widgets/charts/sankey-chart/SankeyChart.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const classes = computed(() => props.class);
4848
const options = computed(
4949
(): EChartsOption => ({
5050
animation: false,
51+
silent: true,
5152
series: {
5253
type: 'sankey',
5354
label: {
@@ -65,20 +66,27 @@ const options = computed(
6566
nodeWidth: 7,
6667
left: 0,
6768
right: 0,
68-
links: props.data.links,
69+
links: props.data.links.map((v) => ({
70+
...v,
71+
animation: true,
72+
lineStyle: { opacity: v.muted ? 0.05 : 0.25 }
73+
})),
6974
data: props.data.labels.map((v) => ({
7075
name: v.name,
7176
id: v.id,
72-
itemStyle: { color: v.color },
77+
itemStyle: {
78+
color: v.color,
79+
opacity: v.muted ? 0.25 : 1
80+
},
7381
label:
7482
v.align === 'left'
7583
? {
7684
align: 'right',
85+
opacity: v.muted ? 0.65 : 1,
7786
padding: [0, 20, 0, 0]
7887
}
79-
: undefined
80-
})),
81-
silent: true
88+
: { opacity: v.muted ? 0.65 : 1 }
89+
}))
8290
}
8391
})
8492
);

src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanel.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const element = computed(() => (props.to ? Link : 'div'));
7272
width: 100%;
7373
height: 100%;
7474
background: v-bind('theme.light.base');
75-
transition: background var(--transition-m);
75+
transition: background var(--transition-s);
7676
7777
&.clickable:hover {
7878
background: v-bind('theme.light.dimmed');

src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanelChart.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
<script lang="ts" setup>
66
import EChart from '@components/charts/echart/EChart.vue';
77
import { ClassNames } from '@utils';
8-
import { GridComponentOption, LineSeriesOption } from 'echarts';
9-
import { LineChart } from 'echarts/charts';
10-
import { GridComponent } from 'echarts/components';
8+
import { GridComponentOption, GridComponent } from 'echarts/components';
9+
import { LineChart, LineSeriesOption } from 'echarts/charts';
1110
import * as echarts from 'echarts/core';
1211
import { SVGRenderer } from 'echarts/renderers';
1312
import { computed } from 'vue';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type HoveredPanel = 'income' | 'expenses';

src/app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
to="/income"
77
:tooltip="t('page.dashboard.jumpToIncome', { year: state.activeYear })"
88
:title="t('page.dashboard.income')"
9+
@pointerenter="emit('hoveredPanel', 'income')"
10+
@pointerleave="emit('hoveredPanel')"
11+
@pointercancel="emit('hoveredPanel')"
912
/>
1013

1114
<SummaryPanel
@@ -15,6 +18,9 @@
1518
:values="expenses"
1619
color="warning"
1720
:title="t('page.dashboard.expenses')"
21+
@pointerenter="emit('hoveredPanel', 'expenses')"
22+
@pointerleave="emit('hoveredPanel')"
23+
@pointercancel="emit('hoveredPanel')"
1824
/>
1925

2026
<SummaryPanel
@@ -50,6 +56,11 @@ import { aggregate, ClassNames, subtract, sum } from '@utils';
5056
import { computed, ref, useCssModule } from 'vue';
5157
import { useI18n } from 'vue-i18n';
5258
import SummaryPanel from './SummaryPanel.vue';
59+
import { HoveredPanel } from '@app/pages/dashboard/overview/widgets/header-panels/SummaryPanels.types.ts';
60+
61+
const emit = defineEmits<{
62+
hoveredPanel: (panel?: HoveredPanel) => void;
63+
}>();
5364
5465
const props = defineProps<{
5566
class?: ClassNames;

src/app/pages/dashboard/summary/Summary.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { useDataStore } from '@store/state';
1818
import { totals } from '@store/state/utils/budgets';
1919
import { computed } from 'vue';
2020
import { useI18n } from 'vue-i18n';
21-
import { useMediaQuery } from '../../../../composables/useMediaQuery';
21+
import { useMediaQuery } from '@composables';
2222
import GroupsSummaryTable from './widgets/tables/GroupsSummaryTable.vue';
2323
import TotalsSummaryTable from './widgets/tables/TotalsSummaryTable.vue';
2424

0 commit comments

Comments
 (0)