Feature/add support for click in portfolio proportion chart (#729)
* Add support for click * Update changelog
This commit is contained in:
parent
93d6746739
commit
b3e58d182a
@ -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
|
||||
|
6
libs/common/src/lib/interfaces/unique-asset.interface.ts
Normal file
6
libs/common/src/lib/interfaces/unique-asset.interface.ts
Normal file
@ -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…
x
Reference in New Issue
Block a user