diff --git a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts index 7f03ea57f..b4565f6cd 100644 --- a/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts +++ b/apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -21,6 +21,7 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, + type ElementRef, EventEmitter, Input, OnChanges, @@ -42,7 +43,8 @@ import { PointElement, TimeScale, Tooltip, - TooltipPosition + type TooltipOptions, + type TooltipPosition } from 'chart.js'; import 'chartjs-adapter-date-fns'; import annotationPlugin from 'chartjs-plugin-annotation'; @@ -78,7 +80,7 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy { @Output() benchmarkChanged = new EventEmitter(); - @ViewChild('chartCanvas') chartCanvas; + @ViewChild('chartCanvas') chartCanvas: ElementRef; public chart: Chart<'line'>; public hasPermissionToAccessAdminControl: boolean; @@ -158,7 +160,7 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy { if (this.chart) { this.chart.data = data; this.chart.options.plugins.tooltip = - this.getTooltipPluginConfiguration() as unknown; + this.getTooltipPluginConfiguration(); this.chart.update(); } else { this.chart = new Chart(this.chartCanvas.nativeElement, { @@ -193,10 +195,11 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy { display: false }, tooltip: this.getTooltipPluginConfiguration(), + // @ts-ignore verticalHoverLine: { color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` } - } as unknown, + }, responsive: true, scales: { x: { @@ -253,7 +256,7 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy { } } - private getTooltipPluginConfiguration() { + private getTooltipPluginConfiguration(): Partial> { return { ...getTooltipOptions({ colorScheme: this.colorScheme, @@ -261,7 +264,8 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy { unit: '%' }), mode: 'index', - position: 'top' as unknown, + // @ts-ignore + position: 'top', xAlign: 'center', yAlign: 'bottom' }; diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 5492ddd4c..c4d2805eb 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -20,6 +20,7 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, + type ElementRef, Input, OnChanges, OnDestroy, @@ -36,7 +37,8 @@ import { PointElement, TimeScale, Tooltip, - TooltipPosition + type TooltipOptions, + type TooltipPosition } from 'chart.js'; import 'chartjs-adapter-date-fns'; import annotationPlugin from 'chartjs-plugin-annotation'; @@ -62,7 +64,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { @Input() locale = getLocale(); @Input() savingsRate = 0; - @ViewChild('chartCanvas') chartCanvas; + @ViewChild('chartCanvas') chartCanvas: ElementRef; public chart: Chart<'bar' | 'line'>; private investments: InvestmentItem[]; @@ -121,12 +123,12 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { }), label: this.benchmarkDataLabel, segment: { - borderColor: (context: unknown) => + borderColor: (context) => this.isInFuture( context, `rgba(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b}, 0.67)` ), - borderDash: (context: unknown) => this.isInFuture(context, [2, 2]) + borderDash: (context) => this.isInFuture(context, [2, 2]) }, stepped: true }, @@ -143,12 +145,12 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { label: $localize`Total Amount`, pointRadius: 0, segment: { - borderColor: (context: unknown) => + borderColor: (context) => this.isInFuture( context, `rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.67)` ), - borderDash: (context: unknown) => this.isInFuture(context, [2, 2]) + borderDash: (context) => this.isInFuture(context, [2, 2]) } } ] @@ -158,7 +160,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { if (this.chart) { this.chart.data = chartData; this.chart.options.plugins.tooltip = - this.getTooltipPluginConfiguration() as unknown; + this.getTooltipPluginConfiguration(); if ( this.savingsRate && @@ -201,7 +203,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { color: 'white', content: $localize`Savings Rate`, display: true, - font: { size: '10px', weight: 'normal' }, + font: { size: 10, weight: 'normal' }, padding: { x: 4, y: 2 @@ -226,10 +228,11 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { display: false }, tooltip: this.getTooltipPluginConfiguration(), + // @ts-ignore verticalHoverLine: { color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` } - } as unknown, + }, responsive: true, scales: { x: { @@ -286,7 +289,9 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { } } - private getTooltipPluginConfiguration() { + private getTooltipPluginConfiguration(): Partial< + TooltipOptions<'bar' | 'line'> + > { return { ...getTooltipOptions({ colorScheme: this.colorScheme, @@ -296,7 +301,8 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { unit: this.isInPercent ? '%' : undefined }), mode: 'index', - position: 'top' as unknown, + // @ts-ignore + position: 'top', xAlign: 'center', yAlign: 'bottom' }; diff --git a/libs/common/src/lib/chart-helper.ts b/libs/common/src/lib/chart-helper.ts index da6473645..cb814a864 100644 --- a/libs/common/src/lib/chart-helper.ts +++ b/libs/common/src/lib/chart-helper.ts @@ -3,6 +3,7 @@ import type { Chart, ChartTypeRegistry, Plugin, + TooltipOptions, TooltipPosition } from 'chart.js'; import { format } from 'date-fns'; @@ -21,7 +22,7 @@ export function formatGroupedDate({ date, groupBy }: { - date: Date; + date: number; groupBy: GroupBy; }) { if (groupBy === 'month') { @@ -45,34 +46,51 @@ export function getTooltipOptions({ groupBy?: GroupBy; locale?: string; unit?: string; -}) { +}): Partial { return { backgroundColor: getBackgroundColor(colorScheme), bodyColor: `rgb(${getTextColor(colorScheme)})`, borderWidth: 1, borderColor: `rgba(${getTextColor(colorScheme)}, 0.1)`, callbacks: { + afterBody: () => '', + afterFooter: () => '', + afterLabel: () => '', + afterTitle: () => '', + beforeBody: () => '', + beforeFooter: () => '', + beforeLabel: () => '', + beforeTitle: () => '', + footer: () => '', label: (context) => { let label = context.dataset.label ?? ''; if (label) { label += ': '; } + // @ts-ignore if (context.parsed.y !== null) { if (currency) { + // @ts-ignore label += `${context.parsed.y.toLocaleString(locale, { maximumFractionDigits: 2, minimumFractionDigits: 2 })} ${currency}`; } else if (unit) { + // @ts-ignore label += `${context.parsed.y.toFixed(2)} ${unit}`; } else { + // @ts-ignore label += context.parsed.y.toFixed(2); } } return label; }, + labelColor: () => {}, + labelPointStyle: () => {}, + labelTextColor: () => {}, title: (contexts) => { if (groupBy) { + // @ts-ignore return formatGroupedDate({ groupBy, date: contexts[0].parsed.x }); } diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts index 6b0bc8dcb..75e9af86d 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -98,10 +98,15 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { @ViewChild('chartCanvas') chartCanvas: ElementRef; public calculatorForm = this.formBuilder.group({ + // @ts-ignore annualInterestRate: new FormControl(undefined), + // @ts-ignore paymentPerPeriod: new FormControl(undefined), + // @ts-ignore principalInvestmentAmount: new FormControl(undefined), + // @ts-ignore projectedTotalAmount: new FormControl(undefined), + // @ts-ignore retirementDate: new FormControl(undefined) }); public chart: Chart<'bar'>; @@ -148,25 +153,25 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.calculatorForm .get('annualInterestRate') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) .subscribe((annualInterestRate) => { this.annualInterestRateChanged.emit(annualInterestRate); }); this.calculatorForm .get('paymentPerPeriod') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) .subscribe((savingsRate) => { this.savingsRateChanged.emit(savingsRate); }); this.calculatorForm .get('projectedTotalAmount') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) .subscribe((projectedTotalAmount) => { this.projectedTotalAmountChanged.emit(projectedTotalAmount); }); this.calculatorForm .get('retirementDate') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) .subscribe((retirementDate) => { this.retirementDateChanged.emit(retirementDate); }); @@ -194,11 +199,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.calculatorForm.patchValue( { annualInterestRate: - this.calculatorForm.get('annualInterestRate').value, + this.calculatorForm.get('annualInterestRate')?.value, paymentPerPeriod: this.getPMT(), principalInvestmentAmount: this.calculatorForm.get( 'principalInvestmentAmount' - ).value, + )?.value, projectedTotalAmount: Math.round(this.getProjectedTotalAmount()) || 0, retirementDate: @@ -208,7 +213,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { emitEvent: false } ); - this.calculatorForm.get('principalInvestmentAmount').disable(); + this.calculatorForm.get('principalInvestmentAmount')?.disable(); this.changeDetectorRef.markForCheck(); }); @@ -217,34 +222,36 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { if (this.hasPermissionToUpdateUserSettings === true) { this.calculatorForm .get('annualInterestRate') - .enable({ emitEvent: false }); - this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); + ?.enable({ emitEvent: false }); + this.calculatorForm.get('paymentPerPeriod')?.enable({ emitEvent: false }); this.calculatorForm .get('projectedTotalAmount') - .enable({ emitEvent: false }); + ?.enable({ emitEvent: false }); } else { this.calculatorForm .get('annualInterestRate') - .disable({ emitEvent: false }); - this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); + ?.disable({ emitEvent: false }); + this.calculatorForm + .get('paymentPerPeriod') + ?.disable({ emitEvent: false }); this.calculatorForm .get('projectedTotalAmount') - .disable({ emitEvent: false }); + ?.disable({ emitEvent: false }); } - this.calculatorForm.get('retirementDate').disable({ emitEvent: false }); + this.calculatorForm.get('retirementDate')?.disable({ emitEvent: false }); } public setMonthAndYear( normalizedMonthAndYear: Date, datepicker: MatDatepicker ) { - const retirementDate = this.calculatorForm.get('retirementDate').value; + const retirementDate = this.calculatorForm.get('retirementDate')!.value; const newRetirementDate = setMonth( setYear(retirementDate, normalizedMonthAndYear.getFullYear()), normalizedMonthAndYear.getMonth() ); - this.calculatorForm.get('retirementDate').setValue(newRetirementDate); + this.calculatorForm.get('retirementDate')?.setValue(newRetirementDate); datepicker.close(); } @@ -270,7 +277,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.chart.update(); } else { - this.chart = new Chart(this.chartCanvas.nativeElement, { + this.chart = new Chart<'bar'>(this.chartCanvas.nativeElement, { data: chartData, options: { plugins: { @@ -280,6 +287,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { callbacks: { footer: (items) => { const totalAmount = items.reduce( + // @ts-ignore (a, b) => a + b.parsed.y, 0 ); @@ -347,7 +355,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { private getChartData() { const currentYear = new Date().getFullYear(); - const labels = []; + const labels: number[] = []; // Principal investment amount const P: number = this.getP(); @@ -360,6 +368,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { // Calculate retirement date // if we want to retire at month x, we need the projectedTotalAmount at month x-1 + // @ts-ignore const lastPeriodDate = sub(this.getRetirementDate(), { months: 1 }); const yearsToRetire = lastPeriodDate.getFullYear() - currentYear; @@ -373,7 +382,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { const datasetDeposit = { backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, - data: [], + data: [] as number[], label: $localize`Deposit` }; @@ -383,7 +392,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ) .lighten(0.5) .hex(), - data: [], + data: [] as number[], label: $localize`Interest` }; @@ -393,7 +402,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ) .lighten(0.25) .hex(), - data: [], + data: [] as number[], label: $localize`Savings` }; @@ -426,12 +435,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getPeriodsToRetire(): number { - if (this.calculatorForm.get('projectedTotalAmount').value) { + if (this.calculatorForm.get('projectedTotalAmount')?.value) { let periods = this.fireCalculatorService.calculatePeriodsToRetire({ P: this.getP(), PMT: this.getPMT(), r: this.getR(), - totalAmount: this.calculatorForm.get('projectedTotalAmount').value + totalAmount: this.calculatorForm.get('projectedTotalAmount')!.value }); if (periods === Infinity) { @@ -452,13 +461,13 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } } - private getPMT() { - return this.calculatorForm.get('paymentPerPeriod').value; + private getPMT(): number { + return this.calculatorForm.get('paymentPerPeriod')!.value; } private getProjectedTotalAmount() { - if (this.calculatorForm.get('projectedTotalAmount').value) { - return this.calculatorForm.get('projectedTotalAmount').value; + if (this.calculatorForm.get('projectedTotalAmount')?.value) { + return this.calculatorForm.get('projectedTotalAmount')!.value; } const { totalAmount } = @@ -473,10 +482,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getR() { - return this.calculatorForm.get('annualInterestRate').value / 100; + return this.calculatorForm.get('annualInterestRate')!.value / 100; } - private getRetirementDate(): Date { + private getRetirementDate(): Date | undefined { if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) { return undefined; } 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 d2425da51..0d1a709b5 100644 --- a/libs/ui/src/lib/line-chart/line-chart.component.ts +++ b/libs/ui/src/lib/line-chart/line-chart.component.ts @@ -19,12 +19,14 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + type ElementRef, Input, OnChanges, OnDestroy, ViewChild } from '@angular/core'; import { + type AnimationSpec, Chart, Filler, LinearScale, @@ -68,7 +70,7 @@ export class GfLineChartComponent @Input() yMin: number; @Input() yMinLabel: string; - @ViewChild('chartCanvas') chartCanvas; + @ViewChild('chartCanvas') chartCanvas: ElementRef; public chart: Chart<'line'>; public isLoading = true; @@ -130,10 +132,11 @@ export class GfLineChartComponent const gradient = this.chartCanvas?.nativeElement ?.getContext('2d') - .createLinearGradient( + ?.createLinearGradient( 0, 0, 0, + // @ts-ignore (this.chartCanvas.nativeElement.parentNode.offsetHeight * 4) / 5 ); @@ -180,6 +183,7 @@ export class GfLineChartComponent this.chart.options.plugins.tooltip = this.getTooltipPluginConfiguration(); this.chart.options.animation = this.isAnimated && { + // @ts-ignore x: this.getAnimationConfigurationForAxis({ labels, axis: 'x' }), y: this.getAnimationConfigurationForAxis({ labels, axis: 'y' }) }; @@ -189,6 +193,7 @@ export class GfLineChartComponent data, options: { animation: this.isAnimated && { + // @ts-ignore x: this.getAnimationConfigurationForAxis({ labels, axis: 'x' }), y: this.getAnimationConfigurationForAxis({ labels, axis: 'y' }) }, @@ -207,6 +212,7 @@ export class GfLineChartComponent position: 'bottom' }, tooltip: this.getTooltipPluginConfiguration(), + // @ts-ignore verticalHoverLine: { color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` } @@ -300,7 +306,7 @@ export class GfLineChartComponent }: { axis: 'x' | 'y'; labels: string[]; - }) { + }): AnimationSpec<'line'> { const delayBetweenPoints = this.ANIMATION_DURATION / labels.length; return { @@ -310,11 +316,14 @@ export class GfLineChartComponent } context[`${axis}Started`] = true; + // @ts-ignore return context.index * delayBetweenPoints; }, duration: delayBetweenPoints, easing: 'linear', + // @ts-ignore from: NaN, + // @ts-ignore type: 'number' }; } @@ -328,6 +337,7 @@ export class GfLineChartComponent unit: this.unit }), mode: 'index', + // @ts-ignore position: 'top', xAlign: 'center', yAlign: 'bottom' diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 9f2287260..c0a33d87a 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -22,11 +22,16 @@ import { } from '@angular/core'; import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; -import { ChartConfiguration, Tooltip } from 'chart.js'; -import { LinearScale } from 'chart.js'; -import { ArcElement } from 'chart.js'; -import { DoughnutController } from 'chart.js'; -import { Chart } from 'chart.js'; +import { + ArcElement, + Chart, + type ChartData, + type ChartDataset, + DoughnutController, + LinearScale, + Tooltip, + type TooltipItem +} from 'chart.js'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import { isUUID } from 'class-validator'; import Color from 'color'; @@ -137,14 +142,16 @@ export class GfPortfolioProportionChartComponent chartData[this.data[symbol][this.keys[0]].toUpperCase()] ?.subCategory?.[this.data[symbol][this.keys[1]]] ) { + // @ts-ignore chartData[ this.data[symbol][this.keys[0]].toUpperCase() ].subCategory[this.data[symbol][this.keys[1]]].value = chartData[ this.data[symbol][this.keys[0]].toUpperCase() - ].subCategory[this.data[symbol][this.keys[1]]].value.plus( + ].subCategory?.[this.data[symbol][this.keys[1]]].value.plus( this.data[symbol].value || 0 ); } else { + // @ts-ignore chartData[ this.data[symbol][this.keys[0]].toUpperCase() ].subCategory[this.data[symbol][this.keys[1]] ?? UNKNOWN_KEY] = { @@ -273,12 +280,14 @@ export class GfPortfolioProportionChartComponent Object.keys(item.subCategory ?? {}).forEach((subCategory) => { if (item.name === UNKNOWN_KEY) { + // @ts-ignore backgroundColorSubCategory.push(item.color); } else { backgroundColorSubCategory.push( Color(item.color).lighten(lightnessRatio).hex() ); } + // @ts-ignore dataSubCategory.push(item.subCategory[subCategory].value.toNumber()); labelSubCategory.push(subCategory); @@ -286,7 +295,7 @@ export class GfPortfolioProportionChartComponent }); }); - const datasets: ChartConfiguration<'doughnut'>['data']['datasets'] = [ + const datasets: ChartDataset<'doughnut'>[] = [ { backgroundColor: chartDataSorted.map(([, item]) => { return item.color; @@ -324,7 +333,7 @@ export class GfPortfolioProportionChartComponent datasets[1].data[1] = Number.MAX_SAFE_INTEGER; } - const data: ChartConfiguration<'doughnut'>['data'] = { + const data: ChartData<'doughnut'> = { datasets, labels }; @@ -332,9 +341,13 @@ export class GfPortfolioProportionChartComponent if (this.chartCanvas) { if (this.chart) { this.chart.data = data; - this.chart.options.plugins.tooltip = this.getTooltipPluginConfiguration( - data - ) as unknown; + + if (!this.chart.options.plugins) { + this.chart.options.plugins = {}; + } + + this.chart.options.plugins.tooltip = + this.getTooltipPluginConfiguration(data); this.chart.update(); } else { this.chart = new Chart<'doughnut'>(this.chartCanvas.nativeElement, { @@ -348,15 +361,17 @@ export class GfPortfolioProportionChartComponent onClick: (event, activeElements) => { try { const dataIndex = activeElements[0].index; + // @ts-ignore const symbol: string = event.chart.data.labels[dataIndex]; - const dataSource = this.data[symbol]?.dataSource; + const dataSource = this.data[symbol]?.dataSource!; this.proportionChartClicked.emit({ dataSource, symbol }); } catch {} }, onHover: (event, chartElement) => { if (this.cursor) { + // @ts-ignore event.native.target.style.cursor = chartElement[0] ? this.cursor : 'default'; @@ -392,7 +407,7 @@ export class GfPortfolioProportionChartComponent legend: { display: false }, tooltip: this.getTooltipPluginConfiguration(data) } - } as unknown, + }, plugins: [ChartDataLabels], type: 'doughnut' }); @@ -419,7 +434,7 @@ export class GfPortfolioProportionChartComponent ]; } - private getTooltipPluginConfiguration(data: ChartConfiguration['data']) { + private getTooltipPluginConfiguration(data: ChartData<'doughnut'>) { return { ...getTooltipOptions({ colorScheme: this.colorScheme, @@ -427,7 +442,7 @@ export class GfPortfolioProportionChartComponent locale: this.locale }), callbacks: { - label: (context) => { + label: (context: TooltipItem<'doughnut'>) => { const labelIndex = (data.datasets[context.datasetIndex - 1]?.data?.length ?? 0) + context.dataIndex; diff --git a/libs/ui/src/lib/treemap-chart/interfaces/interfaces.ts b/libs/ui/src/lib/treemap-chart/interfaces/interfaces.ts index bb673ed64..0b567a8f5 100644 --- a/libs/ui/src/lib/treemap-chart/interfaces/interfaces.ts +++ b/libs/ui/src/lib/treemap-chart/interfaces/interfaces.ts @@ -1,5 +1,18 @@ +import { PortfolioPosition } from '@ghostfolio/common/interfaces'; + +import { + TreemapDataPoint, + TreemapScriptableContext +} from 'chartjs-chart-treemap'; + export interface GetColorParams { annualizedNetPerformancePercent: number; negativeNetPerformancePercentsRange: { max: number; min: number }; positiveNetPerformancePercentsRange: { max: number; min: number }; } + +export interface GfTreemapChartTooltipContext extends TreemapScriptableContext { + raw: TreemapDataPoint & { + _data: PortfolioPosition; + }; +} diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index f6d7048f5..b4b7cd22a 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -25,7 +25,7 @@ import { } from '@angular/core'; import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; -import type { ChartConfiguration, TooltipOptions } from 'chart.js'; +import type { ChartData, TooltipOptions } from 'chart.js'; import { LinearScale } from 'chart.js'; import { Chart, Tooltip } from 'chart.js'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; @@ -35,7 +35,10 @@ import { orderBy } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import OpenColor from 'open-color'; -import { GetColorParams } from './interfaces/interfaces'; +import { + GetColorParams, + GfTreemapChartTooltipContext +} from './interfaces/interfaces'; const { gray, green, red } = OpenColor; @@ -198,10 +201,10 @@ export class GfTreemapChartComponent min: Math.min(...negativeNetPerformancePercents) }; - const data: ChartConfiguration<'treemap'>['data'] = { + const data: ChartData<'treemap'> = { datasets: [ { - backgroundColor: (context) => { + backgroundColor: (context: GfTreemapChartTooltipContext) => { let annualizedNetPerformancePercent = getAnnualizedPerformancePercent({ daysInMarket: differenceInDays( @@ -232,7 +235,7 @@ export class GfTreemapChartComponent key: 'allocationInPercentage', labels: { align: 'left', - color: (context) => { + color: (context: GfTreemapChartTooltipContext) => { let annualizedNetPerformancePercent = getAnnualizedPerformancePercent({ daysInMarket: differenceInDays( @@ -261,7 +264,7 @@ export class GfTreemapChartComponent }, display: true, font: [{ size: 16 }, { lineHeight: 1.5, size: 14 }], - formatter: ({ raw }) => { + formatter: ({ raw }: GfTreemapChartTooltipContext) => { // Round to 4 decimal places let netPerformancePercentWithCurrencyEffect = Math.round( @@ -286,10 +289,11 @@ export class GfTreemapChartComponent position: 'top' }, spacing: 1, + // @ts-ignore tree: this.holdings } ] - } as any; + }; if (this.chartCanvas) { if (this.chart) { @@ -303,7 +307,7 @@ export class GfTreemapChartComponent this.getTooltipPluginConfiguration(); this.chart.update(); } else { - this.chart = new Chart(this.chartCanvas.nativeElement, { + this.chart = new Chart<'treemap'>(this.chartCanvas.nativeElement, { data, options: { animation: false, @@ -313,6 +317,7 @@ export class GfTreemapChartComponent const datasetIndex = activeElements[0].datasetIndex; const dataset = orderBy( + // @ts-ignore event.chart.data.datasets[datasetIndex].tree, ['allocationInPercentage'], ['desc'] @@ -326,6 +331,7 @@ export class GfTreemapChartComponent }, onHover: (event, chartElement) => { if (this.cursor) { + // @ts-ignore event.native.target.style.cursor = chartElement[0] ? this.cursor : 'default'; @@ -351,8 +357,9 @@ export class GfTreemapChartComponent locale: this.locale }), callbacks: { - label: ({ raw }) => { - const allocationInPercentage = `${((raw._data.allocationInPercentage as number) * 100).toFixed(2)}%`; + // @ts-ignore + label: ({ raw }: GfTreemapChartTooltipContext) => { + const allocationInPercentage = `${(raw._data.allocationInPercentage * 100).toFixed(2)}%`; const name = raw._data.name; const sign = raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''; @@ -361,11 +368,11 @@ export class GfTreemapChartComponent const netPerformanceInPercentageWithSign = `${sign}${(raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`; if (raw._data.valueInBaseCurrency !== null) { - const value = raw._data.valueInBaseCurrency as number; + const value = raw._data.valueInBaseCurrency; return [ `${name ?? symbol} (${allocationInPercentage})`, - `${value.toLocaleString(this.locale, { + `${value?.toLocaleString(this.locale, { maximumFractionDigits: 2, minimumFractionDigits: 2 })} ${this.baseCurrency}`,