Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror

This commit is contained in:
ksyasuda 2024-10-10 15:32:31 -07:00
commit 69ab792b46
6 changed files with 55 additions and 11 deletions

View File

@ -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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## 2.114.0 - 2024-10-10
### Added ### 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) - Extended the _Public API_ with the health check endpoint (experimental)
### Changed ### 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 - Moved the tags from the info to the user service
- Switched the `prefer-const` rule from `warn` to `error` in the `eslint` configuration - 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 ## 2.113.0 - 2024-10-06
### Added ### Added

View File

@ -76,7 +76,7 @@ export class PublicController {
}) })
]); ]);
Object.values(markets).forEach((market) => { Object.values(markets ?? {}).forEach((market) => {
delete market.valueInBaseCurrency; delete market.valueInBaseCurrency;
}); });

View File

@ -172,10 +172,10 @@ export class PortfolioController {
}) || }) ||
isRestrictedView(this.request.user) isRestrictedView(this.request.user)
) { ) {
Object.values(markets).forEach((market) => { Object.values(markets ?? {}).forEach((market) => {
delete market.valueInBaseCurrency; delete market.valueInBaseCurrency;
}); });
Object.values(marketsAdvanced).forEach((market) => { Object.values(marketsAdvanced ?? {}).forEach((market) => {
delete market.valueInBaseCurrency; delete market.valueInBaseCurrency;
}); });

View File

@ -38,8 +38,11 @@
<gf-treemap-chart <gf-treemap-chart
class="mt-3" class="mt-3"
cursor="pointer" cursor="pointer"
[baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[dateRange]="user?.settings?.dateRange" [dateRange]="user?.settings?.dateRange"
[holdings]="holdings" [holdings]="holdings"
[locale]="user?.settings?.locale"
(treemapChartClicked)="onHoldingClicked($event)" (treemapChartClicked)="onHoldingClicked($event)"
/> />
} }

View File

@ -2,11 +2,13 @@ import {
getAnnualizedPerformancePercent, getAnnualizedPerformancePercent,
getIntervalFromDateRange getIntervalFromDateRange
} from '@ghostfolio/common/calculation-helper'; } from '@ghostfolio/common/calculation-helper';
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { getLocale } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
PortfolioPosition PortfolioPosition
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types'; import { ColorScheme, DateRange } from '@ghostfolio/common/types';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
@ -25,7 +27,7 @@ import { DataSource } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { ChartConfiguration } from 'chart.js'; import { ChartConfiguration } from 'chart.js';
import { LinearScale } 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 { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
import { differenceInDays, max } from 'date-fns'; import { differenceInDays, max } from 'date-fns';
import { orderBy } from 'lodash'; import { orderBy } from 'lodash';
@ -44,9 +46,12 @@ const { gray, green, red } = require('open-color');
export class GfTreemapChartComponent export class GfTreemapChartComponent
implements AfterViewInit, OnChanges, OnDestroy implements AfterViewInit, OnChanges, OnDestroy
{ {
@Input() baseCurrency: string;
@Input() colorScheme: ColorScheme;
@Input() cursor: string; @Input() cursor: string;
@Input() dateRange: DateRange; @Input() dateRange: DateRange;
@Input() holdings: PortfolioPosition[]; @Input() holdings: PortfolioPosition[];
@Input() locale = getLocale();
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>(); @Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>();
@ -58,7 +63,7 @@ export class GfTreemapChartComponent
public isLoading = true; public isLoading = true;
public constructor() { public constructor() {
Chart.register(LinearScale, TreemapController, TreemapElement); Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement);
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -168,6 +173,9 @@ export class GfTreemapChartComponent
if (this.chartCanvas) { if (this.chartCanvas) {
if (this.chart) { if (this.chart) {
this.chart.data = data; this.chart.data = data;
this.chart.options.plugins.tooltip = <unknown>(
this.getTooltipPluginConfiguration()
);
this.chart.update(); this.chart.update();
} else { } else {
this.chart = new Chart(this.chartCanvas.nativeElement, { this.chart = new Chart(this.chartCanvas.nativeElement, {
@ -199,9 +207,7 @@ export class GfTreemapChartComponent
} }
}, },
plugins: { plugins: {
tooltip: { tooltip: this.getTooltipPluginConfiguration()
enabled: false
}
} }
}, },
type: 'treemap' type: 'treemap'
@ -211,4 +217,34 @@ export class GfTreemapChartComponent
this.isLoading = false; 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 = <number>context.raw._data.valueInBaseCurrency;
return `${value.toLocaleString(this.locale, {
maximumFractionDigits: 2,
minimumFractionDigits: 2
})} ${this.baseCurrency}`;
} else {
const percentage =
<number>context.raw._data.allocationInPercentage * 100;
return `${percentage.toFixed(2)}%`;
}
},
title: () => {
return '';
}
},
xAlign: 'center',
yAlign: 'center'
};
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.113.0", "version": "2.114.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",