@@ -18,6 +18,7 @@ class WorkItemAgeRenderer extends Renderer {
1818 super ( filteredData ) ;
1919 this . states = states . filter ( ( d ) => d !== 'delivered' ) ;
2020 this . data = this . groupData ( filteredData ) ;
21+ this . groupedData = this . groupData ( filteredData ) ;
2122 this . workTicketsURL = workTicketsURL ;
2223 this . chartType = 'WORK_ITEM_AGE' ;
2324 }
@@ -44,6 +45,7 @@ class WorkItemAgeRenderer extends Renderer {
4445 this . drawSvg ( graphElementSelector ) ;
4546 this . drawAxes ( ) ;
4647 this . drawArea ( ) ;
48+ this . drawPercentileLines ( this . data , this . y ) ;
4749 }
4850
4951 drawSvg ( graphElementSelector ) {
@@ -70,7 +72,7 @@ class WorkItemAgeRenderer extends Renderer {
7072 // Draw dots
7173 this . svg
7274 . selectAll ( '.dot' )
73- . data ( this . data )
75+ . data ( this . groupedData )
7476 . enter ( )
7577 . append ( 'circle' )
7678 . attr ( 'class' , 'dot' )
@@ -85,7 +87,7 @@ class WorkItemAgeRenderer extends Renderer {
8587 // Add numbers inside the dots
8688 this . svg
8789 . selectAll ( '.dot-label' )
88- . data ( this . data )
90+ . data ( this . groupedData )
8991 . enter ( )
9092 . append ( 'text' )
9193 . attr ( 'class' , 'dot-label' )
@@ -101,7 +103,7 @@ class WorkItemAgeRenderer extends Renderer {
101103 }
102104
103105 computeDotPositions ( ) {
104- const groupedData = d3 . group ( this . data , ( d ) => d . currentState ) ;
106+ const groupedData = d3 . group ( this . groupedData , ( d ) => d . currentState ) ;
105107
106108 // Generate x positions for dots within each state
107109 const stateWidth = this . x . bandwidth ( ) ;
@@ -139,7 +141,7 @@ class WorkItemAgeRenderer extends Renderer {
139141
140142 updateChartArea ( ) {
141143 this . drawYAxis ( this . gy , this . y ) ;
142- this . computeDotPositions ( ) ;
144+ // this.computeDotPositions();
143145 this . svg
144146 . selectAll ( `.dot` )
145147 . attr ( 'cx' , ( d ) => d . xJitter )
@@ -148,6 +150,8 @@ class WorkItemAgeRenderer extends Renderer {
148150 . selectAll ( `.dot-label` )
149151 . attr ( 'x' , ( d ) => d . xJitter )
150152 . attr ( 'y' , ( d ) => this . y ( d . age ) ) ;
153+ this . displayObservationMarkers ( this . observations ) ;
154+ this . drawPercentileLines ( this . data , this . y ) ;
151155 }
152156
153157 drawAxes ( ) {
@@ -164,12 +168,12 @@ class WorkItemAgeRenderer extends Renderer {
164168 if ( this . timeScale === 'logarithmic' ) {
165169 this . y = d3
166170 . scaleLog ( )
167- . domain ( [ 1 , d3 . max ( this . data , ( d ) => d . age ) ] )
171+ . domain ( [ 1 , d3 . max ( this . groupedData , ( d ) => d . age ) ] )
168172 . range ( [ this . height , 0 ] ) ;
169173 } else if ( this . timeScale === 'linear' ) {
170174 this . y = d3
171175 . scaleLinear ( )
172- . domain ( [ 0 , d3 . max ( this . data , ( d ) => d . age ) ] )
176+ . domain ( [ 0 , d3 . max ( this . groupedData , ( d ) => d . age ) ] )
173177 . range ( [ this . height , 0 ] ) ;
174178 }
175179 }
@@ -282,6 +286,7 @@ class WorkItemAgeRenderer extends Renderer {
282286
283287 setupObservationLogging ( observations ) {
284288 if ( observations ?. data ?. length > 0 ) {
289+ this . observations = observations ;
285290 this . displayObservationMarkers ( observations ) ;
286291 }
287292 }
@@ -302,7 +307,7 @@ class WorkItemAgeRenderer extends Renderer {
302307 this . svg
303308 . selectAll ( 'ring' )
304309 . data (
305- this . data . filter ( ( d ) =>
310+ this . groupedData . filter ( ( d ) =>
306311 this . observations ?. data ?. some (
307312 ( o ) => d . items . find ( ( i ) => i . ticketId === o . work_item . toString ( ) ) && o . chart_type === this . chartType
308313 )
@@ -318,6 +323,69 @@ class WorkItemAgeRenderer extends Renderer {
318323 . attr ( 'stroke' , 'black' )
319324 . attr ( 'stroke-width' , '2px' ) ;
320325 }
326+
327+ //region Percentile lines rendering
328+
329+ computePercentileLine ( data , percent ) {
330+ const percentileIndex = Math . floor ( data . length * percent ) ;
331+ return data [ percentileIndex ] ?. age ;
332+ }
333+
334+ drawPercentileLines ( data , y ) {
335+ console . log ( 'drawPercentileLines' ) ;
336+ const dataSortedByAge = [ ...data ] . sort ( ( a , b ) => a . age - b . age ) ;
337+ console . log ( 'dataSortedByAge' ) ;
338+ console . table ( dataSortedByAge ) ;
339+ const percentile1 = this . computePercentileLine ( dataSortedByAge , 0.5 ) ;
340+ const percentile2 = this . computePercentileLine ( dataSortedByAge , 0.75 ) ;
341+ const percentile3 = this . computePercentileLine ( dataSortedByAge , 0.85 ) ;
342+
343+ percentile1 && this . drawHorizontalLine ( y , percentile1 , 'green' , 'p1' , '50%' ) ;
344+ percentile1 && this . drawHorizontalLine ( y , percentile2 , 'orange' , 'p2' , '75%' ) ;
345+ percentile1 && this . drawHorizontalLine ( y , percentile3 , 'red' , 'p3' , '85%' ) ;
346+ }
347+
348+ drawHorizontalLine ( yScale , yValue , color , id , text = '' ) {
349+ let lineEl = this . svg . select ( '#line-' + id ) ;
350+ let textEl = this . svg . select ( '#text-' + id ) ;
351+
352+ if ( lineEl . empty ( ) ) {
353+ lineEl = this . svg
354+ . append ( 'line' )
355+ . attr ( 'x1' , 0 )
356+ . attr ( 'x2' , this . width )
357+ . attr ( 'id' , 'line-' + id )
358+ . attr ( 'class' , 'average-line' )
359+ . attr ( 'stroke-width' , 3 )
360+ . attr ( 'stroke-dasharray' , '7' ) ;
361+ textEl = this . svg
362+ . append ( 'text' )
363+ . attr ( 'text-anchor' , 'start' )
364+ . attr ( 'id' , 'text-' + id )
365+ . style ( 'font-size' , '12px' ) ;
366+ }
367+ lineEl . attr ( 'y1' , yScale ( yValue ) ) . attr ( 'y2' , yScale ( yValue ) ) . attr ( 'stroke' , color ) ;
368+ if ( text ) {
369+ textEl
370+ . text ( text )
371+ . attr ( 'fill' , color )
372+ . attr ( 'y' , yScale ( yValue ) - 4 ) ;
373+ // Measure text width
374+ const textWidth = this . #getTextWidth( text , '12px' ) ;
375+ const adjustedX = this . width - textWidth ;
376+ textEl . attr ( 'x' , adjustedX ) ;
377+ }
378+ }
379+
380+ #getTextWidth( text , fontSize = '12px' , fontFamily = 'Arial' ) {
381+ const canvas = document . createElement ( 'canvas' ) ;
382+ const context = canvas . getContext ( '2d' ) ;
383+ context . font = `${ fontSize } ${ fontFamily } ` ;
384+ const width = context . measureText ( text ) . width ;
385+ return width ;
386+ }
387+
388+ //endregion
321389}
322390
323391export default WorkItemAgeRenderer ;
0 commit comments