From dc64147bcdaae8f5847aa267ca3ee3108a20bc3c Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Wed, 24 Jun 2026 02:09:49 +0530 Subject: [PATCH 1/2] added data decimation --- .../home-overview/home-overview.html | 2 + .../line-chart.component.stories.ts | 2 + .../lib/line-chart/line-chart.component.ts | 224 ++++++++++-------- 3 files changed, 132 insertions(+), 96 deletions(-) diff --git a/apps/client/src/app/components/home-overview/home-overview.html b/apps/client/src/app/components/home-overview/home-overview.html index 8361c5b88..e59eb3c3f 100644 --- a/apps/client/src/app/components/home-overview/home-overview.html +++ b/apps/client/src/app/components/home-overview/home-overview.html @@ -69,6 +69,8 @@ unit="%" [class.pr-3]="deviceType() === 'mobile'" [colorScheme]="user()?.settings?.colorScheme" + [dataDecimation]="true" + [dataDecimationThreshold]="100" [hidden]="historicalDataItems()?.length === 0" [historicalDataItems]="historicalDataItems()" [isAnimated]="user()?.settings?.dateRange === '1d' ? false : true" diff --git a/libs/ui/src/lib/line-chart/line-chart.component.stories.ts b/libs/ui/src/lib/line-chart/line-chart.component.stories.ts index ba8ee1298..1c56987dd 100644 --- a/libs/ui/src/lib/line-chart/line-chart.component.stories.ts +++ b/libs/ui/src/lib/line-chart/line-chart.component.stories.ts @@ -19,6 +19,8 @@ type Story = StoryObj; export const Simple: Story = { args: { + dataDecimation: true, + dataDecimationThreshold: 1, historicalDataItems: [ { date: '2017-01-01', diff --git a/libs/ui/src/lib/line-chart/line-chart.component.ts b/libs/ui/src/lib/line-chart/line-chart.component.ts index 92ee8e4ec..18062b336 100644 --- a/libs/ui/src/lib/line-chart/line-chart.component.ts +++ b/libs/ui/src/lib/line-chart/line-chart.component.ts @@ -26,11 +26,14 @@ import { import { type AnimationsSpec, Chart, + type ChartData, + Decimation, Filler, LinearScale, LineController, LineElement, PointElement, + type Point, TimeScale, Tooltip, type TooltipOptions @@ -54,6 +57,8 @@ export class GfLineChartComponent @Input() benchmarkLabel = ''; @Input() colorScheme: ColorScheme; @Input() currency: string; + @Input() dataDecimation = false; + @Input() dataDecimationThreshold: number; @Input() historicalDataItems: LineChartItem[]; @Input() isAnimated = false; @Input() label: string; @@ -71,13 +76,14 @@ export class GfLineChartComponent @ViewChild('chartCanvas') chartCanvas: ElementRef; - public chart: Chart<'line'>; + public chart: Chart<'line', Array, string>; public isLoading = true; private readonly ANIMATION_DURATION = 1200; public constructor(private changeDetectorRef: ChangeDetectorRef) { Chart.register( + Decimation, Filler, LineController, LineElement, @@ -118,14 +124,27 @@ export class GfLineChartComponent private initialize() { this.isLoading = true; - const benchmarkPrices: number[] = []; + const benchmarkPrices: Array = []; const labels: string[] = []; - const marketPrices: number[] = []; + const marketPrices: Array = []; this.historicalDataItems?.forEach((historicalDataItem, index) => { - benchmarkPrices.push(this.benchmarkDataItems?.[index]?.value); - labels.push(historicalDataItem.date); - marketPrices.push(historicalDataItem.value); + const label = historicalDataItem.date; + const timestamp = new Date(historicalDataItem.date).getTime(); + const benchmarkValue = this.benchmarkDataItems?.[index]?.value; + const marketValue = historicalDataItem.value; + + benchmarkPrices.push( + this.dataDecimation + ? { x: timestamp, y: benchmarkValue ?? null } + : benchmarkValue + ); + labels.push(label); + marketPrices.push( + this.dataDecimation + ? { x: timestamp, y: marketValue ?? null } + : marketValue + ); }); const gradient = this.chartCanvas?.nativeElement @@ -148,7 +167,7 @@ export class GfLineChartComponent gradient.addColorStop(1, getBackgroundColor(this.colorScheme)); } - const data = { + const data: ChartData<'line', Array, string> = { labels, datasets: [ { @@ -181,6 +200,11 @@ export class GfLineChartComponent if (this.chart) { this.chart.data = data; this.chart.options.plugins ??= {}; + this.chart.options.parsing = this.dataDecimation ? false : undefined; + this.chart.options.plugins.decimation = { + enabled: this.dataDecimation, + threshold: this.dataDecimationThreshold + }; this.chart.options.plugins.tooltip = this.getTooltipPluginConfiguration(); this.chart.options.animations = this.isAnimated @@ -189,106 +213,114 @@ export class GfLineChartComponent this.chart.update(); } else { - this.chart = new Chart(this.chartCanvas.nativeElement, { - data, - options: { - animations: this.isAnimated ? animations : undefined, - aspectRatio: 16 / 9, - elements: { - point: { - hoverBackgroundColor: getBackgroundColor(this.colorScheme), - hoverRadius: 2 - } - }, - interaction: { intersect: false, mode: 'index' }, - plugins: { - legend: { - align: 'start', - display: this.showLegend, - position: 'bottom' + this.chart = new Chart<'line', Array, string>( + this.chartCanvas.nativeElement, + { + data, + options: { + animations: this.isAnimated ? animations : undefined, + aspectRatio: 16 / 9, + elements: { + point: { + hoverBackgroundColor: getBackgroundColor(this.colorScheme), + hoverRadius: 2 + } }, - tooltip: this.getTooltipPluginConfiguration(), - verticalHoverLine: { - color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` - } - }, - scales: { - x: { - border: { - color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` - }, - display: this.showXAxis, - grid: { - display: false + interaction: { intersect: false, mode: 'index' }, + parsing: this.dataDecimation ? false : undefined, + plugins: { + decimation: { + enabled: this.dataDecimation, + threshold: this.dataDecimationThreshold }, - time: { - tooltipFormat: getDateFormatString(this.locale), - unit: 'year' + legend: { + align: 'start', + display: this.showLegend, + position: 'bottom' }, - type: 'time' + tooltip: this.getTooltipPluginConfiguration(), + verticalHoverLine: { + color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` + } }, - y: { - border: { - width: 0 - }, - display: this.showYAxis, - grid: { - color: ({ scale, tick }) => { - if ( - tick.value === 0 || - tick.value === scale.max || - tick.value === scale.min || - tick.value === this.yMax || - tick.value === this.yMin - ) { - return `rgba(${getTextColor(this.colorScheme)}, 0.1)`; - } - - return 'transparent'; - } + scales: { + x: { + border: { + color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` + }, + display: this.showXAxis, + grid: { + display: false + }, + time: { + tooltipFormat: getDateFormatString(this.locale), + unit: 'year' + }, + type: 'time' }, - max: this.yMax, - min: this.yMin, - position: 'right', - ticks: { - callback: (tickValue, index, ticks) => { - if (index === 0 || index === ticks.length - 1) { - // Only print last and first legend entry - - if (index === 0 && this.yMinLabel) { - return this.yMinLabel; - } - - if (index === ticks.length - 1 && this.yMaxLabel) { - return this.yMaxLabel; - } - - if (typeof tickValue === 'number') { - return tickValue.toLocaleString(this.locale, { - maximumFractionDigits: 2, - minimumFractionDigits: 2 - }); + y: { + border: { + width: 0 + }, + display: this.showYAxis, + grid: { + color: ({ scale, tick }) => { + if ( + tick.value === 0 || + tick.value === scale.max || + tick.value === scale.min || + tick.value === this.yMax || + tick.value === this.yMin + ) { + return `rgba(${getTextColor(this.colorScheme)}, 0.1)`; } - return tickValue; + return 'transparent'; } + }, + max: this.yMax, + min: this.yMin, + position: 'right', + ticks: { + callback: (tickValue, index, ticks) => { + if (index === 0 || index === ticks.length - 1) { + // Only print last and first legend entry + + if (index === 0 && this.yMinLabel) { + return this.yMinLabel; + } + + if (index === ticks.length - 1 && this.yMaxLabel) { + return this.yMaxLabel; + } + + if (typeof tickValue === 'number') { + return tickValue.toLocaleString(this.locale, { + maximumFractionDigits: 2, + minimumFractionDigits: 2 + }); + } + + return tickValue; + } - return ''; + return ''; + }, + display: this.showYAxis, + mirror: true, + z: 1 }, - display: this.showYAxis, - mirror: true, - z: 1 - }, - type: 'linear' - } + type: 'linear' + } + }, + spanGaps: true }, - spanGaps: true - }, - plugins: [ - getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme) - ], - type: 'line' - }); + plugins: [ + getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme) + ], + type: 'line' + } + ); } } From 818153b12b3c4737642267f31aa509d9e2c98101 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Wed, 24 Jun 2026 02:12:19 +0530 Subject: [PATCH 2/2] fixed lint problems --- libs/ui/src/lib/line-chart/line-chart.component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/ui/src/lib/line-chart/line-chart.component.ts b/libs/ui/src/lib/line-chart/line-chart.component.ts index 18062b336..40ff6b94b 100644 --- a/libs/ui/src/lib/line-chart/line-chart.component.ts +++ b/libs/ui/src/lib/line-chart/line-chart.component.ts @@ -76,7 +76,7 @@ export class GfLineChartComponent @ViewChild('chartCanvas') chartCanvas: ElementRef; - public chart: Chart<'line', Array, string>; + public chart: Chart<'line', (number | Point | null)[], string>; public isLoading = true; private readonly ANIMATION_DURATION = 1200; @@ -124,9 +124,9 @@ export class GfLineChartComponent private initialize() { this.isLoading = true; - const benchmarkPrices: Array = []; + const benchmarkPrices: (number | Point | null)[] = []; const labels: string[] = []; - const marketPrices: Array = []; + const marketPrices: (number | Point | null)[] = []; this.historicalDataItems?.forEach((historicalDataItem, index) => { const label = historicalDataItem.date; @@ -167,7 +167,7 @@ export class GfLineChartComponent gradient.addColorStop(1, getBackgroundColor(this.colorScheme)); } - const data: ChartData<'line', Array, string> = { + const data: ChartData<'line', (number | Point | null)[], string> = { labels, datasets: [ { @@ -213,7 +213,7 @@ export class GfLineChartComponent this.chart.update(); } else { - this.chart = new Chart<'line', Array, string>( + this.chart = new Chart<'line', (number | Point | null)[], string>( this.chartCanvas.nativeElement, { data,