Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 64 additions & 55 deletions frontend/Exence/src/app/shared/chart/chart-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { formatCurrency } from '@angular/common';
import { Chart, ChartConfiguration, TooltipItem } from 'chart.js';
import { Chart, ChartConfiguration, ChartData, ChartTypeRegistry, PluginOptionsByType, TooltipItem, TooltipOptions } from 'chart.js';
import { Transaction } from '../../data-model/modules/transaction/Transaction';

export const getCssVariableValue = (variableName: string, element: HTMLElement | null = document.documentElement): string => {
export const getCssVariableValue = (
variableName: string,
element: HTMLElement | null | undefined = document.documentElement
): string => {
if (!element) return '';
return getComputedStyle(element).getPropertyValue(variableName).trim();
};
Expand Down Expand Up @@ -36,12 +39,10 @@ export const createCanvasBackgroundPlugin = (): {
},
});

export const createPointerTooltipConfig = (data: Transaction[]): {
callbacks: {
title: (context: TooltipItem<'line'>[]) => string;
label: (context: TooltipItem<'line'>) => string;
};
} => ({
export const createPointerTooltipConfig = (
data: Transaction[]
): TooltipOptions<'line'> => ({
enabled: true,
callbacks: {
title: (context: TooltipItem<'line'>[]) => {
const dataIndex = context[0]?.dataIndex;
Expand All @@ -56,56 +57,64 @@ export const createPointerTooltipConfig = (data: Transaction[]): {
const transaction = data[dataIndex];
return `Amount: ${formatCurrency(transaction.amount, 'en-US', 'Ft', 'hu-HU')}`;
},
},
});

export const getLineChartData = (data: number[], labels: string[]): {
labels: string[];
datasets: {
data: number[];
fill: string;
pointRadius: number;
pointBorderWidth: number;
}[];
} => ({
labels,
datasets: [
{
data,
fill: 'origin',
pointRadius: 4,
pointBorderWidth: 0,
},
],
});
}
} as TooltipOptions<'line'>);

