diff --git a/CHANGELOG.md b/CHANGELOG.md index e819c0dd184381bb209f0e74ec1d8074c4311d0d..7bc0777f45b2545f54207efe1d6356571ef0967f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased](https://github.com/usgs/waterdataui/compare/waterdataui-0.26.0...master) +### Changed +- All IV time series are now available for drawing on the hydrograph not just those in the last 7 days. ## [0.26.0](https://github.com/usgs/waterdataui/compare/waterdataui-0.25.0...waterdataui-0.26.0) - 2020-02-12 ### Added diff --git a/assets/src/scripts/components/hydrograph/audible.js b/assets/src/scripts/components/hydrograph/audible.js index 149810fcc936341a5b9363efba6efcad0d4a0be5..7c7985eb12d3574d53428a21bf4b1bbd8481c088 100644 --- a/assets/src/scripts/components/hydrograph/audible.js +++ b/assets/src/scripts/components/hydrograph/audible.js @@ -2,9 +2,10 @@ import { scaleLinear } from 'd3-scale'; import { select } from 'd3-selection'; import memoize from 'fast-memoize'; import { createSelector, createStructuredSelector } from 'reselect'; + +import {getTimeSeries} from '../../selectors/time-series-selector'; import { tsCursorPointsSelector } from './cursor'; import { getMainXScale, getMainYScale } from './scales'; -import { allTimeSeriesSelector } from './time-series'; import config from '../../config'; import { link } from '../../lib/d3-redux'; import { Actions } from '../../store'; @@ -82,7 +83,7 @@ const audibleScaleSelector = createSelector( ); const audiblePointsSelector = createSelector( - allTimeSeriesSelector, + getTimeSeries, tsCursorPointsSelector('current'), tsCursorPointsSelector('compare'), audibleScaleSelector, diff --git a/assets/src/scripts/components/hydrograph/cursor.js b/assets/src/scripts/components/hydrograph/cursor.js index b469df21141e26331bbd669d6936a7dcef9753f6..479d898de935d50750da74a2567c82976cba0a85 100644 --- a/assets/src/scripts/components/hydrograph/cursor.js +++ b/assets/src/scripts/components/hydrograph/cursor.js @@ -99,7 +99,8 @@ export const tsCursorPointsSelector = memoize(tsKey => createSelector( return {}; } return Object.keys(timeSeries).reduce((data, tsId) => { - if (!config.MULTIPLE_TIME_SERIES_METADATA_SELECTOR_ENABLED || parseInt(tsId.split(':')[0]) === currentMethodId) { + if (timeSeries[tsId].length && + (!config.MULTIPLE_TIME_SERIES_METADATA_SELECTOR_ENABLED || parseInt(tsId.split(':')[0]) === currentMethodId)) { const datum = getNearestTime(timeSeries[tsId], cursorTime).datum; data[tsId] = { ...datum, diff --git a/assets/src/scripts/components/hydrograph/drawing-data.js b/assets/src/scripts/components/hydrograph/drawing-data.js index 8c74b78adeeb7dce8f4c08f6be76a76e49373b64..da476aab69e31c298861996ee3e4dab92b5de75d 100644 --- a/assets/src/scripts/components/hydrograph/drawing-data.js +++ b/assets/src/scripts/components/hydrograph/drawing-data.js @@ -6,11 +6,11 @@ import { createSelector } from 'reselect'; import { format } from 'd3-format'; import config from '../../config'; -import { getVariables, getCurrentMethodID, getTsRequestKey, getRequestTimeRange, getIanaTimeZone } +import { getVariables, getCurrentMethodID, getTimeSeries, getTsRequestKey, getRequestTimeRange, getIanaTimeZone } from '../../selectors/time-series-selector'; import { getCurrentVariableMedianStatistics } from '../../selectors/median-statistics-selector'; -import { allTimeSeriesSelector, currentVariableTimeSeriesSelector, timeSeriesSelector } from './time-series'; +import { getCurrentVariableTimeSeries, getTimeSeriesForTsKey } from './time-series'; export const MASK_DESC = { @@ -68,8 +68,8 @@ const transformToCumulative = function(points) { * @return {Object} where the keys are ts ids and the values are an Array of point Objects. */ export const allPointsSelector = createSelector( - allTimeSeriesSelector, - state => state.series.variables, + getTimeSeries, + getVariables, (timeSeries, variables) => { let allPoints = {}; Object.keys(timeSeries).forEach((tsId) => { @@ -94,7 +94,7 @@ export const allPointsSelector = createSelector( export const pointsByTsKeySelector = memoize((tsKey, period) => createSelector( getTsRequestKey(tsKey, period), allPointsSelector, - state => state.series.timeSeries, + getTimeSeries, (tsRequestKey, points, timeSeries) => { let result = {}; Object.keys(points).forEach((tsId) => { @@ -113,7 +113,7 @@ export const pointsByTsKeySelector = memoize((tsKey, period) => createSelector( */ export const currentVariablePointsByTsIdSelector = memoize(tsKey => createSelector( pointsByTsKeySelector(tsKey), - currentVariableTimeSeriesSelector(tsKey), + getCurrentVariableTimeSeries(tsKey), (points, timeSeries) => { let result = {}; if (points) { @@ -133,7 +133,7 @@ export const currentVariablePointsByTsIdSelector = memoize(tsKey => createSelect */ export const currentVariablePointsSelector = memoize(tsKey => createSelector( pointsByTsKeySelector(tsKey), - currentVariableTimeSeriesSelector(tsKey), + getCurrentVariableTimeSeries(tsKey), (points, timeSeries) => { return timeSeries ? Object.keys(timeSeries).map((tsId) => points[tsId]) : []; } @@ -154,22 +154,6 @@ export const pointsSelector = memoize((tsKey) => createSelector( } )); - -/** - * 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 @@ -267,29 +251,6 @@ export const visiblePointsSelector = createSelector( ); -/** - * 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]. - */ -export const pointsTableDataSelector = memoize(tsKey => createSelector( - pointsByTsKeySelector(tsKey), - (allPoints) => { - return Object.keys(allPoints).reduce((databyTsId, tsId) => { - databyTsId[tsId] = allPoints[tsId].map((value) => { - return [ - value.value || '', - value.dateTime || '', - value.qualifiers && value.qualifiers.length > 0 ? value.qualifiers.join(', ') : '' - ]; - }); - return databyTsId; - }, {}); - } -)); - const getLineClasses = function(pt, isCurrentMethod) { let dataMask = null; if (pt.value === null) { @@ -376,7 +337,7 @@ export const lineSegmentsSelector = memoize((tsKey, period) => createSelector( */ export const lineSegmentsByParmCdSelector = memoize((tsKey, period) => createSelector( lineSegmentsSelector(tsKey, period), - timeSeriesSelector(tsKey, period), + getTimeSeriesForTsKey(tsKey, period), getVariables, (lineSegmentsBySeriesID, timeSeriesMap, variables) => { return Object.keys(lineSegmentsBySeriesID).reduce((byVarID, sID) => { @@ -396,7 +357,7 @@ export const lineSegmentsByParmCdSelector = memoize((tsKey, period) => createSel * @return {Object} - Keys are time series ids and values are the line segment arrays */ export const currentVariableLineSegmentsSelector = memoize(tsKey => createSelector( - currentVariableTimeSeriesSelector(tsKey), + getCurrentVariableTimeSeries(tsKey), lineSegmentsSelector(tsKey), (seriesMap, linesMap) => { return Object.keys(seriesMap).reduce((visMap, sID) => { diff --git a/assets/src/scripts/components/hydrograph/drawing-data.spec.js b/assets/src/scripts/components/hydrograph/drawing-data.spec.js index 3b7a0f95ad6de73f8546f791d139f5be740654ee..ed0f6bd287f0812e87a391c4da96ad27300a5a33 100644 --- a/assets/src/scripts/components/hydrograph/drawing-data.spec.js +++ b/assets/src/scripts/components/hydrograph/drawing-data.spec.js @@ -1,6 +1,6 @@ import { DateTime } from 'luxon'; -import { lineSegmentsSelector, pointsSelector, pointsTableDataSelector, allPointsSelector, pointsByTsKeySelector, classesForPoint, lineSegmentsByParmCdSelector, currentVariableLineSegmentsSelector, currentVariablePointsSelector, currentVariablePointsByTsIdSelector, visiblePointsSelector, getCurrentVariableMedianStatPoints, MAX_LINE_POINT_GAP } from './drawing-data'; +import { lineSegmentsSelector, pointsSelector, allPointsSelector, pointsByTsKeySelector, classesForPoint, lineSegmentsByParmCdSelector, currentVariableLineSegmentsSelector, currentVariablePointsSelector, currentVariablePointsByTsIdSelector, visiblePointsSelector, getCurrentVariableMedianStatPoints, MAX_LINE_POINT_GAP } from './drawing-data'; const TEST_DATA = { series: { @@ -50,12 +50,12 @@ const TEST_DATA = { estimated: false }] }, - '69928:00010': { + '69929:00010': { tsKey: 'compare:P7D', startTime: new Date('2017-03-06T15:45:00.000Z'), endTime: new Date('2017-03-13t13:45:00.000Z'), variable: '45807196', - method: 69928, + method: 69929, points: [{ value: 1, qualifiers: ['P'], @@ -73,12 +73,12 @@ const TEST_DATA = { estimated: false }] }, - '69928:00045': { + '69930:00045': { tsKey: 'current:P7D', startTime: new Date('2017-03-06T15:45:00.000Z'), endTime: new Date('2017-03-13t13:45:00.000Z'), variable: '45807140', - method: 69928, + method: 69930, points: [{ value: 0, qualifiers: ['P'], @@ -206,8 +206,8 @@ describe('drawingData module', () => { it('Return three time series', () => { expect(Object.keys(result).length).toBe(3); expect(result['69928:00060']).toBeDefined(); - expect(result['69928:00010']).toBeDefined(); - expect(result['69928:00045']).toBeDefined(); + expect(result['69929:00010']).toBeDefined(); + expect(result['69930:00045']).toBeDefined(); }); it('Return the points array for time series with parameter code 00060 without modification', () => { @@ -215,7 +215,7 @@ describe('drawingData module', () => { }); it('Return the points array accumulated for the time series with parameter code 00045', () => { - expect(result['69928:00045'].map((point) => point.value)).toEqual([0, 0.01, 0.03, 0.06]); + expect(result['69930:00045'].map((point) => point.value)).toEqual([0, 0.01, 0.03, 0.06]); }); it('Return the empty object if there are no time series', () => { @@ -229,11 +229,12 @@ describe('drawingData module', () => { ...TEST_DATA.series, timeSeries: { ...TEST_DATA.series.timeSeries, - '69928:00045': { + '69930:00045': { tsKey: 'current:P7D', startTime: new Date('2017-03-06T15:45:00.000Z'), endTime: new Date('2017-03-13t13:45:00.000Z'), variable: '45807140', + method: '69930', points: [{ value: 0.01, qualifiers: ['P'], @@ -260,7 +261,7 @@ describe('drawingData module', () => { } }; - expect(allPointsSelector(newTestData)['69928:00045'].map((point) => point.value)).toEqual([0.01, 0.03, null, 0.04]); + expect(allPointsSelector(newTestData)['69930:00045'].map((point) => point.value)).toEqual([0.01, 0.03, null, 0.04]); }); }); @@ -270,7 +271,7 @@ describe('drawingData module', () => { expect(Object.keys(result).length).toBe(2); expect(result['69928:00060']).toBeDefined(); - expect(result['69928:00045']).toBeDefined(); + expect(result['69930:00045']).toBeDefined(); }); it('return the empty object if no time series for series', () => { @@ -326,8 +327,8 @@ describe('drawingData module', () => { }], tsKey: 'current:P7D' }, - '69928:00045': { - ...TEST_DATA.series.timeSeries['69928:00045'], + '69930:00045': { + ...TEST_DATA.series.timeSeries['69930:00045'], tsKey: 'compare:P7D' } } @@ -383,6 +384,7 @@ describe('drawingData module', () => { ...TEST_DATA.series.timeSeries, '69928:00060': { ...TEST_DATA.series.timeSeries['69928:00060'], + variable: '45807197', points: [{ value: 10, qualifiers: ['P'] @@ -395,8 +397,8 @@ describe('drawingData module', () => { }], tsKey: 'current:P7D' }, - '69928:00045': { - ...TEST_DATA.series.timeSeries['00045'], + '69930:00045': { + ...TEST_DATA.series.timeSeries['69930:00045'], tsKey: 'compare:P7D' } } @@ -468,8 +470,8 @@ describe('drawingData module', () => { }], tsKey: 'current:P7D' }, - '69928:00045': { - ...TEST_DATA.series.timeSeries['69928:00045'], + '69930:00045': { + ...TEST_DATA.series.timeSeries['69930:00045'], tsKey: 'compare:P7D' } } @@ -555,8 +557,8 @@ describe('drawingData module', () => { }), tsKey: 'current:P7D' }, - '69928:00045': { - ...TEST_DATA.series.timeSeries['69928:00045'], + '69930:00045': { + ...TEST_DATA.series.timeSeries['69930:00045'], tsKey: 'compare:P7D' } } @@ -633,8 +635,8 @@ describe('drawingData module', () => { }), tsKey: 'current:P7D' }, - '69928:00045': { - ...TEST_DATA.series.timeSeries['69928:00045'], + '69930:00045': { + ...TEST_DATA.series.timeSeries['69930:00045'], tsKey: 'compare:P7D' } } @@ -728,7 +730,7 @@ describe('drawingData module', () => { }] } ], - '69928:00045': [ + '69930:00045': [ { 'classes': { 'approved': false, @@ -895,69 +897,6 @@ describe('drawingData module', () => { }); }); - describe('pointsTableDataSelect', () => { - it('Return an array of arrays if series is visible', () => { - const result = pointsTableDataSelector('current')({ - series: { - requests: { - 'current:P7D': { - timeSeriesCollections: ['coll1'] - } - }, - timeSeriesCollections: { - 'coll1': { - variable: 45807197, - timeSeries: ['one'] - } - }, - timeSeries: { - one: { - tsKey: 'current:P7D', - points: [{ - dateTime: '2018-01-01', - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 15, - approved: false, - estimated: true - }, { - value: 10, - dateTime: '2018-01-03', - qualifiers: ['P', 'Ice'], - approved: false, - estimated: true - }], - variable: '45807197' - } - }, - variables: { - '45807197': { - variableCode: {value: '00060'}, - oid: 45807197 - } - }, - ianaTimeZone: 'Pacific/Honolulu' - }, - timeSeriesState: { - currentVariableID: '45807197', - currentDateRange: 'P7D', - showSeries: { - current: true - } - } - }); - expect(result).toEqual({ - one: [ - ['', '2018-01-01', 'P'], - [15, '', ''], - [10, '2018-01-03', 'P, Ice'] - ] - }); - }); - }); - describe('getCurrentVariableMedianStatPoints', () => { const TEST_VARS = { '45807042': { @@ -977,7 +916,7 @@ describe('drawingData module', () => { queryInfo: { 'current:P7D': { notes: { - requestDT: (new Date(2017, 2, 1, 11, 15, 0, 0)).getTime(), + requestDT: new Date(2017, 2, 1, 11, 15, 0, 0).getTime(), 'filter:timeRange': { mode: 'PERIOD', periodDays: '7', diff --git a/assets/src/scripts/components/hydrograph/graph-controls.js b/assets/src/scripts/components/hydrograph/graph-controls.js index 483c0f62f1f28af855d8b9f631d459cbc9c9bddc..fb53e3695d7a1d4015ea25695ea80feb089d0825 100644 --- a/assets/src/scripts/components/hydrograph/graph-controls.js +++ b/assets/src/scripts/components/hydrograph/graph-controls.js @@ -5,7 +5,7 @@ import { Actions } from '../../store'; import { link } from '../../lib/d3-redux'; import { audibleUI } from './audible'; import { getCurrentVariableMedianStatistics } from '../../selectors/median-statistics-selector'; -import { isVisibleSelector, currentVariableTimeSeriesSelector } from './time-series'; +import { isVisibleSelector, getCurrentVariableTimeSeries } from './time-series'; /* * Create the show audible toggle, last year toggle, and median toggle for the time series graph. @@ -40,7 +40,7 @@ export const drawGraphControls = function(elem, store) { const exists = Object.keys(compareTimeSeries) ? Object.values(compareTimeSeries).filter(tsValues => tsValues.points.length).length > 0 : false; elem.property('disabled', !exists); - }, currentVariableTimeSeriesSelector('compare'))) + }, getCurrentVariableTimeSeries('compare'))) // Sets the state of the toggle .call(link(store,function(elem, checked) { elem.property('checked', checked); diff --git a/assets/src/scripts/components/hydrograph/index.js b/assets/src/scripts/components/hydrograph/index.js index 4fedf1261584fe5c6d711bd0d17bf18ca5d3e6dd..38ec2298a9a95a54abd9e4a5254276631b80eb4c 100644 --- a/assets/src/scripts/components/hydrograph/index.js +++ b/assets/src/scripts/components/hydrograph/index.js @@ -7,7 +7,7 @@ import {createStructuredSelector} from 'reselect'; import {drawWarningAlert, drawInfoAlert} from '../../d3-rendering/alerts'; import {link} from '../../lib/d3-redux'; -import {isLoadingTS, hasAnyTimeSeries} from '../../selectors/time-series-selector'; +import {isLoadingTS, hasAnyTimeSeries, getTimeSeries} from '../../selectors/time-series-selector'; import {Actions} from '../../store'; import {cursorSlider} from './cursor'; @@ -21,7 +21,6 @@ import {drawLoadingIndicator} from '../../d3-rendering/loading-indicator'; import {drawMethodPicker} from './method-picker'; import {plotSeriesSelectTable, availableTimeSeriesSelector} from './parameters'; import {timeSeriesScalesByParmCdSelector} from './scales'; -import {allTimeSeriesSelector} from './time-series'; import {drawTimeSeriesGraph} from './time-series-graph'; @@ -118,7 +117,7 @@ export const attachToNode = function (store, nodeElem.select('.provisional-data-alert') .call(link(store, function(elem, allTimeSeries) { elem.attr('hidden', Object.keys(allTimeSeries).length ? null : true); - }, allTimeSeriesSelector)); + }, getTimeSeries)); } window.onresize = function() { diff --git a/assets/src/scripts/components/hydrograph/method-picker.spec.js b/assets/src/scripts/components/hydrograph/method-picker.spec.js index 1cf4cb9fc984217658c4f64b7bc09e8fbe3a7c5d..b2391a7450ca8fbc9ac4606b62335abb5fa348d0 100644 --- a/assets/src/scripts/components/hydrograph/method-picker.spec.js +++ b/assets/src/scripts/components/hydrograph/method-picker.spec.js @@ -18,15 +18,15 @@ describe('method-picker', () => { const TEST_STATE = { series: { timeSeries: { - '00010:current': { + '69930:00010:current': { points: DATA, tsKey: 'current:P7D', variable: '00010id', method: 69930 }, - '00010:compare': { + '69931:00010:current': { points: DATA, - tsKey: 'compare:P7D', + tsKey: 'current:P7D', variable: '00010id', method: 69931 } @@ -67,35 +67,15 @@ describe('method-picker', () => { div.remove(); }); - it('Creates a picker and sets the currentMethodID', () => { + it('Creates a picker and sets the currentMethodID', (done) => { let store = configureStore(TEST_STATE); div.call(drawMethodPicker, store); - - expect(div.select('div').property('hidden')).toEqual(false); - expect(div.select('select').property('value')).toEqual('69930'); - expect(store.getState().timeSeriesState.currentMethodID).toEqual(69930); - }); - - it('Creates a picker and sets the hidden property based on the number of methods', () => { - let TEST_STATE_ONE_METHOD = { - ...TEST_STATE, - series: { - ...TEST_STATE.series, - timeSeries: { - ...TEST_STATE.series.timeSeries, - '00010:compare': { - ...TEST_STATE.series.timeSeries['00010:compare'], - method: 69930 - } - } - } - }; - let storeWithOneMethod = configureStore(TEST_STATE_ONE_METHOD); - div.call(drawMethodPicker, storeWithOneMethod); - - expect(div.select('div').property('hidden')).toEqual(true); - expect(div.select('select').property('value')).toEqual('69930'); - expect(storeWithOneMethod.getState().timeSeriesState.currentMethodID).toEqual(69930); + window.requestAnimationFrame(() => { + expect(div.select('div').property('hidden')).toEqual(false); + expect(div.select('select').property('value')).toEqual('69930'); + expect(store.getState().timeSeriesState.currentMethodID).toEqual(69930); + done(); + }); }); }); }); diff --git a/assets/src/scripts/components/hydrograph/parameters.js b/assets/src/scripts/components/hydrograph/parameters.js index 9889bebb3efc59e6a0dd768ba9cd5a26f4cd2fa4..153465c307c7621a1294a1e4d093cd732128c87f 100644 --- a/assets/src/scripts/components/hydrograph/parameters.js +++ b/assets/src/scripts/components/hydrograph/parameters.js @@ -2,7 +2,7 @@ import {createSelector} from 'reselect'; import {line} from 'd3-shape'; import {select} from 'd3-selection'; -import {getVariables, getCurrentVariableID} from '../../selectors/time-series-selector'; +import {getVariables, getCurrentVariableID, getTimeSeries} from '../../selectors/time-series-selector'; import {Actions} from '../../store'; import {appendTooltip} from '../../tooltips'; @@ -10,7 +10,6 @@ import {sortedParameters} from '../../utils'; import {MASK_DESC} from './drawing-data'; import {SPARK_LINE_DIM, CIRCLE_RADIUS_SINGLE_PT} from './layout'; -import {allTimeSeriesSelector} from './time-series'; /** * Returns metadata for each available time series. @@ -19,7 +18,7 @@ import {allTimeSeriesSelector} from './time-series'; */ export const availableTimeSeriesSelector = createSelector( getVariables, - allTimeSeriesSelector, + getTimeSeries, getCurrentVariableID, (variables, timeSeries, currentVariableID) => { if (!variables) { @@ -32,7 +31,7 @@ export const availableTimeSeriesSelector = createSelector( const sortedVariables = sortedParameters(variables).map(x => x.oid); for (const variableID of sortedVariables) { // start the next iteration if a variable is not a - // series returned by the allTimeSeriesSelector + // series returned by the getTimeSeries if (!timeSeriesVariables.includes(variableID)) { continue; } @@ -60,6 +59,9 @@ export const availableTimeSeriesSelector = createSelector( * @param {Object} scales - has x property for x scale and y property for y scale */ export const addSparkLine = function(svgSelection, {seriesLineSegments, scales}) { + if (seriesLineSegments.length === 0) { + return; + } let spark = line() .x(function(d) { return scales.x(d.dateTime); @@ -118,16 +120,6 @@ export const addSparkLine = function(svgSelection, {seriesLineSegments, scales}) } }; - -/** - * Draws a table with clickable rows of time series parameter codes. Selecting - * a row changes the active parameter code. - * @param {Object} elem d3 selection - * @param {String} siteno - * @param {Object} availableTimeSeries Time series metadata to display - * @param {Object} lineSegmentsByParmCd line segments for each parameter code - * @param {Object} timeSeriesScalesByParmCd scales for each parameter code - */ /** * Draws a table with clickable rows of time series parameter codes. Selecting * a row changes the active parameter code. diff --git a/assets/src/scripts/components/hydrograph/parameters.spec.js b/assets/src/scripts/components/hydrograph/parameters.spec.js index 29e25cff46b857cc44db80a109d587ec2b6259f8..769f2f8eaa4d52d4c6ec2b53fbce0a3f5da63cf8 100644 --- a/assets/src/scripts/components/hydrograph/parameters.spec.js +++ b/assets/src/scripts/components/hydrograph/parameters.spec.js @@ -115,7 +115,7 @@ describe('Parameters module', () => { ]); }); - it('time series without data points are considered unavailable', () => { + it('time series without data points are considered available', () => { const available = availableTimeSeriesSelector({ series: { timeSeries: { @@ -164,6 +164,7 @@ describe('Parameters module', () => { // Series are ordered by parameter code and have expected values. expect(available).toEqual([ ['00060', {variableID: 'code0', description: 'code0 desc', selected: true, currentTimeSeriesCount: 1}], + ['00061', {variableID: 'code1', description: 'code1 desc', selected: false, currentTimeSeriesCount: 1}], ['00062', {variableID: 'code2', description: 'code2 desc', selected: false, currentTimeSeriesCount: 1}] ]); }); diff --git a/assets/src/scripts/components/hydrograph/scales.js b/assets/src/scripts/components/hydrograph/scales.js index d11d27e414804e4006c8f6e209e0b0ee309bc05f..0bc80d08a704ee79fdb01398082198b7e418d61c 100644 --- a/assets/src/scripts/components/hydrograph/scales.js +++ b/assets/src/scripts/components/hydrograph/scales.js @@ -3,7 +3,7 @@ import memoize from 'fast-memoize'; import { createSelector } from 'reselect'; import { getYDomain, SYMLOG_PARMS } from './domain'; import { getLayout } from './layout'; -import { timeSeriesSelector, TEMPERATURE_PARAMETERS } from './time-series'; +import { getTimeSeriesForTsKey, TEMPERATURE_PARAMETERS } from './time-series'; import { visiblePointsSelector, pointsByTsKeySelector } from './drawing-data'; import { getVariables, getCurrentParmCd, getRequestTimeRange } from '../../selectors/time-series-selector'; import { convertCelsiusToFahrenheit, convertFahrenheitToCelsius } from '../../utils'; @@ -131,7 +131,7 @@ export const getSecondaryYScale = memoize(kind => createSelector( */ const parmCdPointsSelector = memoize((tsKey, period) => createSelector( pointsByTsKeySelector(tsKey, period), - timeSeriesSelector(tsKey, period), + getTimeSeriesForTsKey(tsKey, period), getVariables, (tsPoints, timeSeries, variables) => { return Object.keys(tsPoints).reduce((byParmCd, tsID) => { diff --git a/assets/src/scripts/components/hydrograph/scales.spec.js b/assets/src/scripts/components/hydrograph/scales.spec.js index 6e8625848ab06f16e7f60447e176341bde7a7300..2eec18d6fade099f3da78157413e10e516b6b3af 100644 --- a/assets/src/scripts/components/hydrograph/scales.spec.js +++ b/assets/src/scripts/components/hydrograph/scales.spec.js @@ -133,7 +133,10 @@ describe('scales', () => { } }, timeSeries: { - '00060ID': {} + '00060ID': { + variable: '00060ID', + points: [] + } } }, statisticsData: {}, @@ -166,7 +169,10 @@ describe('scales', () => { } }, timeSeries: { - '00060ID': {} + '00060ID': { + variable: '00060ID', + points: [] + } } }, statisticsData: {}, @@ -195,7 +201,10 @@ describe('scales', () => { } }, timeSeries: { - '00010ID': {} + '00010ID': { + variable: '00010ID', + points: [] + } } }, statisticsData: {}, @@ -225,7 +234,10 @@ describe('scales', () => { } }, timeSeries: { - '00011ID': {} + '00011ID': { + variable: '00011ID', + points: [] + } } }, statisticsData: {}, diff --git a/assets/src/scripts/components/hydrograph/time-series.js b/assets/src/scripts/components/hydrograph/time-series.js index 3eb3a8ad2922df35119b420a12c0c9b392e6b524..7f0c9e970ccda4e40690fe34a2a38397697cc74e 100644 --- a/assets/src/scripts/components/hydrograph/time-series.js +++ b/assets/src/scripts/components/hydrograph/time-series.js @@ -6,7 +6,7 @@ import { createSelector } from 'reselect'; import { getRequestTimeRange, getCurrentVariable, getTsRequestKey, getIanaTimeZone, getCurrentParmCd, getCurrentMethodID, - getMethods + getMethods, getTimeSeries } from '../../selectors/time-series-selector'; @@ -38,28 +38,28 @@ export const TEMPERATURE_PARAMETERS = { // Create a time formatting function from D3's timeFormat const formatTime = timeFormat('%c %Z'); - - /** - * @return {Object} Mapping of time series ID to time series details. Only time series with non zero points are returned + * Returns a selector that, for a given tsKey and period: + * Selects all time series data. If period is null then, the currentDateRange is used + * @param {String} tsKey Time-series key + * @param {String} period or null date range using ISO-8601 duration; + * @param {Object} state Redux state + * @return {Object} - Keys are tsID, values are time-series data */ -export const allTimeSeriesSelector = createSelector( - state => state.series, - (stateSeries) => { - let timeSeries = {}; - if (stateSeries && stateSeries.hasOwnProperty('timeSeries')) { - let stateTimeSeries = stateSeries.timeSeries; - for (let key of Object.keys(stateTimeSeries)) { - const ts = stateTimeSeries[key]; - if (ts.hasOwnProperty('points') && ts.points.length > 0) { - timeSeries[key] = ts; - } +export const getTimeSeriesForTsKey = memoize((tsKey, period) => createSelector( + getTsRequestKey(tsKey, period), + getTimeSeries, + (tsRequestKey, timeSeries) => { + let x = {}; + Object.keys(timeSeries).forEach(key => { + const series = timeSeries[key]; + if (series.tsKey === tsRequestKey) { + x[key] = series; } - } - return timeSeries; + }); + return x; } -); - +)); /** * Returns a selector that, for a given tsKey: @@ -68,32 +68,10 @@ export const allTimeSeriesSelector = createSelector( * @param {Object} state Redux state * @return {Object} Time-series data */ -export const currentVariableTimeSeriesSelector = memoize(tsKey => createSelector( - getTsRequestKey(tsKey), - allTimeSeriesSelector, - getCurrentVariable, - (tsRequestKey, timeSeries, variable) => { - let ts = {}; - if (variable) { - Object.keys(timeSeries).forEach(key => { - const series = timeSeries[key]; - if (series.tsKey === tsRequestKey && series.variable === variable.oid) { - ts[key] = series; - } - }); - } - return ts; - } -)); - -/* - * Return all times series for the current variable. - * @ return {Object} time series data - */ -export const getAllTimeSeriesForCurrentVariable = createSelector( +export const getCurrentVariableTimeSeries = memoize((tsKey, period) => createSelector( + getTimeSeriesForTsKey(tsKey, period), getCurrentVariable, - allTimeSeriesSelector, - (variable, timeSeries) => { + (timeSeries, variable) => { let ts = {}; if (variable) { Object.keys(timeSeries).forEach(key => { @@ -105,7 +83,7 @@ export const getAllTimeSeriesForCurrentVariable = createSelector( } return ts; } -); +)); /* * @param {String} tsKey - current or compare @@ -115,7 +93,7 @@ export const getAllTimeSeriesForCurrentVariable = createSelector( */ export const getAllMethodsForCurrentVariable = createSelector( getMethods, - getAllTimeSeriesForCurrentVariable, + getCurrentVariableTimeSeries('current', 'P7D'), (methods, timeSeries) => { const allMethods = Object.values(methods); const currentMethodIds = uniq(Object.values(timeSeries).map((ts) => ts.method)); @@ -126,31 +104,8 @@ export const getAllMethodsForCurrentVariable = createSelector( } ); -/** - * Returns a selector that, for a given tsKey and period: - * Selects all time series data. If period is null then, the currentDateRange is used - * @param {String} tsKey Time-series key - * @param {String} or null date range using ISO-8601 duration; - * @param {Object} state Redux state - * @return {Object} - Keys are tsID, values are time-series data - */ -export const timeSeriesSelector = memoize((tsKey, period) => createSelector( - getTsRequestKey(tsKey, period), - allTimeSeriesSelector, - (tsRequestKey, timeSeries) => { - let x = {}; - Object.keys(timeSeries).forEach(key => { - const series = timeSeries[key]; - if (series.tsKey === tsRequestKey) { - x[key] = series; - } - }); - return x; - } -)); - export const hasTimeSeriesWithPoints = memoize((tsKey, period) => createSelector( - timeSeriesSelector(tsKey, period), + getTimeSeriesForTsKey(tsKey, period), (timeSeries) => { const seriesWithPoints = Object.values(timeSeries).filter(x => x.points.length > 0); return seriesWithPoints.length > 0; diff --git a/assets/src/scripts/components/hydrograph/time-series.spec.js b/assets/src/scripts/components/hydrograph/time-series.spec.js index 26643f745385a5bffe22beeaecef2e57331054bb..c46984534eaaade8e5893eb096ed7e8090a9bd81 100644 --- a/assets/src/scripts/components/hydrograph/time-series.spec.js +++ b/assets/src/scripts/components/hydrograph/time-series.spec.js @@ -1,7 +1,7 @@ import { - timeSeriesSelector, hasTimeSeriesWithPoints, isVisibleSelector, yLabelSelector, titleSelector, - descriptionSelector, currentVariableTimeSeriesSelector, allTimeSeriesSelector, tsTimeZoneSelector, - getAllTimeSeriesForCurrentVariable, getAllMethodsForCurrentVariable, secondaryYLabelSelector} from './time-series'; + getTimeSeriesForTsKey, hasTimeSeriesWithPoints, isVisibleSelector, yLabelSelector, titleSelector, + descriptionSelector, getCurrentVariableTimeSeries, tsTimeZoneSelector, + getAllMethodsForCurrentVariable, secondaryYLabelSelector} from './time-series'; const TEST_DATA = { @@ -53,7 +53,8 @@ const TEST_DATA = { estimated: false }] }, - '00010:2': {tsKey: 'compare:P7D', + '00010:2': { + tsKey: 'current:P7D', startTime: 1520351100000, endTime: 1520948700000, variable: '45807196', @@ -216,53 +217,9 @@ const TEST_DATA = { describe('TimeSeries module', () => { - describe('allTimesSeriesSelector', () => { - - it('should return all time series if they have data points', () => { - expect(allTimeSeriesSelector({ - series: { - timeSeries: { - '00010': { - points: [1, 2, 3, 4] - }, - '00095': { - points: [8, 9, 10, 11] - } - } - } - })).toEqual({ - '00010': { - points: [1, 2, 3, 4] - }, - '00095': { - points: [8, 9, 10, 11] - } - }); - }); - - it('should exclude time series if they do not have data points', () => { - expect(allTimeSeriesSelector({ - series: { - timeSeries: { - '00010': { - points: [1, 2, 3, 4] - }, - '00095': { - points: [] - } - } - } - })).toEqual({ - '00010': { - points: [1, 2, 3, 4] - } - }); - }); - }); - - describe('currentVariableTimeSeriesSelector', () => { + describe('getCurrentVariableTimeSeries', () => { it('works', () => { - expect(currentVariableTimeSeriesSelector('current')({ + expect(getCurrentVariableTimeSeries('current', 'P7D')({ series: { requests: { 'current:P7D': { @@ -337,13 +294,14 @@ describe('TimeSeries module', () => { } })).toEqual({ one: {item: 'one', points: [1, 2], tsKey: 'current:P7D', variable: 45807197}, + two: {item: 'two', points: [], tsKey: 'current:P7D', variable: 45807197}, three: {item: 'three', points: [3, 4], tsKey: 'current:P7D', variable: 45807197}, four: {item: 'four', points: [4, 5], tsKey: 'current:P7D', variable: 45807197} }); }); it('returns {} if there is no currentVariableId', () => { - expect(currentVariableTimeSeriesSelector('current')({ + expect(getCurrentVariableTimeSeries('current', 'P7D')({ series: {}, timeSeriesState: { currentVariableID: null, @@ -353,87 +311,6 @@ describe('TimeSeries module', () => { }); }); - describe('getAllTimeSeriesForCurrentVariable', () => { - - it('Expect no time series if the current variable is not set', () => { - const newTestData = { - ...TEST_DATA, - timeSeriesState: { - } - }; - expect(getAllTimeSeriesForCurrentVariable(newTestData)).toEqual({}); - }); - - it('Expect no time series if the current variable does not have any timeSeries', () => { - const newTestData = { - ...TEST_DATA, - timeSeriesState: { - ...TEST_DATA.timeSeriesState, - currentVariableID: '55807196' - } - }; - expect(getAllTimeSeriesForCurrentVariable(newTestData)).toEqual({}); - }); - - it('Expect all time series for the current variable', () => { - const newTestData = { - ...TEST_DATA, - timeSeriesState: { - ...TEST_DATA.timeSeriesState, - currentVariableID: '45807196' - } - }; - expect(getAllTimeSeriesForCurrentVariable(newTestData)).toEqual({ - '00010': { - tsKey: 'compare:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807196', - method: 69931, - points: [{ - value: 1, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 2, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 3, - qualifiers: ['P'], - approved: false, - estimated: false - }] - }, - '00010:2': { - tsKey: 'compare:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807196', - method: 69930, - points: [{ - value: 1, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 2, - qualifiers: ['P'], - approved: false, - estimated: false - }, { - value: 3, - qualifiers: ['P'], - approved: false, - estimated: false - }] - } - }); - }); - }); - describe('getAllMethodsForCurrentVariable', () => { it('Expect empty array if current variable has no time series', () => { const newTestData = { @@ -453,8 +330,8 @@ describe('TimeSeries module', () => { ...TEST_DATA.series, timeSeries: { ...TEST_DATA.series.timeSeries, - '00010:current:P30D': { - tsKey: 'current:P30D', + '00010:current:P7D': { + tsKey: 'current:P7D', startTime: 1520351100000, endTime: 1520948700000, variable: '45807196', @@ -496,10 +373,10 @@ describe('TimeSeries module', () => { }); }); - describe('timeSeriesSelector', () => { + describe('getTimeSeriesForTsKey', () => { it('should return the selected time series', () => { - expect(timeSeriesSelector('current')(TEST_DATA)).toEqual({ + expect(getTimeSeriesForTsKey('current')(TEST_DATA)).toEqual({ '00060': { tsKey: 'current:P7D', startTime: 1520351100000, @@ -522,9 +399,32 @@ describe('TimeSeries module', () => { }], variable: '45807197', method: 69929 + }, + '00010:2': { + tsKey: 'current:P7D', + startTime: 1520351100000, + endTime: 1520948700000, + variable: '45807196', + method: 69930, + points: [{ + value: 1, + qualifiers: ['P'], + approved: false, + estimated: false + }, { + value: 2, + qualifiers: ['P'], + approved: false, + estimated: false + }, { + value: 3, + qualifiers: ['P'], + approved: false, + estimated: false + }] } }); - expect(timeSeriesSelector('current','P30D')(TEST_DATA)).toEqual({ + expect(getTimeSeriesForTsKey('current','P30D')(TEST_DATA)).toEqual({ '00060:P30D': { tsKey: 'current:P30D:00060', startTime: 1520351100000, @@ -552,7 +452,7 @@ describe('TimeSeries module', () => { }); it('should return null the empty set if no time series for the selected key exist', () => { - expect(timeSeriesSelector('compare:P7D')(TEST_DATA)).toEqual({}); + expect(getTimeSeriesForTsKey('compare:P7D')(TEST_DATA)).toEqual({}); }); }); @@ -561,32 +461,8 @@ describe('TimeSeries module', () => { expect(hasTimeSeriesWithPoints('current')(TEST_DATA)).toBe(true); expect(hasTimeSeriesWithPoints('current', 'P30D')(TEST_DATA)).toBe(true); }); - it('Returns false if the times series for tsKey and period have zero points', () => { - const newTestData = { - ...TEST_DATA, - series: { - ...TEST_DATA.series, - timeSeries: { - ...TEST_DATA.series.timeSeries, - '00060': { - tsKey: 'current:P7D', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807197', - points: [] - }, - '00060:P30D': { - tsKey: 'current:P30D:00060', - startTime: 1520351100000, - endTime: 1520948700000, - variable: '45807197', - points: [] - } - } - } - }; - expect(hasTimeSeriesWithPoints('current')(newTestData)).toBe(false); - expect(hasTimeSeriesWithPoints('current', 'P30D')(newTestData)).toBe(false); + it('Returns false if the times series for tsKey and period have zero time series', () => { + expect(hasTimeSeriesWithPoints('compare', 'P30D')(TEST_DATA)).toBe(false); }); }); diff --git a/assets/src/scripts/selectors/time-series-selector.js b/assets/src/scripts/selectors/time-series-selector.js index c6e7b5fbed85f04d4d7febe88cc276afc2664e4b..c49ee49f98e260a55163197f5651c5ef40ec91be 100644 --- a/assets/src/scripts/selectors/time-series-selector.js +++ b/assets/src/scripts/selectors/time-series-selector.js @@ -32,6 +32,8 @@ export const getNwisTimeZone = state => state.series.timeZones || {}; export const getCustomTimeRange = state => state.timeSeriesState.customTimeRange; +export const getTimeSeries = state => state.series.timeSeries ? state.series.timeSeries : {}; + export const hasAnyTimeSeries = state => state.series && state.series.timeSeries && state.series.timeSeries != {}; /* * Selectors the return derived data from the state diff --git a/assets/src/scripts/selectors/time-series-selector.spec.js b/assets/src/scripts/selectors/time-series-selector.spec.js index 2eed1e93bfa4a3908d13ffbccf027bb93cf0300e..ead4b611dfdc4f85411fc28ac51e52f2c4e98d33 100644 --- a/assets/src/scripts/selectors/time-series-selector.spec.js +++ b/assets/src/scripts/selectors/time-series-selector.spec.js @@ -1,4 +1,4 @@ -import { getVariables, getSourceInfo, getSiteCodes, getCurrentVariableID, getCurrentDateRange, +import { getVariables, getSourceInfo, getSiteCodes, getCurrentVariableID, getCurrentDateRange, getTimeSeries, getMonitoringLocationName, getAgencyCode, getCurrentVariable, getQueryInfo, getRequests, getCurrentParmCd, hasTimeSeries, getTsRequestKey, getTsQueryInfo, getRequestTimeRange, isLoadingTS, getTSRequest, getTimeSeriesCollectionIds, getIanaTimeZone, getNwisTimeZone } from './time-series-selector'; @@ -33,6 +33,30 @@ describe('timeSeriesSelector', () => { }); }); + describe('getTimeSeries', () => { + it('Return empty object if series is empty', () => { + expect(getTimeSeries({ + series: {} + })).toEqual({}); + }); + + it('Return the timeSeries if in series', () => { + expect(getTimeSeries({ + series: { + timeSeries: { + '00010': { + prop1: 'value1' + } + } + } + })).toEqual({ + '00010': { + prop1: 'value1' + } + }); + }); + }); + describe('getSourceInfo', () => { it('Return an empty object if series is empty', () => { expect(getSourceInfo({ diff --git a/assets/src/scripts/store/index.js b/assets/src/scripts/store/index.js index 44e522961b875b0015036fec5fd53a226c1e5763..ff542a675e1b9e992108dd7a99307b715911fea1 100644 --- a/assets/src/scripts/store/index.js +++ b/assets/src/scripts/store/index.js @@ -49,11 +49,16 @@ const getLatestValue = function(collection, parmCd) { * @param {Object} variables - keys are the variable id */ const getCurrentVariableId = function(timeSeries, variables) { - const tsVariables = Object.values(timeSeries) + const tsVariablesWithData = Object.values(timeSeries) .filter((ts) => ts.points.length) .map((ts) => variables[ts.variable]); - const sortedVars = sortedParameters(tsVariables); - return sortedVars.length ? sortedVars[0].oid : ''; + const sortedVarsWithData = sortedParameters(tsVariablesWithData); + if (sortedVarsWithData.length) { + return sortedVarsWithData[0].oid; + } else { + const sortedVars = sortedParameters(Object.values(variables)); + return sortedVars.length ? sortedVars[0].oid : ''; + } }; diff --git a/assets/src/styles/components/hydrograph/_graph.scss b/assets/src/styles/components/hydrograph/_graph.scss index 787bb8183ab6726b8b81fe46f1b16d6ad3877675..0529dfa71eabba9cbb350de13159c98ca9c0e6f3 100644 --- a/assets/src/styles/components/hydrograph/_graph.scss +++ b/assets/src/styles/components/hydrograph/_graph.scss @@ -86,7 +86,7 @@ svg { } } - .y-axis { + .y-axis, .secondary-y-axis { text { fill: black; &.y-axis-label {