@@ -38,7 +38,7 @@ import {
3838
3939import 'd3-transition' ;
4040
41- import { brushX as d3BrushX } from 'd3-brush' ;
41+ import { BrushBehavior , brushX as d3BrushX } from 'd3-brush' ;
4242import { EventResultDataDTO } from 'src/app/modules/time-series/models/event-result-data.model' ;
4343import { EventResultSeriesDTO } from 'src/app/modules/time-series/models/event-result-series.model' ;
4444import { EventResultPointDTO } from 'src/app/modules/time-series/models/event-result-point.model' ;
@@ -63,13 +63,17 @@ export class LineChartService {
6363 // D3 margin conventions
6464 // > With this convention, all subsequent code can ignore margins.
6565 // see: https://bl.ocks.org/mbostock/3019563
66- private _margin = { top : 40 , right : 70 , bottom : 40 , left : 60 } ;
67- private _width = 600 - this . _margin . left - this . _margin . right ;
68- private _height = 550 - this . _margin . top - this . _margin . bottom ;
69- private _labelGroupHeight ;
70- private _legendGroupTop = this . _margin . top + this . _height + 50 ;
71- private legendDataMap = { } ;
72- private brush ;
66+ private _margin : any = { top : 40 , right : 70 , bottom : 40 , left : 60 } ;
67+ private _width : number = 600 - this . _margin . left - this . _margin . right ;
68+ private _height : number = 550 - this . _margin . top - this . _margin . bottom ;
69+ private _labelGroupHeight : number ;
70+ private _legendGroupTop : number = this . _margin . top + this . _height + 50 ;
71+ private _legendGroupHeight : number ;
72+ private _legendGroupColumnWidth : number ;
73+ private _legendGroupColumns : number ;
74+ private legendDataMap : Object = { } ;
75+ private brush : BrushBehavior < { } > ;
76+ private focusedLegendEntry : string ;
7377
7478 // Map that holds all points clustered by their x-axis values
7579 private _xAxisCluster : any = { } ;
@@ -78,7 +82,6 @@ export class LineChartService {
7882 private _mouseEventCatcher : D3Selection < D3BaseType , { } , D3ContainerElement , { } > ;
7983 private _markerTooltip : D3Selection < HTMLDivElement , { } , D3ContainerElement , { } > ;
8084
81-
8285 constructor ( private translationService : TranslateService ) {
8386 }
8487
@@ -98,19 +101,17 @@ export class LineChartService {
98101 * Draws a line chart for the given data into the given svg
99102 */
100103 public drawLineChart ( incomingData : EventResultDataDTO ) : void {
101-
102- let data : TimeSeries [ ] = this . prepareData ( incomingData ) ;
103-
104- if ( data . length == 0 ) {
104+ if ( incomingData . series . length == 0 ) {
105105 return ;
106106 }
107107
108+ let data : TimeSeries [ ] = this . prepareData ( incomingData ) ;
108109 let chart : D3Selection < D3BaseType , { } , D3ContainerElement , { } > = d3Select ( 'g#time-series-chart-drawing-area' ) ;
109110 let xScale : D3ScaleTime < number , number > = this . getXScale ( data ) ;
110111 let yScale : D3ScaleLinear < number , number > = this . getYScale ( data ) ;
111- this . _labelGroupHeight = data . length * ChartCommons . LABEL_HEIGHT ;
112+ this . calculateLegendDimensions ( ) ;
112113 d3Select ( 'osm-time-series-line-chart' ) . transition ( ) . duration ( 500 ) . style ( 'visibility' , 'visible' ) ;
113- d3Select ( 'svg#time-series-chart' ) . transition ( ) . duration ( 500 ) . attr ( 'height' , this . _height + this . _labelGroupHeight + this . _margin . top + this . _margin . bottom ) ;
114+ d3Select ( 'svg#time-series-chart' ) . transition ( ) . duration ( 500 ) . attr ( 'height' , this . _height + this . _legendGroupHeight + this . _margin . top + this . _margin . bottom ) ;
114115 d3Select ( '.x-axis' ) . transition ( ) . call ( this . updateXAxis , xScale ) ;
115116 d3Select ( '.y-axis' ) . transition ( ) . call ( this . updateYAxis , yScale , this . _width , this . _margin ) ;
116117 this . brush = d3BrushX ( ) . extent ( [ [ 0 , 0 ] , [ this . _width , this . _height ] ] ) ;
@@ -135,6 +136,10 @@ export class LineChartService {
135136 * Set the data for the legend after the incoming data is received
136137 */
137138 public setLegendData ( incomingData : EventResultDataDTO ) {
139+ if ( incomingData . series . length == 0 ) {
140+ return ;
141+ }
142+
138143 let labelDataMap = { } ;
139144 incomingData . series . forEach ( ( data : EventResultSeriesDTO ) => {
140145 if ( incomingData . summaryLabels . length > 0 && incomingData . summaryLabels [ 0 ] . key != "measurand" ) {
@@ -209,6 +214,7 @@ export class LineChartService {
209214 . attr ( 'transform' , `translate(${ this . _margin . left } , ${ this . _margin . top } )` ) ; // translates the origin to the top left corner (default behavior of D3)
210215
211216 svg . append ( 'g' )
217+ . attr ( 'id' , 'time-series-chart-legend' )
212218 . attr ( 'class' , 'legend-group' )
213219 . attr ( 'transform' , `translate(${ this . _margin . left } , ${ this . _legendGroupTop } )` ) ;
214220
@@ -314,6 +320,34 @@ export class LineChartService {
314320 . nice ( ) ;
315321 }
316322
323+ private calculateLegendDimensions ( ) : void {
324+ let maximumLabelWidth : number = 1 ;
325+ let labels = Object . keys ( this . legendDataMap ) ;
326+
327+ d3Select ( 'g#time-series-chart-legend' )
328+ . append ( 'g' )
329+ . attr ( 'id' , 'renderToCalculateMaxWidth' )
330+ . selectAll ( '.renderToCalculateMaxWidth' )
331+ . data ( labels )
332+ . enter ( )
333+ . append ( 'text' )
334+ . attr ( 'class' , 'legend-text' )
335+ . text ( datum => this . legendDataMap [ datum ] . text )
336+ . each ( ( datum , index , groups ) => {
337+ Array . from ( groups ) . forEach ( ( text ) => {
338+ if ( text ) {
339+ maximumLabelWidth = Math . max ( maximumLabelWidth , text . getBoundingClientRect ( ) . width )
340+ }
341+ } ) ;
342+ } ) ;
343+
344+ d3Select ( 'g#renderToCalculateMaxWidth' ) . remove ( ) ;
345+
346+ this . _legendGroupColumnWidth = maximumLabelWidth + ChartCommons . COLOR_PREVIEW_SIZE + 30 ;
347+ this . _legendGroupColumns = Math . floor ( this . _width / this . _legendGroupColumnWidth ) ;
348+ this . _legendGroupHeight = Math . ceil ( labels . length / this . _legendGroupColumns ) * ChartCommons . LABEL_HEIGHT + 30 ;
349+ }
350+
317351 private getMaxValue ( data : TimeSeries [ ] ) : number {
318352 return d3Max ( data , ( dataItem : TimeSeries ) => {
319353 return d3Max ( dataItem . values , ( point : TimeSeriesPoint ) => {
@@ -825,29 +859,36 @@ export class LineChartService {
825859 . style ( 'opacity' , 0 )
826860 . remove ( )
827861 )
828- . attr ( "transform" , ( datum , index ) => this . position ( index ) )
862+ . attr ( "transform" , ( datum , index ) => this . getPosition ( index ) )
829863 . on ( 'click' , ( datum ) => this . onMouseClick ( datum , incomingData ) ) ;
830864 }
831865
832- private position ( index : number ) : string {
833- const columns = 3 ;
834- const columnWidth = 550 ;
835- const xMargin = 10 ;
836- const yMargin = 10 ;
837-
838- const x = index % columns * columnWidth + xMargin ;
839- const y = Math . floor ( index / columns ) * ChartCommons . LABEL_HEIGHT + yMargin ;
866+ private getPosition ( index : number ) : string {
867+ const x = index % this . _legendGroupColumns * this . _legendGroupColumnWidth ;
868+ const y = Math . floor ( index / this . _legendGroupColumns ) * ChartCommons . LABEL_HEIGHT + 12 ;
840869
841870 return "translate(" + x + "," + y + ")" ;
842871 }
843872
844873 private onMouseClick ( labelKey : string , incomingData : EventResultDataDTO ) : void {
845- if ( d3Event . metaKey ) {
846- this . legendDataMap [ labelKey ] . show ? this . legendDataMap [ labelKey ] . show = false : this . legendDataMap [ labelKey ] . show = true ;
874+ if ( d3Event . metaKey || d3Event . ctrlKey ) {
875+ this . legendDataMap [ labelKey ] . show = ! this . legendDataMap [ labelKey ] . show ;
847876 } else {
848- Object . keys ( this . legendDataMap ) . forEach ( ( legend ) => {
849- this . legendDataMap [ legend ] . show = legend === labelKey ;
850- } ) ;
877+ if ( labelKey == this . focusedLegendEntry ) {
878+ Object . keys ( this . legendDataMap ) . forEach ( ( legend ) => {
879+ this . legendDataMap [ legend ] . show = true ;
880+ } ) ;
881+ this . focusedLegendEntry = "" ;
882+ } else {
883+ Object . keys ( this . legendDataMap ) . forEach ( ( legend ) => {
884+ if ( legend === labelKey ) {
885+ this . legendDataMap [ legend ] . show = true ;
886+ this . focusedLegendEntry = legend ;
887+ } else {
888+ this . legendDataMap [ legend ] . show = false ;
889+ }
890+ } ) ;
891+ }
851892 }
852893 this . drawLineChart ( incomingData ) ;
853894 }
0 commit comments