Browse Source

fix(client): treemap chart type safety

pull/6277/head
KenTandrian 6 days ago
parent
commit
0dac0b8412
  1. 2
      libs/common/src/lib/chart-helper.ts
  2. 2
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts
  3. 16
      libs/ui/src/lib/treemap-chart/interfaces/interfaces.ts
  4. 48
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

2
libs/common/src/lib/chart-helper.ts

@ -54,7 +54,7 @@ export function getTooltipOptions<T extends ChartType>({
bodyColor: `rgb(${getTextColor(colorScheme)})`,
borderWidth: 1,
borderColor: `rgba(${getTextColor(colorScheme)}, 0.1)`,
// @ts-expect-error: no need to set all attributes in callbacks.
// @ts-expect-error: no need to set all attributes in callbacks
callbacks: {
label: (context) => {
let label = (context.dataset as ControllerDatasetOptions).label ?? '';

2
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

@ -434,7 +434,7 @@ export class GfPortfolioProportionChartComponent
currency: this.baseCurrency,
locale: this.locale
}),
// @ts-expect-error: no need to set all attributes in callbacks.
// @ts-expect-error: no need to set all attributes in callbacks
callbacks: {
label: (context) => {
const labelIndex =

16
libs/ui/src/lib/treemap-chart/interfaces/interfaces.ts

@ -1,5 +1,21 @@
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { ScriptableContext, TooltipItem } from 'chart.js';
import { TreemapDataPoint } from 'chartjs-chart-treemap';
export interface GetColorParams {
annualizedNetPerformancePercent: number;
negativeNetPerformancePercentsRange: { max: number; min: number };
positiveNetPerformancePercentsRange: { max: number; min: number };
}
export interface GfTreemapScriptableContext extends ScriptableContext<'treemap'> {
raw: TreemapDataPoint & {
_data: PortfolioPosition;
};
}
export interface GfTreemapTooltipItem extends TooltipItem<'treemap'> {
raw: TreemapDataPoint & {
_data: PortfolioPosition;
};
}

48
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 { ChartConfiguration } from 'chart.js';
import type { TooltipOptions, ChartData } from 'chart.js';
import { LinearScale } from 'chart.js';
import { Chart, Tooltip } from 'chart.js';
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
@ -35,7 +35,11 @@ import { orderBy } from 'lodash';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import OpenColor from 'open-color';
import { GetColorParams } from './interfaces/interfaces';
import type {
GetColorParams,
GfTreemapScriptableContext,
GfTreemapTooltipItem
} from './interfaces/interfaces';
const { gray, green, red } = OpenColor;
@ -198,10 +202,10 @@ export class GfTreemapChartComponent
min: Math.min(...negativeNetPerformancePercents)
};
const data: ChartConfiguration<'treemap'>['data'] = {
const data: ChartData<'treemap'> = {
datasets: [
{
backgroundColor: (context) => {
backgroundColor: (context: GfTreemapScriptableContext) => {
let annualizedNetPerformancePercent =
getAnnualizedPerformancePercent({
daysInMarket: differenceInDays(
@ -232,7 +236,7 @@ export class GfTreemapChartComponent
key: 'allocationInPercentage',
labels: {
align: 'left',
color: (context) => {
color: (context: GfTreemapScriptableContext) => {
let annualizedNetPerformancePercent =
getAnnualizedPerformancePercent({
daysInMarket: differenceInDays(
@ -261,7 +265,7 @@ export class GfTreemapChartComponent
},
display: true,
font: [{ size: 16 }, { lineHeight: 1.5, size: 14 }],
formatter: ({ raw }) => {
formatter: ({ raw }: GfTreemapScriptableContext) => {
// Round to 4 decimal places
let netPerformancePercentWithCurrencyEffect =
Math.round(
@ -286,32 +290,34 @@ export class GfTreemapChartComponent
position: 'top'
},
spacing: 1,
// @ts-expect-error: should be PortfolioPosition[]
tree: this.holdings
}
]
} as any;
};
if (this.chartCanvas) {
if (this.chart) {
this.chart.data = data;
this.chart.options.plugins ??= {};
this.chart.options.plugins.tooltip =
this.getTooltipPluginConfiguration() as unknown;
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,
onClick: (event, activeElements) => {
onClick: (_, activeElements, chart: Chart<'treemap'>) => {
try {
const dataIndex = activeElements[0].index;
const datasetIndex = activeElements[0].datasetIndex;
const dataset = orderBy(
event.chart.data.datasets[datasetIndex].tree,
chart.data.datasets[datasetIndex].tree,
['allocationInPercentage'],
['desc']
);
) as PortfolioPosition[];
const dataSource: DataSource = dataset[dataIndex].dataSource;
const symbol: string = dataset[dataIndex].symbol;
@ -321,15 +327,14 @@ export class GfTreemapChartComponent
},
onHover: (event, chartElement) => {
if (this.cursor) {
event.native.target.style.cursor = chartElement[0]
? this.cursor
: 'default';
(event.native?.target as HTMLElement).style.cursor =
chartElement[0] ? this.cursor : 'default';
}
},
plugins: {
tooltip: this.getTooltipPluginConfiguration()
}
} as unknown,
},
type: 'treemap'
});
}
@ -338,16 +343,17 @@ export class GfTreemapChartComponent
this.isLoading = false;
}
private getTooltipPluginConfiguration() {
private getTooltipPluginConfiguration(): Partial<TooltipOptions<'treemap'>> {
return {
...getTooltipOptions({
colorScheme: this.colorScheme,
currency: this.baseCurrency,
locale: this.locale
}),
// @ts-expect-error: no need to set all attributes in callbacks
callbacks: {
label: ({ raw }) => {
const allocationInPercentage = `${((raw._data.allocationInPercentage as number) * 100).toFixed(2)}%`;
label: ({ raw }: GfTreemapTooltipItem) => {
const allocationInPercentage = `${(raw._data.allocationInPercentage * 100).toFixed(2)}%`;
const name = raw._data.name;
const sign =
raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : '';
@ -356,11 +362,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}`,

Loading…
Cancel
Save