Skip to content
Snippets Groups Projects
Commit 00ef8384 authored by Naab, Daniel James's avatar Naab, Daniel James
Browse files

WDFN-74 - Add selection table for all available parameter codes.

TODO: support median data for all parameter codes.
parent 6ea2af61
No related branches found
No related tags found
No related merge requests found
......@@ -11,6 +11,7 @@ const { dispatch, link, provide } = require('../../lib/redux');
const { appendAxes, axesSelector } = require('./axes');
const { ASPECT_RATIO_PERCENT, MARGIN, CIRCLE_RADIUS, layoutSelector } = require('./layout');
const { drawSimpleLegend, legendDisplaySelector, createLegendMarkers } = require('./legend');
const { plotSeriesSelectTable, availableTimeseriesSelector } = require('./parameters');
const { pointsSelector, lineSegmentsSelector, isVisibleSelector, titleSelector, descriptionSelector } = require('./timeseries');
const { xScaleSelector, yScaleSelector } = require('./scales');
const { Actions, configureStore } = require('./store');
......@@ -160,9 +161,12 @@ const timeSeriesGraph = function (elem) {
yscale: yScaleSelector,
medianStatsData: pointsSelector('medianStatistics'),
showLabel: (state) => state.showMedianStatsLabel
})));
elem.call(link(plotSeriesSelectTable, createStructuredSelector({
availableTimeseries: availableTimeseriesSelector
})));
elem.append('div')
.call(link(addSROnlyTable, createStructuredSelector({
columnNames: createSelector(
......
const { createSelector } = require('reselect');
const { Actions } = require('./store');
const { dispatch } = require('../../lib/redux');
/**
* Returns metadata for each available timeseries.
* @param {Object} state Redux state
* @return {Array} Sorted array of [code, metadata] pairs.
*/
export const availableTimeseriesSelector = createSelector(
state => state.tsData,
state => state.currentParameterCode,
(tsData, currentCd) => {
const codes = {};
for (let key of Object.keys(tsData)) {
for (let code of Object.keys(tsData[key])) {
codes[code] = codes[code] || {};
codes[code] = {
description: tsData[key][code].description || codes[code].description,
type: tsData[key][code].type || codes[code].type,
selected: currentCd === code,
currentYear: key === 'current' || codes[code].currentYear === true,
previousYear: key === 'compare' || codes[code].previousYear === true
};
}
}
let sorted = [];
for (let key of Object.keys(codes).sort()) {
sorted.push([key, codes[key]]);
}
return sorted;
}
);
/**
* Draws a table with clickable rows of timeseries parameter codes. Selecting
* a row changes the active parameter code.
* @param {Object} elem d3 selection
* @param {Object} availableTimeseries Timeseries metadata to display
*/
export const plotSeriesSelectTable = function (elem, {availableTimeseries}) {
elem.select('#select-timeseries').remove();
const table = elem
.append('table')
.attr('id', 'select-timeseries')
.classed('usa-table-borderless', true);
table.append('caption').text('Select a timeseries');
table.append('thead')
.append('tr')
.selectAll('th')
.data(['Parameter Code', 'Description', 'Value Type', 'Available Now', 'Available Last Year'])
.enter().append('th')
.attr('scope', 'col')
.text(d => d);
table.append('tbody')
.selectAll('tr')
.data(availableTimeseries)
.enter().append('tr')
.classed('selected', parm => parm[1].selected)
.on('click', dispatch(function (parm) {
if (!parm[1].selected) {
return Actions.setCurrentParameterCode(parm[0]);
}
}))
.call(tr => {
tr.append('td')
.attr('scope', 'row')
.text(parm => parm[0]);
tr.append('td')
.text(parm => parm[1].description);
tr.append('td')
.text(parm => parm[1].type);
tr.append('td')
.html(parm => parm[1].currentYear ? '<i class="fa fa-check" aria-label="Current year data available"></i>' : '');
tr.append('td')
.html(parm => parm[1].previousYear ? '<i class="fa fa-check" aria-label="Previous year data available"></i>' : '');
});
};
const { availableTimeseriesSelector } = require('./parameters');
describe('Parameters module', () => {
it('availableTimeseriesSelector returns ordered parameters', () => {
const available = availableTimeseriesSelector({
tsData: {
current: {
'00060': {},
'00061': {},
'00062': {}
}
},
currentParameterCode: '00060'
});
expect(available.length).toBe(3);
expect(available[0][0]).toEqual('00060');
expect(available[1][0]).toEqual('00061');
expect(available[2][0]).toEqual('00062');
});
it('availableTimeseriesSelector sets attributes correctly', () => {
const available = availableTimeseriesSelector({
tsData: {
current: {
'00060': {description: '00060', type: '00060type'},
'00061': {description: '00061', type: '00061type'},
'00062': {description: '00062', type: '00062type'}
},
compare: {
'00061': {description: '00061', type: '00061type'},
'00062': {description: '00062', type: '00062type'},
'00063': {description: '00063', type: '00063type'}
}
},
currentParameterCode: '00060'
});
expect(available).toEqual([
['00060', {description: '00060', type: '00060type', selected: true, currentYear: true, previousYear: false}],
['00061', {description: '00061', type: '00061type', selected: false, currentYear: true, previousYear: true}],
['00062', {description: '00062', type: '00062type', selected: false, currentYear: true, previousYear: true}],
['00063', {description: '00063', type: '00063type', selected: false, currentYear: false, previousYear: true}]
]);
});
});
......@@ -11,11 +11,11 @@ export const Actions = {
return function (dispatch) {
const timeSeries = getTimeseries({sites: [siteno], params, startDate, endDate}).then(
series => {
dispatch(Actions.addTimeseries('current', series[0]));
dispatch(Actions.addTimeseries('current', series));
// Trigger a call to get last year's data
dispatch(Actions.retrieveCompareTimeseries(siteno, series[0].startTime, series[0].endTime));
return series[0];
return series;
},
() => {
dispatch(Actions.resetTimeseries('current'));
......@@ -24,9 +24,9 @@ export const Actions = {
const medianStatistics = getMedianStatistics({sites: [siteno]});
Promise.all([timeSeries, medianStatistics]).then((data) => {
const [series, stats] = data;
const startDate = series.startTime;
const endDate = series.endTime;
let unit = replaceHtmlEntities(series.name.split(' ').pop());
const startDate = series[0].startTime;
const endDate = series[0].endTime;
let unit = replaceHtmlEntities(series[0].name.split(' ').pop());
let plotableStats = parseMedianData(stats, startDate, endDate, unit);
dispatch(Actions.setMedianStatistics(plotableStats));
});
......@@ -35,7 +35,7 @@ export const Actions = {
retrieveCompareTimeseries(site, startTime, endTime) {
return function (dispatch) {
return getPreviousYearTimeseries({site, startTime, endTime}).then(
series => dispatch(Actions.addTimeseries('compare', series[0], false)),
series => dispatch(Actions.addTimeseries('compare', series, false)),
() => dispatch(Actions.resetTimeseries('compare'))
);
};
......@@ -51,8 +51,12 @@ export const Actions = {
return {
type: 'ADD_TIMESERIES',
key,
data,
show
show,
// Key the data on its parameter code
data: data.reduce(function (acc, series) {
acc[series.code] = series;
return acc;
}, {})
};
},
resetTimeseries(key) {
......@@ -78,6 +82,12 @@ export const Actions = {
type: 'RESIZE_TIMESERIES_PLOT',
width
};
},
setCurrentParameterCode(parameterCode) {
return {
type: 'PARAMETER_CODE_SET',
parameterCode
};
}
};
......@@ -91,7 +101,7 @@ export const timeSeriesReducer = function (state={}, action) {
...state.tsData,
[action.key]: {
...state.tsData[action.key],
[action.data.code]: action.data
...action.data
}
},
showSeries: {
......@@ -157,6 +167,12 @@ export const timeSeriesReducer = function (state={}, action) {
width: action.width
};
case 'PARAMETER_CODE_SET':
return {
...state,
currentParameterCode: action.parameterCode
};
default:
return state;
}
......
......@@ -16,10 +16,10 @@ describe('Redux store', () => {
});
it('should create an action to add a timeseries', () => {
expect(Actions.addTimeseries('current', 'data', false)).toEqual({
expect(Actions.addTimeseries('current', [{code: 'code1'}, {code: 'code2'}], false)).toEqual({
type: 'ADD_TIMESERIES',
key: 'current',
data: 'data',
data: {code1: {code: 'code1'}, code2: {code: 'code2'}},
show: false
});
});
......@@ -59,7 +59,7 @@ describe('Redux store', () => {
expect(timeSeriesReducer({tsData: {}}, {
type: 'ADD_TIMESERIES',
key: 'current',
data: {code: '00060'},
data: {'00060': {code: '00060'}},
show: true
})).toEqual({
tsData: {
......
......@@ -51,8 +51,11 @@ export function getTimeseries({sites, params=null, startDate=null, endDate=null}
id: series.name,
code: series.variable.variableCode[0].value,
name: replaceHtmlEntities(series.variable.variableName),
startTime: new Date(series.values[0].value[0].dateTime),
endTime: new Date(series.values[0].value.slice(-1)[0].dateTime),
type: series.variable.valueType,
startTime: series.values[0].value.length ?
new Date(series.values[0].value[0].dateTime) : null,
endTime: series.values[0].value.length ?
new Date(series.values[0].value.slice(-1)[0].dateTime) : null,
description: series.variable.variableDescription,
values: series.values[0].value.map(datum => {
let date = new Date(datum.dateTime);
......@@ -71,8 +74,10 @@ export function getTimeseries({sites, params=null, startDate=null, endDate=null}
})
};
});
}, (error) => {
return error;
})
.catch(reason => {
console.error(reason);
return [];
});
}
......@@ -185,7 +190,7 @@ export function getPreviousYearTimeseries({site, startTime, endTime}) {
return getTimeseries({sites: [site], startDate: lastYearStartTime, endDate: lastYearEndTime});
}
export function getMedianStatistics({sites, params=['00060']}) {
export function getMedianStatistics({sites, params=null}) {
let medianRDB = getSiteStatistics({sites: sites, statType: 'median', params: params});
return medianRDB.then((response) => {
return parseRDB(response);
......
$approved: darkgreen;
$estimated: black;
table#select-timeseries {
tbody {
tr {
cursor: pointer;
background-color: $color-gray-lightest;
&:hover {
td {
background-color: $color-gray-lightest;
}
}
&.selected {
cursor: default;
td {
background-color: $color-aqua-lightest;
}
}
}
}
}
.hydrograph-container {
display: inline-block;
position: relative;
......@@ -97,4 +117,5 @@ $estimated: black;
cursor: pointer;
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment