@@ -330,17 +330,16 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", {
330330 }
331331
332332
333+ // default "InRange"; promote to "GuideSet" on match so a later guide set can't clobber it
334+ plotData [ 'ReferenceRangeSeries' ] = "InRange" ;
333335 Ext4 . Object . each ( this . guideSetDataMap , function ( guideSetId , guideSetData ) {
334- // for truncating out of range guideset data find first index of plotDate ending at guideset.trainingEnd
335- if ( plotData . guideSetId === guideSetId && plotData . inGuideSetTrainingRange && guideSetData . TrainingEnd <= this . startDate ) {
336+ // guideSetId (map key) is a String; plotData.guideSetId a Number - parse for ===
337+ const guideSetIdInt = parseInt ( guideSetId , 10 ) ;
338+ if ( plotData . guideSetId === guideSetIdInt && plotData . inGuideSetTrainingRange && guideSetData . TrainingEnd <= this . startDate ) {
336339 this . filterPoints [ frag ] [ plotData . MetricId ] [ 'filterPointsFirstIndex' ] = j + 1 ;
337- // ReferenceRangeSeries is used to separate series
338340 plotData [ 'ReferenceRangeSeries' ] = "GuideSet" ;
341+ return false ; // stop once the matching guide set is found
339342 }
340- else {
341- plotData [ 'ReferenceRangeSeries' ] = "InRange" ;
342- }
343-
344343 } , this ) ;
345344
346345 // for truncating out of range guideset data find last index of plotData starting from this.startDate
@@ -365,20 +364,25 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", {
365364 if ( this . showExpRunRange && this . filterPoints ) {
366365
367366 for ( let i = 0 ; i < plotDataRows . length ; i ++ ) {
368- Ext4 . Object . each ( this . filterPoints [ plotDataRows [ i ] . SeriesLabel ] , function ( metricId , filterPointsData ) {
367+ const seriesPoints = this . filterPoints && this . filterPoints [ plotDataRows [ i ] . SeriesLabel ] ;
368+ if ( ! seriesPoints ) {
369+ continue ;
370+ }
371+ Ext4 . Object . each ( seriesPoints , function ( metricId , filterPointsData ) {
369372 // no need to filter if less than 6 data points are present between reference end of guideset and startdate
370373 if ( filterPointsData [ 'filterPointsFirstIndex' ] && filterPointsData [ 'filterPointsLastIndex' ] ) {
371374 if ( filterPointsData [ 'filterPointsLastIndex' ] - filterPointsData [ 'filterPointsFirstIndex' ] < 6 ) {
372- this . filterQCPoints = false ;
373- // set the startDate field = acquired time of the 1st point of 5 points before the experiment run range
374-
375- this . getStartDateField ( ) . setValue ( this . formatDate ( plotDataRows [ i ] . data [ filterPointsData [ 'filterPointsFirstIndex' ] ] . AcquiredTime ) ) ;
375+ // Fewer than 6 out-of-range points for this series/metric, so there is nothing to truncate
376+ // for it. Flag only this entry rather than clearing the global this.filterQCPoints, so that
377+ // other series still truncate and the separator / guide-set line break still render.
378+ filterPointsData [ 'skipTruncation' ] = true ;
379+ // set the startDate field = acquired time of the point right before the experiment run range
380+ this . setStartDateFromFilterIndex ( plotDataRows [ i ] , filterPointsData [ 'filterPointsFirstIndex' ] ) ;
376381 }
377382 else { // skip 5 points
378383 filterPointsData [ 'filterPointsLastIndex' ] = filterPointsData [ 'filterPointsLastIndex' ] - 6 ;
379- // set the startDate field = acquired time of the 1st point of 5 points before the experiment run range
380- // adding 1 as the point is right after filter last index
381- this . getStartDateField ( ) . setValue ( this . formatDate ( plotDataRows [ i ] . data [ filterPointsData [ 'filterPointsLastIndex' ] + 1 ] . AcquiredTime ) ) ;
384+ // set the startDate field = acquired time of the point right after the new filter last index
385+ this . setStartDateFromFilterIndex ( plotDataRows [ i ] , filterPointsData [ 'filterPointsLastIndex' ] + 1 ) ;
382386 }
383387 }
384388 } , this ) ;
@@ -389,6 +393,37 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", {
389393 this . renderPlots ( ) ;
390394 } ,
391395
396+ // filterPoints indices include injected 'missing' entries, but AcquiredTime only exists on raw
397+ // plotDataRow.data - translate to raw-space by counting non-missing entries, and guard the lookup.
398+ setStartDateFromFilterIndex : function ( plotDataRow , fragIndex ) {
399+ if ( ! plotDataRow || fragIndex == null ) {
400+ return ;
401+ }
402+ const fragData = this . fragmentPlotData [ plotDataRow . SeriesLabel ] && this . fragmentPlotData [ plotDataRow . SeriesLabel ] . data ;
403+ if ( ! fragData || fragData . length === 0 ) {
404+ return ;
405+ }
406+ // back up to the nearest non-missing entry at or before the index
407+ let idx = Math . min ( fragIndex , fragData . length - 1 ) ;
408+ while ( idx >= 0 && fragData [ idx ] && fragData [ idx ] . type === 'missing' ) {
409+ idx -- ;
410+ }
411+ if ( idx < 0 ) {
412+ return ;
413+ }
414+ let rawIndex = 0 ; // count of non-missing entries before idx
415+
416+ for ( let k = 0 ; k < idx ; k ++ ) {
417+ if ( ! fragData [ k ] || fragData [ k ] . type !== 'missing' ) {
418+ rawIndex ++ ;
419+ }
420+ }
421+ const rawPoint = plotDataRow . data [ rawIndex ] ;
422+ if ( rawPoint && rawPoint . AcquiredTime ) {
423+ this . getStartDateField ( ) . setValue ( this . formatDate ( rawPoint . AcquiredTime ) ) ;
424+ }
425+ } ,
426+
392427 renderPlots : function ( ) {
393428 if ( this . filterQCPoints ) {
394429 this . truncateOutOfRangeQCPoints ( ) ;
@@ -432,25 +467,27 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", {
432467
433468 truncateOutOfRangeQCPoints : function ( ) {
434469 Ext4 . Object . each ( this . fragmentPlotData , function ( label , fragmentData ) {
435- // traverse plotData backwards from firstIndex to lastIndex and
436- // remove them from the array
437- if ( this . filterQCPoints && this . filterPoints ) {
438-
439- // when we're plotting two different metrics at the same time, then we
440- // have repeated dates (from oldest to newest for metric 1, and then oldest to newest for metric 2, all in the same array).
441- // so, removing the array elements from the back
442- const filterPointsReversed = Object . keys ( this . filterPoints [ label ] ) . reverse ( ) ;
443- const lab = label ;
444-
445- filterPointsReversed . forEach ( metricId => {
446- let firstIndex = this . filterPoints [ lab ] [ metricId ] [ 'filterPointsFirstIndex' ] ;
447- let lastIndex = this . filterPoints [ lab ] [ metricId ] [ 'filterPointsLastIndex' ] ;
470+ if ( this . filterQCPoints && this . filterPoints && this . filterPoints [ label ] ) {
471+
472+ // Points are date-sorted with both metrics interleaved, so the out-of-range block (guide set
473+ // training end -> start date) is one contiguous range spanning both metrics. Splicing the
474+ // per-metric ranges separately would overlap and corrupt indices, so combine them: start after
475+ // the last training point of any metric, end at the last "first in-range" point of any metric.
476+ let firstIndex , lastIndex ;
477+ Ext4 . Object . each ( this . filterPoints [ label ] , function ( metricId , range ) {
478+ if ( range [ 'skipTruncation' ] || range [ 'filterPointsFirstIndex' ] === undefined
479+ || range [ 'filterPointsLastIndex' ] === undefined ) {
480+ return ;
481+ }
482+ firstIndex = firstIndex === undefined ? range [ 'filterPointsFirstIndex' ] : Math . max ( firstIndex , range [ 'filterPointsFirstIndex' ] ) ;
483+ lastIndex = lastIndex === undefined ? range [ 'filterPointsLastIndex' ] : Math . max ( lastIndex , range [ 'filterPointsLastIndex' ] ) ;
484+ } , this ) ;
448485
486+ if ( firstIndex !== undefined && lastIndex !== undefined ) {
449487 for ( let i = lastIndex ; i >= firstIndex ; i -- ) {
450488 fragmentData . data . splice ( i , 1 ) ;
451489 }
452- } ) ;
453-
490+ }
454491 }
455492 } , this ) ;
456493 } ,
0 commit comments