Feature/add support for click in portfolio proportion chart (#729)

* Add support for click

* Update changelog
pull/731/head
Thomas Kaul 2 years ago committed by GitHub
parent 93d6746739
commit b3e58d182a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,12 @@ 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
### Added
- Added support for click in the portfolio proportion chart component
## 1.121.0 - 27.02.2022
### Added

@ -11,7 +11,8 @@ import {
AdminData,
AdminMarketData,
AdminMarketDataDetails,
AdminMarketDataItem
AdminMarketDataItem,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { DataSource, Property } from '@prisma/client';
@ -30,13 +31,7 @@ export class AdminService {
private readonly symbolProfileService: SymbolProfileService
) {}
public async deleteProfileData({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public async deleteProfileData({ dataSource, symbol }: UniqueAsset) {
await this.marketDataService.deleteMany({ dataSource, symbol });
await this.symbolProfileService.delete({ dataSource, symbol });
}
@ -137,10 +132,7 @@ export class AdminService {
public async getMarketDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): Promise<AdminMarketDataDetails> {
}: UniqueAsset): Promise<AdminMarketDataDetails> {
return {
marketData: await this.marketDataService.marketDataItems({
orderBy: {

@ -4,6 +4,7 @@ import {
PROPERTY_LOCKED_DATA_GATHERING
} from '@ghostfolio/common/config';
import { DATE_FORMAT, resetHours } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { Inject, Injectable, Logger } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import {
@ -121,13 +122,7 @@ export class DataGatheringService {
}
}
public async gatherSymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public async gatherSymbol({ dataSource, symbol }: UniqueAsset) {
const isDataGatheringLocked = await this.prismaService.property.findUnique({
where: { key: PROPERTY_LOCKED_DATA_GATHERING }
});

@ -2,6 +2,7 @@ import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-dat
import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { resetHours } from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { DataSource, MarketData, Prisma } from '@prisma/client';
@ -9,13 +10,7 @@ import { DataSource, MarketData, Prisma } from '@prisma/client';
export class MarketDataService {
public constructor(private readonly prismaService: PrismaService) {}
public async deleteMany({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public async deleteMany({ dataSource, symbol }: UniqueAsset) {
return this.prismaService.marketData.deleteMany({
where: {
dataSource,

@ -8,6 +8,7 @@ import {
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
import { DataSource, MarketData } from '@prisma/client';
import { Subject } from 'rxjs';
@ -44,39 +45,21 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
this.fetchAdminMarketData();
}
public onDeleteProfileData({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
this.adminService
.deleteProfileData({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public onGatherProfileDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
this.adminService
.gatherProfileDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public onGatherSymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public onGatherSymbol({ dataSource, symbol }: UniqueAsset) {
this.adminService
.gatherSymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
@ -93,13 +76,7 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
}
}
public setCurrentProfile({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public setCurrentProfile({ dataSource, symbol }: UniqueAsset) {
this.marketDataDetails = [];
if (this.currentSymbol === symbol) {
@ -129,13 +106,7 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
});
}
private fetchAdminMarketDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
private fetchAdminMarketDataBySymbol({ dataSource, symbol }: UniqueAsset) {
this.adminService
.fetchAdminMarketDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))

@ -13,8 +13,8 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { AssetClass, DataSource, Order as OrderModel } from '@prisma/client';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetClass, Order as OrderModel } from '@prisma/client';
import { Subject, Subscription } from 'rxjs';
@Component({
@ -75,13 +75,7 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
this.dataSource.filter = filterValue.trim().toLowerCase();
}*/
public onOpenPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): void {
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset): void {
this.router.navigate([], {
queryParams: { dataSource, symbol, positionDetailDialog: true }
});

@ -10,6 +10,7 @@ import { prettifySymbol } from '@ghostfolio/common/helper';
import {
PortfolioDetails,
PortfolioPosition,
UniqueAsset,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -64,7 +65,12 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
[name: string]: { name: string; value: number };
};
public symbols: {
[name: string]: { name: string; symbol: string; value: number };
[name: string]: {
dataSource?: DataSource;
name: string;
symbol: string;
value: number;
};
};
public user: User;
@ -281,6 +287,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
if (position.assetClass === AssetClass.EQUITY) {
this.symbols[prettifySymbol(symbol)] = {
dataSource: position.dataSource,
name: position.name,
symbol: prettifySymbol(symbol),
value: aPeriod === 'original' ? position.investment : position.value
@ -295,6 +302,14 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.initializeAnalysisData(this.period);
}
public onProportionChartClicked({ dataSource, symbol }: UniqueAsset) {
if (dataSource && symbol) {
this.router.navigate([], {
queryParams: { dataSource, symbol, positionDetailDialog: true }
});
}
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

@ -89,12 +89,14 @@
<mat-card-content>
<gf-portfolio-proportion-chart
class="mx-auto"
cursor="pointer"
[baseCurrency]="user?.settings?.baseCurrency"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['symbol']"
[locale]="user?.settings?.locale"
[positions]="symbols"
[showLabels]="deviceType !== 'mobile'"
(proportionChartClicked)="onProportionChartClicked($event)"
></gf-portfolio-proportion-chart>
</mat-card-content>
</mat-card>

@ -3,7 +3,10 @@ import { Injectable } from '@angular/core';
import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { AdminMarketDataDetails } from '@ghostfolio/common/interfaces';
import {
AdminMarketDataDetails,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { DataSource, MarketData } from '@prisma/client';
import { format, parseISO } from 'date-fns';
import { Observable, map } from 'rxjs';
@ -14,13 +17,7 @@ import { Observable, map } from 'rxjs';
export class AdminService {
public constructor(private http: HttpClient) {}
public deleteProfileData({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public deleteProfileData({ dataSource, symbol }: UniqueAsset) {
return this.http.delete<void>(
`/api/admin/profile-data/${dataSource}/${symbol}`
);
@ -53,13 +50,7 @@ export class AdminService {
return this.http.post<void>(`/api/admin/gather/profile-data`, {});
}
public gatherProfileDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
public gatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
return this.http.post<void>(
`/api/admin/gather/profile-data/${dataSource}/${symbol}`,
{}
@ -70,10 +61,8 @@ export class AdminService {
dataSource,
date,
symbol
}: {
dataSource: DataSource;
}: UniqueAsset & {
date?: Date;
symbol: string;
}) {
let url = `/api/admin/gather/${dataSource}/${symbol}`;

@ -1,5 +1,3 @@
import { Property } from '@prisma/client';
export interface AdminData {
dataGatheringProgress?: number;
exchangeRates: { label1: string; label2: string; value: number }[];

@ -22,6 +22,7 @@ import { PortfolioReport } from './portfolio-report.interface';
import { PortfolioSummary } from './portfolio-summary.interface';
import { Position } from './position.interface';
import { TimelinePosition } from './timeline-position.interface';
import { UniqueAsset } from './unique-asset.interface';
import { UserSettings } from './user-settings.interface';
import { UserWithSettings } from './user-with-settings';
import { User } from './user.interface';
@ -49,6 +50,7 @@ export {
PortfolioSummary,
Position,
TimelinePosition,
UniqueAsset,
User,
UserSettings,
UserWithSettings

@ -0,0 +1,6 @@
import { DataSource } from '@prisma/client';
export interface UniqueAsset {
dataSource: DataSource;
symbol: string;
}

@ -21,6 +21,7 @@ import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { OrderWithAccount } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client';
import Big from 'big.js';
@ -199,13 +200,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
this.import.emit();
}
public onOpenPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): void {
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset): void {
this.router.navigate([], {
queryParams: { dataSource, symbol, positionDetailDialog: true }
});

@ -3,14 +3,17 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
ViewChild
} from '@angular/core';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import { DataSource } from '@prisma/client';
import Big from 'big.js';
import { Tooltip } from 'chart.js';
import { LinearScale } from 'chart.js';
@ -30,6 +33,7 @@ export class PortfolioProportionChartComponent
implements AfterViewInit, OnChanges, OnDestroy
{
@Input() baseCurrency: string;
@Input() cursor: string;
@Input() isInPercent = false;
@Input() keys: string[] = [];
@Input() locale = '';
@ -37,11 +41,14 @@ export class PortfolioProportionChartComponent
@Input() showLabels = false;
@Input() positions: {
[symbol: string]: Pick<PortfolioPosition, 'type'> & {
dataSource?: DataSource;
name: string;
value: number;
};
} = {};
@Output() proportionChartClicked = new EventEmitter<UniqueAsset>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
public chart: Chart;
@ -256,6 +263,21 @@ export class PortfolioProportionChartComponent
layout: {
padding: this.showLabels === true ? 100 : 0
},
onClick: (event, activeElements) => {
const dataIndex = activeElements[0].index;
const symbol: string = event.chart.data.labels[dataIndex];
const dataSource = this.positions[symbol]?.dataSource;
this.proportionChartClicked.emit({ dataSource, symbol });
},
onHover: (event, chartElement) => {
if (this.cursor) {
event.native.target.style.cursor = chartElement[0]
? this.cursor
: 'default';
}
},
plugins: {
datalabels: {
color: (context) => {

Loading…
Cancel
Save