export const lineChartOptions: ChartConfiguration['options'] = {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: { top: 30, left: 30, right: 30, bottom: 30 },
},
animations: {
tension: {
duration: 2000,
},
backgroundColor: {
duration: 0,
export const getLineChartOptions = (
color?: string,
gridColor?: string,
plugins?: Omit<Partial<PluginOptionsByType<keyof ChartTypeRegistry>>, 'legend'>
): ChartConfiguration['options'] => {
return {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: { top: 30, left: 30, right: 30, bottom: 30 },
},
},
elements: {
line: {
tension: 0.3, // Smoothen the line
animations: {
tension: { duration: 2000 },
backgroundClor: { duration: 0 }
},
},
plugins: {
legend: { display: false },
},
scales: {
x: {
elements: {
line: { tension: 0.3 },
},
y: {
beginAtZero: true,
plugins: {
legend: { display: false },
...plugins
},
},
scales: {
x: {
grid: { color: gridColor },
border: { color: gridColor },
ticks: { color },
},
y: {
beginAtZero: true,
grid: { color: gridColor },
border: { color: gridColor },
ticks: { color },
}
}
};
};

export const getLineChartData = (
data?: number[],
labels?: string[],
bgColor?: string,
hoverColor?: string,
): ChartData<'line'> => {
return {
labels: labels ?? [],
datasets: [{
data: data ?? [],
fill: 'origin',
pointRadius: 4,
pointBorderWidth: 0,
backgroundColor: hexToRgba(bgColor ?? '', 0.25),
borderColor: bgColor,
pointBackgroundColor: bgColor,
pointHoverBackgroundColor: hoverColor,
}]
};
};
4 changes: 2 additions & 2 deletions frontend/Exence/src/app/shared/chart/chart.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<div class="chart-container">
<canvas
baseChart
[type]="'line'"
[type]="lineChartType"
[data]="lineChartData()"
[options]="lineChartOptions"
[options]="lineChartOptions()"
[legend]="false"
>
</canvas>
Expand Down
105 changes: 48 additions & 57 deletions frontend/Exence/src/app/shared/chart/chart.component.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { afterNextRender, Component, computed, inject, input, viewChild } from '@angular/core';
import { afterNextRender, Component, computed, effect, inject, input, signal, viewChild } from '@angular/core';

import { Chart } from 'chart.js';
import { Chart, ChartConfiguration, ChartData, ChartType } from 'chart.js';
import { format } from 'date-fns';
import { BaseChartDirective } from 'ng2-charts';
import { Transaction } from '../../data-model/modules/transaction/Transaction';
import { TransactionType } from '../../data-model/modules/transaction/TransactionType';
import { BaseComponent } from '../base-component/base.component';
import { DisplayThemeService } from '../display-theme.service';
import { createCanvasBackgroundPlugin, createPointerTooltipConfig, getCssVariableValue, getLineChartData, hexToRgba, lineChartOptions } from './chart-config';
import { createCanvasBackgroundPlugin, createPointerTooltipConfig, getCssVariableValue, getLineChartData, getLineChartOptions } from './chart-config';

@Component({
selector: 'ex-chart',
imports: [BaseChartDirective],
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.scss'],
imports: [
BaseChartDirective,
],
})
export class ChartComponent extends BaseComponent {
private themeService = inject(DisplayThemeService);
Expand All @@ -22,6 +24,7 @@ export class ChartComponent extends BaseComponent {

private chart = viewChild<BaseChartDirective>(BaseChartDirective);

lineChartType: ChartType = 'line';
balanceData = computed(() => {
// has to come from backend later
const sortedData = this.data().sort((a, b) => a.date.localeCompare(b.date));
Expand All @@ -37,80 +40,68 @@ export class ChartComponent extends BaseComponent {
return currentBalance;
});
});

chartLabels = computed(() =>
this.data().sort((a, b) => a.date.localeCompare(b.date)).map(transaction => format(new Date(transaction.date), 'dd/MM'))
);
lineChartData = computed(() =>
getLineChartData(this.balanceData(), this.chartLabels())
this.data()
.sort((a, b) => a.date.localeCompare(b.date))
.map(transaction => format(new Date(transaction.date), 'dd/MM'))
);
lineChartData = signal<ChartData<'line'>>(getLineChartData());
lineChartOptions = signal<ChartConfiguration['options']>(getLineChartOptions()); // colors, font style, etc.

lineChartOptions = lineChartOptions;

get canvas(): HTMLCanvasElement | undefined {
return this.chart()?.chart?.canvas;
}
get canvas(): HTMLCanvasElement | undefined { return this.chart()?.chart?.canvas; }

constructor() {
super();

// initial
afterNextRender(() => {
this.registerCanvasBackground();
this.setThemeColors();
});

// when data changes
effect(() => {
this.data();
this.balanceData();
this.chartLabels();

if (this.canvas) {
this.setThemeColors();
}
});

// when theme changes
this.addSubscription(this.themeService.themeChangedEvent.subscribe(() => this.setThemeColors()));
}

private registerCanvasBackground(): void {
const canvasBgPlugin = createCanvasBackgroundPlugin();
Chart.register(canvasBgPlugin);
}

private setThemeColors(): void {
if (!this.canvas) return;
const element = this.canvas;

// background
const canvasBgPlugin = createCanvasBackgroundPlugin();
Chart.register(canvasBgPlugin);

// grid colors
// get colors
const color = getCssVariableValue('--default-text-color', element);
const colorGrid = getCssVariableValue('--border-color', element);
const gridColors = {
color: colorGrid,
borderColor: colorGrid
};
const bgColor = getCssVariableValue('--primary-color', element);
const hoverColor = getCssVariableValue('--app-hover-color', element);

const gridData = {
grid: gridColors,
ticks: { color: color }
};

// new object reference is needed to trigger change detection
this.lineChartOptions = {
...this.lineChartOptions,
scales: {
x: gridData,
y: { beginAtZero: true, ...gridData }
},
plugins: {
...this.lineChartOptions?.plugins, // point tooltips
tooltip: createPointerTooltipConfig(this.data()),
}
};
// update chart options with new colors
this.lineChartOptions.set(getLineChartOptions(
color,
colorGrid,
{ tooltip: createPointerTooltipConfig(this.data()) },
));

// line colors
const bgColor = getCssVariableValue('--primary-color', element);

const pointColors = {
backgroundColor: hexToRgba(bgColor, 0.25),
borderColor: getCssVariableValue('--primary-color', element),
pointBackgroundColor: getCssVariableValue('--primary-color', element),
pointHoverBackgroundColor: getCssVariableValue('--app-hover-color', element)
};
const lineData = this.lineChartData();
const currDataset = lineData.datasets[0];
lineData.datasets[0] = {
...currDataset,
...pointColors
};

this.chart()?.update();
// update chart data with new colors
this.lineChartData.set(getLineChartData(
this.balanceData(),
this.chartLabels(),
bgColor,
hoverColor,
));
}
}
Loading