diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ede230c42b0e8599caf03b0da88a6874f9a429..c85cd62d41ab3f4b858f080cfc1287cecf34be19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased](https://code.usgs.gov/wma/iow/waterdataui/-/compare/waterdataui-1.4.0...main) ### Changed -The active sites for the map are now retrieved from sensor things. +- The cursor slider component was converted to vue. +- The active sites for the map are now retrieved from sensor things. + ## [1.4.0](https://code.usgs.gov/wma/iow/waterdataui/-/compare/waterdataui-1.4.0...waterdataui-1.3.0) - 2022-07-21 ### Fixed diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/HydrographApp.vue b/assets/src/scripts/monitoring-location/components/hydrograph/HydrographApp.vue index 97d7b10784337ec9069c1a75f81130d348a30a88..84bea2243b1165bb35e03f7f361e4061f5ee221d 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/HydrographApp.vue +++ b/assets/src/scripts/monitoring-location/components/hydrograph/HydrographApp.vue @@ -1,13 +1,18 @@ <template> - <GraphBrush /> + <div> + <CursorSlider /> + <GraphBrush /> + </div> </template> <script> import GraphBrush from './vue-components/graph-brush.vue'; +import CursorSlider from './vue-components/cursor-slider.vue'; export default { name: 'HydrographApp', components: { - GraphBrush + GraphBrush, + CursorSlider } }; </script> \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/cursor-slider.js b/assets/src/scripts/monitoring-location/components/hydrograph/cursor-slider.js deleted file mode 100644 index 0e9efe51bc890f546710e02407b1dff3d00b7115..0000000000000000000000000000000000000000 --- a/assets/src/scripts/monitoring-location/components/hydrograph/cursor-slider.js +++ /dev/null @@ -1,56 +0,0 @@ -import {sliderTop} from 'd3-simple-slider'; -import {createStructuredSelector} from 'reselect'; - -import {link} from 'ui/lib/d3-redux'; - -import {getGraphCursorOffset} from 'ml/selectors/hydrograph-state-selector'; - -import {setGraphCursorOffset} from 'ml/store/hydrograph-state'; - -import {getMainXScale} from './selectors/scales'; -import {getMainLayout} from './selectors/layout'; - - - -/* - * Renders the cursor slider and sets up the redux event handlers to update the slider - * when needed. - * @param {D3 selection} svg - * @param {Redux store} store - */ -export const drawCursorSlider = function(svg, store) { - const slider = sliderTop() - .displayValue(false) - .ticks(0); - - svg.append('g') - .attr('class', 'cursor-slider-group') - .attr('ga-on', 'click') - .attr('ga-event-category', 'hydrograph-interaction') - .attr('ga-event-action', 'clickOnSlider') - .call(link(store, (group, {xScale, layout}, store ) => { - const [startMillis, endMillis] = xScale.domain(); - const [startX, endX] = xScale.range(); - const cursorOffset = getGraphCursorOffset(store.getState()); - - slider - .min(startMillis) - .max(endMillis) - .width(endX - startX); - slider.silentValue(cursorOffset ? slider.min() + cursorOffset : slider.max()); - - group - .attr('transform', `translate(${layout.margin.left},15)`) - .call(slider); - slider.on('onchange', (val) => { - store.dispatch(setGraphCursorOffset(val - startMillis)); - }); - }, createStructuredSelector({ - xScale: getMainXScale('current'), - layout: getMainLayout - }), store)) - .call(link(store, (group, cursorOffset) => { - slider.silentValue(cursorOffset ? slider.min() + cursorOffset : slider.max()); - }, getGraphCursorOffset)); -}; - diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/cursor-slider.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/cursor-slider.test.js deleted file mode 100644 index 5d31c0c6f019f86a478e9c5508cbc47a6851e6af..0000000000000000000000000000000000000000 --- a/assets/src/scripts/monitoring-location/components/hydrograph/cursor-slider.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import {select} from 'd3-selection'; - -import config from 'ui/config'; -import * as utils from 'ui/utils'; - -import {configureStore} from 'ml/store'; - -import {TEST_PRIMARY_IV_DATA, TEST_CURRENT_TIME_RANGE} from './mock-hydrograph-state'; -import {drawCursorSlider} from './cursor-slider'; - -describe('monitoring-location/components/hydrograph/cursor-slider', () => { - utils.mediaQuery = jest.fn().mockReturnValue(true); - config.locationTimeZone = 'America/Chicago'; - const TEST_STATE = { - hydrographData: { - primaryIVData: TEST_PRIMARY_IV_DATA, - currentTimeRAnge: TEST_CURRENT_TIME_RANGE - }, - hydrographState: { - graphCursorOffset: 500000 - }, - ui: { - windowWidth: 1300, - width: 990 - } - }; - - describe('drawCursorSlider', () => { - let svg; - let store; - beforeEach(() => { - svg = select('body').append('svg'); - store = configureStore(TEST_STATE); - }); - - afterEach(() => { - svg.remove(); - }); - - it('Creates a cursor slider', () => { - drawCursorSlider(svg, store); - - const sliderGroup = svg.selectAll('.cursor-slider-group'); - expect(sliderGroup.size()).toBe(1); - expect(sliderGroup.selectAll('.slider').size()).toBe(1); - }); - }); -}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index fc3220f682ded6a653d271a592b61a9f3c3e3c61..b3c046270bbc9871810074e7aa9450a1be5449b1 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -32,7 +32,6 @@ import {showDataIndicators} from './data-indicator'; import {drawTimeSeriesLegend} from './legend'; import {initializeTimeSeriesGraph, drawTimeSeriesGraphData} from './time-series-graph'; -import {initializeTooltipCursorSlider, drawTooltipCursorSlider} from './tooltip'; import DataTablesApp from './DataTablesApp.vue'; import GraphControlsApp from './GraphControlsApp.vue'; @@ -142,7 +141,7 @@ export const attachToNode = function(store, graphContainer.call(initializeTimeSeriesGraph, store, siteno, agencyCd, sitename, thisShowMLName, !thisShowOnlyGraph); showDataIndicators(true, store); if (!showOnlyGraph) { - initializeTooltipCursorSlider(graphContainer, store); + //TODO: The tooltips, legend and the main hydrograph can be added to the HydrographApp. // The main hydrograph should be converted to a Vue component last. As part of that task we // will figure out how to handle the loading indicator and the no data overlay @@ -196,10 +195,6 @@ export const attachToNode = function(store, showDataIndicators(false, store); graphContainer.call(drawTimeSeriesGraphData, store, !thisShowOnlyGraph); - if (!thisShowOnlyGraph) { - graphContainer - .call(drawTooltipCursorSlider, store); - } legendControlsContainer.call(drawTimeSeriesLegend, store); if (!thisShowOnlyGraph) { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js index 21a5d340aca3818331099c3714bb26506d42904e..fb41a48183af3827e6743bcc966f271dcbfdeb22 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.js @@ -15,7 +15,6 @@ import {getCursorTime, getIVDataCursorPoint, getIVDataTooltipPoint, getGroundwat import {getMainLayout} from './selectors/layout'; import {getMainXScale, getMainYScale} from './selectors/scales'; import {getPrimaryParameter} from './selectors/time-series-data'; -import {drawCursorSlider} from './cursor-slider'; const getIVDataTooltipTextInfo = function(point, dataKind, unitCode) { @@ -165,28 +164,3 @@ export const drawTooltipFocus = function(elem, store) { setGraphCursorOffset) ); }; - -/* - * Initial rendering of the tooltip slider - * @param {D3 selection} elem - * @param {Redux store} store - */ -export const initializeTooltipCursorSlider = function(elem, store) { - elem.append('svg') - .classed('cursor-slider-svg', true) - .attr('xmlns', 'http://www.w3.org/2000/svg') - .call(link(store, (elem, layout) => { - elem.attr('viewBox', `0 0 ${layout.width + layout.margin.left + layout.margin.right} 25`); - }, getMainLayout)); -}; - -/* - * Renders the cursor slider used to move the tooltip focus and sets up handlers - * to update the slider as the tooltip focus changes. - * @param {D3 selection} elem - * @param {Redux store} store - */ -export const drawTooltipCursorSlider = function(elem, store) { - elem.select('.cursor-slider-svg') - .call(drawCursorSlider, store); -}; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js index 5d6d75deff9d715bcc512418f91fe274d5364839..d7c34ade17a75e5fbcadda7cb83af0e6822e3b6c 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/tooltip.test.js @@ -6,7 +6,7 @@ import * as utils from 'ui/utils'; import {configureStore} from 'ml/store'; import {TEST_GW_LEVELS, TEST_PRIMARY_IV_DATA, TEST_SECONDARY_IV_DATA, TEST_CURRENT_TIME_RANGE} from './mock-hydrograph-state'; -import {drawTooltipText, drawTooltipFocus, drawTooltipCursorSlider, initializeTooltipCursorSlider} from './tooltip'; +import {drawTooltipText, drawTooltipFocus} from './tooltip'; describe('monitoring-location/components/hydrograph/tooltip module', () => { utils.mediaQuery = jest.fn().mockReturnValue(true); @@ -146,27 +146,4 @@ describe('monitoring-location/components/hydrograph/tooltip module', () => { expect(svg.select('.focus-overlay').size()).toBe(1); }); }); - - describe('initializeTooltipCursorSlider and drawTooltipCursorSlider', () => { - let div; - beforeEach(() => { - div = select('body').append('div'); - }); - - afterEach(() => { - div.remove(); - }); - - it('should render the cursor slider', () => { - let store = configureStore(TEST_STATE); - initializeTooltipCursorSlider(div, store); - drawTooltipCursorSlider(div, store); - - const sliderSvg = div.selectAll('.cursor-slider-svg'); - const slider = sliderSvg.selectAll('.slider'); - - expect(sliderSvg.size()).toBe(1); - expect(slider.size()).toBe(1); - }); - }); }); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/cursor-slider.test.js b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/cursor-slider.test.js new file mode 100644 index 0000000000000000000000000000000000000000..765ddd8f9c2c7c47c998181a238a6dbdc34cbee9 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/cursor-slider.test.js @@ -0,0 +1,68 @@ +import {mount} from '@vue/test-utils'; +import {bindActionCreators} from 'redux'; +import ReduxConnectVue from 'redux-connect-vue'; +import {createStructuredSelector} from 'reselect'; +import {configureStore} from 'ml/store'; +import * as utils from 'ui/utils'; +import {Actions as uiActions} from 'ml/store/ui-state'; +import {TEST_PRIMARY_IV_DATA, TEST_CURRENT_TIME_RANGE} from '../mock-hydrograph-state'; + +import CursorSlider from './cursor-slider.vue'; + +describe('monitoring-location/components/hydrograph/cursor-slider', () => { + const TEST_STATE = { + hydrographData: { + primaryIVData: TEST_PRIMARY_IV_DATA, + currentTimeRAnge: TEST_CURRENT_TIME_RANGE + }, + hydrographState: { + graphCursorOffset: 500000 + }, + ui: { + windowWidth: 1300, + width: 990 + } + }; + + let store; + let wrapper; + + describe('drawCursorSlider', () => { + utils.mediaQuery = jest.fn().mockReturnValue(true); + beforeEach(() => { + store = configureStore(TEST_STATE); + + wrapper = mount(CursorSlider, { + global: { + plugins: [ + [ReduxConnectVue, { + store, + mapDispatchToPropsFactory: (actionCreators) => (dispatch) => bindActionCreators(actionCreators, dispatch), + mapStateToPropsFactory: createStructuredSelector + }] + ], + provide: { + store: store + } + } + }); + }); + + it('Creates a cursor slider', async() => { + const sliderGroup = wrapper.findAll('.cursor-slider-group'); + expect(sliderGroup).toHaveLength(1); + expect(wrapper.vm.group.getAttribute('transform')).toContain('translate'); + }); + + it('Expects that changing the ui width will change the viewBox and transforms', async() => { + const viewBox = wrapper.find('.cursor-slider-svg').attributes('viewBox'); + const sliderGroup = wrapper.find('.cursor-slider-group'); + utils.mediaQuery.mockReturnValue(false); + store.dispatch(uiActions.resizeUI(400, 350)); + await wrapper.vm.$nextTick(); + + expect(wrapper.find('.cursor-slider-svg').attributes('viewBox')).not.toEqual(viewBox); + expect(wrapper.find('.cursor-slider-group').attributes('translate')).not.toEqual(sliderGroup); + }); + }); +}); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/cursor-slider.vue b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/cursor-slider.vue new file mode 100644 index 0000000000000000000000000000000000000000..0db15a86424538a146aed8d86e768f86a8721729 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/cursor-slider.vue @@ -0,0 +1,90 @@ +<template> + <svg + class="cursor-slider-svg" + xmlns="http://www.w3.org/2000/svg" + :viewBox="viewBox" + > + <g + ref="group" + class="cursor-slider-group" + ga-on="click" + ga-event-category="hydrograph-interaction" + ga-event-action="clickOnSlider" + :transform="transform" + /> + </svg> +</template> + +<script> +import {useActions, useState} from 'redux-connect-vue'; +import {computed, ref, watchEffect} from 'vue'; + +import {sliderTop} from 'd3-simple-slider'; +import {select} from 'd3-selection'; + +import {getGraphCursorOffset} from 'ml/selectors/hydrograph-state-selector'; + +import {setGraphCursorOffset} from 'ml/store/hydrograph-state'; + +import {getMainXScale} from '../selectors/scales'; +import {getMainLayout} from '../selectors/layout'; + +export default { + name: 'CursorSlider', + setup() { + const state = useState({ + layout: getMainLayout, + xScale: getMainXScale('current'), + cursorOffset: getGraphCursorOffset + }); + + const actions = useActions({ + setGraphCursorOffset + }); + + const group = ref(null); + + const transform = computed(() => { + return `translate(${state.layout.value.margin.left},15)`; + }); + + const viewBox = computed(() => { + return `0 0 ${state.layout.value.width + state.layout.value.margin.left + state.layout.value.margin.right} 25`; + }); + + const slider = sliderTop() + .displayValue(false) + .ticks(0); + + watchEffect(() => { + const [startMillis, endMillis] = state.xScale.value.domain(); + const [startX, endX] = state.xScale.value.range(); + + slider + .min(startMillis) + .max(endMillis) + .width(endX - startX); + slider.silentValue(state.cursorOffset.value ? slider.min() + state.cursorOffset.value : slider.max()); + + if (group.value) { + select(group.value).call(slider); + } + + slider.on('onchange', (val) => { + actions.setGraphCursorOffset(val - startMillis); + }); + }); + + watchEffect(() => { + slider.silentValue(state.cursorOffset.value ? slider.min() + state.cursorOffset.value : slider.max()); + }); + + return { + ...state, + viewBox, + group, + transform + }; + } +}; +</script> \ No newline at end of file diff --git a/wdfn-server/waterdata/tests/test_location_utils.py b/wdfn-server/waterdata/tests/test_location_utils.py index 4309046cfa89694e94966af42b826eb3a8554395..a8a69a7cd9141483b71d6b88e78852663ced6334 100644 --- a/wdfn-server/waterdata/tests/test_location_utils.py +++ b/wdfn-server/waterdata/tests/test_location_utils.py @@ -686,4 +686,4 @@ class TestGetDefaultParameterCode(TestCase): self.assertEqual(get_default_parameter_code({}, { '72019': {}, '65536': {} - }), '72019') + }), '72019') \ No newline at end of file