diff --git a/api/src/org/labkey/api/data/JsonWriter.java b/api/src/org/labkey/api/data/JsonWriter.java index fb203055ed5..86ac67c5f7b 100644 --- a/api/src/org/labkey/api/data/JsonWriter.java +++ b/api/src/org/labkey/api/data/JsonWriter.java @@ -180,6 +180,7 @@ public static Map getMetaData(DisplayColumn dc, FieldKey fieldKe props.put("wrappedColumnName", cinfo == null ? null : cinfo.getWrappedColumnName()); props.put("valueExpression", cinfo == null ? null : cinfo.getValueExpression()); + props.put("displayWidth", cinfo == null ? null : cinfo.getDisplayWidth()); ColumnInfo displayField = dc.getDisplayColumnInfo(); if (displayField != null && displayField != cinfo) diff --git a/api/src/org/labkey/api/data/statistics/StatsService.java b/api/src/org/labkey/api/data/statistics/StatsService.java index 19e5ca9ac6c..ccb00577b37 100644 --- a/api/src/org/labkey/api/data/statistics/StatsService.java +++ b/api/src/org/labkey/api/data/statistics/StatsService.java @@ -18,8 +18,10 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.services.ServiceRegistry; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.List; /** * Created by klum on 1/14/14. @@ -38,22 +40,27 @@ static void setInstance(StatsService impl) enum CurveFitType { - THREE_PARAMETER("Three Parameter", "3pl"), - FOUR_PARAMETER("Four Parameter", "4pl"), - FIVE_PARAMETER("Five Parameter", "5pl"), - THREE_PARAMETER_ALT("3 Parameter", "3param"), - FOUR_PARAMETER_SIMPLEX("4 Parameter", "4param"), - POLYNOMIAL("Polynomial", "poly"), - LINEAR("Linear", "linear"), - NONE("None", "none"); + // TODO see updated labels for these equations in genericChartHelper.js TRENDLINE_OPTIONS + // we should update the labels here as well at some point (but this would like need to include an upgrade script for saved chart configs) + THREE_PARAMETER("Three Parameter", "3pl", Arrays.asList("min", "max", "inflection")), + FOUR_PARAMETER("Four Parameter", "4pl", Arrays.asList("min", "max", "slope", "inflection")), + FIVE_PARAMETER("Five Parameter", "5pl", Arrays.asList("min", "max", "slope", "inflection", "asymmetry")), + THREE_PARAMETER_ALT("3 Parameter", "3param", Arrays.asList("min", "max", "inflection")), + FOUR_PARAMETER_SIMPLEX("4 Parameter", "4param", Arrays.asList("min", "max", "slope", "inflection")), + FIVE_PARAMETER_ALT("5 Parameter", "5param", Arrays.asList("min", "max", "slope", "inflection", "asymmetry")), + POLYNOMIAL("Polynomial", "poly", Arrays.asList("coefficients")), + LINEAR("Linear", "linear", Arrays.asList("slope", "intercept")), + NONE("None", "none", Arrays.asList()); private final String _label; private final String _colSuffix; + private final List _parameterNames = new ArrayList<>(); - CurveFitType(String label, String colSuffix) + CurveFitType(String label, String colSuffix, List parameterNames) { _label = label; _colSuffix = colSuffix; + _parameterNames.addAll(parameterNames); } // Consider : moving the col suffix portion of this back into assays... @@ -68,6 +75,11 @@ public String getLabel() return _label; } + public List getParameterNames() + { + return _parameterNames; + } + @Override public String toString() { diff --git a/core/src/org/labkey/core/statistics/FiveParameterCurveFit.java b/core/src/org/labkey/core/statistics/FiveParameterCurveFit.java new file mode 100644 index 00000000000..5647f129429 --- /dev/null +++ b/core/src/org/labkey/core/statistics/FiveParameterCurveFit.java @@ -0,0 +1,41 @@ +package org.labkey.core.statistics; + +import org.jetbrains.annotations.Nullable; +import org.labkey.api.data.statistics.DoublePoint; + +import static org.labkey.api.data.statistics.StatsService.CurveFitType.FIVE_PARAMETER_ALT; + +/* +* Equation: Asymmetrical Sigmoidal, 5PL (aka Richards five-parameter dose-response curve) +* Y = Bottom + (Numerator/Denominator) +* Numerator = Top - Bottom +* Denominator = (1+(2^(1/S)-1)*((EC50/X)^HillSlope))^S +*/ +public class FiveParameterCurveFit extends ParameterCurveFit +{ + public FiveParameterCurveFit(DoublePoint[] data, @Nullable Double asymptoteMin, @Nullable Double asymptoteMax) + { + super(data, FIVE_PARAMETER_ALT, asymptoteMin, asymptoteMax); + } + + @Override + public double solveForX(double y) + { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public double fitCurve(double x, SigmoidalParameters params) + { + if (params != null) + { + double topMinusBottom = params.getMax() - params.getMin(); + double exponent = 1.0 / params.getAsymmetry(); + double base = Math.pow(2.0, exponent) - 1.0; + double ec50OverXPowerHillSlope = Math.pow(params.getInflection() / x, params.getSlope()); + double denominator = Math.pow(1.0 + (base * ec50OverXPowerHillSlope), params.getAsymmetry()); + return params.getMin() + (topMinusBottom / denominator); + } + throw new IllegalArgumentException("No curve fit parameters for " + _fitType.name()); + } +} diff --git a/core/src/org/labkey/core/statistics/ParameterCurveFit.java b/core/src/org/labkey/core/statistics/ParameterCurveFit.java index f505a0a18e5..12ed7d5723b 100644 --- a/core/src/org/labkey/core/statistics/ParameterCurveFit.java +++ b/core/src/org/labkey/core/statistics/ParameterCurveFit.java @@ -228,7 +228,7 @@ public double adjustedRSquared(SigmoidalParameters parameters) { case THREE_PARAMETER, THREE_PARAMETER_ALT -> adjustedRSquared(parameters, 3); case FOUR_PARAMETER -> adjustedRSquared(parameters, 4); - case FIVE_PARAMETER -> adjustedRSquared(parameters, 5); + case FIVE_PARAMETER, FIVE_PARAMETER_ALT -> adjustedRSquared(parameters, 5); default -> throw new IllegalStateException("Unsupported curve fit type: " + _fitType.name()); }; } @@ -276,6 +276,7 @@ protected SigmoidalParameters calculateFitParameters(double minValue, double max switch (_fitType) { case FIVE_PARAMETER: + case FIVE_PARAMETER_ALT: for (double asymmetryFactor = 0; asymmetryFactor < Math.PI; asymmetryFactor += Math.PI / 30) { parameters.asymmetry = asymmetryFactor; diff --git a/core/src/org/labkey/core/statistics/StatsServiceImpl.java b/core/src/org/labkey/core/statistics/StatsServiceImpl.java index c882c9ccaa0..b991e1ea553 100644 --- a/core/src/org/labkey/core/statistics/StatsServiceImpl.java +++ b/core/src/org/labkey/core/statistics/StatsServiceImpl.java @@ -71,6 +71,8 @@ public CurveFit getCurveFit(CurveFitType type, DoublePoint[] data, @Nullable Dou return new ThreeParameterCurveFit(data, asymptoteMax); case FOUR_PARAMETER_SIMPLEX: return new FourParameterSimplex(data); + case FIVE_PARAMETER_ALT: + return new FiveParameterCurveFit(data, asymptoteMin, asymptoteMax); case THREE_PARAMETER: return new ParameterCurveFit(data, type, 0.0, asymptoteMax); case FOUR_PARAMETER: @@ -133,6 +135,7 @@ public void TestCurveFits() throws Exception v1.setResults(CurveFitType.THREE_PARAMETER, new CurveResults(3.09, .065, .065)); v1.setResults(CurveFitType.THREE_PARAMETER_ALT, new CurveResults(3.09, .065, .065)); v1.setResults(CurveFitType.FOUR_PARAMETER, new CurveResults(2.5, .031, .045)); + v1.setResults(CurveFitType.FIVE_PARAMETER_ALT, new CurveResults(2.08, .046, .054)); v1.setResults(CurveFitType.FIVE_PARAMETER, new CurveResults(2.2, .046, .054)); v1.setResults(CurveFitType.LINEAR, new CurveResults(6.8, .070, .070)); validations.add(v1); @@ -143,6 +146,7 @@ public void TestCurveFits() throws Exception v2.setResults(CurveFitType.THREE_PARAMETER, new CurveResults(2.93, .414, .414)); v2.setResults(CurveFitType.THREE_PARAMETER_ALT, new CurveResults(2.93, .414, .414)); v2.setResults(CurveFitType.FOUR_PARAMETER, new CurveResults(3.4, .403, .403)); + v2.setResults(CurveFitType.FIVE_PARAMETER_ALT, new CurveResults(2.44, .420, .420)); v2.setResults(CurveFitType.FIVE_PARAMETER, new CurveResults(3.1, .420, .420)); v2.setResults(CurveFitType.LINEAR, new CurveResults(36.8, .553, .553)); validations.add(v2); @@ -153,6 +157,7 @@ public void TestCurveFits() throws Exception v3.setResults(CurveFitType.THREE_PARAMETER, new CurveResults(5.0, .078, .078)); v3.setResults(CurveFitType.THREE_PARAMETER_ALT, new CurveResults(5.0, .078, .078)); v3.setResults(CurveFitType.FOUR_PARAMETER, new CurveResults(4.7, .048, .049)); + v3.setResults(CurveFitType.FIVE_PARAMETER_ALT, new CurveResults(4.6, .049, .051)); v3.setResults(CurveFitType.FIVE_PARAMETER, new CurveResults(4.6, .080, .082)); v3.setResults(CurveFitType.LINEAR, new CurveResults(5.9, .070, .070)); validations.add(v3); @@ -163,6 +168,7 @@ public void TestCurveFits() throws Exception v4.setResults(CurveFitType.THREE_PARAMETER, new CurveResults(4.05, .265, .265)); v4.setResults(CurveFitType.THREE_PARAMETER_ALT, new CurveResults(4.05, .265, .265)); v4.setResults(CurveFitType.FOUR_PARAMETER, new CurveResults(4.5, .226, .247)); + v4.setResults(CurveFitType.FIVE_PARAMETER_ALT, new CurveResults(3.29, .245, .251)); v4.setResults(CurveFitType.FIVE_PARAMETER, new CurveResults(3.7, .245, .262)); v4.setResults(CurveFitType.LINEAR, new CurveResults(27.5, .374, .374)); validations.add(v4); @@ -173,6 +179,7 @@ public void TestCurveFits() throws Exception v5.setResults(CurveFitType.THREE_PARAMETER, new CurveResults(7.86, .281, .281)); v5.setResults(CurveFitType.THREE_PARAMETER_ALT, new CurveResults(7.86, .281, .281)); v5.setResults(CurveFitType.FOUR_PARAMETER, new CurveResults(5, .201, .263)); + v5.setResults(CurveFitType.FIVE_PARAMETER_ALT, new CurveResults(4.105, .221, .277)); v5.setResults(CurveFitType.FIVE_PARAMETER, new CurveResults(5.1, .221, .277)); v5.setResults(CurveFitType.LINEAR, new CurveResults(38.0, .363, .363)); validations.add(v5); @@ -187,9 +194,9 @@ public void TestCurveFits() throws Exception CurveResults results = validation.getResults(fitType); // validate calculated and expected fit error and auc - assertEquals(results.getFitError(), fit.getFitError(), 0.05); - assertEquals(results.getAuc(), fit.calculateAUC(AUCType.NORMAL), 0.005); - assertEquals(results.getPositiveAuc(), fit.calculateAUC(AUCType.POSITIVE), 0.005); + assertEquals(fitType.getLabel(), results.getFitError(), fit.getFitError(), 0.05); + assertEquals(fitType.getLabel(), results.getAuc(), fit.calculateAUC(AUCType.NORMAL), 0.005); + assertEquals(fitType.getLabel(), results.getPositiveAuc(), fit.calculateAUC(AUCType.POSITIVE), 0.005); } } } @@ -328,6 +335,15 @@ public void TestCurveFitParameters() throws Exception fit.setLogXScale(true); verifySigmoidalParameters(fit, 0.67, 4.0, 2.246, 0.095, 1.466); assertEquals(0.997, fit.rSquared(fit.getParameters()), delta); + + fit = service.getCurveFit(CurveFitType.FIVE_PARAMETER_ALT, data1); + fit.setLogXScale(false); + verifySigmoidalParameters(fit, 0.67, 3.17, 9.5144, 0.096, 0.2094); + assertEquals(0.9978, fit.rSquared(fit.getParameters()), delta); + fit = service.getCurveFit(CurveFitType.FIVE_PARAMETER_ALT, data1, null, 4.0); + fit.setLogXScale(true); + verifySigmoidalParameters(fit, 0.67, 4.0, 1.7321, 0.0946, 3.0369); + assertEquals(0.9207, fit.rSquared(fit.getParameters()), delta); } @Test @@ -353,6 +369,12 @@ public void TestCurveFitParametersNAbCase() throws Exception assertEquals(3.493, fit.getFitError(), delta); assertEquals(0.990, fit.rSquared(fit.getParameters()), delta); + fit = service.getCurveFit(CurveFitType.FIVE_PARAMETER_ALT, data1); + fit.setLogXScale(true); + verifySigmoidalParameters(fit, -2.0, 93.0, -1.1106, 10.6605, 0.2094); + assertEquals(2.3816, fit.getFitError(), delta); + assertEquals(0.9954, fit.rSquared(fit.getParameters()), delta); + fit = service.getCurveFit(CurveFitType.FOUR_PARAMETER, data1); fit.setLogXScale(true); verifySigmoidalParameters(fit, -2.0, 103.0, -0.325, 6.907, 1.0); diff --git a/core/src/org/labkey/core/statistics/ThreeParameterCurveFit.java b/core/src/org/labkey/core/statistics/ThreeParameterCurveFit.java index dac795382e9..672d3339517 100644 --- a/core/src/org/labkey/core/statistics/ThreeParameterCurveFit.java +++ b/core/src/org/labkey/core/statistics/ThreeParameterCurveFit.java @@ -43,10 +43,4 @@ public double fitCurve(double x, SigmoidalParameters params) } throw new IllegalArgumentException("No curve fit parameters for " + _fitType.name()); } - - @Override - public double adjustedRSquared(SigmoidalParameters parameters) - { - return adjustedRSquared(parameters, 3); - } } diff --git a/visualization/resources/web/vis/chartWizard/chartTypeDialog/trendlineField.js b/visualization/resources/web/vis/chartWizard/chartTypeDialog/trendlineField.js index 93611aaf571..3cbbfe535ac 100644 --- a/visualization/resources/web/vis/chartWizard/chartTypeDialog/trendlineField.js +++ b/visualization/resources/web/vis/chartWizard/chartTypeDialog/trendlineField.js @@ -8,7 +8,7 @@ Ext4.define('LABKEY.vis.TrendlineField', { baseQueryKey: null, initData: null, - options: ['', 'Linear', 'Polynomial', '3 Parameter', 'Three Parameter', '4 Parameter', 'Four Parameter', 'Five Parameter'], + options: ['', 'Linear', 'Polynomial', '3 Parameter', 'Three Parameter', '4 Parameter', 'Four Parameter', 'Five Parameter', '5 Parameter'], initComponent: function() { if (this.initData == null) diff --git a/visualization/resources/web/vis/chartWizard/genericChartPanel.js b/visualization/resources/web/vis/chartWizard/genericChartPanel.js index 799812bdae0..03e7a745539 100644 --- a/visualization/resources/web/vis/chartWizard/genericChartPanel.js +++ b/visualization/resources/web/vis/chartWizard/genericChartPanel.js @@ -1023,6 +1023,7 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', { config.geomOptions.trendlineType = this.trendline.trendlineType; config.geomOptions.trendlineAsymptoteMin = this.trendline.trendlineAsymptoteMin; config.geomOptions.trendlineAsymptoteMax = this.trendline.trendlineAsymptoteMax; + config.geomOptions.trendlineParameters = this.trendline.trendlineParameters; } if (this.getCustomChartOptions) @@ -1325,6 +1326,7 @@ Ext4.define('LABKEY.ext4.GenericChartPanel', { trendlineType: chartConfig.geomOptions.trendlineType, trendlineAsymptoteMin: chartConfig.geomOptions.trendlineAsymptoteMin, trendlineAsymptoteMax: chartConfig.geomOptions.trendlineAsymptoteMax, + trendlineParameters: chartConfig.geomOptions.trendlineParameters, } } } diff --git a/visualization/resources/web/vis/genericChart/genericChartHelper.js b/visualization/resources/web/vis/genericChart/genericChartHelper.js index 2c3db024862..5fd704b0604 100644 --- a/visualization/resources/web/vis/genericChart/genericChartHelper.js +++ b/visualization/resources/web/vis/genericChart/genericChartHelper.js @@ -1263,11 +1263,12 @@ LABKEY.vis.GenericChartHelper = new function(){ '': { label: 'Point-to-Point', value: '' }, 'Linear': { label: 'Linear Regression', value: 'Linear', equation: 'y = x * slope + intercept' }, 'Polynomial': { label: 'Polynomial', value: 'Polynomial', equation: 'y = a0 + a1 * x + a2 * x^2' }, - '3 Parameter': { label: 'Nonlinear 3PL', value: '3 Parameter', showMax: true, schemaPrefix: 'assay', equation: 'y = max * abs(x/inflection)^abs(slope) / [1 + abs(x/inflection)^abs(slope)]' }, + '3 Parameter': { label: 'Nonlinear 3PL (Hill)', value: '3 Parameter', showMax: true, schemaPrefix: 'assay', equation: 'y = max * abs(x/inflection)^abs(slope) / [1 + abs(x/inflection)^abs(slope)]' }, 'Three Parameter': { label: 'Nonlinear 3PL (Alternate)', value: 'Three Parameter', showMax: true, schemaPrefix: 'assay', equation: 'y = max / [1 + (inflection - x) * slope]' }, - '4 Parameter': { label: 'Nonlinear 4PL', value: '4 Parameter', schemaPrefix: 'assay', equation: 'y = max + (min - max) / [1 + (x/inflection)^slope]' }, + '4 Parameter': { label: 'Nonlinear 4PL (Hill)', value: '4 Parameter', schemaPrefix: 'assay', equation: 'y = max + (min - max) / [1 + (x/inflection)^slope]' }, 'Four Parameter': { label: 'Nonlinear 4PL (Alternate)', value: 'Four Parameter', showMin: true, showMax: true, schemaPrefix: 'assay', equation: 'y = min + (max - min) / [1 + (inflection - x) * slope]' }, - 'Five Parameter': { label: 'Nonlinear 5PL', value: 'Five Parameter', showMin: true, showMax: true, schemaPrefix: 'assay', equation: 'y = min + (max - min) / [[1 + (inflection - x) * slope]^asymmetry]' }, + '5 Parameter': { label: 'Nonlinear 5PL (Hill/Richards)', value: '5 Parameter', showMin: true, showMax: true, schemaPrefix: 'assay', equation: 'y = min + (max - min) / (1 + (2^(1/asymmetry) - 1) * ((inflection / x)^hillSlope))^asymmetry' }, + 'Five Parameter': { label: 'Nonlinear 5PL (Alternate)', value: 'Five Parameter', showMin: true, showMax: true, schemaPrefix: 'assay', equation: 'y = min + (max - min) / [[1 + (inflection - x) * slope]^asymmetry]' }, } const generateTrendlinePathHover = function(trendline) { @@ -1319,6 +1320,7 @@ LABKEY.vis.GenericChartHelper = new function(){ logXScale: chartConfig.scales.x && chartConfig.scales.x.trans === 'log', asymptoteMin: chartConfig.geomOptions.trendlineAsymptoteMin, asymptoteMax: chartConfig.geomOptions.trendlineAsymptoteMax, + parameters: chartConfig.geomOptions.trendlineParameters, data: chartConfig.measures.series ? LABKEY.vis.groupCountData(data, generateGroupingAcc(chartConfig.measures.series.name)) : [{name: 'All', rawData: data}], @@ -1338,8 +1340,11 @@ LABKEY.vis.GenericChartHelper = new function(){ // we need at least 2 data points for curve fitting if (series.rawData.length > 1) { series.data = await _querySeriesTrendlineData(trendlineConfig, series, xName, yName); + } else { + series.error = 'Series ' + series.name + ': Not enough data points for trendline calculation.'; } } catch (e) { + series.error = e; console.error(e); } } @@ -1358,16 +1363,47 @@ LABKEY.vis.GenericChartHelper = new function(){ y: _getRowValue(row, yName, 'value'), }; }); - const xAcc = function(row) { return row.x }; + const xAcc = function(row) { + // if log scale, filter out non-positive x values for min/max calculation used for generated points + if (trendlineConfig.logXScale && row.x <= 0) { + return undefined; + } + return row.x; + }; const xMin = d3.min(points, xAcc); const xMax = d3.max(points, xAcc); + // Get the first non-null value for curveFitName in the seriesData.rawData. + // Also make sure that if we have > 1 non-null value, they are all the same. + const curveFitName = trendlineConfig.parameters; + let providedParams; + if (curveFitName) { + for (let i = 0; i < seriesData.rawData.length; i++) { + const row = seriesData.rawData[i]; + const curveFitValue = _getRowValue(row, curveFitName, 'value'); + if (curveFitValue !== null && curveFitValue !== undefined) { + if (providedParams === undefined) { + providedParams = curveFitValue; + } else if (providedParams !== curveFitValue) { + reject('Series "' + seriesData.name + '" - Inconsistent curve fit parameters in "' + curveFitName + '" variable.'); + return; + } + } + } + + if (!providedParams) { + reject('Series "' + seriesData.name + '" - No curve fit parameters found in "' + curveFitName + '" variable.'); + return; + } + } + LABKEY.Ajax.request({ url: LABKEY.ActionURL.buildURL('premium', 'calculateCurveFit.api'), method: 'POST', jsonData: { curveFitType: trendlineConfig.type, points: points, + parametersStr: providedParams, logXScale: trendlineConfig.logXScale, asymptoteMin: trendlineConfig.asymptoteMin, asymptoteMax: trendlineConfig.asymptoteMax, @@ -1379,7 +1415,7 @@ LABKEY.vis.GenericChartHelper = new function(){ resolve(response); }), failure : LABKEY.Utils.getCallbackWrapper(function(reason) { - reject(reason); + reject('Series "' + seriesData.name + '" - ' + reason.exception); }, this, true), }); }); @@ -1572,9 +1608,14 @@ LABKEY.vis.GenericChartHelper = new function(){ } else if (hasZeroes) { - message = "Some " + measureName + "-axis values are 0. Plotting all " + measureName + "-axis values as value+1."; + message = "Some " + measureName + "-axis values are 0. Plotting all " + measureName + "-axis zero values as Number.EPSILON."; var accFn = aes[measureName]; - aes[measureName] = function(row){return accFn(row) + 1}; + aes[measureName] = function(row){ + if (accFn(row) === 0) { + return Number.EPSILON; + } + return accFn(row); + }; } } @@ -1613,7 +1654,8 @@ LABKEY.vis.GenericChartHelper = new function(){ var getAllowableTypes = function(field) { var numericTypes = ['int', 'float', 'double', 'INTEGER', 'DOUBLE'], nonNumericTypes = ['string', 'date', 'boolean', 'STRING', 'TEXT', 'DATE', 'BOOLEAN'], - numericAndDateTypes = numericTypes.concat(['date','DATE']); + numericAndDateTypes = numericTypes.concat(['date','DATE']), + textTypes = ['string', 'STRING', 'TEXT']; if (field.altSelectionOnly) return []; @@ -1623,6 +1665,8 @@ LABKEY.vis.GenericChartHelper = new function(){ return nonNumericTypes; else if (field.numericOrDateOnly) return numericAndDateTypes; + else if (field.textOnly) + return textTypes; else return numericTypes.concat(nonNumericTypes); } @@ -1943,7 +1987,7 @@ LABKEY.vis.GenericChartHelper = new function(){ data = generateDataForChartType(chartConfig, chartType, geom, data); } - var validation = _validateChartConfig(chartConfig, aes, scales, measureStore); + var validation = _validateChartConfig(chartConfig, aes, scales, measureStore, trendlineData); _renderMessages(renderTo, validation.messages); if (!validation.success) return; @@ -1967,12 +2011,14 @@ LABKEY.vis.GenericChartHelper = new function(){ var _renderMessages = function(divId, messages) { if (messages && messages.length > 0) { var errorDiv = document.createElement('div'); - errorDiv.innerHTML = '

Error rendering chart:

' + messages.join('
') + '
'; + for (var i = 0; i < messages.length; i++) { + errorDiv.innerHTML += '
Error: ' + messages[i] + '
'; + } document.getElementById(divId).appendChild(errorDiv); } }; - var _validateChartConfig = function(chartConfig, aes, scales, measureStore) { + var _validateChartConfig = function(chartConfig, aes, scales, measureStore, trendlineData) { var hasNoDataMsg = validateResponseHasData(measureStore, false); if (hasNoDataMsg != null) return {success: false, messages: [hasNoDataMsg]}; @@ -2006,6 +2052,14 @@ LABKEY.vis.GenericChartHelper = new function(){ } } + if (trendlineData) { + for (var i = 0; i < trendlineData.length; i++) { + if (trendlineData[i].error) { + messages.push(trendlineData[i].error); + } + } + } + return {success: true, messages: messages}; };