@ -24,7 +24,7 @@ import {
Input ,
Input ,
OnChanges ,
OnChanges ,
OnDestroy ,
OnDestroy ,
V iewChild
v iewChild
} from '@angular/core' ;
} from '@angular/core' ;
import {
import {
BarController ,
BarController ,
@ -55,20 +55,21 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
templateUrl : './investment-chart.component.html'
templateUrl : './investment-chart.component.html'
} )
} )
export class GfInvestmentChartComponent implements OnChanges , OnDestroy {
export class GfInvestmentChartComponent implements OnChanges , OnDestroy {
@Input ( ) benchmarkDataItems : InvestmentItem [ ] = [ ] ;
@Input ( ) public readonly benchmarkDataItems : InvestmentItem [ ] = [ ] ;
@Input ( ) benchmarkDataLabel = '' ;
@Input ( ) public readonly benchmarkDataLabel = '' ;
@Input ( ) colorScheme : ColorScheme ;
@Input ( ) public readonly colorScheme : ColorScheme ;
@Input ( ) currency : string ;
@Input ( ) public readonly currency : string ;
@Input ( ) groupBy : GroupBy ;
@Input ( ) public readonly groupBy : GroupBy ;
@Input ( ) historicalDataItems : LineChartItem [ ] = [ ] ;
@Input ( ) public readonly historicalDataItems : LineChartItem [ ] = [ ] ;
@Input ( ) isInPercentage = false ;
@Input ( ) public readonly isInPercentage = false ;
@Input ( ) isLoading = false ;
@Input ( ) public readonly isLoading = false ;
@Input ( ) locale = getLocale ( ) ;
@Input ( ) public readonly locale = getLocale ( ) ;
@Input ( ) savingsRate = 0 ;
@Input ( ) public readonly savingsRate = 0 ;
@ViewChild ( 'chartCanvas' ) chartCanvas : ElementRef < HTMLCanvasElement > ;
private readonly chartCanvas =
viewChild . required < ElementRef < HTMLCanvasElement > > ( 'chartCanvas' ) ;
public chart : Chart < 'bar' | 'line' > ;
private chart : Chart < 'bar' | 'line' > ;
private investments : InvestmentItem [ ] ;
private investments : InvestmentItem [ ] ;
private values : LineChartItem [ ] ;
private values : LineChartItem [ ] ;
@ -118,7 +119,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
borderWidth : this.groupBy ? 0 : 1 ,
borderWidth : this.groupBy ? 0 : 1 ,
data : this.investments.map ( ( { date , investment } ) = > {
data : this.investments.map ( ( { date , investment } ) = > {
return {
return {
x : parseDate ( date ) . getTime ( ) ,
x : parseDate ( date ) ? . getTime ( ) ? ? null ,
y : this.isInPercentage ? investment * 100 : investment
y : this.isInPercentage ? investment * 100 : investment
} ;
} ;
} ) ,
} ) ,
@ -138,7 +139,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
borderWidth : 2 ,
borderWidth : 2 ,
data : this.values.map ( ( { date , value } ) = > {
data : this.values.map ( ( { date , value } ) = > {
return {
return {
x : parseDate ( date ) . getTime ( ) ,
x : parseDate ( date ) ? . getTime ( ) ? ? null ,
y : this.isInPercentage ? value * 100 : value
y : this.isInPercentage ? value * 100 : value
} ;
} ;
} ) ,
} ) ,
@ -165,123 +166,126 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
this . getTooltipPluginConfiguration ( ) ;
this . getTooltipPluginConfiguration ( ) ;
const annotations = this . chart . options . plugins . annotation
const annotations = this . chart . options . plugins . annotation
. annotations as Record < string , AnnotationOptions < ' line ' > > ;
? . annotations as Record < string , AnnotationOptions < ' line ' > > ;
if ( this . savingsRate && annotations . savingsRate ) {
if ( this . savingsRate && annotations . savingsRate ) {
annotations . savingsRate . value = this . savingsRate ;
annotations . savingsRate . value = this . savingsRate ;
}
}
this . chart . update ( ) ;
this . chart . update ( ) ;
} else {
} else {
this . chart = new Chart ( this . chartCanvas . nativeElement , {
this . chart = new Chart < 'bar' | 'line' > (
data : chartData ,
this . chartCanvas ( ) . nativeElement ,
options : {
{
animation : false ,
data : chartData ,
elements : {
options : {
line : {
animation : false ,
tension : 0
elements : {
line : {
tension : 0
} ,
point : {
hoverBackgroundColor : getBackgroundColor ( this . colorScheme ) ,
hoverRadius : 2 ,
radius : 0
}
} ,
} ,
point : {
interaction : { intersect : false , mode : 'index' } ,
hoverBackgroundColor : getBackgroundColor ( this . colorScheme ) ,
maintainAspectRatio : true ,
hoverRadius : 2 ,
plugins : {
radius : 0
annotation : {
}
annotations : {
} ,
savingsRate : this.savingsRate
interaction : { intersect : false , mode : 'index' } ,
? {
maintainAspectRatio : true ,
borderColor : ` rgba( ${ primaryColorRgb . r } , ${ primaryColorRgb . g } , ${ primaryColorRgb . b } , 0.75) ` ,
plugins : {
borderWidth : 1 ,
annotation : {
label : {
annotations : {
backgroundColor : ` rgb( ${ primaryColorRgb . r } , ${ primaryColorRgb . g } , ${ primaryColorRgb . b } ) ` ,
savingsRate : this.savingsRate
borderRadius : 2 ,
? {
color : 'white' ,
borderColor : ` rgba( ${ primaryColorRgb . r } , ${ primaryColorRgb . g } , ${ primaryColorRgb . b } , 0.75) ` ,
content : $localize ` Savings Rate ` ,
borderWidth : 1 ,
display : true ,
label : {
font : { size : 10 , weight : 'normal' } ,
backgroundColor : ` rgb( ${ primaryColorRgb . r } , ${ primaryColorRgb . g } , ${ primaryColorRgb . b } ) ` ,
padding : {
borderRadius : 2 ,
x : 4 ,
color : 'white' ,
y : 2
content : $localize ` Savings Rate ` ,
} ,
display : true ,
position : 'start'
font : { size : 10 , weight : 'normal' } ,
padding : {
x : 4 ,
y : 2
} ,
} ,
position : 'start'
scaleID : 'y' ,
} ,
type : 'line' ,
scaleID : 'y' ,
value : this.savingsRate
type : 'line' ,
}
value : this.savingsRate
: undefined ,
}
yAxis : {
: undefined ,
borderColor : ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) ` ,
yAxis : {
borderWidth : 1 ,
borderColor : ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) ` ,
scaleID : 'y' ,
borderWidth : 1 ,
type : 'line' ,
scaleID : 'y' ,
value : 0
type : 'line' ,
}
value : 0
}
}
}
} ,
legend : {
display : false
} ,
tooltip : this.getTooltipPluginConfiguration ( ) ,
verticalHoverLine : {
color : ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) `
}
} ,
responsive : true ,
scales : {
x : {
border : {
color : ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) ` ,
width : this.groupBy ? 0 : 1
} ,
} ,
display : true ,
legend : {
grid : {
display : false
display : false
} ,
} ,
type : 'time' ,
tooltip : this.getTooltipPluginConfiguration ( ) ,
time : {
verticalHoverLine : {
tooltipFormat : getDateFormatString ( this . locale ) ,
color : ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) `
unit : 'year'
}
}
} ,
} ,
y : {
responsive : true ,
border : {
scales : {
display : false
x : {
} ,
border : {
display : ! this . isInPercentage ,
color : ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) ` ,
grid : {
width : this.groupBy ? 0 : 1
color : ( { scale , tick } ) = > {
} ,
if (
display : true ,
tick . value === 0 ||
grid : {
tick . value === scale . max ||
display : false
tick . value === scale . min
} ,
) {
type : 'time' ,
return ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) ` ;
time : {
}
tooltipFormat : getDateFormatString ( this . locale ) ,
unit : 'year'
return 'transparent' ;
}
}
} ,
} ,
position : 'right' ,
y : {
ticks : {
border : {
callback : ( value : number ) = > {
display : false
return transformTickToAbbreviation ( value ) ;
} ,
} ,
display : true ,
display : ! this . isInPercentage ,
mirror : true ,
grid : {
z : 1
color : ( { scale , tick } ) = > {
if (
tick . value === 0 ||
tick . value === scale . max ||
tick . value === scale . min
) {
return ` rgba( ${ getTextColor ( this . colorScheme ) } , 0.1) ` ;
}
return 'transparent' ;
}
} ,
position : 'right' ,
ticks : {
callback : ( value : number ) = > {
return transformTickToAbbreviation ( value ) ;
} ,
display : true ,
mirror : true ,
z : 1
}
}
}
}
}
}
} ,
} ,
plugins : [
plugins : [
getVerticalHoverLinePlugin ( this . chartCanvas ( ) , this . colorScheme )
getVerticalHoverLinePlugin ( this . chartCanvas , this . colorScheme )
] ,
] ,
type : this . groupBy ? 'bar' : 'line'
type : this . groupBy ? 'bar' : 'line'
}
} ) ;
) ;
}
}
}
}
}
}
@ -305,8 +309,12 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy {
}
}
private isInFuture < T > ( aContext : ScriptableLineSegmentContext , aValue : T ) {
private isInFuture < T > ( aContext : ScriptableLineSegmentContext , aValue : T ) {
return isAfter ( new Date ( aContext ? . p1 ? . parsed ? . x ) , new Date ( ) )
const xValue = aContext ? . p1 ? . parsed ? . x ;
? aValue
: undefined ;
if ( xValue == null ) {
return undefined ;
}
return isAfter ( new Date ( xValue ) , new Date ( ) ) ? aValue : undefined ;
}
}
}
}