diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js b/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js index 87e6be6fb6466a82bbd18fd02b26206276d57b4f..9b353c93c3a316b06beb8cebfdc7ae044ae3708f 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/graph-controls.js @@ -13,6 +13,11 @@ import {isVisible} from './selectors/time-series-data'; import {showDataIndicators} from './data-indicator'; +const hasIVData = function(parameterCode) { + console.log(`${config}`); + return config.ivPeriodOfRecord && parameterCode in config.ivPeriodOfRecord; +}; + /* * Create the last year toggle, and median toggle for the time series graph. @@ -54,14 +59,16 @@ export const drawGraphControls = function(elem, store, siteno) { } }) // Sets the state of the toggle - .call(link(store,function(elem, {checked, selectedDateRange}) { + .call(link(store,function(elem, {checked, selectedDateRange, parameterCode}) { elem.property('checked', checked) .attr('disabled', + hasIVData(parameterCode) && selectedDateRange !== 'custom' && config.ALLOW_COMPARE_DATA_FOR_PERIODS.includes(selectedDateRange) ? null : true); }, createStructuredSelector({ checked: isVisible('compare'), - selectedDateRange: getSelectedDateRange + selectedDateRange: getSelectedDateRange, + parameterCode: getSelectedParameterCode }))); compareControlDiv.append('label') .classed('usa-checkbox__label', true) diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index 6acf7a6383944b0681ea56172c0db8f4036f4917..6c5e509a844f35f671e5321d1a8d21c26da5cf89 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -71,12 +71,13 @@ export const attachToNode = function(store, DateTime.fromISO(startDT, {zone: config.locationTimeZone}).toISO() : null; const initialEndTime = endDT ? DateTime.fromISO(endDT, {zone: config.locationTimeZone}).endOf('day').toISO() : null; + const initialLoadCompare = compare === 'true' || compare === true ? true : false; const fetchHydrographDataPromise = store.dispatch(retrieveHydrographData(siteno, { parameterCode: parameterCode, period: initialPeriod === 'custom' ? null : initialPeriod, startTime: initialStartTime, endTime: initialEndTime, - loadCompare: compare, + loadCompare: initialLoadCompare, loadMedian: false })); @@ -87,7 +88,7 @@ export const attachToNode = function(store, // Initialize all hydrograph state variables if showing the control store.dispatch(setSelectedParameterCode(parameterCode)); - store.dispatch(setCompareDataVisibility(compare)); + store.dispatch(setCompareDataVisibility(initialLoadCompare)); if (period) { store.dispatch(setSelectedDateRange(period)); } else if (startDT && endDT) { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js index 37d9a6d6d1f8e2990b53d2ca69541669fcc11018..09fea72011406c5227d85d02986ed0afccc9a0e1 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/cursor.js @@ -58,7 +58,7 @@ export const getIVDataCursorPoint = memoize((dataKind, timeRangeKind) => createS isVisible(dataKind), getGraphTimeRange('MAIN', timeRangeKind), (ivData, cursorTime, isVisible, timeRange) => { - if (!ivData.length || !cursorTime || !isVisible || !timeRange) { + if (!ivData || !ivData.length || !cursorTime || !isVisible || !timeRange) { return null; } const visiblePoints = ivData.filter(point => isInTimeRange(point.dateTime, timeRange)); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js index c8d2bacf0cbdabdada1d00b2f9b43778923b4a2d..950f10da9cbd4c28a30b9608e0535c1292afee4d 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.js @@ -82,13 +82,15 @@ export const getIVDataPoints = memoize(dataKind => createSelector( getSelectedIVMethodID, (ivData, selectedMethodID) => { if (!ivData) { - return []; + return null; } let selectedIVPoints; if (selectedMethodID && selectedMethodID in ivData.values) { selectedIVPoints = ivData.values[selectedMethodID].points; - } else { + } else if (Object.keys(ivData.values).length) { selectedIVPoints = Object.values(ivData.values)[0].points; + } else { + selectedIVPoints = []; } if (PARM_CODES_TO_ACCUMULATE.includes(ivData.parameter.parameterCode)) { selectedIVPoints = transformToCumulative(selectedIVPoints); @@ -168,7 +170,9 @@ export const getIVTableData = memoize(dataKind => createSelector( export const getIVDataSegments = memoize(dataKind => createSelector( getIVDataPoints(dataKind), (points) => { - if (!points.length) { + if (!points) { + return null; + } else if (!points.length) { return []; } const getNewSegment = function(point) { @@ -233,17 +237,19 @@ export const getIVDataSegments = memoize(dataKind => createSelector( export const getIVUniqueDataKinds = memoize(dataKind => createSelector( getIVDataSegments(dataKind), (segments) => { - if (!segments.length) { + if (!segments) { + return null; + } else if (!segments.length) { return []; + } else { + const mappedSegments = segments.map(segment => { + return { + isMasked: segment.isMasked, + label: segment.label, + class: segment.class + }; + }); + return uniqWith(mappedSegments, isEqual); } - - const mappedSegments = segments.map(segment => { - return { - isMasked: segment.isMasked, - label: segment.label, - class: segment.class - }; - }); - return uniqWith(mappedSegments, isEqual); }) ); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js index 9b5d5e5bad7daacc2c682164941cb4ab34c30893..8a58bd70c83d6b212f4e56b38a79059b9acf1afa 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/iv-data.test.js @@ -37,15 +37,15 @@ describe('monitoring-location/components/hydrograph/selectors/iv-data', () => { }; describe('getIVDataPoints', () => { - it('Returns null if no data of data kind exists', () => { - expect(getIVDataPoints('compare')(TEST_STATE)).toBeNull(); + it('Returns an empty array if no data of data kind exists', () => { + expect(getIVDataPoints('compare')(TEST_STATE)).toHaveLength(0); }); it('Returns the iv data points with the correct properties', () => { const points = getIVDataPoints('primary')(TEST_STATE); - expect(points['90649']).toBeDefined(); - expect(points['90649']).toHaveLength(9); - expect(points['90649'][0]).toEqual({ + expect(points).toBeDefined(); + expect(points).toHaveLength(9); + expect(points[0]).toEqual({ value: 24.2, dateTime: 1582560900000, isMasked: false, @@ -54,7 +54,7 @@ describe('monitoring-location/components/hydrograph/selectors/iv-data', () => { label: 'Approved', class: 'approved' }); - expect(points['90649'][2]).toEqual({ + expect(points[2]).toEqual({ value: null, dateTime: 1582562700000, isMasked: true, @@ -63,7 +63,7 @@ describe('monitoring-location/components/hydrograph/selectors/iv-data', () => { label: 'Ice Affected', class: 'ice-affected-mask' }); - expect(points['90649'][4]).toEqual({ + expect(points[4]).toEqual({ value: 25.2, dateTime: 1582570800000, isMasked: false, @@ -72,7 +72,7 @@ describe('monitoring-location/components/hydrograph/selectors/iv-data', () => { label: 'Estimated', class: 'estimated' }); - expect(points['90649'][7]).toEqual({ + expect(points[7]).toEqual({ value: 26.5, dateTime: 1600619400000, isMasked: false, @@ -123,14 +123,12 @@ describe('monitoring-location/components/hydrograph/selectors/iv-data', () => { }); }); describe('getIVDataSegments', () => { - it('Expects null if no IV of the data kind exists', () => { - expect(getIVDataSegments('compare')(TEST_STATE)).toBeNull(); + it('Expects an empty array if no IV of the data kind exists', () => { + expect(getIVDataSegments('compare')(TEST_STATE)).toHaveLength(0); }); it('Returns the expected data segments', () => { - const segmentsByMethodID = getIVDataSegments('primary')(TEST_STATE); - expect(segmentsByMethodID['90649']).toBeDefined(); - const segments = segmentsByMethodID['90649']; + const segments = getIVDataSegments('primary')(TEST_STATE); expect(segments).toHaveLength(5); expect(segments[0]).toEqual({ isMasked: false, diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js index 181619895584e0a276c2e5f12f78de2b7a8398be..1ad5abf8500b2295f8ea806a03bdd32f81d69554 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js @@ -1,9 +1,11 @@ import {createSelector} from 'reselect'; +import config from 'ui/config'; + import {defineLineMarker, defineRectangleMarker, defineCircleMarker, defineTextOnlyMarker} from 'd3render/markers'; import {getWaterwatchFloodLevels, isWaterwatchVisible} from 'ml/selectors/flood-data-selector'; -import {getPrimaryMedianStatisticsData} from 'ml/selectors/hydrograph-data-selector'; +import {getPrimaryMedianStatisticsData, getPrimaryParameter} from 'ml/selectors/hydrograph-data-selector'; import {isCompareIVDataVisible, isMedianDataVisible} from 'ml/selectors/hydrograph-state-selector'; import {getIVUniqueDataKinds, HASH_ID} from './iv-data'; @@ -32,28 +34,39 @@ const getLegendDisplay = createSelector( isWaterwatchVisible, getWaterwatchFloodLevels, getUniqueGWKinds, - (showCompare, showMedian, medianSeries, currentClasses, compareClasses, showWaterWatch, floodLevels, gwLevelKinds) => { + getPrimaryParameter, + (showCompare, showMedian, medianSeries, currentClasses, compareClasses, showWaterWatch, floodLevels, gwLevelKinds, + primaryParameter) => { + const parameterCode = primaryParameter ? primaryParameter.parameterCode : null; + const hasIVData = config.ivPeriodOfRecord && parameterCode ? parameterCode in config.ivPeriodOfRecord : false; + const hasGWLevelsData = config.gwPeriodOfRecord && parameterCode ? parameterCode in config.gwPeriodOfRecord : false; return { - primaryIV: currentClasses.length ? currentClasses : undefined, - compareIV: showCompare && compareClasses.length ? compareClasses : undefined, + primaryIV: hasIVData ? currentClasses : undefined, + compareIV: hasIVData && showCompare ? compareClasses : undefined, median: showMedian ? medianSeries : undefined, floodLevels: showWaterWatch ? floodLevels : undefined, - groundwaterLevels: gwLevelKinds.length ? gwLevelKinds : undefined + groundwaterLevels: hasGWLevelsData ? gwLevelKinds : undefined }; } ); const getIVMarkers = function(dataKind, uniqueIVKinds) { - let maskMarkers = []; - let lineMarkers = []; - uniqueIVKinds.forEach(ivKind => { - if (ivKind.isMasked) { - maskMarkers.push(defineRectangleMarker(null, `mask ${ivKind.class}`, ivKind.label, `url(#${HASH_ID[dataKind]})`)); - } else { - return lineMarkers.push(defineLineMarker(null, `line-segment ${ivKind.class} ts-${dataKind}`, ivKind.label)); - } - }); - return [...lineMarkers, ...maskMarkers]; + if (!uniqueIVKinds) { + return []; + } else if (uniqueIVKinds.length) { + let maskMarkers = []; + let lineMarkers = []; + uniqueIVKinds.forEach(ivKind => { + if (ivKind.isMasked) { + maskMarkers.push(defineRectangleMarker(null, `mask ${ivKind.class}`, ivKind.label, `url(#${HASH_ID[dataKind]})`)); + } else { + return lineMarkers.push(defineLineMarker(null, `line-segment ${ivKind.class} ts-${dataKind}`, ivKind.label)); + } + }); + return [...lineMarkers, ...maskMarkers]; + } else { + return [defineTextOnlyMarker('No data')]; + } }; /* @@ -61,32 +74,41 @@ const getIVMarkers = function(dataKind, uniqueIVKinds) { * @return {Array of Array} - each subarray represents the markes for a time series median data */ const getMedianMarkers = function(medianMetaData) { - if (!Object.keys(medianMetaData).length) { + if (!medianMetaData) { return []; + } else if (!Object.keys(medianMetaData).length) { + return [[defineTextOnlyMarker(TS_LABEL.median), defineTextOnlyMarker('No data')]]; + } else { + return Object.values(medianMetaData).map((stats, index) => { + // Get the unique non-null years, in chronological order + let years = []; + if (stats.beginYear) { + years.push(stats.beginYear); + } + if (stats.endYear && stats.beginYear !== stats.endYear) { + years.push(stats.endYear); + } + const dateText = years.join(' - '); + + const descriptionText = stats.description ? `${stats.description} ` : ''; + const classes = `median-data-series median-step median-step-${index % 6}`; + const label = `${descriptionText}${dateText}`; + + return [defineTextOnlyMarker(TS_LABEL.median), defineLineMarker(null, classes, label)]; + }); } - return Object.values(medianMetaData).map((stats, index) => { - // Get the unique non-null years, in chronological order - let years = []; - if (stats.beginYear) { - years.push(stats.beginYear); - } - if (stats.endYear && stats.beginYear !== stats.endYear) { - years.push(stats.endYear); - } - const dateText = years.join(' - '); - - const descriptionText = stats.description ? `${stats.description} ` : ''; - const classes = `median-data-series median-step median-step-${index % 6}`; - const label = `${descriptionText}${dateText}`; - - return [defineTextOnlyMarker(TS_LABEL.median), defineLineMarker(null, classes, label)]; - }); }; const getGWMarkers = function(gwLevelKinds) { - return gwLevelKinds.map(kind => { - return defineCircleMarker(null, kind.classes.join(' '), kind.radius, kind.label); - }); + if (!gwLevelKinds) { + return []; + } else if (gwLevelKinds.length) { + return gwLevelKinds.map(kind => { + return defineCircleMarker(null, kind.classes.join(' '), kind.radius, kind.label); + }); + } else { + return [defineTextOnlyMarker('No data')]; + } }; const floodLevelDisplay = function(floodLevels) { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js index 5029704ac9894eb54e85e815550d72b12b35b8a6..1c70f6ce3a80380c278d513d23b4384400c9cde7 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.js @@ -34,7 +34,13 @@ export const isVisible = memoize(dataKind => createSelector( }) ); -export const hasVisibleIVData = memoize(dataKind => createSelector( +/* + * Returns a Redux selector that returns true if there is non-empty data and it is + * selected to be visible. + * @param {String} dataKind - 'primary' or 'compare' + * @return {Function} + */ +const hasVisibleIVData = memoize(dataKind => createSelector( isVisible(dataKind), getIVData(dataKind), getSelectedIVMethodID, @@ -44,13 +50,13 @@ export const hasVisibleIVData = memoize(dataKind => createSelector( } )); -export const hasVisibleMedianStatisticisData = createSelector( +const hasVisibleMedianStatisticsData = createSelector( isVisible('median'), getMedianStatisticsData, (isVisible, medianStats) => isVisible && medianStats ? Object.keys(medianStats).length > 0 : false ); -export const hasVisibleGroundwaterLevels = createSelector( +const hasVisibleGroundwaterLevels = createSelector( getGroundwaterLevels, (gwLevels) => gwLevels && gwLevels.values ? gwLevels.values.length > 0 : false ); @@ -58,7 +64,7 @@ export const hasVisibleGroundwaterLevels = createSelector( export const hasAnyVisibleData = createSelector( hasVisibleIVData('primary'), hasVisibleIVData('compare'), - hasVisibleMedianStatisticisData, + hasVisibleMedianStatisticsData, hasVisibleGroundwaterLevels, (visiblePrimaryIVData, visibleCompareData, visibleMedianStats, visibleGWLevels) => { return visiblePrimaryIVData || visibleCompareData || visibleMedianStats || visibleGWLevels; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js index 2d7b705e4a286dd99fc712e7e137485c29cdcd31..7d4d27100c24f0ac283fd142805988c275207d9e 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/time-series-data.test.js @@ -1,7 +1,8 @@ import config from 'ui/config'; import { - isVisible, getTitle, getDescription, getPrimaryParameterUnitCode, + isVisible, hasVisibleIVData, hasVisibleMedianStatisticisData, hasVisibleGroundwaterLevels, + hasAnyVisibleData, getTitle, getDescription, getPrimaryParameterUnitCode, getPreferredIVMethodID } from './time-series-data'; @@ -29,7 +30,7 @@ const TEST_STATE = { } }, '252055': { - points: [], + points: [{value: 24.2, qualifiers: ['A'], dateTime: 1582560900000}], method: { methodDescription: 'From multiparameter sonde', methodID: '252055' @@ -69,6 +70,61 @@ describe('monitoring-location/components/hydrograph/time-series module', () => { }); }); + describe('hasVisibleIVData', () => { + it('Expects to return true when data is available and visible', () => { + expect(hasVisibleIVData('primary')(TEST_STATE)).toBe(true); + expect(hasVisibleIVData('compare')({ + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + compareIVData: { + ...TEST_STATE.hydrographData.primaryIVData + } + }, + hydrographState: { + ...TEST_STATE.hydrographState, + showCompareIVData: true + } + })).toBe(true); + }); + + it('Expects to return false if data exists but is not visible', () => { + expect(hasVisibleIVData('compare')({ + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + compareIVData: { + ...TEST_STATE.hydrographData.primaryIVData + } + } + })).toBe(false); + }); + + it('Expects to return false when no data is available and visible', () => { + expect(hasVisibleIVData('primary')({ + ...TEST_STATE, + hydrographState: { + ...TEST_STATE.hydrographState, + selectedIVMethodID: '69937' + } + })).toBe(false); + expect(hasVisibleIVData('compare')({ + ...TEST_STATE, + hydrographData: { + ...TEST_STATE.hydrographData, + compareIVData: { + ...TEST_STATE.hydrographData.primaryIVData + } + }, + hydrographState: { + ...TEST_STATE.hydrographState, + selectedIVMethodID: '69937', + showCompareIVData: true + } + })).toBe(false); + }); + }); + describe('getTitle', () => { it('Returns an empty if no IV data exists', () => { expect(getTitle({ diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js index 52e8e67f9314aa6849ee5a4fd9970ccfad18c75a..f9c6d3028eb9d326303e2c10b9a66fd4d85d781b 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/time-series-lines.js @@ -91,7 +91,7 @@ export const drawDataSegments = function(elem, {visible, segments, dataKind, xSc elem.selectAll(`.${elemClass}`).remove(); const lineGroup = elem.append('g') .attr('class', elemClass); - if (!visible || !segments.length) { + if (!visible || !segments || !segments.length) { return; } diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-data.js b/assets/src/scripts/monitoring-location/store/hydrograph-data.js index 2d230eda33a6c19a3ad994a4e72e44bf94a8321f..f70e6ad5c785c93b153a590ba662b93bf1504eab 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-data.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-data.js @@ -103,10 +103,12 @@ const retrieveIVData = function(siteno, dataKind, {parameterCode, period, startT startTime: startTime, endTime: endTime }).then(data => { + let parameter; + let values; if (data.value && data.value.timeSeries && data.value.timeSeries.length) { const tsData = data.value.timeSeries[0]; // Create parameter object and adjust data if parameter code is calculated - let parameter = { + parameter = { parameterCode: tsData.variable.variableCode[0].value, name: tsData.variable.variableName, description: tsData.variable.variableDescription, @@ -119,7 +121,7 @@ const retrieveIVData = function(siteno, dataKind, {parameterCode, period, startT // Convert values from strings to float and set to null if they have the noDataValue. // If calculated parameter code, update the value. const noDataValue = tsData.variable.noDataValue; - const values = tsData.values.reduce((valuesByMethodId, value) => { + values = tsData.values.reduce((valuesByMethodId, value) => { valuesByMethodId[value.method[0].methodID] = { points: value.value.map(point => { let pointValue = parseFloat(point.value); @@ -140,12 +142,16 @@ const retrieveIVData = function(siteno, dataKind, {parameterCode, period, startT }; return valuesByMethodId; }, {}); - - dispatch(addIVHydrographData(dataKind, { - parameter, - values - })); + } else { + parameter = { + parameterCode: parameterCode + }; + values = {}; } + dispatch(addIVHydrographData(dataKind, { + parameter, + values + })); }); }; };