Skip to content

Commit 1262191

Browse files
authored
Merge pull request #254 from Caltech-IPAC/dm-3612_errorbars
DM-3612 scatter plot errorbars
2 parents 595e734 + 5a3a6a7 commit 1262191

File tree

11 files changed

+1018
-219
lines changed

11 files changed

+1018
-219
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: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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_COL_EXPR = "xErrColOrExpr";
25+
private static final String XERR_LOW_COL_EXPR = "xErrLowColOrExpr";
26+
private static final String XERR_HIGH_COL_EXPR = "xErrHighColOrExpr";
27+
private static final String YERR_COL_EXPR = "yErrColOrExpr";
28+
private static final String YERR_LOW_COL_EXPR = "yErrLowColOrExpr";
29+
private static final String YERR_HIGH_COL_EXPR = "yErrHighColOrExpr";
30+
31+
private static final Col NO_COL = new Col();
32+
33+
@Override
34+
protected File loadDataFile(TableServerRequest request) throws IOException, DataAccessException {
35+
String searchRequestJson = request.getParam(SEARCH_REQUEST);
36+
DataGroup dg = SearchRequestUtils.dataGroupFromSearchRequest(searchRequestJson);
37+
String xColOrExpr = request.getParam(X_COL_EXPR);
38+
String yColOrExpr = request.getParam(Y_COL_EXPR);
39+
String xErrColOrExpr = request.getParam(XERR_COL_EXPR);
40+
String xErrLowColOrExpr = request.getParam(XERR_LOW_COL_EXPR);
41+
String xErrHighColOrExpr = request.getParam(XERR_HIGH_COL_EXPR);
42+
String yErrColOrExpr = request.getParam(YERR_COL_EXPR);
43+
String yErrLowColOrExpr = request.getParam(YERR_LOW_COL_EXPR);
44+
String yErrHighColOrExpr = request.getParam(YERR_HIGH_COL_EXPR);
45+
46+
boolean hasXError = !StringUtils.isEmpty(xErrColOrExpr);
47+
boolean hasXLowError = !StringUtils.isEmpty(xErrLowColOrExpr);
48+
boolean hasXHighError = !StringUtils.isEmpty(xErrHighColOrExpr) ;
49+
boolean hasYError = !StringUtils.isEmpty(yErrColOrExpr);
50+
boolean hasYLowError = !StringUtils.isEmpty(yErrLowColOrExpr);
51+
boolean hasYHighError = !StringUtils.isEmpty(yErrHighColOrExpr);
52+
if ((hasXError || hasXLowError || hasXHighError || hasYError || hasYLowError || hasYHighError) && dg.size()>=QueryUtil.DECI_ENABLE_SIZE) {
53+
throw new DataAccessException("Errors for more than "+QueryUtil.DECI_ENABLE_SIZE+" are not supported");
54+
}
55+
56+
// the output table columns: rowIdx, x, y, [[left, right], [low, high]]
57+
DataType[] dataTypes = dg.getDataDefinitions();
58+
59+
// create the array of getters, which know how to get double values
60+
ArrayList<Col> colsLst = new ArrayList<>();
61+
colsLst.add(getCol(dataTypes, xColOrExpr,"x", false));
62+
colsLst.add(getCol(dataTypes, yColOrExpr, "y", false));
63+
64+
if (hasXError) {
65+
colsLst.add(getCol(dataTypes, xErrColOrExpr, "xErr", true));
66+
}
67+
if (hasXLowError) {
68+
colsLst.add(getCol(dataTypes, xErrLowColOrExpr, "xErrLow", true));
69+
}
70+
if (hasXHighError) {
71+
colsLst.add(getCol(dataTypes, xErrHighColOrExpr, "xErrHigh", true));
72+
}
73+
if (hasYError) {
74+
colsLst.add(getCol(dataTypes, yErrColOrExpr, "yErr", true));
75+
}
76+
if (hasYLowError) {
77+
colsLst.add(getCol(dataTypes, yErrLowColOrExpr, "yErrLow", true));
78+
}
79+
if (hasYHighError) {
80+
colsLst.add(getCol(dataTypes, yErrHighColOrExpr, "yErrHigh", true));
81+
}
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+
columnList.add(new DataType("rowIdx", Integer.class));
88+
ArrayList<DataGroup.Attribute> colMeta = new ArrayList<>();
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+
102+
DataObject retrow;
103+
int ncols = cols.length;
104+
Col col;
105+
DataType dt;
106+
double val;
107+
String formatted;
108+
DataType dtRowIdx = columns[0];
109+
110+
for (int rIdx = 0; rIdx < dg.size(); rIdx++) {
111+
DataObject row = dg.get(rIdx);
112+
retrow = new DataObject(retval);
113+
for (int c = 0; c < ncols ; c++) {
114+
col = cols[c];
115+
val = col.getter.getValue(row);
116+
if (Double.isNaN(val) && !col.canBeNaN) {
117+
retrow = null;
118+
break;
119+
} else {
120+
dt = columns[c+1];
121+
formatted = col.getter.getFormattedValue(row);
122+
if (formatted == null) {
123+
retrow.setDataElement(dt, QueryUtil.convertData(dt.getDataType(), val));
124+
} else {
125+
retrow.setFormattedData(dt, formatted);
126+
}
127+
}
128+
}
129+
if (retrow != null) {
130+
retrow.setDataElement(dtRowIdx, rIdx);
131+
retval.add(retrow);
132+
}
133+
}
134+
135+
for (Col c : cols) {
136+
colMeta.add(new DataGroup.Attribute(c.exprColName, c.colOrExpr));
137+
}
138+
retval.setAttributes(colMeta);
139+
140+
retval.shrinkToFitData();
141+
File outFile = createFile(request);
142+
DataGroupWriter.write(outFile, retval);
143+
return outFile;
144+
}
145+
146+
private Col getCol(DataType[] dataTypes, String colOrExpr, String exprColName, boolean canBeNaN) throws DataAccessException {
147+
Col col = new Col(dataTypes, colOrExpr, exprColName, canBeNaN);
148+
if (!col.getter.isValid()) {
149+
throw new DataAccessException("Invalid column or expression: "+colOrExpr);
150+
}
151+
return col;
152+
}
153+
154+
155+
private static class Col {
156+
DataObjectUtil.DoubleValueGetter getter;
157+
String colname;
158+
String exprColName;
159+
String colOrExpr;
160+
boolean canBeNaN;
161+
162+
Col() {
163+
this.canBeNaN = true;
164+
}
165+
166+
Col(DataType[] dataTypes, String colOrExpr, String exprColName, boolean canBeNaN) {
167+
this.colOrExpr = colOrExpr;
168+
this.exprColName = exprColName;
169+
this.getter = new DataObjectUtil.DoubleValueGetter(dataTypes, colOrExpr);
170+
if (getter.isExpression()) {
171+
this.colname = exprColName;
172+
} else {
173+
this.colname = colOrExpr;
174+
}
175+
this.canBeNaN = canBeNaN;
176+
}
177+
}
178+
}
179+

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: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,47 @@ export const getHighlighted = function(xyPlotParams, tblId) {
2929
const tableModel = getTblById(tblId);
3030
if (tableModel && xyPlotParams) {
3131
const rowIdx = tableModel.highlightedRow;
32-
const xIn = xyPlotParams.x.columnOrExpr;
33-
const yIn = xyPlotParams.y.columnOrExpr;
34-
35-
var x, y;
36-
if (getColumnIdx(tableModel, xIn) >= 0) {
37-
x = getCellValue(tableModel, rowIdx, xIn);
38-
} else {
39-
x = getExpressionValue(xIn, tableModel, rowIdx);
40-
}
32+
const highlighted = {rowIdx};
33+
[
34+
{n:'x',v:xyPlotParams.x.columnOrExpr},
35+
{n:'y',v:xyPlotParams.y.columnOrExpr},
36+
{n:'xErr', v:xyPlotParams.x.error},
37+
{n:'xErrLow', v:xyPlotParams.x.errorLow},
38+
{n:'xErrHigh', v:xyPlotParams.x.errorHigh},
39+
{n:'yErr', v:xyPlotParams.y.error},
40+
{n:'yErrLow', v:xyPlotParams.y.errorLow},
41+
{n:'yErrHigh', v:xyPlotParams.y.errorHigh}
42+
].map((entry) => {
43+
if (entry.v) {
44+
highlighted[entry.n] = getColOrExprValue(tableModel, rowIdx, entry.v);
45+
}
46+
});
47+
return highlighted;
48+
}
49+
};
4150

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

51-
function getExpressionValue(strExpr, tableModel, rowIdx) {
72+
function getExpressionValue(tableModel, rowIdx, strExpr) {
5273

5374
const expr = new Expression(strExpr); // no check for allowed variables, already validated
5475
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)