Newer
Older
Bucknell, Mary S.
committed
const memoize = require('fast-memoize');
const { createSelector } = require('reselect');
const { format } = require('d3-format');
Bucknell, Mary S.
committed
Bucknell, Mary S.
committed
const {allTimeSeriesSelector, currentVariableTimeSeriesSelector, timeSeriesSelector } = require('./timeSeries');
Bucknell, Mary S.
committed
const { getVariables, getTsRequestKey } = require('../../selectors/timeSeriesSelector');
Bucknell, Mary S.
committed
export const MASK_DESC = {
ice: 'Ice Affected',
fld: 'Flood',
bkw: 'Backwater',
zfl: 'Zeroflow',
dry: 'Dry',
ssn: 'Seasonal',
pr: 'Partial Record',
rat: 'Rating Development',
eqp: 'Equipment Malfunction',
mnt: 'Maintenance',
dis: 'Discontinued',
tst: 'Test',
pmp: 'Pump',
'***': 'Unavailable'
};
export const HASH_ID = {
current: 'hash-45',
compare: 'hash-135'
};
// Lines will be split if the difference exceeds 72 minutes.
export const MAX_LINE_POINT_GAP = 60 * 1000 * 72;
Bucknell, Mary S.
committed
const PARM_CODES_TO_ACCUMULATE = ['00045'];
Bucknell, Mary S.
committed
const toNumberString = format('.2f');
Bucknell, Mary S.
committed
/*
* @param {Array} points - Array of point objects
* @return {Array} - Returns the array of points accumulated. If a null value is found,
* the accumulator is set back to zero.
Bucknell, Mary S.
committed
*/
const transformToCumulative = function(points) {
let accumulatedValue = 0;
return points.map((point) => {
let result = {...point};
if (point.value !== null) {
accumulatedValue += point.value;
Bucknell, Mary S.
committed
result.value = parseFloat(toNumberString(accumulatedValue));
Bucknell, Mary S.
committed
} else {
accumulatedValue = 0;
}
return result;
});
};
/* Factory function that returns a function that returns an object where the properties are ts IDs and the values
* are array of point objects that can be used to render a time series graph.
* @return {Object} where the keys are ts ids and the values are an Array of point Objects.
Bucknell, Mary S.
committed
export const allPointsSelector = createSelector(
allTimeSeriesSelector,
state => state.series.variables,
(timeSeries, variables) => {
let allPoints = {};
Object.keys(timeSeries).forEach((tsId) => {
const ts = timeSeries[tsId];
const variableId = ts.variable;
const parmCd = variables[variableId].variableCode.value;
Bucknell, Mary S.
committed
if (ts.tsKey !== 'median' && PARM_CODES_TO_ACCUMULATE.includes(parmCd)) {
Bucknell, Mary S.
committed
allPoints[tsId] = transformToCumulative(ts.points);
} else {
allPoints[tsId] = ts.points;
}
});
return allPoints;
}
);
/* Factory function that for a given tsKey returns an object with keys that are the tsID and values an array of point objects
Bucknell, Mary S.
committed
* @param {Object} state
* @param {String} tsKey
* @return {Object} of keys are tsId, values are Array of point Objects
Bucknell, Mary S.
committed
*/
Bucknell, Mary S.
committed
export const pointsByTsKeySelector = memoize((tsKey, period) => createSelector(
Bucknell, Mary S.
committed
getTsRequestKey(tsKey, period),
Bucknell, Mary S.
committed
allPointsSelector,
state => state.series.timeSeries,
Bucknell, Mary S.
committed
(tsRequestKey, points, timeSeries) => {
Bucknell, Mary S.
committed
let result = {};
Object.keys(points).forEach((tsId) => {
Bucknell, Mary S.
committed
if (timeSeries[tsId].tsKey === tsRequestKey) {
Bucknell, Mary S.
committed
result[tsId] = points[tsId];
}
});
return result;
Bucknell, Mary S.
committed
}));
Bucknell, Mary S.
committed
/* Returns a select that returns all time series point for the ccurrent variable and in the select series, tsKey
* by tsId.
* @param {Object} state
* @param {String} tsKey
* @return Object
*/
Bucknell, Mary S.
committed
export const currentVariablePointsByTsIdSelector = memoize(tsKey => createSelector(
Bucknell, Mary S.
committed
pointsByTsKeySelector(tsKey),
Bucknell, Mary S.
committed
currentVariableTimeSeriesSelector(tsKey),
(points, timeSeries) => {
let result = {};
if (points) {
result = Object.keys(timeSeries).reduce((data, tsId) => {
data[tsId] = points[tsId];
return data;
Bucknell, Mary S.
committed
}, {});
}
return result;
}
));
/* Returns a selector that returns all time series points for the current variable and in the selected series, tsKey.
* @param {Object} state
* @param {String} tsKey
* @return Array of Array of points
Bucknell, Mary S.
committed
*/
export const currentVariablePointsSelector = memoize(tsKey => createSelector(
Bucknell, Mary S.
committed
pointsByTsKeySelector(tsKey),
Bucknell, Mary S.
committed
currentVariableTimeSeriesSelector(tsKey),
(points, timeSeries) => {
return timeSeries ? Object.keys(timeSeries).map((tsId) => points[tsId]) : [];
}
));
Bucknell, Mary S.
committed
/**
* Returns a selector that, for a given tsKey:
* Returns an array of time points for all time series.
Bucknell, Mary S.
committed
* @param {Object} state Redux store
* @param {String} tsKey Time series key
Bucknell, Mary S.
committed
* @return {Array} Array of array of points.
*/
export const pointsSelector = memoize((tsKey) => createSelector(
Bucknell, Mary S.
committed
pointsByTsKeySelector(tsKey),
Bucknell, Mary S.
committed
(points) => {
return Object.values(points);
}
));
/**
* Factory function that returns a selector for a given tsKey, that:
* Returns a single array of all points.
* @param {Object} state Redux state
* @return {Array} Array of points.
*/
export const flatPointsSelector = memoize(tsKey => createSelector(
pointsSelector(tsKey),
tsPointsList => tsPointsList.reduce((finalPoints, points) => {
Array.prototype.push.apply(finalPoints, points);
return finalPoints;
}, [])
));
/*
* Returns an object which identifies which classes to use for the point
* @param {Object} point
* @return {Object}
*/
Bucknell, Mary S.
committed
export const classesForPoint = point => {
return {
approved: point.qualifiers.indexOf('A') > -1,
estimated: point.qualifiers.indexOf('E') > -1
};
};
/**
* Factory function create a function that
* returns an array of points for each visible timeseries.
Bucknell, Mary S.
committed
* @param {Object} state Redux store
* @return {Array} Array of point arrays.
*/
export const visiblePointsSelector = createSelector(
currentVariablePointsSelector('current'),
currentVariablePointsSelector('compare'),
currentVariablePointsSelector('median'),
(state) => state.timeSeriesState.showSeries,
Bucknell, Mary S.
committed
(current, compare, median, showSeries) => {
const pointArray = [];
if (showSeries['current']) {
Array.prototype.push.apply(pointArray, current);
}
if (showSeries['compare']) {
Array.prototype.push.apply(pointArray, compare);
}
if (showSeries['median']) {
Array.prototype.push.apply(pointArray, median);
}
return pointArray;
}
);
/**
* Factory function creates a function that, for a given tsKey:
* Returns all point data as an array of [value, time, qualifiers].
* @param {Object} state - Redux store
* @param {String} tsKey - Time series key
* @param {Object} - keys are ts id, values are an array of points where each point is an Array as follows: [value, time, qualifiers].
Bucknell, Mary S.
committed
*/
export const pointsTableDataSelector = memoize(tsKey => createSelector(
Bucknell, Mary S.
committed
pointsByTsKeySelector(tsKey),
(allPoints) => {
return Object.keys(allPoints).reduce((databyTsId, tsId) => {
databyTsId[tsId] = allPoints[tsId].map((value) => {
Bucknell, Mary S.
committed
return [
value.value || '',
value.dateTime || '',
value.qualifiers && value.qualifiers.length > 0 ? value.qualifiers.join(', ') : ''
];
});
Bucknell, Mary S.
committed
}, {});
}
));
const getLineClasses = function(pt) {
let dataMask = null;
if (pt.value === null) {
let qualifiers = new Set(pt.qualifiers.map(q => q.toLowerCase()));
// current business rules specify that a particular data point
// will only have at most one masking qualifier
let maskIntersection = Object.keys(MASK_DESC).filter(x => qualifiers.has(x));
dataMask = maskIntersection[0];
}
return {
...classesForPoint(pt),
dataMask
};
};
/**
* Factory function creates a function that:
* Returns all points in a time series grouped into line segments, for each time series.
Bucknell, Mary S.
committed
* @param {Object} state Redux store
* @param {String} tsKey Time series key
* @return {Object} Keys are ts Ids, values are of array of line segments.
Bucknell, Mary S.
committed
*/
Bucknell, Mary S.
committed
export const lineSegmentsSelector = memoize((tsKey, period) => createSelector(
pointsByTsKeySelector(tsKey, period),
Bucknell, Mary S.
committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
(tsPoints) => {
let seriesLines = {};
Object.keys(tsPoints).forEach((tsId) => {
const points = tsPoints[tsId];
let lines = [];
// Accumulate data into line groups, splitting on the estimated and
// approval status.
let lastClasses = {};
for (let pt of points) {
// Classes to put on the line with this point.
let lineClasses = getLineClasses(pt);
// If this is a non-masked data point, split lines if the gap
// from the period point exceeds MAX_LINE_POINT_GAP.
let splitOnGap = false;
if (!lineClasses.dataMask && lines.length > 0) {
const lastPoints = lines[lines.length - 1].points;
const lastPtDateTime = lastPoints[lastPoints.length - 1].dateTime;
if (pt.dateTime - lastPtDateTime > MAX_LINE_POINT_GAP) {
splitOnGap = true;
}
}
// If this point doesn't have the same classes as the last point,
// create a new line for it.
if (lastClasses.approved !== lineClasses.approved ||
lastClasses.estimated !== lineClasses.estimated ||
lastClasses.dataMask !== lineClasses.dataMask ||
splitOnGap) {
lines.push({
classes: lineClasses,
points: []
});
}
// Add this point to the current line.
lines[lines.length - 1].points.push(pt);
// Cache the classes for the next loop iteration.
lastClasses = lineClasses;
}
seriesLines[tsId] = lines;
});
return seriesLines;
}
Bucknell, Mary S.
committed
));
Bucknell, Mary S.
committed
/**
* Factory function creates a function that, for a given tsKey:
* @return {Object} - Mapping of parameter code Array of line segments.
Bucknell, Mary S.
committed
*/
Bucknell, Mary S.
committed
export const lineSegmentsByParmCdSelector = memoize((tsKey, period) => createSelector(
lineSegmentsSelector(tsKey, period),
timeSeriesSelector(tsKey, period),
Bucknell, Mary S.
committed
(lineSegmentsBySeriesID, timeSeriesMap, variables) => {
return Object.keys(lineSegmentsBySeriesID).reduce((byVarID, sID) => {
const series = timeSeriesMap[sID];
const parmCd = variables[series.variable].variableCode.value;
byVarID[parmCd] = byVarID[parmCd] || [];
byVarID[parmCd].push(lineSegmentsBySeriesID[sID]);
return byVarID;
}, {});
}
Bucknell, Mary S.
committed
));
Bucknell, Mary S.
committed
/**
* Factory function creates a function that, for a given tsKey:
* Returns mapping of series ID to line segments for the currently selected variable.
* @return {Object} - Keys are time series ids and values are the line segment arrays
Bucknell, Mary S.
committed
*/
export const currentVariableLineSegmentsSelector = memoize(tsKey => createSelector(
currentVariableTimeSeriesSelector(tsKey),
Bucknell, Mary S.
committed
lineSegmentsSelector(tsKey),
Bucknell, Mary S.
committed
(seriesMap, linesMap) => {
return Object.keys(seriesMap).reduce((visMap, sID) => {
Bucknell, Mary S.
committed
visMap[sID] = linesMap[sID];
return visMap;
}, {});
}
));