From c20b4c2497e7c81a2745168ade532caee45c8926 Mon Sep 17 00:00:00 2001 From: Ankur Juneja Date: Wed, 22 May 2024 07:13:39 -0700 Subject: [PATCH] Issue 49975: Lower and Upper bound lines are displayed incorrectly on qc plots (#5515) --- core/webapp/vis/src/plot.js | 201 ++++++++++++++++++++++++----------- core/webapp/vis/src/utils.js | 8 ++ 2 files changed, 149 insertions(+), 60 deletions(-) diff --git a/core/webapp/vis/src/plot.js b/core/webapp/vis/src/plot.js index 25e018f38ec..aa823f314b7 100644 --- a/core/webapp/vis/src/plot.js +++ b/core/webapp/vis/src/plot.js @@ -1814,25 +1814,25 @@ boxPlot.render(); && config.properties.valueConversion === 'percentDeviation') { maxValue = mean * LABKEY.vis.Stat.MOVING_RANGE_UPPER_LIMIT_WEIGHT; minValue = mean; - } else if (config.qcPlotType === LABKEY.vis.TrendingLinePlotType.LeveyJennings - && config.properties.valueConversion === 'standardDeviation') { + } else if (config.qcPlotType === LABKEY.vis.TrendingLinePlotType.LeveyJennings) { if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.StandardDeviation) { maxValue = config.properties.upperBound + cushion; minValue = config.properties.lowerBound - cushion; } + else if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.MeanDeviation) { + maxValue = mean + config.properties.upperBound + cushion; + minValue = mean + config.properties.lowerBound - cushion; + if (config.properties.valueConversion === 'percentDeviation') { + // Multiplying by 10 to get the yAxisDomain[min & max] to be in the same range as the percent range + maxValue = mean + config.properties.upperBound * 10 + cushion; + minValue = mean + config.properties.lowerBound * 10 - cushion; + } + } else { maxValue = 3.2; minValue = -3.2; } } - else if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.Absolute) { - maxValue = config.properties.upperBound + cushion; - minValue = config.properties.lowerBound - cushion; - } - else if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.MeanDeviation) { - maxValue = mean + config.properties.upperBound + cushion; - minValue = mean + config.properties.lowerBound - cushion; - } else if (!config.properties.combined && stddev) { maxValue = mean + ((config.properties.upperBound + cushion) * stddev); minValue = mean + ((config.properties.lowerBound - cushion) * stddev); @@ -1940,6 +1940,58 @@ boxPlot.render(); // Handle value conversions convertValues(config.properties.valueConversion); + if (config.qcPlotType === LABKEY.vis.TrendingLinePlotType.LeveyJennings) { + meanStdDevData[index] = row; + if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.Absolute) { + row.upperBound = config.properties.upperBound; + row.lowerBound = config.properties.lowerBound; + } + else if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.MeanDeviation) { + row.upperBound = config.properties.upperBound + row[meanProp]; + row.lowerBound = config.properties.lowerBound + row[meanProp]; + } + + if (config.properties.valueConversion === 'percentDeviation') { + row.upperBound = convertToPercentDeviation(row.upperBound, row[meanProp]); + row.lowerBound = convertToPercentDeviation(row.lowerBound, row[meanProp]); + if (config.legendData && config.legendData.length > 0) { + for (let i = 0; i < config.legendData.length; i++) { + let legendRow = config.legendData[i]; + if (legendRow.text.indexOf("% of Mean") === -1) { + if (legendRow.text.indexOf("Upper") > -1) { + legendRow.text += " (" + row.upperBound + "% of Mean)"; + } else if (legendRow.text.indexOf("Lower") > -1) { + legendRow.text += " (" + row.lowerBound + "% of Mean)"; + } + } + } + } + } + else if (config.properties.valueConversion === 'standardDeviation') { + row.upperBound = convertToStandardDeviation(row.upperBound, row[meanProp], row[sdProp]); + row.lowerBound = convertToStandardDeviation(row.lowerBound, row[meanProp], row[sdProp]); + if (config.legendData && config.legendData.length > 0) { + for (let i = 0; i < config.legendData.length; i++) { + let legendRow = config.legendData[i]; + if (legendRow.text.indexOf("SD") === -1) { + if (legendRow.text.indexOf("Upper") > -1) { + legendRow.text += " (" + row.upperBound + " SD)"; + } else if (legendRow.text.indexOf("Lower") > -1) { + legendRow.text += " (" + row.lowerBound + " SD)"; + } + } + } + } + } + if (config.properties.yAxisDomain) { + if (config.properties.yAxisDomain[0] > row.lowerBound) { + config.properties.yAxisDomain[0] = row.lowerBound - 0.2; + } + if (config.properties.yAxisDomain[1] < row.upperBound) { + config.properties.yAxisDomain[1] = row.upperBound + 0.2; + } + } + } if (config.properties.valueConversion === 'percentDeviation') { row[sdProp] = convertToPercentDeviation(row[sdProp], row[meanProp]); row[meanProp] = 100; @@ -2060,12 +2112,6 @@ boxPlot.render(); tickLabelMap[index] = row[config.properties.xTickLabel]; row.seqValue = index; - - if (config.qcPlotType === LABKEY.vis.TrendingLinePlotType.LeveyJennings) { - meanStdDevData[index] = row; - row.upperBound = config.properties.upperBound; - row.lowerBound = config.properties.lowerBound; - } } // min x-axis tick length is 10 by default @@ -2212,69 +2258,85 @@ boxPlot.render(); } else { var barWidth = Math.max(config.width / config.data[config.data.length-1].seqValue / 4, 3); - + // the below if-else sections add the mean/SD/error bars to the plots if (config.qcPlotType === LABKEY.vis.TrendingLinePlotType.LeveyJennings) { config.layers = []; - if (config.properties.mean !== undefined) { - if (config.properties.stdDev !== undefined && - config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.StandardDeviation && - config.properties.showBoundLines) { + if (config.properties.stdDev !== undefined && + config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.StandardDeviation && + config.properties.showBoundLines) { + config.layers.push(new LABKEY.vis.Layer({ + geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: LABKEY.vis.PlotProperties.Color.Outlier, dashed: true, width: barWidth, topOnly: true}), + data: meanStdDevData, + aes: { + error: function (row) { + return row[config.properties.stdDev] * config.properties.upperBound; + }, + yLeft: config.properties.combined ? config.properties.stdDev : config.properties.mean + } + })); + config.layers.push(new LABKEY.vis.Layer({ + geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: LABKEY.vis.PlotProperties.Color.Outlier, dashed: true, width: barWidth, topOnly: true}), + data: meanStdDevData, + aes: { + error: function (row) { + return row[config.properties.stdDev] * config.properties.lowerBound; + }, + yLeft: config.properties.combined ? config.properties.stdDev : config.properties.mean + } + })); + if (config.properties.hideSDLines !== true) { config.layers.push(new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: 'red', dashed: true, width: barWidth, topOnly: true}), + geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: LABKEY.vis.PlotProperties.TwoSD, dashed: true, width: barWidth}), data: meanStdDevData, aes: { - error: function (row) { - return row[config.properties.stdDev] * config.properties.upperBound; - }, + error: function(row){return row[config.properties.stdDev] * 2;}, yLeft: config.properties.mean } })); + config.layers.push(new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: 'red', dashed: true, width: barWidth, topOnly: true}), + geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: LABKEY.vis.PlotProperties.Color.OneSD, dashed: true, width: barWidth}), data: meanStdDevData, aes: { - error: function (row) { - return row[config.properties.stdDev] * config.properties.lowerBound; - }, + error: function(row){return row[config.properties.stdDev];}, yLeft: config.properties.mean } })); - if (config.properties.hideSDLines !== true) { - config.layers.push(new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: 'blue', dashed: true, width: barWidth}), - data: meanStdDevData, - aes: { - error: function(row){return row[config.properties.stdDev] * 2;}, - yLeft: config.properties.mean - } - })); - - config.layers.push(new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: 'green', dashed: true, width: barWidth}), - data: meanStdDevData, - aes: { - error: function(row){return row[config.properties.stdDev];}, - yLeft: config.properties.mean - } - })); - } } + } + + // add the mean line + if (config.properties.mean !== undefined) { config.layers.push(new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({size: 1, color: 'darkgrey', width: barWidth, topOnly: true}), + geom: new LABKEY.vis.Geom.ErrorBar({ + size: 1, + color: LABKEY.vis.PlotProperties.Color.Mean, + width: barWidth, + topOnly: true + }), data: meanStdDevData, aes: { - error: function(row){return 0;}, + error: function (row) { + return 0; + }, yLeft: config.properties.mean } })); } - if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.Absolute) { + // add the upper and lower bound lines for absolute bound types + if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.Absolute && !config.properties.combined) { if (config.properties.lowerBound) { const lowerBoundLayer = new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({ size: 1, color: 'red', width: barWidth, dashed: true }), + geom: new LABKEY.vis.Geom.ErrorBar({ + size: 1, + color: LABKEY.vis.PlotProperties.Color.Outlier, + width: barWidth, + dashed: true, + topOnly: true + }), data: meanStdDevData, aes: { error: function (row) { @@ -2287,7 +2349,13 @@ boxPlot.render(); } if (config.properties.upperBound) { const upperBoundLayer = new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({ size: 1, color: 'red', width: barWidth, dashed: true }), + geom: new LABKEY.vis.Geom.ErrorBar({ + size: 1, + color: LABKEY.vis.PlotProperties.Color.Outlier, + width: barWidth, + dashed: true, + topOnly: true + }), data: meanStdDevData, aes: { error: function (row) { @@ -2300,14 +2368,21 @@ boxPlot.render(); } } - if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.MeanDeviation) { + // add the upper and lower bound lines for mean deviation bound types + if (config.properties.boundType === LABKEY.vis.PlotProperties.BoundType.MeanDeviation && !config.properties.combined) { if (config.properties.lowerBound) { const lowerBoundLayer = new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({ size: 1, color: 'red', width: barWidth, dashed: true }), + geom: new LABKEY.vis.Geom.ErrorBar({ + size: 1, + color: LABKEY.vis.PlotProperties.Color.Outlier, + width: barWidth, + dashed: true, + topOnly: true + }), data: meanStdDevData, aes: { error: function (row) { - return row[config.properties.mean]; + return 0; }, yLeft: 'lowerBound' } @@ -2316,11 +2391,17 @@ boxPlot.render(); } if (config.properties.upperBound) { const upperBoundLayer = new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ErrorBar({ size: 1, color: 'red', width: barWidth, dashed: true }), + geom: new LABKEY.vis.Geom.ErrorBar({ + size: 1, + color: LABKEY.vis.PlotProperties.Color.Outlier, + width: barWidth, + dashed: true, + topOnly: true + }), data: meanStdDevData, aes: { error: function (row) { - return row[config.properties.mean]; + return 0; }, yLeft: 'upperBound' } @@ -2331,7 +2412,7 @@ boxPlot.render(); } else if (config.qcPlotType === LABKEY.vis.TrendingLinePlotType.CUSUM) { var range = new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ControlRange({size: 1, color: 'red', dashed: true, width: barWidth}), + geom: new LABKEY.vis.Geom.ControlRange({size: 1, color: LABKEY.vis.PlotProperties.Color.Outlier, dashed: true, width: barWidth}), data: config.data, aes: { upper: function(){return LABKEY.vis.Stat.CUSUM_CONTROL_LIMIT;}, @@ -2347,7 +2428,7 @@ boxPlot.render(); } else { var range = new LABKEY.vis.Layer({ - geom: new LABKEY.vis.Geom.ControlRange({size: 1, color: 'red', dashed: true, width: barWidth}), + geom: new LABKEY.vis.Geom.ControlRange({size: 1, color: LABKEY.vis.PlotProperties.Color.Outlier, dashed: true, width: barWidth}), data: config.data, aes: { upper: function(row){return row[config.properties.meanMR] * LABKEY.vis.Stat.MOVING_RANGE_UPPER_LIMIT_WEIGHT;}, diff --git a/core/webapp/vis/src/utils.js b/core/webapp/vis/src/utils.js index 6b89dda6025..a23ee520d5b 100644 --- a/core/webapp/vis/src/utils.js +++ b/core/webapp/vis/src/utils.js @@ -27,6 +27,14 @@ if (!LABKEY.vis.PlotProperties) { StandardDeviation: 'stddev' } } + if (!LABKEY.vis.PlotProperties.Color) { + LABKEY.vis.PlotProperties.Color = { + Outlier: 'red', + Mean: 'darkgrey', + OneSD: 'green', + TwoSD: 'blue', + } + } } LABKEY.vis.makeLine = function(x1, y1, x2, y2){