import { Component , Input , OnChanges , OnDestroy , OnInit , SimpleChanges } from "@angular/core" ; import { YearProgressStyleModel } from "./models/year-progress-style.model" ; import { ViewableYearProgressDataModel } from "./models/viewable-year-progress-data.model" ; import * as moment from "moment" ; import { Moment } from "moment" ; import * as _ from "lodash" ; import { YearProgressModel } from "../shared/models/year-progress.model" ; import { ProgressModel } from "../shared/models/progress.model" ; import * as d3 from "d3" ; import * as MG from "metrics-graphics" ; import { MetricsGraphicsEventModel } from "../../shared/models/graphs/metrics-graphics-event.model" ; import { ProgressType } from "../shared/enums/progress-type.enum" ; import { GraphPointModel } from "../../shared/models/graphs/graph-point.model" ; import { YearProgressTypeModel } from "../shared/models/year-progress-type.model" ; import { YearProgressService } from "../shared/services/year-progress.service" ; import { TargetProgressModel } from "../shared/models/target-progress.model" ; import { SideNavService } from "../../shared/services/side-nav/side-nav.service" ; import { WindowService } from "../../shared/services/window/window.service" ; import { Subscription } from "rxjs" ; import { LoggerService } from "../../shared/services/logging/logger.service" ; @ Component ( { selector : "app-year-progress-graph" , templateUrl : "./year-progress-graph.component.html" , styleUrls : [ "./year-progress-graph.component.scss" ] } ) export class YearProgressGraphComponent implements OnInit , OnChanges , OnDestroy { public static readonly GRAPH_DOM_ELEMENT_ID : string = "yearProgressGraph" ; public static readonly GRAPH_WRAPPER_DOM_ELEMENT_ID : string = "graphWrapper" ; public static readonly GRAPH_TARGET_LINE_COLOR : string = "grey" ; public readonly ProgressType = ProgressType ; constructor ( public yearProgressService , public sideNavService , public windowService , public logger ) { } @ Input ( "isGraphExpanded" ) public isGraphExpanded : boolean ; @ Input ( "selectedYears" ) public selectedYears : number [ ] ; @ Input ( "selectedProgressType" ) public selectedProgressType : YearProgressTypeModel ; @ Input ( "yearProgressions" ) public yearProgressions : YearProgressModel [ ] ; @ Input ( "targetProgressModels" ) public targetProgressModels : TargetProgressModel [ ] ; @ Input ( "yearProgressStyleModel" ) public yearProgressStyleModel : YearProgressStyleModel ; public viewableYearProgressDataModel : ViewableYearProgressDataModel ; public graphConfig : any ; public isMomentWatchedToday : boolean ; public sideNavChangesSubscription : Subscription ; public windowResizingSubscription : Subscription ; public initialized = false ; public static clearSvgGraphContent ( ) { const svgElement = document . getElementById ( YearProgressGraphComponent . GRAPH_DOM_ELEMENT_ID ) . children [ 0 ] ; if ( svgElement ) { svgElement . remove ( ) ; } } public ngOnInit ( ) { this . viewableYearProgressDataModel = new ViewableYearProgressDataModel ( ) ; const defaultMarkerMoment = this . yearProgressService . momentWatched ; this . viewableYearProgressDataModel . setMarkerMoment ( defaultMarkerMoment ) ; this . isMomentWatchedToday = this . isMomentToday ( defaultMarkerMoment ) ; this . setupGraphConfig ( ) ; this . setupViewableGraphData ( ) ; this . updateGraph ( ) ; this . setupComponentSizeChangeHandlers ( ) ; this . initialized = true ; } public ngOnChanges ( changes ) { if ( ! this . initialized ) { return ; } if ( changes . selectedYears ) { YearProgressGraphComponent . clearSvgGraphContent ( ) ; } if ( changes . isGraphExpanded ) { this . onComponentSizeChanged ( ) ; } this . reloadGraph ( ) ; } public setupViewableGraphData ( ) { const yearLines = [ ] ; _ . forEach ( this . yearProgressions , ( yearProgressModel ) => { const isYearSelected = ( _ . indexOf ( this . selectedYears , yearProgressModel . year ) !== - 1 ) ; if ( isYearSelected ) { const yearLine = [ ] ; _ . forEach ( yearProgressModel . progressions , ( progressModel ) => { const graphPoint = { date : moment ( ) . dayOfYear ( progressModel . dayOfYear ) . format ( "YYYY-MM-DD" ) , hidden : progressModel . isFuture } ; switch ( this . selectedProgressType . type ) { case ProgressType . DISTANCE : graphPoint . value = progressModel . distance ; break ; case ProgressType . TIME : graphPoint . value = progressModel . time ; break ; case ProgressType . ELEVATION : graphPoint . value = progressModel . elevation ; break ; case ProgressType . COUNT : graphPoint . value = progressModel . count ; break ; default : throw new Error ( "Unknown progress type: " + this . selectedProgressType . type ) ; } yearLine . push ( graphPoint as GraphPointModel ) ; } ) ; yearLines . push ( yearLine ) ; } } ) ; this . viewableYearProgressDataModel . setGraphicsYearLines ( yearLines ) ; if ( this . targetProgressModels ) { const targetLine = [ ] ; _ . forEach ( this . targetProgressModels , ( targetProgressModel ) => { const graphPoint = { date : moment ( ) . dayOfYear ( targetProgressModel . dayOfYear ) . format ( "YYYY-MM-DD" ) , value : targetProgressModel . value , hidden : false } ; targetLine . push ( graphPoint as GraphPointModel ) ; } ) ; this . viewableYearProgressDataModel . setGraphicsTargetLine ( targetLine ) ; } else { this . viewableYearProgressDataModel . setGraphicsTargetLine ( [ ] ) ; } } public updateGraph ( partialUpdate ? ) { try { this . updateViewableData ( partialUpdate ) ; this . draw ( ) ; } catch ( error ) { this . logger . warn ( error ) ; } } public updateViewableData ( partialUpdate ? ) { this . graphConfig . markers = this . viewableYearProgressDataModel . markers ; if ( partialUpdate === true ) { return ; } this . graphConfig . data = this . viewableYearProgressDataModel . yearLines ; this . graphConfig . colors = this . colorsOfSelectedYears ( this . selectedYears ) ; this . graphConfig . markers = this . viewableYearProgressDataModel . markers ; if ( this . targetProgressModels ) { this . graphConfig . data . unshift ( this . viewableYearProgressDataModel . targetLine ) ; this . graphConfig . colors . unshift ( YearProgressGraphComponent . GRAPH_TARGET_LINE_COLOR ) ; } this . graphConfig . max_data_size = this . graphConfig . data . length ; } public draw ( ) { _ . defer ( ( ) => { MG . data_graphic ( this . graphConfig ) ; } ) ; } public reloadGraph ( ) { this . setupViewableGraphData ( ) ; this . updateGraph ( ) ; } param { number [ ] } yearSelection returns { string [ ] } public colorsOfSelectedYears ( yearSelection ) { const colors = [ ] ; _ . forEachRight ( yearSelection , ( year ) => { colors . push ( this . yearProgressStyleModel . yearsColorsMap . get ( year ) ) ; } ) ; return colors ; } param { moment . Moment } pMoment returns { boolean } public isMomentToday ( pMoment ) { return ( pMoment . dayOfYear ( ) === moment ( ) . dayOfYear ( ) ) ; } param { MetricsGraphicsEventModel } mgEvent public onGraphClick ( mgEvent ) { const momentWatched = moment ( mgEvent . key || mgEvent . date ) ; this . viewableYearProgressDataModel . setMarkerMoment ( momentWatched ) ; this . updateGraph ( true ) ; this . isMomentWatchedToday = this . isMomentToday ( momentWatched ) ; } param { MetricsGraphicsEventModel } mgEvent public onGraphMouseOver ( mgEvent ) { const momentWatched = moment ( mgEvent . key || mgEvent . date ) ; this . yearProgressService . onMomentWatchedChange ( momentWatched ) ; } param { MetricsGraphicsEventModel } event public onGraphMouseOut ( ) { const momentWatched = this . viewableYearProgressDataModel . getMarkerMoment ( ) ; this . yearProgressService . onMomentWatchedChange ( momentWatched ) ; } public onResetMomentWatched ( ) { const defaultMomentWatched = this . yearProgressService . resetMomentWatched ( ) ; this . viewableYearProgressDataModel . setMarkerMoment ( defaultMomentWatched ) ; this . updateGraph ( true ) ; this . isMomentWatchedToday = this . isMomentToday ( defaultMomentWatched ) ; } public onComponentSizeChanged ( ) { this . applyGraphHeight ( ) ; this . draw ( ) ; } public setupComponentSizeChangeHandlers ( ) { this . windowResizingSubscription = this . windowService . resizing . subscribe ( ( ) => this . onComponentSizeChanged ( ) ) ; this . sideNavChangesSubscription = this . sideNavService . changes . subscribe ( ( ) => this . onComponentSizeChanged ( ) ) ; } public setupGraphConfig ( ) { this . graphConfig = { data : [ ] , full_width : true , height : - 1 , top : 20 , right : 15 , left : 70 , baselines : [ ] , animate_on_load : false , transition_on_update : false , aggregate_rollover : true , interpolate : d3 . curveLinear , missing_is_hidden : true , missing_is_hidden_accessor : "hidden" , xax_count : 12 , yax_count : 10 , target : "#" + YearProgressGraphComponent . GRAPH_DOM_ELEMENT_ID , x_accessor : "date" , y_accessor : "value" , inflator : 1.1 , showActivePoint : false , markers : [ ] , legend : null , colors : [ ] , yax_format : d3 . format ( "" ) , max_data_size : 0 , click : ( metricsGraphicsEvent ) => { this . onGraphClick ( metricsGraphicsEvent ) ; } , mouseover : ( data ) => { this . onGraphMouseOver ( data ) ; } , mouseout : ( data ) => { this . onGraphMouseOut ( ) ; } } ; this . applyGraphHeight ( ) ; } return public findGraphicHeight ( ) { return window . innerHeight * 0.65 * ( ( this . isGraphExpanded ) ? 0.80 : 1 ) ; } public applyGraphHeight ( ) { const height = this . findGraphicHeight ( ) ; this . graphConfig . height = this . findGraphicHeight ( ) ; document . getElementById ( YearProgressGraphComponent . GRAPH_WRAPPER_DOM_ELEMENT_ID ) . style . height = height + "px" ; } public ngOnDestroy ( ) { this . windowResizingSubscription . unsubscribe ( ) ; this . sideNavChangesSubscription . unsubscribe ( ) ; } }