Skip to content

DM-9590 XY plot is unrecoverable after it fails #329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,14 @@ private boolean isInSelection( int idx, ArrayList<Integer> list){
return false;
}

private double[] getColumnData(DataGroup dg) {
private double[] getColumnData(DataGroup dg) throws DataAccessException {
List<DataObject> objList = dg.values();
int nRow = objList.size();
DataType[] dataTypes=dg.getDataDefinitions();
DataObjectUtil.DoubleValueGetter dGetter = new DataObjectUtil.DoubleValueGetter(dataTypes, columnExpression);
if (!dGetter.isValid()) {
throw new DataAccessException("Invalid column or expression: "+columnExpression);
}

double[] data = new double[nRow];
for (int i = 0; i < nRow; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ public static DataAccessException createEndUserException(String msg, String deta
* @param decimateInfo DecimateInfo object
* @return decimated data group
*/
public static DataGroup doDecimation(DataGroup dg, DecimateInfo decimateInfo) {
public static DataGroup doDecimation(DataGroup dg, DecimateInfo decimateInfo) throws DataAccessException {

double xMax = Double.NEGATIVE_INFINITY, xMin = Double.POSITIVE_INFINITY, yMax = Double.NEGATIVE_INFINITY, yMin = Double.POSITIVE_INFINITY;

Expand All @@ -633,7 +633,7 @@ public static DataGroup doDecimation(DataGroup dg, DecimateInfo decimateInfo) {

if (!xValGetter.isValid() || !yValGetter.isValid()) {
System.out.println("QueryUtil.doDecimation: invalid x or y column.");
return null; // TODO: handle null return in the caller?
throw new DataAccessException("Invalid column or expression");
}

int maxPoints = decimateInfo.getMaxPoints() == 0 ? DECI_DEF_MAX_POINTS : decimateInfo.getMaxPoints();
Expand Down
29 changes: 19 additions & 10 deletions src/firefly/js/charts/dataTypes/HistogramCDT.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,25 @@ function fetchColData(dispatch, chartId, chartDataElementId) {
}
).catch(
(reason) => {
const message = 'Failed to fetch histogram data';
logError(`${message}: ${reason}`);
dispatch(chartDataUpdate(
{
chartId,
chartDataElementId,
isDataReady: true,
error: {message, reason},
data: undefined
}));
dispatchError(dispatch, chartId, chartDataElementId, reason);
}
);
}
function dispatchError(dispatch, chartId, chartDataElementId, reason) {
const message = 'Failed to fetch histogram data';
logError(`${message}: ${reason}`);
let reasonStr = `${reason}`.toLowerCase();
if (reasonStr.match(/invalid column/)) {
reasonStr = 'Non-existent column or invalid expression. Please use valid value.';
} else {
reasonStr = 'Please contact Help Desk. Check browser console for more information.';
}
dispatch(chartDataUpdate(
{
chartId,
chartDataElementId,
isDataReady: true,
error: {message, reason: reasonStr},
data: undefined
}));
}
80 changes: 52 additions & 28 deletions src/firefly/js/charts/dataTypes/XYColsCDT.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,19 @@ function serverParamsChanged(oldParams, newParams, chartDataElement) {
} else {
// 'x', 'y', 'sortBy', 'xErr', 'xErrLow', 'xErrHigh', 'yErr', 'yErrLow', 'yErrHigh'
// server call parameters are present in the data
const newOpts = omitBy({
sortBy: newParams.sortColOrExpr,
x: newParams.x.columnOrExpr,
xErr: newParams.x.error,
xErrLow: newParams.x.errorLow,
xErrHigh: newParams.x.errorHigh,
y: newParams.y.columnOrExpr,
yErr: newParams.y.error,
yErrLow: newParams.y.errorLow,
yErrHigh: newParams.y.errorHigh
}, isUndefined);
return Object.keys(newOpts).some((o) => {
return newOpts[o] !== data[o];
});
const newOpts = getServerCallParameters(newParams, false);
if (data) {
// if data available, see if the new parameters are different from those used to obtain the data
return Object.keys(newOpts).some((o) => {
return newOpts[o] !== data[o];
});
} else {
// if data are not available, compare with the old parameters
const oldOpts = getServerCallParameters(oldParams, false);
return Object.keys(newOpts).some((o) => {
return newOpts[o] !== oldOpts[o];
});
}
}
}

Expand All @@ -249,22 +248,39 @@ function isLargeTable(tblId) {
// xyPlotParams.x.errorHigh || xyPlotParams.y.errorHigh);
//}

function getServerCallParameters(xyPlotParams) {
if (!xyPlotParams) { return []; }
function getServerCallParameters(xyPlotParams, isLargeTable=true) {
if (isLargeTable) {
if (!xyPlotParams) {
return [];
}

if (xyPlotParams.zoom) {
var {xMin, xMax, yMin, yMax} = xyPlotParams.zoom;
}
if (xyPlotParams.zoom) {
var {xMin, xMax, yMin, yMax} = xyPlotParams.zoom;
}

let maxBins = 10000;
let xyRatio = xyPlotParams.xyRatio || 1.0;
if (xyPlotParams.nbins) {
const {x, y} = xyPlotParams.nbins;
maxBins = x*y;
xyRatio = x/y;
let maxBins = 10000;
let xyRatio = xyPlotParams.xyRatio || 1.0;
if (xyPlotParams.nbins) {
const {x, y} = xyPlotParams.nbins;
maxBins = x * y;
xyRatio = x / y;
}
// order should match the order of the parameters in serializeDecimateInfo
return [xyPlotParams.x.columnOrExpr, xyPlotParams.y.columnOrExpr, maxBins, xyRatio, xMin, xMax, yMin, yMax];
} else {
// smaller (not decimated) table
return omitBy({
sortBy: xyPlotParams.sortColOrExpr,
x: xyPlotParams.x.columnOrExpr,
xErr: xyPlotParams.x.error,
xErrLow: xyPlotParams.x.errorLow,
xErrHigh: xyPlotParams.x.errorHigh,
y: xyPlotParams.y.columnOrExpr,
yErr: xyPlotParams.y.error,
yErrLow: xyPlotParams.y.errorLow,
yErrHigh: xyPlotParams.y.errorHigh
}, isUndefined);
}
// order should match the order of the parameters in serializeDecimateInfo
return [xyPlotParams.x.columnOrExpr, xyPlotParams.y.columnOrExpr, maxBins, xyRatio, xMin, xMax, yMin, yMax];
}

export function getDataBoundaries(xyPlotData) {
Expand Down Expand Up @@ -574,12 +590,20 @@ function fetchXYWithErrorsOrSort(dispatch, chartId, chartDataElementId) {
function dispatchError(dispatch, chartId, chartDataElementId, reason) {
const message = 'Failed to fetch XY plot data';
logError(`${message}: ${reason}`);
let reasonStr = `${reason}`.toLowerCase();
if (reasonStr.match(/not supported/)) {
reasonStr = 'Unsupported feature requested. Please choose valid options.';
} else if (reasonStr.match(/invalid column/)) {
reasonStr = 'Non-existent column or invalid expression. Please choose valid X and Y.';
} else {
reasonStr = 'Please contact Help Desk. Check browser console for more information.';
}
dispatch(chartDataUpdate(
{
chartId,
chartDataElementId,
isDataReady: true,
error: {message, reason},
error: {message, reason: reasonStr},
data: undefined
}));
}
Expand Down
27 changes: 26 additions & 1 deletion src/firefly/js/charts/ui/ColumnOrExpression.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React, {PropTypes} from 'react';

import {get} from 'lodash';
import {Expression} from '../../util/expr/Expression.js';
import {dispatchValueChange} from '../../fieldGroup/FieldGroupCntlr.js';
import {TextButton} from '../../ui/TextButton.jsx';
import {SuggestBoxInputField} from '../../ui/SuggestBoxInputField.jsx';
Expand All @@ -29,6 +30,24 @@ function parseSuggestboxContent(text) {
return {token, priorContent};
}

export function getColValidator(colValStats, required=true) {
const colNames = colValStats.map((colVal) => {return colVal.name;});
return (val) => {
let retval = {valid: true, message: ''};
if (!val) {
if (required) {
return {valid: false, message: 'Can not be empty. Please provide value or expression'};
}
} else if (colNames.indexOf(val) < 0) {
const expr = new Expression(val, colNames);
if (!expr.isValid()) {
retval = {valid: false, message: `${expr.getError().error}. Unable to parse ${val}.`};
}
}
return retval;
};
}

export function ColumnOrExpression({colValStats,params,groupKey,fldPath,label,labelWidth=30,tooltip,nullAllowed}) {

// the suggestions are indexes in the colValStats array - it makes it easier to render then with labels
Expand All @@ -55,12 +74,18 @@ export function ColumnOrExpression({colValStats,params,groupKey,fldPath,label,la
val = colName;
dispatchValueChange({fieldKey: fldPath, groupKey, value: colName, valid: true});
};
const colValidator = getColValidator(colValStats,!nullAllowed);
const value = get(params, fldPath);
const {valid=true, message=''} = value ? colValidator(value) : {};
return (
<div style={{whiteSpace: 'nowrap'}}>
<SuggestBoxInputField
inline={true}
initialState= {{
value: get(params, fldPath),
value,
valid,
message,
validator: colValidator,
tooltip: `Column or expression for ${tooltip}`,
label: `${label}:`,
nullAllowed
Expand Down
24 changes: 19 additions & 5 deletions src/firefly/js/charts/ui/HistogramOptions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import {DATATYPE_HISTOGRAM} from '../dataTypes/HistogramCDT.js';
import CompleteButton from '../../ui/CompleteButton.jsx';
import {FieldGroup} from '../../ui/FieldGroup.jsx';
import FieldGroupUtils from '../../fieldGroup/FieldGroupUtils.js';
import {dispatchMultiValueChange} from '../../fieldGroup/FieldGroupCntlr.js';
import {dispatchValueChange, dispatchMultiValueChange} from '../../fieldGroup/FieldGroupCntlr.js';
import {InputGroup} from '../../ui/InputGroup.jsx';
import Validate from '../../util/Validate.js';
import {ValidationField} from '../../ui/ValidationField.jsx';
import {CheckboxGroupInputField} from '../../ui/CheckboxGroupInputField.jsx';
import {RadioGroupInputField} from '../../ui/RadioGroupInputField.jsx';
import {FieldGroupCollapsible} from '../../ui/panel/CollapsiblePanel.jsx';
import {ColumnOrExpression} from './ColumnOrExpression.jsx';
import {ColumnOrExpression, getColValidator} from './ColumnOrExpression.jsx';
import {getAppOptions} from '../../core/AppDataCntlr.js';


export const histogramParamsShape = PropTypes.shape({
algorithm : PropTypes.oneOf(['fixedSizeBins','bayesianBlocks']),
numBins : PropTypes.string,
numBins : PropTypes.oneOfType([React.PropTypes.string,React.PropTypes.number]),
falsePositiveRate : PropTypes.string,
minCutoff : PropTypes.number,
maxCutoff : PropTypes.number
Expand Down Expand Up @@ -85,6 +85,18 @@ export class HistogramOptions extends React.Component {
this.setState({fields});
}
});
// make sure column validator matches current columns
const {colValStats, groupKey, histogramParams} = this.props;
if (colValStats) {
const colValidator = getColValidator(colValStats);
var payload = {groupKey, fieldKey: 'columnOrExpr', validator: colValidator};
const value = get(histogramParams, 'columnOrExpr');
if (value) {
var {valid, message} = colValidator(value);
payload = Object.assign(payload, {value, valid, message});
}
dispatchValueChange(payload);
}
this.iAmMounted= true;
}

Expand Down Expand Up @@ -133,11 +145,13 @@ export class HistogramOptions extends React.Component {
const { colValStats, groupKey, histogramParams, defaultParams, onOptionsSelected} = this.props;
const {fixedAlgorithm=false} = this.state;
const xProps = {colValStats,params:histogramParams,groupKey,fldPath:'columnOrExpr',label:'Column or expression', labelWidth:120, tooltip:'X Axis',nullAllowed:false};

const algorithm = get(histogramParams, 'algorithm', 'fixedSizeBins');
const m_algorithmOptions = fixedAlgorithm ? algorithmOptions.filter((el) => el.value === algorithm) : algorithmOptions;
// set minimum height to fit full height suggest box,
// to avoid width change due to scroll bar appearing when full height suggest box is rendered
return (
<div style={{padding:'0 5px'}}>
<div style={{padding:'0 5px', minHeight: 250}}>
<FieldGroup groupKey={groupKey} validatorFunc={null} keepState={true}>
{onOptionsSelected &&
<div style={{display: 'flex', flexDirection: 'row', padding: '5px 0 15px'}}>
Expand Down
23 changes: 2 additions & 21 deletions src/firefly/js/charts/ui/XYPlotOptions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import {FieldGroup} from '../../ui/FieldGroup.jsx';
import FieldGroupUtils from '../../fieldGroup/FieldGroupUtils.js';
import {dispatchMultiValueChange, VALUE_CHANGE, MULTI_VALUE_CHANGE} from '../../fieldGroup/FieldGroupCntlr.js';
import Validate from '../../util/Validate.js';
import {Expression} from '../../util/expr/Expression.js';
import {ValidationField} from '../../ui/ValidationField.jsx';
import {CheckboxGroupInputField} from '../../ui/CheckboxGroupInputField.jsx';
import {RadioGroupInputField} from '../../ui/RadioGroupInputField.jsx';
import {FieldGroupCollapsible} from '../../ui/panel/CollapsiblePanel.jsx';
import {plotParamsShape} from './XYPlot.jsx';
import {hideColSelectPopup} from './ColSelectView.jsx';
import {ColumnOrExpression} from './ColumnOrExpression.jsx';
import {ColumnOrExpression, getColValidator} from './ColumnOrExpression.jsx';
import {updateSet} from '../../util/WebUtil.js';

const DECI_ENABLE_SIZE = 5000;
Expand Down Expand Up @@ -113,7 +112,7 @@ export function resultsSuccess(callback, flds, optionParameters) {
x : { columnOrExpr : xName, error: xErr, label : xLabel, unit : xUnit, options : xOptions},
y : { columnOrExpr : yName, error: yErr, label : yLabel, unit : yUnit, options : yOptions},
tblId,
zoom,
zoom
}, isUndefined);

if (xErr || yErr) {
Expand Down Expand Up @@ -155,24 +154,6 @@ export function setOptions(groupKey, xyPlotParams) {
dispatchMultiValueChange(groupKey, flds);
}

export function getColValidator(colValStats, required=true) {
const colNames = colValStats.map((colVal) => {return colVal.name;});
return (val) => {
let retval = {valid: true, message: ''};
if (!val) {
if (required) {
return {valid: false, message: 'Can not be empty. Please provide value or expression'};
}
} else if (colNames.indexOf(val) < 0) {
const expr = new Expression(val, colNames);
if (!expr.isValid()) {
retval = {valid: false, message: `${expr.getError().error}. Unable to parse ${val}.`};
}
}
return retval;
};
}

/**
* Reducer from field group component,
* clears label, unit, and userSetBoundaries whenever x or y field changes,
Expand Down