Extend investment timeline by month (#1066)

* Extend investment timeline grouped by month

* Update changelog
This commit is contained in:
Thomas Kaul
2022-07-09 21:18:05 +02:00
committed by GitHub
parent 7b5454e7de
commit 60e2aff488
13 changed files with 183 additions and 37 deletions

View File

@@ -22,7 +22,10 @@ import {
transformTickToAbbreviation
} from '@ghostfolio/common/helper';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { GroupBy } from '@ghostfolio/common/types';
import {
BarController,
BarElement,
Chart,
LineController,
LineElement,
@@ -42,6 +45,7 @@ import { addDays, isAfter, parseISO, subDays } from 'date-fns';
export class InvestmentChartComponent implements OnChanges, OnDestroy {
@Input() currency: string;
@Input() daysInMarket: number;
@Input() groupBy: GroupBy;
@Input() investments: InvestmentItem[];
@Input() isInPercent = false;
@Input() locale: string;
@@ -53,6 +57,8 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
public constructor() {
Chart.register(
BarController,
BarElement,
LinearScale,
LineController,
LineElement,
@@ -78,7 +84,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
private initialize() {
this.isLoading = true;
if (this.investments?.length > 0) {
if (!this.groupBy && this.investments?.length > 0) {
// Extend chart by 5% of days in market (before)
const firstItem = this.investments[0];
this.investments.unshift({
@@ -102,13 +108,14 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
}
const data = {
labels: this.investments.map((position) => {
return position.date;
labels: this.investments.map((investmentItem) => {
return investmentItem.date;
}),
datasets: [
{
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
borderWidth: 2,
borderWidth: this.groupBy ? 0 : 2,
data: this.investments.map((position) => {
return position.investment;
}),
@@ -137,6 +144,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
this.chart = new Chart(this.chartCanvas.nativeElement, {
data,
options: {
animation: false,
elements: {
line: {
tension: 0
@@ -192,12 +200,12 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
}
},
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
type: 'line'
type: this.groupBy ? 'bar' : 'line'
});
this.isLoading = false;
}
}
this.isLoading = false;
}
private getTooltipPluginConfiguration() {

View File

@@ -4,6 +4,7 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Position, User } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { GroupBy, ToggleOption } from '@ghostfolio/common/types';
import { differenceInDays } from 'date-fns';
import { sortBy } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector';
@@ -22,6 +23,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public deviceType: string;
public hasImpersonationId: boolean;
public investments: InvestmentItem[];
public investmentsByMonth: InvestmentItem[];
public mode: GroupBy;
public modeOptions: ToggleOption[] = [
{ label: 'Monthly', value: 'month' },
{ label: 'Accumulating', value: undefined }
];
public top3: Position[];
public user: User;
@@ -55,6 +62,15 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchInvestmentsByMonth()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ investments }) => {
this.investmentsByMonth = investments;
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchPositions({ range: 'max' })
.pipe(takeUntil(this.unsubscribeSubject))
@@ -86,6 +102,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
});
}
public onChangeGroupBy(aMode: GroupBy) {
this.mode = aMode;
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

View File

@@ -2,8 +2,19 @@
<div class="investment-chart row">
<div class="col-lg">
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
<div class="mb-3">
<div class="h5 mb-3" i18n>Investment Timeline</div>
<div class="mb-4">
<div class="align-items-center d-flex mb-4">
<div class="flex-grow-1 h5 mb-0 text-truncate" i18n>
Investment Timeline
</div>
<gf-toggle
class="d-none d-lg-block"
[defaultValue]="mode"
[isLoading]="false"
[options]="modeOptions"
(change)="onChangeGroupBy($event.value)"
></gf-toggle>
</div>
<gf-investment-chart
class="h-100"
[currency]="user?.settings?.baseCurrency"
@@ -11,6 +22,17 @@
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[investments]="investments"
[locale]="user?.settings?.locale"
[ngClass]="{ 'd-none': mode }"
></gf-investment-chart>
<gf-investment-chart
class="h-100"
groupBy="month"
[currency]="user?.settings?.baseCurrency"
[daysInMarket]="daysInMarket"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[investments]="investmentsByMonth"
[locale]="user?.settings?.locale"
[ngClass]="{ 'd-none': !mode }"
></gf-investment-chart>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
import { GfValueModule } from '@ghostfolio/ui/value';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@@ -14,6 +15,7 @@ import { AnalysisPageComponent } from './analysis-page.component';
AnalysisPageRoutingModule,
CommonModule,
GfInvestmentChartModule,
GfToggleModule,
GfValueModule,
MatCardModule,
NgxSkeletonLoaderModule

View File

@@ -204,6 +204,22 @@ export class DataService {
);
}
public fetchInvestmentsByMonth(): Observable<PortfolioInvestments> {
return this.http
.get<any>('/api/v1/portfolio/investments', {
params: { groupBy: 'month' }
})
.pipe(
map((response) => {
if (response.firstOrderDate) {
response.firstOrderDate = parseISO(response.firstOrderDate);
}
return response;
})
);
}
public fetchSymbolItem({
dataSource,
includeHistoricalData,