From b75340a139d79428f9e4a2d050b68d66c0be9d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Sun, 18 Jan 2026 12:03:18 +0100 Subject: [PATCH] #EX-194: Fix chart color bug, refactor chart, chart-config --- .../src/app/shared/chart/chart-config.ts | 119 ++++++++++-------- .../src/app/shared/chart/chart.component.html | 4 +- .../src/app/shared/chart/chart.component.ts | 105 +++++++--------- 3 files changed, 114 insertions(+), 114 deletions(-) diff --git a/frontend/Exence/src/app/shared/chart/chart-config.ts b/frontend/Exence/src/app/shared/chart/chart-config.ts index 8b356dc0..dd48a8bb 100644 --- a/frontend/Exence/src/app/shared/chart/chart-config.ts +++ b/frontend/Exence/src/app/shared/chart/chart-config.ts @@ -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(); }; @@ -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; @@ -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>, '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, + }] + }; +}; \ No newline at end of file diff --git a/frontend/Exence/src/app/shared/chart/chart.component.html b/frontend/Exence/src/app/shared/chart/chart.component.html index 2e2a5f67..377ed495 100644 --- a/frontend/Exence/src/app/shared/chart/chart.component.html +++ b/frontend/Exence/src/app/shared/chart/chart.component.html @@ -1,9 +1,9 @@
diff --git a/frontend/Exence/src/app/shared/chart/chart.component.ts b/frontend/Exence/src/app/shared/chart/chart.component.ts index d21a755c..a1fd279c 100644 --- a/frontend/Exence/src/app/shared/chart/chart.component.ts +++ b/frontend/Exence/src/app/shared/chart/chart.component.ts @@ -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); @@ -22,6 +24,7 @@ export class ChartComponent extends BaseComponent { private chart = viewChild(BaseChartDirective); + lineChartType: ChartType = 'line'; balanceData = computed(() => { // has to come from backend later const sortedData = this.data().sort((a, b) => a.date.localeCompare(b.date)); @@ -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>(getLineChartData()); + lineChartOptions = signal(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, + )); } }