diff --git a/CHANGELOG.md b/CHANGELOG.md index 2685620c763b4ae89fbe1ad83e1c757cae1748c6..46a7a54b006eb8899dd845e82a48afd71ac18df4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Create a GraphBrush vue component that is now used to implement the graph brush for the IV and DV graphs. +- Groundwater visit data is now available on the daily value graph. ## [1.6.0](https://code.usgs.gov/wma/iow/waterdataui/-/compare/waterdataui-1.5.0...waterdataui-1.6.0) - 2022-09-01 ### Added diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/DvHydrographApp.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/DvHydrographApp.test.js index ed2b72059e8a9bc3cdab8ee760039607dbc7b78d..f6cc48f239bb70c76af835ec36d98fcf1f43a7a0 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/DvHydrographApp.test.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/DvHydrographApp.test.js @@ -140,7 +140,7 @@ describe('monitoring-location/components/daily-value-hydrograph/DvHydrographApp. const cursorSliderComponent = wrapper.findComponent(CursorSlider); expect(cursorSliderComponent.props().cursorOffset).toBe(1262476800000); - expect(cursorSliderComponent.props().xScaleDomain).toStrictEqual([1262304000000, 1262563200000]); + expect(cursorSliderComponent.props().xScaleDomain).toStrictEqual([245008800000, 1665079200000]); expect(cursorSliderComponent.props().xScaleRange).toStrictEqual([0, 775]); expect(cursorSliderComponent.props().layout).toStrictEqual({ 'height': 400, diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/d3/discrete-data.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/d3/discrete-data.js new file mode 100644 index 0000000000000000000000000000000000000000..2152824768b4d23da697a105d4d2360add03a06f --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/d3/discrete-data.js @@ -0,0 +1,26 @@ +/* + * Render the ground water level symbols on the svg in their own group. If the group exists, remove + * it before rendering again. + * @param {D3 elem} svg (could also be a group) + * @param {Array of Object} levels - each object, should have properties needed to draw the groundwater levels + * including dateTime, value, radius, and class + * @param {D3 scale} xScale + * @param {D3 scale } yScale + * @param {Boolean} enableClip + */ +export const drawGroundwaterLevels = function(svg, {levels, xScale, yScale, enableClip}) { + svg.selectAll('.dv-graph-gw-levels-group').remove(); + const group = svg.append('g') + .attr('class', 'dv-graph-gw-levels-group'); + if (enableClip) { + group.attr('clip-path', 'url(#dv-graph-clip)'); + } + + levels.forEach((level) => { + group.append('circle') + .attr('class', level.classes.join(' ')) + .attr('r', level.radius) + .attr('cx', xScale(level.dateTime)) + .attr('cy', yScale(level.value)); + }); +}; diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/d3/discrete-data.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/d3/discrete-data.test.js new file mode 100644 index 0000000000000000000000000000000000000000..29dc44680098b044b6111dc62cf5d83f6111e5d0 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/d3/discrete-data.test.js @@ -0,0 +1,60 @@ +import {select} from 'd3-selection'; +import {scaleLinear} from 'd3-scale'; + +import {drawGroundwaterLevels} from './discrete-data'; + + +describe('monitoring-location/components/daily-value-hydrograph/d3/discrete-data', () => { + describe('drawGroundwaterLevels', () => { + + let svg, gwLevels, xScale, yScale; + + beforeEach(() => { + svg = select('body').append('svg'); + gwLevels = [ + {value: 14.0, displayValue: '14.0', dateTime: 1491055200000, label: 'Approved', classes: ['gw-class', 'approved'], radius: 5}, + {value: 14.5, displayValue: '14.5', dateTime: 1490882400000, label: 'Provisional', classes: ['gw-class', 'provisional'], radius: 5}, + {value: 13.0, displayValue: '13.0', dateTime: 1490536800000, label: 'Revised', classes: ['gw-class', 'revised'], radius: 5}, + {value: 12.0, displayValue: '12.0', dateTime: 1489672800000, label: 'Provisional', classes: ['gw-class', 'provisional'], radius: 5}, + {value: 11.0, displayValue: '11.0', dateTime: 1489672300000, label: 'Provisional', classes: ['gw-class', 'provisional'], radius: 5}, + {value: 13.0, displayValue: '13.0', dateTime: 1489672100000, label: 'Provisional', classes: ['gw-class', 'provisional'], radius: 5} + ]; + xScale = scaleLinear() + .range([0, 100]) + .domain([1489000000000, 1500000000000]); + yScale = scaleLinear() + .range([0, 100]) + .domain([11.0, 15.0]); + }); + + afterEach(() => { + svg.remove(); + }); + + it('Renders correct number of circles with correct class for each gw level', () => { + drawGroundwaterLevels(svg, { + levels: gwLevels, + xScale: xScale, + yScale: yScale + }); + expect(svg.selectAll('circle').size()).toBe(6); + expect(svg.selectAll('.approved').size()).toBe(1); + expect(svg.selectAll('.provisional').size()).toBe(4); + expect(svg.selectAll('.revised').size()).toBe(1); + }); + + it('A second call to render with no gw points renders no circles', () => { + drawGroundwaterLevels(svg, { + levels: gwLevels, + xScale: xScale, + yScale: yScale + }); + drawGroundwaterLevels(svg, { + levels: [], + xScale: xScale, + yScale: yScale + }); + expect(svg.selectAll('circle').size()).toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/mock-daily-value-hydrograph-state.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/mock-daily-value-hydrograph-state.js index ac222908c8821eb89626dfd7cb2c0e85b5aa44a0..d1918276976d7ed8960cf76478027420a293c732 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/mock-daily-value-hydrograph-state.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/mock-daily-value-hydrograph-state.js @@ -1,3 +1,67 @@ +export const TEST_GW_LEVELS_DATA = [{ + observedPropertyName: 'Groundwater level above NGVD 1929, feet', + USGSParameterCode: '62610', + unitOfMeasureName: 'ft', + phenomenonTimeStart: '1977-10-06 18:00:00', + phenomenonTimeEnd:'2022-10-06 18:00:00', + observations: [{ + phenomenonTime: '2021-11-15T21:29', + phenomenonTimeAccuracy: 'minute', + result: '1315.49', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637011740000 + }, { + phenomenonTime: '2021-11-16T16:03', + phenomenonTimeAccuracy: 'minute', + result: '1315.45', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637078580000 + }, { + phenomenonTime: '2022-01-27T19:53', + phenomenonTimeAccuracy: 'minute', + result: '1314.33', + resultAccuracy: '0.01', + approvals: ['Provisional'], + qualifiers: ['Static'], + epochTime: 1643313180000 + }] + }, { + observedPropertyName: 'Depth to water level, feet below land surface', + USGSParameterCode: '72019', + unitOfMeasureName: 'ft', + phenomenonTimeStart: '1977-10-06 18:00:00', + phenomenonTimeEnd:'2022-10-06 18:00:00', + observations: [{ + phenomenonTime: '2021-11-15T21:29', + phenomenonTimeAccuracy: 'minute', + result: '29.40', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637011740000 + }, { + phenomenonTime: '2021-11-16T16:03', + phenomenonTimeAccuracy: 'minute', + result: '29.44', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637078580000 + }, { + phenomenonTime: '2022-01-27T19:53', + phenomenonTimeAccuracy: 'minute', + result: '30.56', + resultAccuracy: '0.01', + approvals: ['Provisional'], + qualifiers: ['Static'], + epochTime: 1643313180000 + }] + }]; + export const TEST_STATE = { dailyValueTimeSeriesData: { availableDVTimeSeries: [{ @@ -75,5 +139,8 @@ export const TEST_STATE = { ui: { windowWidth: 1024, width: 800 + }, + groundwaterLevelData: { + all: TEST_GW_LEVELS_DATA } }; diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/discrete-data.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/discrete-data.js new file mode 100644 index 0000000000000000000000000000000000000000..82d7162af9a2fe57acd1d689a463be772b8858d0 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/discrete-data.js @@ -0,0 +1,137 @@ +import {createSelector} from 'reselect'; +import isEqual from 'lodash/isEqual'; +import uniqWith from 'lodash/uniqWith'; +import {DateTime} from 'luxon'; + +import {getCurrentDVTimeSeriesParameterCode} from 'ml/selectors/daily-value-time-series-selector'; + +import {getAllGroundwaterLevels} from 'ml/selectors/groundwater-level-field-visits-selector'; + +const GW_LEVEL_RADIUS = 7; +const GW_LEVEL_CLASS = 'gw-level-point'; + +const APPROVAL_DATA = { + 'Approved': {label: 'Approved', class: 'approved'}, + 'Provisional': {label: 'Provisional', class: 'provisional'}, + 'Revised': {label: 'Revised', class: 'revised'} +}; + +const getDetailsForApprovalCode = function(approvals) { + const approval = approvals.find(approval => approval in APPROVAL_DATA); + if (approval) { + return APPROVAL_DATA[approval]; + } else { + return APPROVAL_DATA['Provisional']; + } +}; + +const getEpochMilliseconds = function(dateStr) { + return DateTime.fromISO(dateStr.replace(' ', 'T'), {zone: 'UTC'}).toMillis(); +}; + +/* + * Selector function that returns an object containing the groundwater parameter data and and array of values + * that correspond to the active time series' parameter code. + * @return {Function} which returns null if no GW data is availale or an {Object} with the properties: + * @prop {Object} parameter - contains the parameter code, name, description, unit, startTime, and endTime + * @prop {Array} values + * } + */ +export const getSelectedGroundwaterLevels = createSelector( + getCurrentDVTimeSeriesParameterCode, + getAllGroundwaterLevels, + (parameterCode, groundwaterLevelsData) => { + const parameterCodeDatastream = + groundwaterLevelsData.find(datastream => datastream.USGSParameterCode === parameterCode); + if (parameterCodeDatastream) { + const observationsWithTime = + parameterCodeDatastream.observations.filter(observation => observation.epochTime); + return { + parameter: { + parameterCode: parameterCodeDatastream.USGSParameterCode, + name: parameterCodeDatastream.observedPropertyName, + description: parameterCodeDatastream.observedPropertyName, + unit: parameterCodeDatastream.unitOfMeasureName, + startTime: parameterCodeDatastream.phenomenonTimeStart, + endTime: parameterCodeDatastream?.phenomenonTimeEnd + }, + values: observationsWithTime + }; + } else { + return null; + } + } +); + +/* + * Returns a selector function that returns the groundwater levels that will be visible + * on the hydrograph + * @return {Function} which returns an {Array} of groundwater level object with properties: + * @prop {Float} value + * @prop {String} displayValue - the string to use for display with precision retained. + * @prop {Number} dateTime + * @prop {Array of String} classes - a class that can be used to style this point + * @prop {String} label - a human readable label for this kind of point + * @prop {Number} radius - used to draw the circle marker + */ +export const getGroundwaterLevelPoints = createSelector( + getSelectedGroundwaterLevels, + gwLevels => { + if (!gwLevels) { + return []; + } + return gwLevels.values + .filter(value => value.result) + .map(value => { + const approvalDetails = getDetailsForApprovalCode(value.approvals); + return { + value: parseFloat(value.result), + displayValue: value.result, + dateTime: value.epochTime, + classes: [GW_LEVEL_CLASS, approvalDetails.class], + label: approvalDetails.label, + radius: GW_LEVEL_RADIUS + }; + }); + } +); + +/* + * Returns a selector function which returns the unique classes/label/radius for the gw level points + * @return {Function} which returns an {Array of Object} with the following properties: + * @prop {Array of String} classes + * @prop {String} label + * @prop {Number} radius + */ +export const getUniqueGWKinds = createSelector( + getGroundwaterLevelPoints, + gwPoints => { + const allKinds = gwPoints.map(point => { + return { + classes: point.classes, + label: point.label, + radius: point.radius + }; + }); + return uniqWith(allKinds, isEqual); + } +); + +/* + * Returns a selector function which returns the time range of the + * @return {Function} which returns null or an {Object} with the following properties: + * @prop {Number} startTime + * @prop {Number} endTime + */ +export const getCurrentGWTimeRange = createSelector( + getSelectedGroundwaterLevels, + (gwLevels) => { + if (gwLevels && gwLevels.parameter.startTime && gwLevels.parameter.endTime) { + return { + startTime: getEpochMilliseconds(gwLevels.parameter.startTime), + endTime: getEpochMilliseconds(gwLevels.parameter.endTime) + }; + } + return null; + } +); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/discrete-data.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/discrete-data.test.js new file mode 100644 index 0000000000000000000000000000000000000000..db30a68f4aa0d2a571576836e8a9ca06749f70d2 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/discrete-data.test.js @@ -0,0 +1,213 @@ +import config from 'ui/config'; + +import {getGroundwaterLevelPoints, getUniqueGWKinds, getSelectedGroundwaterLevels, getCurrentGWTimeRange} from './discrete-data'; +import {TEST_GW_LEVELS_DATA} from '../mock-daily-value-hydrograph-state'; + +describe('monitoring-location/components/daily-value-hydrograph/selectors/discrete-data', () => { + config.locationTimeZone = 'America/Chicago'; + + const TEST_STATE = { + groundwaterLevelData: { + all: TEST_GW_LEVELS_DATA + }, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: { + min: null, + mean: '12346', + max: null + } + }, + dailyValueTimeSeriesData: { + availableDVTimeSeries: [{id: '12346', parameterCode:'72019'}, {id: '12346', parameterCode: '22222'}], + dvTimeSeries: { + '72019': { + type: 'Feature', + id: '72019', + properties: { + phenomenonTimeStart: '2033-02-01', + phenomenonTimeEnd: '2040-12-01' + } + }, + '12346': { + type: 'Feature', + id: '12346', + properties: { + phenomenonTimeStart: '2019-01-01', + phenomenonTimeEnd: '2021-11-01' + } + }, + '12347': { + type: 'Feature', + id: '12347', + properties: { + phenomenonTimeStart: '2010-01-01', + phenomenonTimeEnd: '2019-12-31' + } + } + } + } + }; + + describe('getGroundwaterLevelPoints', () => { + it('Return empty array if no groundwater levels are defined', () => { + expect(getGroundwaterLevelPoints({ + ...TEST_STATE, + groundwaterLevelData: { + all: [] + } + })).toHaveLength(0); + }); + + it('Return the ground water points when groundwater levels are defined', () => { + const points = getGroundwaterLevelPoints(TEST_STATE); + expect(points).toHaveLength(3); + + expect(points[0].value).toEqual(29.4); + expect(points[0].displayValue).toEqual('29.40'); + expect(points[0].dateTime).toEqual(1637011740000); + expect(points[0].classes).toContain('approved'); + expect(points[0].label).toEqual('Approved'); + expect(points[0].radius).toBeDefined(); + + expect(points[1].value).toEqual(29.44); + expect(points[1].displayValue).toEqual('29.44'); + expect(points[1].dateTime).toEqual(1637078580000); + expect(points[1].classes).toContain('approved'); + expect(points[1].label).toEqual('Approved'); + expect(points[1].radius).toBeDefined(); + + expect(points[2].value).toEqual(30.56); + expect(points[2].displayValue).toEqual('30.56'); + expect(points[2].dateTime).toEqual(1643313180000); + expect(points[2].classes).toContain('provisional'); + expect(points[2].label).toEqual('Provisional'); + expect(points[2].radius).toBeDefined(); + }); + }); + + describe('getUniqueGWKinds', () => { + it('Return empty array if no groundwater levels are defined', () => { + expect(getUniqueGWKinds({ + ...TEST_STATE, + groundwaterLevelData: { + all: [] + } + })).toHaveLength(0); + }); + + it('Return the unique kinds when groundwater levels are defined', () => { + const points = getUniqueGWKinds(TEST_STATE); + + expect(points).toHaveLength(2); + expect(points[0].classes).toContain('approved'); + expect(points[0].label).toContain('Approved'); + expect(points[0].radius).toBeDefined(); + + expect(points[1].classes).toContain('provisional'); + expect(points[1].label).toContain('Provisional'); + expect(points[1].radius).toBeDefined(); + }); + }); + + describe('getSelectedGroundwaterLevels', () => { + + it('Expects null if the selected parameter code does not have any gw data', () => { + expect(getSelectedGroundwaterLevels({ + groundwaterLevelData: { + all: TEST_GW_LEVELS_DATA + }, + dailyValueTimeSeriesData: { + availableDVTimeSeries: [{id: '1', parameterCode:'62610'}, {id: '2', parameterCode: '22222'}] + }, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: { + min: '2', + mean: '', + max: '' + } + } + })).toBeNull(); + }); + + it('Expects the selected observations to be returned when the selectedParameterCode has discrete groundwater data', () => { + const result = getSelectedGroundwaterLevels({ + groundwaterLevelData: { + all: TEST_GW_LEVELS_DATA + }, + dailyValueTimeSeriesData: { + availableDVTimeSeries: [{id: '1', parameterCode:'62610'}, {id: '2', parameterCode: '22222'}], + dvTimeSeries: { + '1': { + type: 'Feature', + id: '1', + properties: { + phenomenonTimeStart: '2010-02-01', + phenomenonTimeEnd: '2022-12-01' + } + }, + '12346': { + type: 'Feature', + id: '12347', + properties: { + phenomenonTimeStart: '2010-01-01', + phenomenonTimeEnd: '2019-11-01' + } + }, + '12347': { + type: 'Feature', + id: '12347', + properties: { + phenomenonTimeStart: '2010-01-01', + phenomenonTimeEnd: '2019-12-31' + } + } + } + }, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: { + min: '1', + mean: '', + max: '' + } + } + }); + + expect(result.parameter).toEqual({ + parameterCode: '62610', + name: 'Groundwater level above NGVD 1929, feet', + description: 'Groundwater level above NGVD 1929, feet', + unit: 'ft', + startTime: '1977-10-06 18:00:00', + endTime: '2022-10-06 18:00:00' + }); + expect(result.values).toHaveLength(3); + expect(result.values[0]).toEqual({ + phenomenonTime: '2021-11-15T21:29', + phenomenonTimeAccuracy: 'minute', + result: '1315.49', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637011740000 + }); + }); + }); + + describe('getCurrentGWTimeRange', () => { + it('returns null if there is no selected gw level data', () => { + const result = getCurrentGWTimeRange({ + ...TEST_STATE, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: '' + } + }); + expect(result).toBe(null); + }); + + it('Returns the startTime and EndTime in epoch milliseconds', () => { + const result = getCurrentGWTimeRange(TEST_STATE); + expect(result.endTime).toBe(1665079200000); + expect(result.startTime).toBe(245008800000); + }); + }); +}); diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.js index 3f115476fe79af6f52418013cc6c82d451f30c65..4fe9b1d72cfc0b81ea7d6171d6bda3a88a7b47a8 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.js @@ -1,8 +1,9 @@ import {createSelector} from 'reselect'; -import {defineLineMarker, defineRectangleMarker, defineTextOnlyMarker} from 'd3render/markers'; +import {defineLineMarker, defineRectangleMarker, defineTextOnlyMarker, defineCircleMarker} from 'd3render/markers'; import {MASK_PATTERN_ID, getCurrentUniqueDataKinds} from './time-series-data'; +import {getUniqueGWKinds} from './discrete-data'; const TS_LABEL = { min: 'Minimum:', @@ -10,6 +11,18 @@ const TS_LABEL = { max: 'Maximum:' }; +const getGWMarkers = function(gwLevelKinds) { + 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')]; + } +}; + /* * Factory function that returns array of markers to be used for the * DV time series graph legend @@ -17,7 +30,8 @@ const TS_LABEL = { */ export const getLegendMarkers = createSelector( getCurrentUniqueDataKinds, - (dataKinds) => { + getUniqueGWKinds, + (dataKinds, discreteGWDataKinds) => { const getTsMarkers = function(tsKey, dataKinds) { const lineKinds = dataKinds.filter(kind => !kind.isMasked).sort((a, b) => a.label > b.label ? 1 : -1); const maskKinds = dataKinds.filter(kind => kind.isMasked); @@ -42,6 +56,10 @@ export const getLegendMarkers = createSelector( if (dataKinds.max.length) { result.push(getTsMarkers('max', dataKinds.max)); } + if (discreteGWDataKinds.length) { + const gwLevelMarkerRows = getGWMarkers(discreteGWDataKinds); + result.push([defineTextOnlyMarker('Field visit: ')].concat(gwLevelMarkerRows)); + } return result; } ); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.test.js index 7b0d811b5b5c017633fa018bbbf1d59808d460b7..6168d06058c3db3cebb21c8bcd602c6fcba4197d 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.test.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/legend-data.test.js @@ -1,7 +1,9 @@ -import {lineMarker, rectangleMarker, textOnlyMarker} from 'd3render/markers'; +import {lineMarker, rectangleMarker, textOnlyMarker, circleMarker} from 'd3render/markers'; import {getLegendMarkers} from './legend-data'; +import {TEST_GW_LEVELS_DATA} from '../mock-daily-value-hydrograph-state'; + describe('monitoring-location/components/daily-value-hydrograph/legend-data', () => { const TEST_STATE = { @@ -35,7 +37,8 @@ describe('monitoring-location/components/daily-value-hydrograph/legend-data', () grades: [['50'], ['50'], ['60'], ['60']] } } - } + }, + availableDVTimeSeries: [{id: '12345', parameterCode:'22222'}, {id: '12346', parameterCode: '72019'}] }, dailyValueTimeSeriesState: { currentDVTimeSeriesId: { @@ -47,26 +50,67 @@ describe('monitoring-location/components/daily-value-hydrograph/legend-data', () ui: { windowWidth: 1024, width: 800 + }, + groundwaterLevelData: { + all: TEST_GW_LEVELS_DATA } }; describe('getLegendMarkers', () => { - it('Should return no markers if no time series to show', () => { + it('Should return no markers if no time series or gw visits to show', () => { let newData = { ...TEST_STATE, dailyValueTimeSeriesData: { ...TEST_STATE.dailyValueTimeSeriesData, dvTimeSeries: {} + }, + groundwaterLevelData: { + all: [] } }; expect(getLegendMarkers(newData)).toEqual([]); }); + it('Should return gw markers if no time series to show', () => { + let newData = { + ...TEST_STATE, + dailyValueTimeSeriesData: { + ...TEST_STATE.dailyValueTimeSeriesData, + dvTimeSeries: {} + } + }; + + expect(getLegendMarkers(newData)).toEqual([[ + { + domClass: null, + domId: null, + text: 'Field visit: ', + type: textOnlyMarker + }, + { + domClass: 'gw-level-point approved', + domId: null, + fill: null, + radius: 7, + text: 'Approved', + type: circleMarker + }, + { + domClass: 'gw-level-point provisional', + domId: null, + fill: null, + radius: 7, + text: 'Provisional', + type: circleMarker + } + ]]); + }); + it('Should return markers for the selected variable', () => { const result = getLegendMarkers(TEST_STATE); - expect(result).toHaveLength(2); + expect(result).toHaveLength(3); expect(result[0]).toHaveLength(4); expect(result[0]).toContainEqual({ type: textOnlyMarker, @@ -111,6 +155,12 @@ describe('monitoring-location/components/daily-value-hydrograph/legend-data', () text: 'Approved' }); + + const gwRow = result[2]; + expect(gwRow).toHaveLength(3); + expect(gwRow[0].type).toEqual(textOnlyMarker); + expect(gwRow[1].type).toEqual(circleMarker); + expect(gwRow[2].type).toEqual(circleMarker); }); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.js index 7a97b248b992d5d73d135c02d71d09cb1d9309f3..1f3e5f631393962f08b89f865a6f2cabffc63bb9 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.js @@ -6,12 +6,12 @@ import config from 'ui/config.js'; import { getDVGraphBrushOffset, getCurrentDVTimeSeriesTimeRange, - getCurrentDVTimeSeriesValueRange, getCurrentDVTimeSeriesData, getCurrentDVTimeSeriesParameterCode } from 'ml/selectors/daily-value-time-series-selector'; import {getLayout} from './layout'; +import {getCurrentGWTimeRange, getSelectedGroundwaterLevels} from './discrete-data'; /* @@ -22,10 +22,19 @@ export const getXScale = memoize((kind) => createSelector( getLayout(kind), getCurrentDVTimeSeriesTimeRange, getDVGraphBrushOffset, - (layout, timeRange, dvGraphBrushOffset) => { + getCurrentGWTimeRange, + (layout, dvTimeRange, dvGraphBrushOffset, gwTimeRange) => { let xScale = scaleLinear() .range([0, layout.width - layout.margin.right]); - + let timeRange; + if (gwTimeRange && dvTimeRange) { + timeRange = { + startTime: gwTimeRange.startTime < dvTimeRange.startTime ? gwTimeRange.startTime : dvTimeRange.startTime, + endTime: gwTimeRange.endTime > dvTimeRange.endTime ? gwTimeRange.endTime : dvTimeRange.endTime + }; + } else { + timeRange = dvTimeRange; + } if (kind !== 'BRUSH' && timeRange && dvGraphBrushOffset) { xScale.domain([timeRange.startTime + dvGraphBrushOffset.start, timeRange.endTime - dvGraphBrushOffset.end]); } else if (timeRange) { @@ -69,23 +78,29 @@ const createYScale = function(layout, valueRange, parameterCode) { /* * Returns a selector which returns the YScale to be used for the main hydrograph */ -export const getMainYScale = createSelector( - getLayout('MAIN'), +export const getYScale = memoize((kind) => createSelector( + getLayout(kind), getCurrentDVTimeSeriesData, - getXScale('MAIN'), + getXScale(kind), getCurrentDVTimeSeriesParameterCode, - (layout, allTSData, xScale, parameterCode) => { + getSelectedGroundwaterLevels, + (layout, allTSData, xScale, parameterCode, gwData) => { const [startTime, endTime] = xScale.domain(); let minValues = []; let maxValues = []; Object.values(allTSData).forEach((tsData) => { - if (tsData.length) { + if (tsData.length || gwData?.values.length) { const tsValues = tsData .filter(point => point.dateTime >= startTime && point.dateTime <= endTime) - .map(point => parseFloat(point.value)); - minValues.push(Math.min(...tsValues)); - maxValues.push(Math.max(...tsValues)); + .map(point => parseFloat(point.value)) || []; + + const gwValues = gwData?.values + .filter(point => point.epochTime >= startTime && point.epochTime <= endTime) + .map(point => parseFloat(point.result)) || []; + + minValues.push(Math.min(...tsValues, ...gwValues)); + maxValues.push(Math.max(...tsValues, ...gwValues)); } }); @@ -98,28 +113,14 @@ export const getMainYScale = createSelector( } return createYScale(layout, valueRange, parameterCode); } -); +)); /* * Returns a selector which returns the YScale to be used for the brush hydrograph */ -export const getBrushYScale = createSelector( - getLayout('BRUSH'), - getCurrentDVTimeSeriesValueRange, - getCurrentDVTimeSeriesParameterCode, - (layout, valueRange, parameterCode) => { - return createYScale(layout, valueRange, parameterCode); - } -); +export const getBrushYScale = getYScale('BRUSH'); /* * Returns a selector which returns the YScale to be used for the hydrograph of kind */ -export const getYScale = memoize((kind) => { - switch (kind) { - case 'BRUSH': - return getBrushYScale; - default: - return getMainYScale; - } -}); +export const getMainYScale = getYScale('MAIN'); diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.test.js index 935e115fd66b2b29df5ff75e6310dddb39c9d6c7..2e5a50c3418355dedf6b683e6ae7d5d9a6f1dce2 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.test.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/scales.test.js @@ -1,4 +1,5 @@ import {getMainXScale, getBrushXScale, getMainYScale} from './scales'; +import {TEST_GW_LEVELS_DATA} from '../mock-daily-value-hydrograph-state'; describe('monitoring-location/components/daily-value-hydrograph/selectors/scales', () => { @@ -75,6 +76,9 @@ describe('monitoring-location/components/daily-value-hydrograph/selectors/scales ui: { windowWidth: 1024, width: 800 + }, + groundwaterLevelData: { + all: TEST_GW_LEVELS_DATA } }; vi.mock('ui/ui-utils', async() => { @@ -102,6 +106,53 @@ describe('monitoring-location/components/daily-value-hydrograph/selectors/scales expect(getMainXScale(TEST_STATE).domain()).toEqual([1262304000000, 1262563200000]); }); + it('Should have the expected domain if a current time series is set and gw data has a wider time range', () => { + expect(getMainXScale({ + ...TEST_STATE, + dailyValueTimeSeriesState: { + ...TEST_STATE.dailyValueTimeSeriesState, + dvGraphBrushOffset: { + start: 10000, + end: 50000 + } + }, + groundwaterLevelData: { + all: [{ + observedPropertyName: 'Groundwater level above NGVD 1929, feet', + USGSParameterCode: '12345', + unitOfMeasureName: 'ft', + phenomenonTimeStart: '1970-10-06 18:00:00', + phenomenonTimeEnd:'2030-10-06 18:00:00', + observations: [{ + phenomenonTime: '2021-11-15T21:29', + phenomenonTimeAccuracy: 'minute', + result: '1315.49', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637011740000 + }, { + phenomenonTime: '2021-11-16T16:03', + phenomenonTimeAccuracy: 'minute', + result: '1315.45', + resultAccuracy: '0.01', + approvals: ['Approved'], + qualifiers: ['Static'], + epochTime: 1637078580000 + }, { + phenomenonTime: '2022-01-27T19:53', + phenomenonTimeAccuracy: 'minute', + result: '1314.33', + resultAccuracy: '0.01', + approvals: ['Provisional'], + qualifiers: ['Static'], + epochTime: 1643313180000 + }] + }] + } + }).domain()).toEqual([24084010000, 1917539950000]); + }); + it('Should have the expected domain if a current time series is set and dvGraphBrushOffset is set', () => { expect(getMainXScale({ ...TEST_STATE, @@ -242,5 +293,95 @@ describe('monitoring-location/components/daily-value-hydrograph/selectors/scales }).range(); expect(result[1]).toEqual(0); }); + + it('Should have a range if there are only gw points visible', () => { + const result = getMainYScale({ + ...TEST_STATE, + dailyValueTimeSeriesData: { + dvTimeSeries: { + '22222': { + type: 'Feature', + id: '22222', + properties: { + phenomenonTimeStart: '2010-01-01', + phenomenonTimeEnd: '2010-01-04', + timeStep: [], + result: [] + } + } + }, + availableDVTimeSeries: TEST_STATE.dailyValueTimeSeriesData.availableDVTimeSeries + }, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: { + min: '', + mean: '22222', + max: '' + } + } + }).range(); + expect(result).toEqual([365, 0]); + }); + + it('Should have a range if there are only dv time series points visible', () => { + const result = getMainYScale({ + ...TEST_STATE, + dailyValueTimeSeriesData: { + dvTimeSeries: { + '22222': { + type: 'Feature', + id: '22222', + properties: { + phenomenonTimeStart: '2010-01-01', + phenomenonTimeEnd: '2010-01-04', + timeStep: ['2010-01-01', '2010-01-02', '2010-01-03', '2010-01-04'], + result: ['4.5', '3.2', '4.6', '0.1'] + } + } + }, + availableDVTimeSeries: TEST_STATE.dailyValueTimeSeriesData.availableDVTimeSeries + }, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: { + min: '', + mean: '22222', + max: '' + } + }, + groundwaterLevelData: { + all: [] + } + }).range(); + expect(result).toEqual([365, 0]); + }); + + it('Should have a range that takes the extreme of discrete and gw points if there are both', () => { + const result = getMainYScale({ + ...TEST_STATE, + dailyValueTimeSeriesData: { + dvTimeSeries: { + '22222': { + type: 'Feature', + id: '22222', + properties: { + phenomenonTimeStart: '2010-01-01', + phenomenonTimeEnd: '2010-01-04', + timeStep: ['2010-01-01', '2010-01-02', '2010-01-03', '2010-01-04'], + result: ['9999', '3.2', '4.6', '0.1'] + } + } + }, + availableDVTimeSeries: TEST_STATE.dailyValueTimeSeriesData.availableDVTimeSeries + }, + dailyValueTimeSeriesState: { + currentDVTimeSeriesId: { + min: '', + mean: '22222', + max: '' + } + } + }).range(); + expect(result).toEqual([365, 0]); + }); }); }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.js index 7ada24ab36e7712e591825933796c6905cbb44ae..553e30505f187b9496b61abd1a0abb77523e0e32 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.js @@ -6,7 +6,8 @@ import {DateTime} from 'luxon'; import {createSelector} from 'reselect'; import {getNearestTime} from 'ui/utils'; - +import {getGroundwaterLevelPoints, getSelectedGroundwaterLevels} from './discrete-data'; +import {getCurrentDVTimeSeriesTimeRange} from 'ml/selectors/daily-value-time-series-selector'; import {getCurrentDVTimeSeriesData, getDVGraphCursorOffset, getCurrentDVTimeSeriesUnitOfMeasure} from 'ml/selectors/daily-value-time-series-selector'; @@ -44,7 +45,8 @@ const LINE_CLASSES = { const TOOLTIP_LABEL = { min: 'Min', mean: 'Mean', - max: 'Max' + max: 'Max', + gw: 'Visit' }; export const MASK_PATTERN_ID = { @@ -53,7 +55,6 @@ export const MASK_PATTERN_ID = { max: 'dv-max-masked-pattern' }; - /* * Returns a selector function function returns an Object. The object has three properties, min, mean, and max. * Each are an Array of Objects that can be used to visualize the @@ -268,6 +269,36 @@ export const getCursorDateTime = createSelector( } ); +/* + * Redux selector function that returns a function that returns the nearest ground water level point + * @return {Function] - the function returns an object with dateTime, value, and qualifier attributes. Null + * is returned if there are no visible groundwater level points or the cursor is not + * on the graph + */ +export const getGroundwaterLevelCursorPoint = createSelector( + getSelectedGroundwaterLevels, + getGroundwaterLevelPoints, + getCursorEpochTime, + getCurrentDVTimeSeriesTimeRange, + (gwLevels, gwLevelPoints, cursorTime, timeRange) => { + if (!cursorTime || !gwLevelPoints.length || !timeRange) { + return null; + } + + const datum = getNearestTime(gwLevelPoints, cursorTime); + const unitCode = gwLevels.parameter ? gwLevels.parameter.unit : ''; + const valueLabel = datum.value !== null ? `${datum.value} ${unitCode}` : ' '; + const timeLabel = DateTime.fromMillis(datum.dateTime, {zone: 'UTC'}).toFormat('MMM dd, yyyy hh:mm:ss a ZZZZ'); + + return { + value: datum.value, + dateTime: datum.dateTime, + class: datum.classes[1], + label: `${valueLabel} - ${timeLabel}`, + dataKind: 'gw-current' + }; +}); + /* * Return a selector which returns an Array of Objects. Each property in the Object * represents a point nearest the cursor's epoch time. Each object has the following properties: @@ -282,8 +313,13 @@ export const getCurrentTooltipData = createSelector( getCursorEpochTime, getCurrentTimeSeriesPoints, getCurrentDVTimeSeriesUnitOfMeasure, - getCurrentDVTimeSeriesUnitOfMeasure, - (cursorEpochTime, points, unitCode) => { + getGroundwaterLevelCursorPoint, + (cursorEpochTime, tsPoints, unitCode, gwPoint) => { + const points = { + ...tsPoints, + gw: gwPoint === null ? [] : [gwPoint] + }; + return Object.keys(points) .filter(key => points[key].length) .map(key => { @@ -310,7 +346,8 @@ export const getCurrentCursorPoints = createSelector( getCurrentTooltipData, getMainXScale, getMainYScale, - (points, xScale, yScale) => { + getGroundwaterLevelCursorPoint, + (points, xScale, yScale, gwPoint) => { let result = []; Object.values(points).forEach((point) => { if (point) { @@ -320,6 +357,12 @@ export const getCurrentCursorPoints = createSelector( }); } }); + if (gwPoint) { + result.push({ + x: xScale(gwPoint.dateTime), + y: yScale(gwPoint.value) + }); + } return result; } ); diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.test.js index deec0ef212d16fb26d0e7954ee560b101a3468ee..70fa49f52a39efdad21661e574ac83645cc8f8a3 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.test.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/selectors/time-series-data.test.js @@ -3,7 +3,8 @@ import { getCurrentTimeSeriesSegments, getCursorEpochTime, getCurrentTooltipData, - getCurrentUniqueDataKinds + getCurrentUniqueDataKinds, + getGroundwaterLevelCursorPoint } from './time-series-data'; describe('monitoring-location/components/daily-value-hydrograph/time-series-data module', () => { @@ -15,6 +16,15 @@ describe('monitoring-location/components/daily-value-hydrograph/time-series-data }; }); + const TEST_GROUNDWATER_LEVELS = { + USGSParameterCode: '72019', + unitOfMeasureName: 'ft', + observations: [ + {result: '20.1', approvals: ['Revised'], qualifiers: ['Static'], epochTime: 1514923716000}, + {result: '18.3', approvals: ['Approved'], qualifiers: ['Static'], epochTime: 1582561800001} + ] + }; + const TEST_STATE = { dailyValueTimeSeriesData: { dvTimeSeries: { @@ -56,7 +66,8 @@ describe('monitoring-location/components/daily-value-hydrograph/time-series-data grades: [['50'], ['50'], ['50'], ['60'], ['50'], ['50'], ['50'], ['50'], ['50']] } } - } + }, + availableDVTimeSeries: [{id: '12345', parameterCode:'72019'}, {id: '2', parameterCode: '22222'}] }, dailyValueTimeSeriesState: { currentDVTimeSeriesId: { @@ -68,6 +79,9 @@ describe('monitoring-location/components/daily-value-hydrograph/time-series-data ui: { windowWidth: 1024, width: 800 + }, + groundwaterLevelData: { + all: [TEST_GROUNDWATER_LEVELS] } }; @@ -365,5 +379,67 @@ describe('monitoring-location/components/daily-value-hydrograph/time-series-data dataKind: 'dv - max' }); }); + + it('should return gw points near the cursor offset', () => { + const result = getCurrentTooltipData({ + ...TEST_STATE, + dailyValueTimeSeriesState: { + ...TEST_STATE.dailyValueTimeSeriesState, + dvGraphCursorOffset: 1582560900000 + } + }); + + expect(result[2]).toEqual({ + value: 18.3, + dateTime: 1582561800001, + classes: ['dv-tooltip-text', 'approved'], + label: 'Visit 18.3 ft - 2020-02-24', + dataKind: 'dv - gw' + }); + + }); + }); + + describe('getGroundwaterLevelCursorPoint', () => { + it('Return null if no groundwater levels data', () => { + expect(getGroundwaterLevelCursorPoint({ + ...TEST_STATE, + dailyValueTimeSeriesData: {} + })).toBeNull(); + }); + + it('Return nearest point when brush offset is zero', () => { + const result = getGroundwaterLevelCursorPoint({ + ...TEST_STATE, + dailyValueTimeSeriesState: { + ...TEST_STATE.dailyValueTimeSeriesState, + dvGraphCursorOffset: 0 + } + }); + expect(result).toEqual({ + value: 20.1, + dateTime: 1514923716000, + class: 'revised', + label: '20.1 ft - Jan 02, 2018 08:08:36 PM UTC', + dataKind: 'gw-current' + }); + }); + + it('Return nearest point brush offset is not zero', () => { + const result = getGroundwaterLevelCursorPoint({ + ...TEST_STATE, + dailyValueTimeSeriesState: { + ...TEST_STATE.dailyValueTimeSeriesState, + dvGraphCursorOffset: 1582560900000 + } + }); + expect(result).toEqual({ + value: 18.3, + dateTime: 1582561800001, + class: 'approved', + label: '18.3 ft - Feb 24, 2020 04:30:00 PM UTC', + dataKind: 'gw-current' + }); + }); }); }); diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.test.js index e28c1856bde9727e9a115787de30a16d03944a62..54411fd9f1d7a9a4861130f5377f5e36f6938ff8 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.test.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.test.js @@ -10,6 +10,16 @@ import {configureStore} from 'ml/store'; import DvGraphBrush from './DvGraphBrush.vue'; describe('monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.vue', () => { + const TEST_GROUNDWATER_LEVELS = { + USGSParameterCode: '72019', + observations: [ + {result: '20.1', approvals: ['Revised'], qualifiers: ['Static'], epochTime: 1582560900000}, + {result: '18.3.2', approvals: ['Approved'], qualifiers: ['Static'], epochTime: 1582561800000} + ], + phenomenonTimeStart: '2010-08-19 17:11:00', + phenomenonTimeEnd: '2022-09-20 19:33:00' + }; + const TEST_STATE = { dailyValueTimeSeriesData: { dvTimeSeries: { @@ -35,7 +45,8 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D grades: [['50'], ['50'], ['50'], ['50']] } } - } + }, + availableDVTimeSeries: [{parameterCode: '72019', id: '12345'}] }, dailyValueTimeSeriesState: { currentDVTimeSeriesId: { @@ -52,6 +63,9 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D ui: { windowWidth: 1024, width: 800 + }, + groundwaterLevelData: { + all: [TEST_GROUNDWATER_LEVELS] } }; @@ -88,7 +102,7 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D expect(wrapper.find('.graph-brush-group').attributes('transform')).not.toBe(''); expect(wrapper.find('.brush').exists()).toBe(true); expect(wrapper.find('.x-axis').exists()).toBe(true); - expect(wrapper.findAll('.tick')).toHaveLength(5); + expect(wrapper.findAll('.tick')).toHaveLength(8); }); it('Expects that the daily-values-line-group should be rendered and it should contain paths', () => { @@ -96,6 +110,12 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D expect(dvGroup.exists()).toBe(true); expect(dvGroup.findAll('path')).not.toHaveLength(0); }); + + it('Expects that the dv-graph-gw-levels-group should be rendered and it should contain circles', () => { + const gwGroup = wrapper.find('.dv-graph-gw-levels-group'); + expect(gwGroup.exists()).toBe(true); + expect(gwGroup.findAll('circle')).not.toHaveLength(0); + }); }); it('Expects that if the GraphBrush component emits an updateGraphBrushOffset event that the dv brush offset is updated', async() => { diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.vue b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.vue index c1d27c1e36df1cc5b7a937ac266180536eb127aa..a62f616f0d010863292c5c984f5cac9f17c4b270 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.vue +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvGraphBrush.vue @@ -29,11 +29,13 @@ import {getDVGraphBrushOffset} from 'ml/selectors/daily-value-time-series-select import {Actions} from 'ml/store/daily-value-time-series'; import {drawDataSegments} from '../d3/time-series-line'; +import {drawGroundwaterLevels} from '../d3/discrete-data'; import {getXAxis as getBrushXAxis} from '../selectors/axes'; import {getBrushLayout} from '../selectors/layout'; import {getBrushXScale, getBrushYScale} from '../selectors/scales'; import {getCurrentTimeSeriesSegments} from '../selectors/time-series-data'; +import {getGroundwaterLevelPoints} from '../selectors/discrete-data'; export default { @@ -48,7 +50,8 @@ export default { xScale: getBrushXScale, yScale: getBrushYScale, brushOffset: getDVGraphBrushOffset, - segments: getCurrentTimeSeriesSegments + segments: getCurrentTimeSeriesSegments, + gwLevelPoints: getGroundwaterLevelPoints }); const actions = useActions({ setDVGraphBrushOffset: Actions.setDVGraphBrushOffset @@ -75,6 +78,17 @@ export default { } }); + watchEffect(() => { + if (graphBrushGroup.value) { + drawGroundwaterLevels(select(graphBrushGroup.value), { + levels: state.gwLevelPoints.value, + xScale: state.xScale.value, + yScale: state.yScale.value, + enableClip: true + }); + } + }); + const updateGraphBrushOffset = function(start, end) { actions.setDVGraphBrushOffset(start, end); }; diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.test.js b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.test.js index 329c310451ebde349d5e677107e591f7a8712e25..522f7d0e303431b56b551e10f585fffe5cda27f3 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.test.js +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.test.js @@ -19,6 +19,16 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D }; }); + const TEST_GROUNDWATER_LEVELS = { + USGSParameterCode: '72019', + observations: [ + {result: '20.1', approvals: ['Revised'], qualifiers: ['Static'], epochTime: 1582560900000}, + {result: '18.3.2', approvals: ['Approved'], qualifiers: ['Static'], epochTime: 1582561800000} + ], + phenomenonTimeStart: '2010-08-19 17:11:00', + phenomenonTimeEnd: '2022-09-20 19:33:00' + }; + const TEST_STATE = { dailyValueTimeSeriesData: { dvTimeSeries: { @@ -44,7 +54,8 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D grades: [['50'], ['50'], ['50'], ['50']] } } - } + }, + availableDVTimeSeries: [{id: '12345', parameterCode:'72019'}, {id: '2', parameterCode: '22222'}] }, dailyValueTimeSeriesState: { currentDVTimeSeriesId: { @@ -57,6 +68,9 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D ui: { windowWidth: 1024, width: 800 + }, + groundwaterLevelData: { + all: [TEST_GROUNDWATER_LEVELS] } }; @@ -102,7 +116,6 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D ] } }); - expect(wrapper.find('.time-series-graph-title').text()).toBe('Water level, depth LSD, ft'); await wrapper.vm.$nextTick(); const dataGroup = wrapper.find('.daily-values-graph-group'); @@ -110,6 +123,7 @@ describe('monitoring-location/components/daily-value-hydrograph/vue-components/D expect(dataGroup.find('.x-axis').exists()).toBe(true); expect(dataGroup.find('.y-axis').exists()).toBe(true); expect(dataGroup.find('.daily-values-lines-group').exists()).toBe(true); + expect(dataGroup.find('.dv-graph-gw-levels-group').exists()).toBe(true); }); it('Expects the DV time series graph emits a updateCursorOffset event that the store is updated', async() => { diff --git a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.vue b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.vue index eb220a55c9779be20485e166dde8fd2c4a21675f..03a5b74e19379dfa21416ebcc7695ccb1d3e5d2a 100644 --- a/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.vue +++ b/assets/src/scripts/monitoring-location/components/daily-value-hydrograph/vue-components/DvTimeSeriesGraph.vue @@ -39,6 +39,8 @@ import TimeSeriesGraph from 'ui/vue-components/TimeSeriesGraph.vue'; import {Actions} from 'ml/store/daily-value-time-series'; import {drawDataSegments} from '../d3/time-series-line'; +import {drawGroundwaterLevels} from '../d3/discrete-data'; +import {getGroundwaterLevelPoints} from '../selectors/discrete-data'; import {getXAxis, getYAxis} from '../selectors/axes'; import {getCurrentTimeSeriesDescription, getCurrentTimeSeriesTitle, getCurrentTimeSeriesYTitle} from '../selectors/labels'; @@ -75,6 +77,7 @@ export default { tooltipData: getCurrentTooltipData, cursorDataPoints: getCurrentCursorPoints, currentCursorTime: getCursorDateTime, + gwLevelPoints: getGroundwaterLevelPoints, xAxis: getXAxis('MAIN'), yAxis: getYAxis('MAIN'), @@ -112,6 +115,17 @@ export default { } }); + watchEffect(() => { + if (dataLinesGroup.value) { + drawGroundwaterLevels(select(dataLinesGroup.value), { + levels: state.gwLevelPoints.value, + xScale: state.mainXScale.value, + yScale: state.mainYScale.value, + enableClip: true + }); + } + }); + const updateCursorOffset = function(offset) { actions.setDVGraphCursorOffset(offset); }; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/d3/discrete-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/d3/discrete-data.js index 19a894b9b5c53d908d3777609aa486ea298984a4..5f189d26f63a336c3d2d2c8e658d67c041254721 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/d3/discrete-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/d3/discrete-data.js @@ -6,6 +6,7 @@ * including dateTime, value, radius, and class * @param {D3 scale} xScale * @param {D3 scale } yScale + * @param {Boolean} enableClip */ export const drawGroundwaterLevels = function(svg, {levels, xScale, yScale, enableClip}) { svg.selectAll('.iv-graph-gw-levels-group').remove(); diff --git a/assets/src/scripts/monitoring-location/selectors/daily-value-time-series-selector.js b/assets/src/scripts/monitoring-location/selectors/daily-value-time-series-selector.js index ead25832f5885c419b476781b66367174162f5f0..35421072761e5d377a977c755c5ae470f0d44ae8 100644 --- a/assets/src/scripts/monitoring-location/selectors/daily-value-time-series-selector.js +++ b/assets/src/scripts/monitoring-location/selectors/daily-value-time-series-selector.js @@ -64,7 +64,7 @@ export const getCurrentDVTimeSeriesParameterCode = createSelector( getCurrentDVTimeSeriesIds, getAvailableDVTimeSeries, (currentIds, timeSeriesArray) => { - if (!timeSeriesArray || !timeSeriesArray.length) { + if (!timeSeriesArray || !timeSeriesArray.length || !currentIds) { return ''; } const tsId = currentIds.min || currentIds.max || currentIds.mean || null; diff --git a/assets/src/styles/components/_dv-hydrograph.scss b/assets/src/styles/components/_dv-hydrograph.scss index 1c40156adb61a02cb09072023ebc11c7b3abf90a..06f0ae34192659f4ef60e8588d350c7cc3a2f30d 100644 --- a/assets/src/styles/components/_dv-hydrograph.scss +++ b/assets/src/styles/components/_dv-hydrograph.scss @@ -260,6 +260,27 @@ $provisional-time-series: #d95f02; .standard-brush-handle { fill: uswds.color('black'); } + + /* This case will catch any groundwater approval codes that are not approved (including provisional) + and color them to match provisional. + */ + .gw-level-point { + stroke: $provisional-time-series; + stroke-width: 2px; + fill: none; + } + + .gw-level-point.approved { + stroke: $approved-time-series; + stroke-width: 2px; + fill: none; + } + + .gw-level-point.revised { + stroke: $estimated-time-series; + stroke-width: 2px; + fill: none; + } } } }