Skip to content

Commit ba051be

Browse files
committed
DM-3612 XY plot errorbars
1 parent d384654 commit ba051be

File tree

11 files changed

+986
-213
lines changed

11 files changed

+986
-213
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"react-addons-perf" : "15.3.1",
3232
"react-component-resizable": "1.0.1",
3333
"react-grid-layout": "0.13.3",
34-
"react-highcharts": "10.0.0",
34+
"react-highcharts": "11.0.0",
3535
"react-split-pane": "0.1.44",
3636
"redux": "3.5.2",
3737
"redux-thunk": "2.1.0",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
3+
*/
4+
package edu.caltech.ipac.firefly.server.query;
5+
6+
import edu.caltech.ipac.firefly.data.ServerParams;
7+
import edu.caltech.ipac.firefly.data.TableServerRequest;
8+
import edu.caltech.ipac.firefly.server.packagedata.FileInfo;
9+
import edu.caltech.ipac.firefly.server.util.QueryUtil;
10+
import edu.caltech.ipac.firefly.server.util.ipactable.DataGroupReader;
11+
import edu.caltech.ipac.util.DataGroup;
12+
import org.json.simple.JSONObject;
13+
import org.json.simple.JSONValue;
14+
15+
import java.io.File;
16+
import java.io.IOException;
17+
18+
/**
19+
* We have a few search processors, which are accepting the search request as a parameter
20+
* This class contains the shared code
21+
* @author tatianag
22+
*/
23+
public class SearchRequestUtils {
24+
25+
/**
26+
* Get data group created by the search request
27+
* @param searchRequestJson search request in JSON format
28+
* @return DataGroup data group
29+
* @throws IOException
30+
* @throws DataAccessException
31+
*/
32+
static DataGroup dataGroupFromSearchRequest(String searchRequestJson) throws IOException, DataAccessException {
33+
if (searchRequestJson == null) {
34+
throw new DataAccessException("Missing search request");
35+
}
36+
JSONObject searchRequestJSON = (JSONObject) JSONValue.parse(searchRequestJson);
37+
String searchId = (String) searchRequestJSON.get(ServerParams.ID);
38+
if (searchId == null) {
39+
throw new DataAccessException("Search request must contain " + ServerParams.ID);
40+
}
41+
TableServerRequest sReq = QueryUtil.convertToServerRequest(searchRequestJson);
42+
43+
FileInfo fi = new SearchManager().getFileInfo(sReq);
44+
if (fi == null) {
45+
throw new DataAccessException("Unable to get file location info");
46+
}
47+
if (fi.getInternalFilename() == null) {
48+
throw new DataAccessException("File not available");
49+
}
50+
if (!fi.hasAccess()) {
51+
throw new SecurityException("Access is not permitted.");
52+
}
53+
54+
return DataGroupReader.readAnyFormat(new File(fi.getInternalFilename()));
55+
}
56+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
3+
*/
4+
package edu.caltech.ipac.firefly.server.query;
5+
6+
import edu.caltech.ipac.firefly.data.TableServerRequest;
7+
import edu.caltech.ipac.firefly.server.util.QueryUtil;
8+
import edu.caltech.ipac.firefly.server.util.ipactable.DataGroupWriter;
9+
import edu.caltech.ipac.util.*;
10+
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.util.ArrayList;
14+
15+
16+
/**
17+
* @author tatianag
18+
*/
19+
@SearchProcessorImpl(id = "XYWithErrors")
20+
public class XYWithErrorsProcessor extends IpacTablePartProcessor {
21+
private static final String SEARCH_REQUEST = "searchRequest";
22+
private static final String X_COL_EXPR = "xColOrExpr";
23+
private static final String Y_COL_EXPR = "yColOrExpr" ;
24+
private static final String XERR_LOW_COL_EXPR = "xErrLowColOrExpr";
25+
private static final String XERR_HIGH_COL_EXPR = "xErrHighColOrExpr";
26+
private static final String YERR_LOW_COL_EXPR = "yErrLowColOrExpr";
27+
private static final String YERR_HIGH_COL_EXPR = "yErrHighColOrExpr";
28+
29+
private static final Col NO_COL = new Col();
30+
31+
@Override
32+
protected File loadDataFile(TableServerRequest request) throws IOException, DataAccessException {
33+
String searchRequestJson = request.getParam(SEARCH_REQUEST);
34+
DataGroup dg = SearchRequestUtils.dataGroupFromSearchRequest(searchRequestJson);
35+
String xColOrExpr = request.getParam(X_COL_EXPR);
36+
String yColOrExpr = request.getParam(Y_COL_EXPR);
37+
String xErrLowColOrExpr = request.getParam(XERR_LOW_COL_EXPR);
38+
String xErrHighColOrExpr = request.getParam(XERR_HIGH_COL_EXPR);
39+
String yErrLowColOrExpr = request.getParam(YERR_LOW_COL_EXPR);
40+
String yErrHighColOrExpr = request.getParam(YERR_HIGH_COL_EXPR);
41+
42+
boolean hasXLowError = !StringUtils.isEmpty(xErrLowColOrExpr);
43+
boolean hasXHighError = !StringUtils.isEmpty(xErrHighColOrExpr) ;
44+
boolean hasYLowError = !StringUtils.isEmpty(yErrLowColOrExpr);
45+
boolean hasYHighError = !StringUtils.isEmpty(yErrHighColOrExpr);
46+
if ((hasXLowError || hasXHighError || hasYLowError || hasYHighError) && dg.size()>=QueryUtil.DECI_ENABLE_SIZE) {
47+
throw new DataAccessException("Errors for more than "+QueryUtil.DECI_ENABLE_SIZE+" are not supported");
48+
}
49+
50+
// the output table columns: rowIdx, x, y, [[left, right], [low, high]]
51+
DataType[] dataTypes = dg.getDataDefinitions();
52+
Col xCol = getCol(dataTypes, xColOrExpr,"x", false);
53+
Col yCol = getCol(dataTypes, yColOrExpr, "y", false);
54+
Col xLowCol = NO_COL;
55+
Col xHighCol = NO_COL;
56+
Col yLowCol = NO_COL;
57+
Col yHighCol = NO_COL;
58+
if (hasXLowError || hasXHighError) {
59+
String xLowColOrExpr = hasXLowError ? xColOrExpr+"-"+xErrLowColOrExpr : xColOrExpr;
60+
xLowCol = getCol(dataTypes, xLowColOrExpr, "left", true);
61+
String xHighColOrExpr = hasXHighError ? xColOrExpr+"+"+xErrHighColOrExpr : xColOrExpr;
62+
xHighCol = getCol(dataTypes, xHighColOrExpr, "right", true);
63+
}
64+
if (hasYLowError || hasYHighError) {
65+
String yLowColOrExpr = hasYLowError ? yColOrExpr+"-"+yErrLowColOrExpr : yColOrExpr;
66+
yLowCol = getCol(dataTypes, yLowColOrExpr, "low", true);
67+
String yHighColOrExpr = hasYHighError ? yColOrExpr+"+"+yErrHighColOrExpr : yColOrExpr;
68+
yHighCol = getCol(dataTypes, yHighColOrExpr, "high", true);
69+
}
70+
71+
// create the array of getters, which know how to get double values
72+
ArrayList<Col> colsLst = new ArrayList<>();
73+
colsLst.add(xCol);
74+
colsLst.add(yCol);
75+
if (hasXLowError || hasXHighError) {
76+
colsLst.add(xLowCol);
77+
colsLst.add(xHighCol);
78+
}
79+
if (hasYLowError || hasYHighError) {
80+
colsLst.add(yLowCol);
81+
colsLst.add(yHighCol);
82+
}
83+
Col[] cols = colsLst.toArray(new Col[colsLst.size()]);
84+
85+
// create the array of output columns
86+
ArrayList<DataType> columnList = new ArrayList<>();
87+
ArrayList<DataGroup.Attribute> colMeta = new ArrayList<>();
88+
columnList.add(new DataType("rowIdx", Integer.class));
89+
for (Col col : cols) {
90+
if (col.getter.isExpression()) {
91+
columnList.add(new DataType(col.colname, col.colname, Double.class, DataType.Importance.HIGH, "", false));
92+
} else {
93+
columnList.add(dg.getDataDefintion(col.colname).copyWithNoColumnIdx(columnList.size()));
94+
colMeta.addAll(IpacTableUtil.getAllColMeta(dg.getAttributes().values(), col.colname));
95+
}
96+
}
97+
DataType columns [] = columnList.toArray(new DataType[columnList.size()]);
98+
99+
// create the return data group
100+
DataGroup retval = new DataGroup("XY with Errors", columns);
101+
retval.setAttributes(colMeta);
102+
103+
DataObject retrow;
104+
int ncols = cols.length;
105+
Col col;
106+
DataType dt;
107+
double val;
108+
String formatted;
109+
DataType dtRowIdx = columns[0];
110+
111+
for (int rIdx = 0; rIdx < dg.size(); rIdx++) {
112+
DataObject row = dg.get(rIdx);
113+
retrow = new DataObject(retval);
114+
for (int c = 0; c < ncols ; c++) {
115+
col = cols[c];
116+
val = col.getter.getValue(row);
117+
if (Double.isNaN(val) && !col.canBeNaN) {
118+
retrow = null;
119+
break;
120+
} else {
121+
dt = columns[c+1];
122+
formatted = col.getter.getFormattedValue(row);
123+
if (formatted == null) {
124+
retrow.setDataElement(dt, QueryUtil.convertData(dt.getDataType(), val));
125+
} else {
126+
retrow.setFormattedData(dt, formatted);
127+
}
128+
col.updateMinMax(val);
129+
}
130+
}
131+
if (retrow != null) {
132+
retrow.setDataElement(dtRowIdx, rIdx);
133+
retval.add(retrow);
134+
}
135+
}
136+
137+
if (retval.size() > 0) {
138+
double xMin = Math.min(xCol.minVal, xLowCol.minVal);
139+
retval.addAttribute("X-MIN", String.valueOf(xMin));
140+
double xMax = Math.max(xCol.maxVal, xHighCol.maxVal);
141+
retval.addAttribute("X-MAX", String.valueOf(xMax));
142+
143+
double yMin = Math.min(yCol.minVal, yLowCol.minVal);
144+
retval.addAttribute("Y-MIN", String.valueOf(yMin));
145+
double yMax = Math.max(yCol.maxVal, yHighCol.maxVal);
146+
retval.addAttribute("Y-MAX", String.valueOf(yMax));
147+
}
148+
149+
retval.shrinkToFitData();
150+
File outFile = createFile(request);
151+
DataGroupWriter.write(outFile, retval);
152+
return outFile;
153+
}
154+
155+
private Col getCol(DataType[] dataTypes, String colOrExpr, String exprColName, boolean canBeNaN) throws DataAccessException {
156+
Col col = new Col(dataTypes, colOrExpr, exprColName, canBeNaN);
157+
if (!col.getter.isValid()) {
158+
throw new DataAccessException("Invalid column or expression: "+colOrExpr);
159+
}
160+
return col;
161+
}
162+
163+
164+
private static class Col {
165+
DataObjectUtil.DoubleValueGetter getter;
166+
String colname;
167+
boolean canBeNaN;
168+
double minVal = Double.MAX_VALUE;
169+
double maxVal = Double.MIN_VALUE;
170+
171+
Col() {
172+
this.canBeNaN = true;
173+
}
174+
175+
Col(DataType[] dataTypes, String colOrExpr, String exprColName, boolean canBeNaN) {
176+
this.getter = new DataObjectUtil.DoubleValueGetter(dataTypes, colOrExpr);
177+
if (getter.isExpression()) {
178+
this.colname = exprColName;
179+
} else {
180+
this.colname = colOrExpr;
181+
}
182+
this.canBeNaN = canBeNaN;
183+
}
184+
185+
186+
public void updateMinMax(double val) {
187+
if (val > maxVal) { maxVal = val; }
188+
if (val < minVal) { minVal = val; }
189+
}
190+
}
191+
}
192+

src/firefly/java/edu/caltech/ipac/firefly/server/util/QueryUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class QueryUtil {
5454
public static final Logger.LoggerImpl LOGGER = Logger.getLogger();
5555

5656
private static final int DECI_DEF_MAX_POINTS = AppProperties.getIntProperty("decimation.def.max.points", 100000);
57-
private static final int DECI_ENABLE_SIZE = AppProperties.getIntProperty("decimation.enable.size", 5000);
57+
public static final int DECI_ENABLE_SIZE = AppProperties.getIntProperty("decimation.enable.size", 5000);
5858

5959
public static String makeUrlBase(String url) {
6060

@@ -915,7 +915,7 @@ private static String getFormatterString(double min, double max, int numSigDigit
915915
return "%."+needSigDigits+"g";
916916
}
917917

918-
private static Object convertData(Class dataType, double x) {
918+
public static Object convertData(Class dataType, double x) {
919919
if (dataType.isAssignableFrom(Double.class)) {
920920
return x;
921921
} else if (dataType.isAssignableFrom(Float.class)) {

src/firefly/js/charts/ChartUtil.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,47 @@ export const getHighlighted = function(xyPlotParams, tblId) {
3131
const rowIdx = tableModel.highlightedRow;
3232
const xIn = xyPlotParams.x.columnOrExpr;
3333
const yIn = xyPlotParams.y.columnOrExpr;
34+
const xErr= xyPlotParams.x.error;
35+
const yErr= xyPlotParams.y.error;
36+
37+
const x = getColOrExprValue(tableModel, rowIdx, xIn);
38+
const y = getColOrExprValue(tableModel, rowIdx, yIn);
39+
const highlighted = {x, y, rowIdx};
40+
if (xErr) {
41+
highlighted['left'] = getColOrExprValue(tableModel, rowIdx, `${xIn}-${xErr}`);
42+
highlighted['right'] = getColOrExprValue(tableModel, rowIdx, `${xIn}+${xErr}`);
43+
}
44+
if (yErr) {
45+
highlighted['low'] = getColOrExprValue(tableModel, rowIdx, `${yIn}-${yErr}`);
46+
highlighted['high'] = getColOrExprValue(tableModel, rowIdx, `${yIn}+${yErr}`);
3447

35-
var x, y;
36-
if (getColumnIdx(tableModel, xIn) >= 0) {
37-
x = getCellValue(tableModel, rowIdx, xIn);
38-
} else {
39-
x = getExpressionValue(xIn, tableModel, rowIdx);
4048
}
49+
return highlighted;
50+
}
51+
};
4152

42-
if (getColumnIdx(tableModel, yIn) >= 0) {
43-
y = getCellValue(tableModel, rowIdx, yIn);
53+
/**
54+
* This method returns the value of the column cell or an expression from multiple column cells in a given row
55+
*
56+
* @param {TableModel} tableModel - table model
57+
* @param {number} rowIdx - row index in the table
58+
* @param {string} colOrExpr - column name or expression
59+
* @returns {number} value of the column or expression in the given row
60+
*/
61+
export function getColOrExprValue(tableModel, rowIdx, colOrExpr) {
62+
if (tableModel) {
63+
var val;
64+
if (getColumnIdx(tableModel, colOrExpr) >= 0) {
65+
val = getCellValue(tableModel, rowIdx, colOrExpr);
66+
val = isFinite(parseFloat(val)) ? Number(val) : Number.NaN;
4467
} else {
45-
y = getExpressionValue(yIn, tableModel, rowIdx);
68+
val = getExpressionValue(tableModel, rowIdx, colOrExpr);
4669
}
47-
return {x:Number(x), y:Number(y), rowIdx};
70+
return val;
4871
}
49-
};
72+
}
5073

51-
function getExpressionValue(strExpr, tableModel, rowIdx) {
74+
function getExpressionValue(tableModel, rowIdx, strExpr) {
5275

5376
const expr = new Expression(strExpr); // no check for allowed variables, already validated
5477
if (expr.isValid()) {

src/firefly/js/charts/ChartsCntlr.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,25 @@ function doChartDataFetch(dispatch, payload, getChartDataType) {
243243
let newMeta = meta;
244244

245245
if (tblId) {
246-
const tblSource = get(getTblById(tblId), 'tableMeta.tblFilePath');
246+
const tblModel = getTblById(tblId);
247+
248+
// if table load produced an error, we can not get chart data
249+
const error = get(tblModel, error);
250+
if (error) {
251+
const message = 'Failed to fetch chart data';
252+
logError(`${message}: ${error}`);
253+
dispatch(chartDataUpdate(
254+
{
255+
chartId,
256+
chartDataElementId,
257+
isDataReady: true,
258+
error: {message, error},
259+
data: undefined
260+
}));
261+
return;
262+
}
263+
264+
const tblSource = get(tblModel, 'tableMeta.tblFilePath');
247265
const tblSourceChart = get(meta, 'tblSource');
248266

249267
if (tblSourceChart !== tblSource) {

src/firefly/js/charts/chartTypes/ScatterTblView.jsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,10 @@ function addSelection(chartId, tblId, tableModel) {
288288
const {xMin, xMax, yMin, yMax} = selection;
289289
const selectInfoCls = SelectInfo.newInstance({rowCount: tableModel.totalRows});
290290
// add all rows which fall into selection
291-
const xIdx = 0, yIdx = 1, rowIdx = 2;
292291
rows.forEach((arow) => {
293-
const x = Number(arow[xIdx]);
294-
const y = Number(arow[yIdx]);
292+
const {x, y, rowIdx} = arow;
295293
if (x >= xMin && x <= xMax && y >= yMin && y <= yMax) {
296-
selectInfoCls.setRowSelect(Number(arow[rowIdx]), true);
294+
selectInfoCls.setRowSelect(rowIdx, true);
297295
}
298296
});
299297
const selectInfo = selectInfoCls.data;
@@ -309,10 +307,8 @@ function selectionNotEmpty(chartId, selection) {
309307
if (rows) {
310308
if (selection) {
311309
const {xMin, xMax, yMin, yMax} = selection;
312-
const xIdx = 0, yIdx = 1;
313310
const aPt = rows.find((arow) => {
314-
const x = Number(arow[xIdx]);
315-
const y = Number(arow[yIdx]);
311+
const {x, y} = arow;
316312
return (x >= xMin && x <= xMax && y >= yMin && y <= yMax);
317313
});
318314
return Boolean(aPt);

0 commit comments

Comments
 (0)