@ -3,20 +3,25 @@ import {
getChartBorderColor ,
getChartBorderColor ,
getChartElementsOptions ,
getChartElementsOptions ,
getTimeAxisOptions ,
getTimeAxisOptions ,
getTimeAxisUnit ,
getValueAxisOptions ,
getValueAxisOptions ,
getVerticalHoverLinePlugin ,
getVerticalHoverLinePlugin ,
getZeroLineAnnotation
getZeroLineAnnotation
} from '@ghostfolio/common/chart-helper' ;
} from '@ghostfolio/common/chart-helper' ;
import { primaryColorRgb } from '@ghostfolio/common/config' ;
import { primaryColorRgb } from '@ghostfolio/common/config' ;
import { getLocale , parseDate } from '@ghostfolio/common/helper' ;
import {
getLocale ,
isIntradayDateRange ,
parseDate
} from '@ghostfolio/common/helper' ;
import {
import {
BenchmarkSeries ,
BenchmarkSeries ,
LineChartItem ,
Nullable LineChartItem,
User
User
} from '@ghostfolio/common/interfaces' ;
} from '@ghostfolio/common/interfaces' ;
import { hasPermission , permissions } from '@ghostfolio/common/permissions' ;
import { hasPermission , permissions } from '@ghostfolio/common/permissions' ;
import { internalRoutes } from '@ghostfolio/common/routes/routes' ;
import { internalRoutes } from '@ghostfolio/common/routes/routes' ;
import { ColorScheme } from '@ghostfolio/common/types' ;
import { ColorScheme , DateRange } from '@ghostfolio/common/types' ;
import type { SymbolProfile } from '@ghostfolio/prisma/browser' ;
import type { SymbolProfile } from '@ghostfolio/prisma/browser' ;
import {
import {
getTimeSeriesTooltipOptions ,
getTimeSeriesTooltipOptions ,
@ -48,6 +53,7 @@ import {
LineElement ,
LineElement ,
PointElement ,
PointElement ,
TimeScale ,
TimeScale ,
TimeSeriesScale ,
Tooltip ,
Tooltip ,
type TooltipOptions
type TooltipOptions
} from 'chart.js' ;
} from 'chart.js' ;
@ -75,6 +81,7 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
public readonly benchmarkSeries = input < BenchmarkSeries [ ] > ( [ ] ) ;
public readonly benchmarkSeries = input < BenchmarkSeries [ ] > ( [ ] ) ;
public readonly benchmarks = input < Partial < SymbolProfile > [ ] > ( ) ;
public readonly benchmarks = input < Partial < SymbolProfile > [ ] > ( ) ;
public readonly colorScheme = input < ColorScheme > ( ) ;
public readonly colorScheme = input < ColorScheme > ( ) ;
public readonly dateRange = input < DateRange > ( ) ;
public readonly isLoading = input < boolean > ( ) ;
public readonly isLoading = input < boolean > ( ) ;
public readonly locale = input ( getLocale ( ) , {
public readonly locale = input ( getLocale ( ) , {
@ -83,7 +90,8 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
}
}
} ) ;
} ) ;
public readonly performanceDataItems = input . required < LineChartItem [ ] > ( ) ;
public readonly performanceDataItems =
input . required < NullableLineChartItem [ ] > ( ) ;
public readonly selectedBenchmarkIds = input < string [ ] > ( [ ] ) ;
public readonly selectedBenchmarkIds = input < string [ ] > ( [ ] ) ;
public readonly user = input < User > ( ) ;
public readonly user = input < User > ( ) ;
@ -104,6 +112,7 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
LineElement ,
LineElement ,
PointElement ,
PointElement ,
TimeScale ,
TimeScale ,
TimeSeriesScale ,
Tooltip
Tooltip
) ;
) ;
@ -157,14 +166,14 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
data : this.performanceDataItems ( ) . map ( ( { date , value } ) = > {
data : this.performanceDataItems ( ) . map ( ( { date , value } ) = > {
return {
return {
x : parseDate ( date ) ? . getTime ( ) ? ? null ,
x : parseDate ( date ) ? . getTime ( ) ? ? null ,
y : value * 100
y : value === null ? null : value * 100
} ;
} ;
} ) ,
} ) ,
label : $localize ` Portfolio `
label : $localize ` Portfolio `
} ,
} ,
. . . this . benchmarkSeries ( ) . map ( ( { items , name } , index ) = > {
. . . this . benchmarkSeries ( ) . map ( ( { items , name } , index ) = > {
const color = getBenchmarkColor ( index ) ;
const color = getBenchmarkColor ( index ) ;
const valuesByDate : Record < string , number > = { } ;
const valuesByDate : Record < string , number | null > = { } ;
for ( const { date , value } of items ) {
for ( const { date , value } of items ) {
valuesByDate [ date ] = value ;
valuesByDate [ date ] = value ;
@ -177,7 +186,8 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
data : this.performanceDataItems ( ) . map ( ( { date } ) = > {
data : this.performanceDataItems ( ) . map ( ( { date } ) = > {
return {
return {
x : parseDate ( date ) ? . getTime ( ) ? ? null ,
x : parseDate ( date ) ? . getTime ( ) ? ? null ,
y : valuesByDate [ date ]
// The benchmarks are already in percent
y : valuesByDate [ date ] ? ? null
} ;
} ;
} ) ,
} ) ,
label : name ? ? $localize ` Benchmark `
label : name ? ? $localize ` Benchmark `
@ -193,6 +203,10 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
this . chart . options . plugins . tooltip =
this . chart . options . plugins . tooltip =
this . getTooltipPluginConfiguration ( ) ;
this . getTooltipPluginConfiguration ( ) ;
// The date range can change while the chart stays alive
this . chart . options . scales ? ? = { } ;
this . chart . options . scales [ 'x' ] = this . getTimeAxisConfiguration ( ) ;
this . chart . update ( ) ;
this . chart . update ( ) ;
} else {
} else {
this . chart = new Chart < 'line' > ( this . chartCanvas ( ) . nativeElement , {
this . chart = new Chart < 'line' > ( this . chartCanvas ( ) . nativeElement , {
@ -219,10 +233,7 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
} ,
} ,
responsive : true ,
responsive : true ,
scales : {
scales : {
x : getTimeAxisOptions ( {
x : this.getTimeAxisConfiguration ( ) ,
colorScheme : this.colorScheme ( ) ,
locale : this.locale ( )
} ) ,
y : getValueAxisOptions ( {
y : getValueAxisOptions ( {
colorScheme : this.colorScheme ( ) ,
colorScheme : this.colorScheme ( ) ,
tickCallback : ( tickValue ) = > {
tickCallback : ( tickValue ) = > {
@ -240,6 +251,19 @@ export class GfBenchmarkComparatorComponent implements OnChanges, OnDestroy {
}
}
}
}
private getTimeAxisConfiguration() {
const dateRange = this . dateRange ( ) ;
return getTimeAxisOptions ( {
colorScheme : this.colorScheme ( ) ,
locale : this.locale ( ) ,
// Equidistant points: the closed markets (nights, weekends) are skipped
type : dateRange ? 'timeseries' : 'time' ,
unit : getTimeAxisUnit ( dateRange ) ,
withTime : isIntradayDateRange ( dateRange )
} ) ;
}
private getTooltipPluginConfiguration ( ) : Partial < TooltipOptions < ' line ' > > {
private getTooltipPluginConfiguration ( ) : Partial < TooltipOptions < ' line ' > > {
return {
return {
. . . getTimeSeriesTooltipOptions < 'line' > ( {
. . . getTimeSeriesTooltipOptions < 'line' > ( {