|
|
|
@ -15,18 +15,16 @@ import { |
|
|
|
ChangeDetectionStrategy, |
|
|
|
Component, |
|
|
|
ElementRef, |
|
|
|
EventEmitter, |
|
|
|
Input, |
|
|
|
input, |
|
|
|
OnChanges, |
|
|
|
OnDestroy, |
|
|
|
Output, |
|
|
|
ViewChild |
|
|
|
output, |
|
|
|
viewChild |
|
|
|
} from '@angular/core'; |
|
|
|
import { DataSource } from '@prisma/client'; |
|
|
|
import { Big } from 'big.js'; |
|
|
|
import type { ChartData, TooltipOptions } from 'chart.js'; |
|
|
|
import { LinearScale } from 'chart.js'; |
|
|
|
import { Chart, Tooltip } from 'chart.js'; |
|
|
|
import { Chart, LinearScale, Tooltip } from 'chart.js'; |
|
|
|
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; |
|
|
|
import { isUUID } from 'class-validator'; |
|
|
|
import { differenceInDays, max } from 'date-fns'; |
|
|
|
@ -52,33 +50,31 @@ const { gray, green, red } = OpenColor; |
|
|
|
export class GfTreemapChartComponent |
|
|
|
implements AfterViewInit, OnChanges, OnDestroy |
|
|
|
{ |
|
|
|
@Input() baseCurrency: string; |
|
|
|
@Input() colorScheme: ColorScheme; |
|
|
|
@Input() cursor: string; |
|
|
|
@Input() dateRange: DateRange; |
|
|
|
@Input() holdings: PortfolioPosition[]; |
|
|
|
@Input() locale = getLocale(); |
|
|
|
public readonly baseCurrency = input.required<string>(); |
|
|
|
public readonly colorScheme = input.required<ColorScheme>(); |
|
|
|
public readonly cursor = input.required<string>(); |
|
|
|
public readonly dateRange = input.required<DateRange>(); |
|
|
|
public readonly holdings = input<PortfolioPosition[]>(); |
|
|
|
public readonly locale = input<string>(getLocale()); |
|
|
|
|
|
|
|
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>(); |
|
|
|
public readonly treemapChartClicked = output<AssetProfileIdentifier>(); |
|
|
|
|
|
|
|
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>; |
|
|
|
protected isLoading = true; |
|
|
|
|
|
|
|
public chart: Chart<'treemap'>; |
|
|
|
public isLoading = true; |
|
|
|
private chart: Chart<'treemap'>; |
|
|
|
private readonly chartCanvas = |
|
|
|
viewChild.required<ElementRef<HTMLCanvasElement>>('chartCanvas'); |
|
|
|
|
|
|
|
public constructor() { |
|
|
|
Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement); |
|
|
|
} |
|
|
|
|
|
|
|
public ngAfterViewInit() { |
|
|
|
if (this.holdings) { |
|
|
|
this.initialize(); |
|
|
|
} |
|
|
|
this.initialize(); |
|
|
|
} |
|
|
|
|
|
|
|
public ngOnChanges() { |
|
|
|
if (this.holdings) { |
|
|
|
this.initialize(); |
|
|
|
} |
|
|
|
this.initialize(); |
|
|
|
} |
|
|
|
|
|
|
|
public ngOnDestroy() { |
|
|
|
@ -159,13 +155,19 @@ export class GfTreemapChartComponent |
|
|
|
} |
|
|
|
|
|
|
|
private initialize() { |
|
|
|
const holdings = this.holdings(); |
|
|
|
|
|
|
|
if (!holdings) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
this.isLoading = true; |
|
|
|
|
|
|
|
const { endDate, startDate } = getIntervalFromDateRange({ |
|
|
|
dateRange: this.dateRange |
|
|
|
dateRange: this.dateRange() |
|
|
|
}); |
|
|
|
|
|
|
|
const netPerformancePercentsWithCurrencyEffect = this.holdings.map( |
|
|
|
const netPerformancePercentsWithCurrencyEffect = holdings.map( |
|
|
|
({ dateOfFirstActivity, netPerformancePercentWithCurrencyEffect }) => { |
|
|
|
return getAnnualizedPerformancePercent({ |
|
|
|
daysInMarket: differenceInDays( |
|
|
|
@ -301,12 +303,12 @@ export class GfTreemapChartComponent |
|
|
|
}, |
|
|
|
spacing: 1, |
|
|
|
// @ts-expect-error: should be PortfolioPosition[]
|
|
|
|
tree: this.holdings |
|
|
|
tree: this.holdings() |
|
|
|
} |
|
|
|
] |
|
|
|
}; |
|
|
|
|
|
|
|
if (this.chartCanvas) { |
|
|
|
if (this.chartCanvas()) { |
|
|
|
if (this.chart) { |
|
|
|
this.chart.data = data; |
|
|
|
this.chart.options.plugins ??= {}; |
|
|
|
@ -315,7 +317,7 @@ export class GfTreemapChartComponent |
|
|
|
|
|
|
|
this.chart.update(); |
|
|
|
} else { |
|
|
|
this.chart = new Chart<'treemap'>(this.chartCanvas.nativeElement, { |
|
|
|
this.chart = new Chart<'treemap'>(this.chartCanvas().nativeElement, { |
|
|
|
data, |
|
|
|
options: { |
|
|
|
animation: false, |
|
|
|
@ -339,9 +341,9 @@ export class GfTreemapChartComponent |
|
|
|
} catch {} |
|
|
|
}, |
|
|
|
onHover: (event, chartElement) => { |
|
|
|
if (this.cursor) { |
|
|
|
if (this.cursor()) { |
|
|
|
(event.native?.target as HTMLElement).style.cursor = |
|
|
|
chartElement[0] ? this.cursor : 'default'; |
|
|
|
chartElement[0] ? this.cursor() : 'default'; |
|
|
|
} |
|
|
|
}, |
|
|
|
plugins: { |
|
|
|
@ -359,9 +361,9 @@ export class GfTreemapChartComponent |
|
|
|
private getTooltipPluginConfiguration(): Partial<TooltipOptions<'treemap'>> { |
|
|
|
return { |
|
|
|
...getTooltipOptions({ |
|
|
|
colorScheme: this.colorScheme, |
|
|
|
currency: this.baseCurrency, |
|
|
|
locale: this.locale |
|
|
|
colorScheme: this.colorScheme(), |
|
|
|
currency: this.baseCurrency(), |
|
|
|
locale: this.locale() |
|
|
|
}), |
|
|
|
// @ts-expect-error: no need to set all attributes in callbacks
|
|
|
|
callbacks: { |
|
|
|
@ -381,19 +383,19 @@ export class GfTreemapChartComponent |
|
|
|
|
|
|
|
return [ |
|
|
|
`${name ?? symbol} (${allocationInPercentage})`, |
|
|
|
`${value?.toLocaleString(this.locale, { |
|
|
|
`${value?.toLocaleString(this.locale(), { |
|
|
|
maximumFractionDigits: 2, |
|
|
|
minimumFractionDigits: 2 |
|
|
|
})} ${this.baseCurrency}`,
|
|
|
|
})} ${this.baseCurrency()}`,
|
|
|
|
'', |
|
|
|
$localize`Change` + ' (' + $localize`Performance` + ')', |
|
|
|
`${sign}${raw._data.netPerformanceWithCurrencyEffect.toLocaleString( |
|
|
|
this.locale, |
|
|
|
this.locale(), |
|
|
|
{ |
|
|
|
maximumFractionDigits: 2, |
|
|
|
minimumFractionDigits: 2 |
|
|
|
} |
|
|
|
)} ${this.baseCurrency} (${netPerformanceInPercentageWithSign})` |
|
|
|
)} ${this.baseCurrency()} (${netPerformanceInPercentageWithSign})` |
|
|
|
]; |
|
|
|
} else { |
|
|
|
return [ |
|
|
|
|