diff --git a/CHANGELOG.md b/CHANGELOG.md index 752fc2397..e66630572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.114.0 - 2024-10-10 ### Added +- Added a tooltip to the chart of the holdings tab on the home page (experimental) - Extended the _Public API_ with the health check endpoint (experimental) ### Changed @@ -16,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved the tags from the info to the user service - Switched the `prefer-const` rule from `warn` to `error` in the `eslint` configuration +### Fixed + +- Fixed an exception in the portfolio details endpoint caused by a calculation of the allocations by market + ## 2.113.0 - 2024-10-06 ### Added diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index 9399f97bf..7488e4201 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -76,7 +76,7 @@ export class PublicController { }) ]); - Object.values(markets).forEach((market) => { + Object.values(markets ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index ff0c31060..326dda151 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -172,10 +172,10 @@ export class PortfolioController { }) || isRestrictedView(this.request.user) ) { - Object.values(markets).forEach((market) => { + Object.values(markets ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); - Object.values(marketsAdvanced).forEach((market) => { + Object.values(marketsAdvanced ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index 01afe31a2..f1c4e7e88 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -38,8 +38,11 @@ } 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 8915707fa..b278180ea 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -2,11 +2,13 @@ import { getAnnualizedPerformancePercent, getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; +import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; +import { getLocale } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, PortfolioPosition } from '@ghostfolio/common/interfaces'; -import { DateRange } from '@ghostfolio/common/types'; +import { ColorScheme, DateRange } from '@ghostfolio/common/types'; import { CommonModule } from '@angular/common'; import { @@ -25,7 +27,7 @@ import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; import { ChartConfiguration } from 'chart.js'; import { LinearScale } from 'chart.js'; -import { Chart } from 'chart.js'; +import { Chart, Tooltip } from 'chart.js'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; import { differenceInDays, max } from 'date-fns'; import { orderBy } from 'lodash'; @@ -44,9 +46,12 @@ const { gray, green, red } = require('open-color'); 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(); @Output() treemapChartClicked = new EventEmitter(); @@ -58,7 +63,7 @@ export class GfTreemapChartComponent public isLoading = true; public constructor() { - Chart.register(LinearScale, TreemapController, TreemapElement); + Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement); } public ngAfterViewInit() { @@ -168,6 +173,9 @@ export class GfTreemapChartComponent if (this.chartCanvas) { if (this.chart) { this.chart.data = data; + this.chart.options.plugins.tooltip = ( + this.getTooltipPluginConfiguration() + ); this.chart.update(); } else { this.chart = new Chart(this.chartCanvas.nativeElement, { @@ -199,9 +207,7 @@ export class GfTreemapChartComponent } }, plugins: { - tooltip: { - enabled: false - } + tooltip: this.getTooltipPluginConfiguration() } }, type: 'treemap' @@ -211,4 +217,34 @@ export class GfTreemapChartComponent this.isLoading = false; } + + private getTooltipPluginConfiguration() { + return { + ...getTooltipOptions({ + colorScheme: this.colorScheme, + currency: this.baseCurrency, + locale: this.locale + }), + callbacks: { + label: (context) => { + if (context.raw._data.valueInBaseCurrency !== null) { + const value = context.raw._data.valueInBaseCurrency; + return `${value.toLocaleString(this.locale, { + maximumFractionDigits: 2, + minimumFractionDigits: 2 + })} ${this.baseCurrency}`; + } else { + const percentage = + context.raw._data.allocationInPercentage * 100; + return `${percentage.toFixed(2)}%`; + } + }, + title: () => { + return ''; + } + }, + xAlign: 'center', + yAlign: 'center' + }; + } } diff --git a/package.json b/package.json index 894b0d192..e283d6a23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.113.0", + "version": "2.114.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio",