diff --git a/assets/src/scripts/components/hydrograph/axes.js b/assets/src/scripts/components/hydrograph/axes.js index e5dc6d8a572f65a964fbee7a606bd75b6eb10dc5..ecfcaa0689c537818012da99501961764a903c75 100644 --- a/assets/src/scripts/components/hydrograph/axes.js +++ b/assets/src/scripts/components/hydrograph/axes.js @@ -6,6 +6,7 @@ const { createSelector } = require('reselect'); const { layoutSelector, MARGIN } = require('./layout'); const { xScaleSelector, yScaleSelector } = require('./scales'); +const { yLabelSelector } = require('./timeseries'); const yTickCount = 5; @@ -56,7 +57,7 @@ const axesSelector = createSelector( xScaleSelector('current'), yScaleSelector, layoutSelector, - (state) => state.plotYLabel, + yLabelSelector, (xScale, yScale, layout, plotYLabel) => { return { ...createAxes({xScale, yScale}, -layout.width + MARGIN.right), diff --git a/assets/src/scripts/components/hydrograph/index.js b/assets/src/scripts/components/hydrograph/index.js index fc47f8ca38c4b290241a4947a9f319425b980c6a..832b86419a7ea4cdee37bf6c0153fd7d8773095f 100644 --- a/assets/src/scripts/components/hydrograph/index.js +++ b/assets/src/scripts/components/hydrograph/index.js @@ -11,7 +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 { pointsSelector, lineSegmentsSelector, isVisibleSelector } = require('./points'); +const { pointsSelector, lineSegmentsSelector, isVisibleSelector, titleSelector, descriptionSelector } = require('./timeseries'); const { xScaleSelector, yScaleSelector } = require('./scales'); const { Actions, configureStore } = require('./store'); const { createTooltip } = require('./tooltip'); @@ -121,8 +121,8 @@ const timeSeriesGraph = function (elem) { .append('svg') .call(link((elem, layout) => elem.attr('viewBox', `0 0 ${layout.width} ${layout.height}`), layoutSelector)) .call(link(addSVGAccessibility, createStructuredSelector({ - title: state => state.title, - description: state => state.desc, + titleSelector, + descriptionSelector, isInteractive: () => true }))) .call(link(plotLegend, createStructuredSelector({ @@ -166,7 +166,7 @@ const timeSeriesGraph = function (elem) { elem.append('div') .call(link(addSROnlyTable, createStructuredSelector({ columnNames: createSelector( - (state) => state.title, + titleSelector, (title) => [title, 'Time'] ), data: createSelector( @@ -182,7 +182,7 @@ const timeSeriesGraph = function (elem) { elem.append('div') .call(link(addSROnlyTable, createStructuredSelector({ columnNames: createSelector( - (state) => state.title, + titleSelector, (title) => [`Median ${title}`, 'Time'] ), data: createSelector( diff --git a/assets/src/scripts/components/hydrograph/index.spec.js b/assets/src/scripts/components/hydrograph/index.spec.js index 9048ab8e7cae272c553715d69fed5f37dcc566bb..1fffeff4ae2f38d2001b9e32d141f0143727d7ba 100644 --- a/assets/src/scripts/components/hydrograph/index.spec.js +++ b/assets/src/scripts/components/hydrograph/index.spec.js @@ -27,16 +27,28 @@ describe('Hydrograph charting module', () => { it('single data point renders', () => { const store = configureStore({ tsData: { - current: [{ - time: new Date(), - value: 10, - label: 'Label', - qualifiers: ['P'], - approved: false, - estimated: false - }], - compare: [], - medianStatistics: [] + current: { + '00060': { + values: [{ + time: new Date(), + value: 10, + label: 'Label', + qualifiers: ['P'], + approved: false, + estimated: false + }], + } + }, + compare: { + '00060': { + values: [] + } + }, + medianStatistics: { + '00060': { + values: [] + } + } }, showSeries: { current: true, @@ -61,16 +73,28 @@ describe('Hydrograph charting module', () => { beforeEach(() => { const store = configureStore({ tsData: { - current: [{ - time: new Date(), - value: 10, - label: 'Label', - qualifiers: ['P'], - approved: false, - estimated: false - }], - compare: [], - medianStatistics: [] + current: { + '00060': { + values: [{ + time: new Date(), + value: 10, + label: 'Label', + qualifiers: ['P'], + approved: false, + estimated: false + }], + }, + }, + compare: { + '00060': { + values: [] + } + }, + medianStatistics: { + '00060': { + values: [] + } + }, }, showSeries: { current: true, @@ -106,16 +130,28 @@ describe('Hydrograph charting module', () => { beforeEach(() => { store = configureStore({ tsData: { - current: [{ - time: new Date(), - value: 10, - label: 'Label', - qualifiers: ['P'], - approved: false, - estimated: false - }], - compare: [], - medianStatistics: MOCK_MEDIAN_STAT_DATA + current: { + '00060': { + values: [{ + time: new Date(), + value: 10, + label: 'Label', + qualifiers: ['P'], + approved: false, + estimated: false + }], + }, + }, + compare: { + '00060': { + values: [] + } + }, + medianStatistics: { + '00060': { + values: MOCK_MEDIAN_STAT_DATA + } + } }, showSeries: { current: true, @@ -168,25 +204,35 @@ describe('Hydrograph charting module', () => { beforeEach(() => { store = configureStore({ tsData: { - current: [{ - time: new Date(), - value: 10, - label: 'Label', - qualifiers: ['P'], - approved: false, - estimated: false - - }], - compare: [{ - time: new Date(), - value: 10, - label: 'Label', - qualifiers: ['P'], - approved: false, - estimated: false - - }], - medianStatistics: [] + current: { + '00060': { + values: [{ + time: new Date(), + value: 10, + label: 'Label', + qualifiers: ['P'], + approved: false, + estimated: false + }], + } + }, + compare: { + '00060': { + values: [{ + time: new Date(), + value: 10, + label: 'Label', + qualifiers: ['P'], + approved: false, + estimated: false + }], + } + }, + medianStatistics: { + '00060': { + values: [] + } + } }, showSeries: { current: true, diff --git a/assets/src/scripts/components/hydrograph/parameters.js b/assets/src/scripts/components/hydrograph/parameters.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/assets/src/scripts/components/hydrograph/points.spec.js b/assets/src/scripts/components/hydrograph/points.spec.js deleted file mode 100644 index 2df28b356ac2e08f58ff94bbd7fb118c2795f7f9..0000000000000000000000000000000000000000 --- a/assets/src/scripts/components/hydrograph/points.spec.js +++ /dev/null @@ -1,140 +0,0 @@ -const { lineSegmentsSelector } = require('./points'); - - -describe('Points module', () => { - describe('line segment selector', () => { - it('should separate on approved', () => { - expect(lineSegmentsSelector('current')({ - tsData: { - current: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 10, - qualifiers: ['P'], - approved: true, - estimated: false - }, { - value: 10, - qualifiers: ['P'], - approved: true, - estimated: false - }] - } - })).toEqual([{ - classes: { - approved: false, - estimated: false - }, - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }, { - classes: { - approved: true, - estimated: false - }, - points: [{ - value: 10, - qualifiers: ['P'], - approved: true, - estimated: false - }, { - value: 10, - qualifiers: ['P'], - approved: true, - estimated: false - }] - }]); - }); - - it('should separate on estimated', () => { - expect(lineSegmentsSelector('current')({ - tsData: { - current: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 10, - qualifiers: ['P'], - approved: false, - estimated: true - }, { - value: 10, - qualifiers: ['P'], - approved: false, - estimated: true - }] - } - })).toEqual([{ - classes: { - approved: false, - estimated: false - }, - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }, { - classes: { - approved: false, - estimated: true - }, - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: true - }, { - value: 10, - qualifiers: ['P'], - approved: false, - estimated: true - }] - }]); - }); - - it('should ignore masked values', () => { - expect(lineSegmentsSelector('current')({ - tsData: { - current: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'ICE'], - approved: false, - estimated: false - }, { - value: null, - qualifiers: ['P', 'ICE'], - approved: false, - estimated: false - }] - } - })).toEqual([{ - classes: { - approved: false, - estimated: false - }, - points: [{ - value: 10, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }]); - }); - }); -}); diff --git a/assets/src/scripts/components/hydrograph/scales.js b/assets/src/scripts/components/hydrograph/scales.js index 53364fbe15e809a2e1e020a804410f1694bd2941..0424f21ac0a183cf66086c6a27182c3fef7c5b69 100644 --- a/assets/src/scripts/components/hydrograph/scales.js +++ b/assets/src/scripts/components/hydrograph/scales.js @@ -4,6 +4,7 @@ const { createSelector, defaultMemoize: memoize } = require('reselect'); const { default: scaleSymlog } = require('../../lib/symlog'); const { layoutSelector, MARGIN } = require('./layout'); +const { pointsSelector } = require('./timeseries'); const paddingRatio = 0.2; @@ -43,18 +44,21 @@ function createXScale(values, xSize) { /** * Create an yscale oriented on the bottom - * @param {Array} tsData - where values are Array contains {value, ...} + * @param {Array} tsData - where xScale are Array contains {value, ...} * @param {Object} showSeries - keys match keys in tsData and values are Boolean * @param {Number} ySize - range of scale * @eturn {Object} d3 scale for value. */ -function createYScale(tsData, showSeries, ySize) { +function createYScale(tsData, parmCd, showSeries, ySize) { let yExtent; // Calculate max and min for data for (let key of Object.keys(tsData)) { - let points = tsData[key].filter(pt => pt.value !== null); + if (!tsData[key][parmCd]) { + continue; + } + let points = tsData[key][parmCd].values.filter(pt => pt.value !== null); if (!showSeries[key] || points.length === 0) { continue; } @@ -92,13 +96,9 @@ function createYScale(tsData, showSeries, ySize) { */ const xScaleSelector = memoize(tsDataKey => createSelector( layoutSelector, - (state) => state.tsData, - (layout, tsData) => { - if (tsData[tsDataKey]) { - return createXScale(tsData[tsDataKey], layout.width - MARGIN.right); - } else { - return null; - } + pointsSelector(tsDataKey), + (layout, points) => { + return createXScale(points, layout.width - MARGIN.right); } )); @@ -112,7 +112,8 @@ const yScaleSelector = createSelector( layoutSelector, (state) => state.tsData, (state) => state.showSeries, - (layout, tsData, showSeries) => createYScale(tsData, showSeries, layout.height - (MARGIN.top + MARGIN.bottom)) + state => state.currentParameterCode, + (layout, tsData, showSeries, parmCd) => createYScale(tsData, parmCd, showSeries, layout.height - (MARGIN.top + MARGIN.bottom)) ); diff --git a/assets/src/scripts/components/hydrograph/scales.spec.js b/assets/src/scripts/components/hydrograph/scales.spec.js index 2af362934806ae542d6e8f4074deca5058e0beb7..271432b3fd295b2331294e8b21b0acb0694f6cd4 100644 --- a/assets/src/scripts/components/hydrograph/scales.spec.js +++ b/assets/src/scripts/components/hydrograph/scales.spec.js @@ -9,10 +9,10 @@ describe('Charting scales', () => { value: hour }; }); - let tsData = {current: data}; + let tsData = {current: {'00060': {values: data}}}; let showSeries = {current: true}; - let xScale = createXScale(tsData.current, 200); - let yScale = createYScale(tsData, showSeries, 100); + let xScale = createXScale(data, 200); + let yScale = createYScale(tsData, '00060', showSeries, 100); it('scales created', () => { expect(xScale).toEqual(jasmine.any(Function)); diff --git a/assets/src/scripts/components/hydrograph/store.js b/assets/src/scripts/components/hydrograph/store.js index 66f113759239e249b2642fa2f9094a94416fbb16..6f4dd6fe0651c531448132a079b26cfd637f1fb0 100644 --- a/assets/src/scripts/components/hydrograph/store.js +++ b/assets/src/scripts/components/hydrograph/store.js @@ -1,4 +1,3 @@ -const { timeFormat } = require('d3-time-format'); const { applyMiddleware, createStore, compose } = require('redux'); const { default: thunk } = require('redux-thunk'); @@ -6,31 +5,28 @@ const { getMedianStatistics, getPreviousYearTimeseries, getTimeseries, parseMedianData } = require('../../models'); const { replaceHtmlEntities } = require('../../utils'); -// Create a time formatting function from D3's timeFormat -const formatTime = timeFormat('%c %Z'); - export const Actions = { - retrieveTimeseries(siteno, startDate=null, endDate=null) { + retrieveTimeseries(siteno, params=null, startDate=null, endDate=null) { return function (dispatch) { - const timeSeries = getTimeseries({sites: [siteno], startDate, endDate}).then( + const timeSeries = getTimeseries({sites: [siteno], params, startDate, endDate}).then( series => { - dispatch(Actions.addTimeseries('current', siteno, series[0])); + dispatch(Actions.addTimeseries('current', series[0])); // Trigger a call to get last year's data - const startTime = series[0].seriesStartDate; - const endTime = series[0].seriesEndDate; - dispatch(Actions.retrieveCompareTimeseries(siteno, startTime, endTime)); + dispatch(Actions.retrieveCompareTimeseries(siteno, series[0].startTime, series[0].endTime)); return series[0]; }, - () => dispatch(Actions.resetTimeseries('current')) + () => { + dispatch(Actions.resetTimeseries('current')); + } ); const medianStatistics = getMedianStatistics({sites: [siteno]}); Promise.all([timeSeries, medianStatistics]).then((data) => { const [series, stats] = data; - const startDate = series.seriesStartDate; - const endDate = series.seriesEndDate; - let unit = replaceHtmlEntities(series.variableName.split(' ').pop()); + const startDate = series.startTime; + const endDate = series.endTime; + let unit = replaceHtmlEntities(series.name.split(' ').pop()); let plotableStats = parseMedianData(stats, startDate, endDate, unit); dispatch(Actions.setMedianStatistics(plotableStats)); }); @@ -39,7 +35,7 @@ export const Actions = { retrieveCompareTimeseries(site, startTime, endTime) { return function (dispatch) { return getPreviousYearTimeseries({site, startTime, endTime}).then( - series => dispatch(Actions.addTimeseries('compare', site, series[0], false)), + series => dispatch(Actions.addTimeseries('compare', series[0], false)), () => dispatch(Actions.resetTimeseries('compare')) ); }; @@ -51,11 +47,10 @@ export const Actions = { show }; }, - addTimeseries(key, siteno, data, show=true) { + addTimeseries(key, data, show=true) { return { type: 'ADD_TIMESERIES', key, - siteno, data, show }; @@ -90,28 +85,20 @@ export const Actions = { export const timeSeriesReducer = function (state={}, action) { switch (action.type) { case 'ADD_TIMESERIES': - // If data is valid - if (action.data && action.data.values) { - let variableName = replaceHtmlEntities(action.data.variableName); - return { - ...state, - tsData: { - ...state.tsData, - [action.key]: action.data.values - }, - showSeries: { - ...state.showSeries, - [action.key]: action.show - }, - title: variableName, - plotYLabel: action.data.variableDescription, - desc: action.data.variableDescription + ' from ' + - formatTime(action.data.seriesStartDate) + ' to ' + - formatTime(action.data.seriesEndDate) - }; - } else { - return state.dispatch(Actions.resetTimeseries()); - } + return { + ...state, + tsData: { + ...state.tsData, + [action.key]: { + ...state.tsData[action.key], + [action.data.code]: action.data + } + }, + showSeries: { + ...state.showSeries, + [action.key]: action.show + } + }; case 'TOGGLE_TIMESERIES': return { @@ -127,7 +114,7 @@ export const timeSeriesReducer = function (state={}, action) { ...state, tsData: { ...state.tsData, - [action.key]: [] + [action.key]: {} }, showSeries: { ...state.showSeries, @@ -140,7 +127,12 @@ export const timeSeriesReducer = function (state={}, action) { ...state, tsData: { ...state.tsData, - medianStatistics: action.medianStatistics.values + medianStatistics: { + '00060': { + values: action.medianStatistics.values, + name: '00060:median' + } + } }, showSeries: { ...state.showSeries, @@ -177,9 +169,18 @@ const MIDDLEWARES = [thunk]; export const configureStore = function (initialState) { initialState = { tsData: { - current: [], - compare: [], - medianStatistics: [] + current: { + }, + compare: { + '00060': { + values: [] + } + }, + medianStatistics: { + '00060': { + values: [] + } + } }, statisticalMetaData: { beginYear: '', @@ -190,8 +191,7 @@ export const configureStore = function (initialState) { compare: false, medianStatistics: false }, - title: '', - desc: '', + currentParameterCode: '00060', width: 800, showMedianStatsLabel: false, ...initialState diff --git a/assets/src/scripts/components/hydrograph/store.spec.js b/assets/src/scripts/components/hydrograph/store.spec.js index aa3141a62a7c453c235070b854b0f87943dc5383..c487748fc621cdd05daabd94318bdf0e2f157d88 100644 --- a/assets/src/scripts/components/hydrograph/store.spec.js +++ b/assets/src/scripts/components/hydrograph/store.spec.js @@ -16,10 +16,9 @@ describe('Redux store', () => { }); it('should create an action to add a timeseries', () => { - expect(Actions.addTimeseries('current', 1234, 'data', false)).toEqual({ + expect(Actions.addTimeseries('current', 'data', false)).toEqual({ type: 'ADD_TIMESERIES', key: 'current', - siteno: 1234, data: 'data', show: false }); @@ -57,30 +56,21 @@ describe('Redux store', () => { describe('reducers', () => { it('should handle ADD_TIMESERIES', () => { - expect(timeSeriesReducer({}, { + expect(timeSeriesReducer({tsData: {}}, { type: 'ADD_TIMESERIES', key: 'current', - data: { - values: [{ - value: 'test' - }], - variableName: 'var name', - variableDescription: 'var description', - seriesStartDate: new Date('2017-01-01T15:00:00.000-06:00'), - seriesEndDate: new Date('2017-01-10T15:00:00.000-06:00') - }, + data: {code: '00060'}, show: true - })).toEqual(jasmine.objectContaining({ + })).toEqual({ tsData: { - current: [{ - value: 'test' - }] + current: { + '00060': {code: '00060'} + } }, showSeries: { current: true - }, - title: 'var name' - })); + } + }); }); it('should handle TOGGLE_TIMESERIES', () => { @@ -101,7 +91,7 @@ describe('Redux store', () => { key: 'previous' })).toEqual({ tsData: { - previous: [] + previous: {} }, showSeries: { previous: false @@ -115,7 +105,12 @@ describe('Redux store', () => { medianStatistics: {beginYear: '2000', endYear: '2010', values: ['a']} })).toEqual({ tsData: { - medianStatistics: ['a'] + medianStatistics: { + '00060': { + name: '00060:median', + values: ['a'] + } + } }, showSeries: { medianStatistics: true diff --git a/assets/src/scripts/components/hydrograph/points.js b/assets/src/scripts/components/hydrograph/timeseries.js similarity index 63% rename from assets/src/scripts/components/hydrograph/points.js rename to assets/src/scripts/components/hydrograph/timeseries.js index dedab8a8f64cd0921f1a604c767650cafcb3d3ab..5fa85d75d86114307693d8bcd553e55686347174 100644 --- a/assets/src/scripts/components/hydrograph/points.js +++ b/assets/src/scripts/components/hydrograph/timeseries.js @@ -1,5 +1,9 @@ +const { timeFormat } = require('d3-time-format'); const { createSelector, defaultMemoize: memoize } = require('reselect'); +// Create a time formatting function from D3's timeFormat +const formatTime = timeFormat('%c %Z'); + /** * Returns the points for a given timeseries. @@ -9,7 +13,14 @@ const { createSelector, defaultMemoize: memoize } = require('reselect'); */ const pointsSelector = memoize(tsDataKey => createSelector( state => state.tsData, - tsData => tsData[tsDataKey] + state => state.currentParameterCode, + (tsData, parmCd) => { + if (tsData[tsDataKey] && tsData[tsDataKey][parmCd]) { + return tsData[tsDataKey][parmCd].values; + } else { + return []; + } + } )); /** @@ -74,4 +85,37 @@ const lineSegmentsSelector = memoize(tsDataKey => createSelector( )); -module.exports = { pointsSelector, lineSegmentsSelector, isVisibleSelector }; +/** + * Returns the first valid timeseries for the currently selected parameter + * code, to be used for reference data like plot title, description, etc. + * @type {Object} Timeseries, or empty object. + */ +const referenceSeriesSelector = createSelector( + state => state.tsData['current'][state.currentParameterCode], + state => state.tsData['compare'][state.currentParameterCode], + (current, compare) => current || compare || {} +); + + +const yLabelSelector = createSelector( + referenceSeriesSelector, + series => series.description || '' +); + + +const titleSelector = createSelector( + referenceSeriesSelector, + series => series.name || '' +); + + +const descriptionSelector = createSelector( + referenceSeriesSelector, + series => series.description + ' from ' + + formatTime(series.startTime) + ' to ' + + formatTime(series.endTime) +); + + +module.exports = { pointsSelector, lineSegmentsSelector, isVisibleSelector, + yLabelSelector, titleSelector, descriptionSelector }; diff --git a/assets/src/scripts/components/hydrograph/timeseries.spec.js b/assets/src/scripts/components/hydrograph/timeseries.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..233d7c44b273d9fb07b490db8895c04e96fcfba5 --- /dev/null +++ b/assets/src/scripts/components/hydrograph/timeseries.spec.js @@ -0,0 +1,155 @@ +const { lineSegmentsSelector } = require('./timeseries'); + + +describe('Points module', () => { + describe('line segment selector', () => { + it('should separate on approved', () => { + expect(lineSegmentsSelector('current')({ + tsData: { + current: { + '00060': { + values: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: false + }, { + value: 10, + qualifiers: ['P'], + approved: true, + estimated: false + }, { + value: 10, + qualifiers: ['P'], + approved: true, + estimated: false + }] + } + } + }, + currentParameterCode: '00060' + })).toEqual([{ + classes: { + approved: false, + estimated: false + }, + points: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: false + }] + }, { + classes: { + approved: true, + estimated: false + }, + points: [{ + value: 10, + qualifiers: ['P'], + approved: true, + estimated: false + }, { + value: 10, + qualifiers: ['P'], + approved: true, + estimated: false + }] + }]); + }); + + it('should separate on estimated', () => { + expect(lineSegmentsSelector('current')({ + tsData: { + current: { + '00060': { + values: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: false + }, { + value: 10, + qualifiers: ['P'], + approved: false, + estimated: true + }, { + value: 10, + qualifiers: ['P'], + approved: false, + estimated: true + }] + } + } + }, + currentParameterCode: '00060' + })).toEqual([{ + classes: { + approved: false, + estimated: false + }, + points: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: false + }] + }, { + classes: { + approved: false, + estimated: true + }, + points: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: true + }, { + value: 10, + qualifiers: ['P'], + approved: false, + estimated: true + }] + }]); + }); + + it('should ignore masked values', () => { + expect(lineSegmentsSelector('current')({ + tsData: { + current: { + '00060': { + values: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: false + }, { + value: null, + qualifiers: ['P', 'ICE'], + approved: false, + estimated: false + }, { + value: null, + qualifiers: ['P', 'ICE'], + approved: false, + estimated: false + }] + } + } + }, + currentParameterCode: '00060' + })).toEqual([{ + classes: { + approved: false, + estimated: false + }, + points: [{ + value: 10, + qualifiers: ['P'], + approved: false, + estimated: false + }] + }]); + }); + }); +}); diff --git a/assets/src/scripts/components/hydrograph/tooltip.js b/assets/src/scripts/components/hydrograph/tooltip.js index 04ec1e53148ac559473df3bdd4c709956ab14c82..6fee3d694b100c0ac973b9a6ba4ce32549a557f1 100644 --- a/assets/src/scripts/components/hydrograph/tooltip.js +++ b/assets/src/scripts/components/hydrograph/tooltip.js @@ -166,4 +166,4 @@ const createTooltip = function(elem, {xScale, yScale, compareXScale, currentTsDa }); }; -module.exports = {getNearestTime, createTooltip}; \ No newline at end of file +module.exports = {getNearestTime, createTooltip}; diff --git a/assets/src/scripts/models.js b/assets/src/scripts/models.js index 0374b2816af659e8b1b2970bef57c37fd9a62e3c..0714688c81ff55a9f50a6785439affe5525c40fa 100644 --- a/assets/src/scripts/models.js +++ b/assets/src/scripts/models.js @@ -1,6 +1,6 @@ const { timeFormat, utcFormat } = require('d3-time-format'); const { get } = require('./ajax'); -const { deltaDays } = require('./utils'); +const { deltaDays, replaceHtmlEntities } = require('./utils'); // Define Water Services root URL - use global variable if defined, otherwise @@ -23,12 +23,12 @@ function tsServiceRoot(date) { /** * Get a given timeseries dataset from Water Services. * @param {Array} sites Array of site IDs to retrieve. - * @param {Array} params List of parameter codes + * @param {Array} params Optional array of parameter codes * @param {Date} startDate * @param {Date} endData * @return {Promise} resolves to an array of timeseries model object, rejects to an error */ -export function getTimeseries({sites, params=['00060'], startDate=null, endDate=null}) { +export function getTimeseries({sites, params=null, startDate=null, endDate=null}) { let timeParams; let serviceRoot; if (!startDate && !endDate) { @@ -40,48 +40,46 @@ export function getTimeseries({sites, params=['00060'], startDate=null, endDate= timeParams = `startDT=${startString}&endDT=${endString}`; serviceRoot = tsServiceRoot(startDate); } - let url = `${serviceRoot}/iv/?sites=${sites.join(',')}¶meterCd=${params.join(',')}&${timeParams}&indent=on&siteStatus=all&format=json`; + let paramCds = params !== null ? `¶meterCd=${params.join(',')}` : ''; + let url = `${serviceRoot}/iv/?sites=${sites.join(',')}${paramCds}&${timeParams}&indent=on&siteStatus=all&format=json`; return get(url) .then((response) => { - let data = JSON.parse(response); - return data.value.timeSeries.map(series => { - let startDate = new Date(series.values[0].value[0].dateTime); - let endDate = new Date( - series.values[0].value.slice(-1)[0].dateTime); - let noDataValue = series.variable.noDataValue; + let data = JSON.parse(response); + return data.value.timeSeries.map(series => { + let noDataValue = series.variable.noDataValue; + return { + 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), + description: series.variable.variableDescription, + values: series.values[0].value.map(datum => { + let date = new Date(datum.dateTime); + let value = parseFloat(datum.value); + if (value === noDataValue) { + value = null; + } return { - code: series.variable.variableCode[0].value, - variableName: series.variable.variableName, - variableDescription: series.variable.variableDescription, - seriesStartDate: startDate, - seriesEndDate: endDate, - values: series.values[0].value.map(datum => { - let date = new Date(datum.dateTime); - let value = parseFloat(datum.value); - if (value === noDataValue) { - value = null; - } - return { - time: date, - value: value, - qualifiers: datum.qualifiers, - approved: datum.qualifiers.indexOf('A') > -1, - estimated: datum.qualifiers.indexOf('E') > -1, - label: `${formatTime(date)}\n${value} ${series.variable.unit.unitCode} (Qualifiers: ${datum.qualifiers.join(', ')})` - }; - }) + time: date, + value: value, + qualifiers: datum.qualifiers, + approved: datum.qualifiers.indexOf('A') > -1, + estimated: datum.qualifiers.indexOf('E') > -1, + label: `${formatTime(date)}\n${value} ${series.variable.unit.unitCode} (Qualifiers: ${datum.qualifiers.join(', ')})` }; - } - ); - }, - (error) => { - return error; + }) + }; }); + }, (error) => { + return error; + }); } -export function getSiteStatistics({sites, statType, params=['00060']}) { - let url = `${SERVICE_ROOT}/stat/?format=rdb&sites=${sites.join(',')}&statReportType=daily&statTypeCd=${statType}¶meterCd=${params.join(',')}`; +export function getSiteStatistics({sites, statType, params=null}) { + let paramCds = params !== null ? `¶meterCd=${params.join(',')}` : ''; + let url = `${SERVICE_ROOT}/stat/?format=rdb&sites=${sites.join(',')}&statReportType=daily&statTypeCd=${statType}${paramCds}`; return get(url); } diff --git a/assets/src/scripts/models.spec.js b/assets/src/scripts/models.spec.js index d7d22c89c017307aad4e478c0d72c1986d1c0664..e7cf3796b343c333d05c93d8ee09793054a0bcaa 100644 --- a/assets/src/scripts/models.spec.js +++ b/assets/src/scripts/models.spec.js @@ -61,12 +61,12 @@ describe('Models module', () => { models.getTimeseries({sites: [siteID], params: [paramCode]}).then((series) => { expect(series.length).toBe(1); expect(series[0].code).toBe(paramCode); - expect(series[0].variableName).toBe('Streamflow, ft³/s'); - expect(series[0].variableDescription). + expect(series[0].name).toBe('Streamflow, ft³/s'); + expect(series[0].description). toBe('Discharge, cubic feet per second'); - expect(series[0].seriesStartDate). + expect(series[0].startTime). toEqual(new Date('1/2/2018, 3:00:00 PM -0600')); - expect(series[0].seriesEndDate). + expect(series[0].endTime). toEqual(new Date('1/9/2018, 2:15:00 PM -0600')); expect(series[0].values.length).toBe(670); done(); @@ -126,12 +126,12 @@ describe('Models module', () => { models.getPreviousYearTimeseries({site: siteID, startTime: startDate, endTime: endDate}).then((series) => { expect(series.length).toBe(1); expect(series[0].code).toBe(paramCode); - expect(series[0].variableName).toBe('Streamflow, ft³/s'); - expect(series[0].variableDescription). + expect(series[0].name).toBe('Streamflow, ft³/s'); + expect(series[0].description). toBe('Discharge, cubic feet per second'); - expect(series[0].seriesStartDate). + expect(series[0].startTime). toEqual(new Date('1/2/2017, 3:00:00 PM -0600')); - expect(series[0].seriesEndDate). + expect(series[0].endTime). toEqual(new Date('1/2/2017, 4:45:00 PM -0600')); expect(series[0].values.length).toBe(8); done();