From b5a283f1b4ca35cd552cc7a86dd6dc4062871829 Mon Sep 17 00:00:00 2001 From: mbucknell <mbucknell@usgs.gov> Date: Fri, 12 Feb 2021 07:23:12 -0600 Subject: [PATCH] Trying to simplify the date-controls. I have decided to wait on this and move on to other controls in the hydrograph. --- .../components/hydrograph/date-controls.js | 153 ++++++++---------- .../components/hydrograph/index.js | 97 +++++------ .../selectors/hydrograph-state-selector.js | 25 +++ .../templates/macros/components.html | 1 - 4 files changed, 146 insertions(+), 130 deletions(-) diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/date-controls.js b/assets/src/scripts/monitoring-location/components/hydrograph/date-controls.js index bafd9163c..9361c5111 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/date-controls.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/date-controls.js @@ -6,70 +6,70 @@ import components from 'uswds/src/js/components'; import config from 'ui/config'; import {link} from 'ui/lib/d3-redux'; -import {drawLoadingIndicator} from 'd3render/loading-indicator'; - -import {getAllGroundwaterLevels} from 'ml/selectors/discrete-data-selector'; -import { - isLoadingTS, - hasAnyTimeSeries, - getUserInputsForSelectingTimespan, - getCustomTimeRange, - getCurrentParmCd -} from 'ml/selectors/time-series-selector'; -import {getIanaTimeZone} from 'ml/selectors/time-zone-selector'; - -import {Actions as ivTimeSeriesDataActions} from 'ml/store/instantaneous-value-time-series-data'; -import {Actions as ivTimeSeriesStateActions} from 'ml/store/instantaneous-value-time-series-state'; - - -export const drawDateRangeControls = function(elem, store, siteno) { - const DATE_RANGE = [{ - name: '7 days', - period: 'P7D' - }, { - name: '30 days', - period: 'P30D' - }, { - name: '1 year', - period: 'P1Y' - }, { - name: 'Custom', - period: 'custom', +import {getInputsForRetrieval, getSelectedDateRange, getSelectedCustomTimeRange} from 'ml/selectors/hydrograph-state-selector'; + +import {retrieveHydrographData} from 'ml/store/hydrograph-data'; +import {clearGraphBrushOffset, setSelectedCustomTimeRange, setSelectedDateRange} from 'ml/store/hydrograph-state'; + +const DATE_RANGE = [{ + name: '7 days', + period: 'P7D' +}, { + name: '30 days', + period: 'P30D' +}, { + name: '1 year', + period: 'P365D' +}, { + name: 'Custom', + period: 'custom', + ariaExpanded: false +}]; +const CUSTOM_TIMEFRAME_RADIO_BUTTON_DETAILS = [ + { + id: 'custom-input-days-before-today', + value: 'days', + text: 'Days before today', ariaExpanded: false - }]; - const CUSTOM_TIMEFRAME_RADIO_BUTTON_DETAILS = [ - { - id: 'custom-input-days-before-today', - value: 'days', - text: 'Days before today', - checked: true, - ariaExpanded: false - }, - { - id: 'custom-input-calendar-days', - value: 'calendar', - text: 'Calendar days', - checked: false, - ariaExpanded: false - } - ]; + }, + { + id: 'custom-input-calendar-days', + value: 'calendar', + text: 'Calendar days', + ariaExpanded: false + } +]; + +const isCustomPeriod = function(dateRange) { + return dateRange !== 'custom' && !DATE_RANGE.find(range => range.period === dateRange); +} +const showCustomContainer = function(dateRange) { + return dateRange === 'custom' ? true : !DATE_RANGE.find(range => range.period === dateRange); +} + +export const drawDateRangeControls = function(elem, store, siteno, initialPeriod, initialStartTime, initialEndTime) { + let isCustomPeriod = false; + let isCustomCalendarDays = false; + if (initialPeriod) { + store.dispatch(setSelectedDateRange(initialPeriod)); + isCustomPeriod = !DATE_RANGE.find(range => range.period === period); + } else if (initialStartTime && initialEndTime) { + isCustomCalendarDays = true; + store.dispatch(setSelectedCustomTimeRange( + DateTime.fromISO(initialStartTime, {zone: config.locationTimeZone}).toMillis(), + DateTime.fromISO(initialEndTime, {zone: config.locationTimeZone}).toMillis())); + } + const isCustomSelected = isCustomPeriod || isCustomCalendarDays; const containerRadioGroupMainSelectButtons = elem.insert('div', ':nth-child(2)') .attr('id', 'ts-daterange-select-container') .attr('role', 'radiogroup') - .attr('aria-label', 'Time interval select') - .call(link(store,function(container, showControls) { - container.attr('hidden', showControls ? null : true); - }, (state) => { - return hasAnyTimeSeries(state) || getAllGroundwaterLevels(state); - })); + .attr('aria-label', 'Time interval select'); // Add a container that holds the custom selection radio buttons and the form fields const containerRadioGroupAndFormButtons = elem.insert('div', ':nth-child(3)') .attr('class', 'container-radio-group-and-form-buttons') - .call(link(store, (container, userInputsForSelectingTimespan) => { - container.attr('hidden', userInputsForSelectingTimespan.mainTimeRangeSelectionButton === 'custom' ? null : true); - }, getUserInputsForSelectingTimespan)); + .attr('hidden', isCustomSelected ? null : true); const containerRadioGroupCustomSelectButtons = containerRadioGroupAndFormButtons.append('div') .attr('id', 'ts-custom-date-radio-group') @@ -80,18 +80,14 @@ export const drawDateRangeControls = function(elem, store, siteno) { .attr('id', 'ts-custom-days-before-today-select-container') .attr('class', 'usa-form') .attr('aria-label', 'Custom date by days before today specification') - .call(link(store, (container, userInputsForSelectingTimespan) => { - container.attr('hidden', userInputsForSelectingTimespan.customTimeRangeSelectionButton === 'days-input' && userInputsForSelectingTimespan.mainTimeRangeSelectionButton === 'custom' ? null : true); - }, getUserInputsForSelectingTimespan)); + .attr('hidden', isCustomCalendarDays ? true : null); const containerCustomCalendarDays = containerRadioGroupAndFormButtons.append('div') .attr('id', 'ts-customdaterange-select-container') .attr('role', 'customdate') .attr('class', 'usa-form') .attr('aria-label', 'Custom date specification') - .call(link(store, (container, userInputsForSelectingTimespan) => { - container.attr('hidden', userInputsForSelectingTimespan.customTimeRangeSelectionButton === 'calendar-input' && userInputsForSelectingTimespan.mainTimeRangeSelectionButton === 'custom' ? null : true); - }, getUserInputsForSelectingTimespan)); + .attr('hidden', isCustomCalendarDays ? null : true); const createRadioButtonsForCustomDaterangeSelection = function(containerRadioGroupCustomSelectButtons) { containerRadioGroupCustomSelectButtons.append('p').text('Enter timespan using'); @@ -108,23 +104,19 @@ export const drawDateRangeControls = function(elem, store, siteno) { .attr('id', d => `${d.value}-input`) .attr('class', 'usa-radio__input') .attr('value', d => d.value) - .property('checked', d => d.checked) + .property('checked', d => d.value === 'days' && !isCustomCalendarDays || isCustomCalendarDays) .attr('ga-on', 'click') - .attr('aria-expanded', d => d.ariaExpanded) + .attr('aria-expanded', d => d.value === 'days' && !isCustomCalendarDays || isCustomCalendarDays) .attr('ga-event-category', 'TimeSeriesGraph') .attr('ga-event-action', d => `changeDateRangeWith${d.value}`) - .on('change', function() { - const selected = listItemForCustomSelectRadioButtons.select('input:checked'); - const selectedVal = selected.attr('id'); - store.dispatch(ivTimeSeriesStateActions.setUserInputsForSelectingTimespan('customTimeRangeSelectionButton', selectedVal)); + .on('change', function(_, d) { + containerCustomDaysBeforeToday.attr('hidden', d.value === 'days' ? null : true); + containerCustomCalendarDays.attr('hidden', d.value === 'calendar' ? null : true); }); listItemForCustomSelectRadioButtons.append('label') .attr('class', 'usa-radio__label') .attr('for', (d) => `${d.value}-input`) .text((d) => d.text); - listItemForCustomSelectRadioButtons.call(link(store, (elem, userInputsForSelectingTimespan) => { - elem.select(`#${userInputsForSelectingTimespan.customTimeRangeSelectionButton}`).property('checked', true); - }, getUserInputsForSelectingTimespan)); }; const createControlsForSelectingTimeSpanInDaysFromToday = function() { @@ -145,7 +137,9 @@ export const drawDateRangeControls = function(elem, store, siteno) { .attr('id', 'with-hint-input-days-from-today') .attr('maxlength', `${config.MAX_DIGITS_FOR_DAYS_FROM_TODAY}`) .attr('name', 'with-hint-input-days-from-today') + .attr('value', isCustomPeriod ? initialPeriod.slice(1, -2) : '') .attr('aria-describedby', 'with-hint-input-days-from-today-info with-hint-input-days-from-today-hint'); + numberOfDaysSelection.append('span') .text(`${config.MAX_DIGITS_FOR_DAYS_FROM_TODAY} digits allowed`) .attr('id', 'with-hint-input-days-from-today-info') @@ -162,12 +156,7 @@ export const drawDateRangeControls = function(elem, store, siteno) { customDaysBeforeTodayAlertBody.append('h3') .attr('class', 'usa-alert__heading') .text('Requirements'); - numberOfDaysSelection.call(link(store, (container, userInputsForSelectingTimespan) => { - if (userInputsForSelectingTimespan.mainTimeRangeSelectionButton === 'custom' && userInputsForSelectingTimespan.customTimeRangeSelectionButton === 'days-input') { - container.select('#with-hint-input-days-from-today') - .property('value', userInputsForSelectingTimespan.numberOfDaysFieldValue); - } - }, getUserInputsForSelectingTimespan)); + // Adds controls for the 'days before today' submit button const daysBeforeTodaySubmitContainer = containerCustomDaysBeforeToday.append('div') .attr('class', 'submit-button'); @@ -189,14 +178,12 @@ export const drawDateRangeControls = function(elem, store, siteno) { customDaysBeforeTodayValidationContainer.attr('hidden', null); } else { customDaysBeforeTodayValidationContainer.attr('hidden', true); - const parameterCode = getCurrentParmCd(store.getState()); - store.dispatch(ivTimeSeriesStateActions.setUserInputsForSelectingTimespan('numberOfDaysFieldValue', userSpecifiedNumberOfDays)); - store.dispatch(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries( - siteno, - parameterCode, - formattedPeriodQueryParameter - )).then(() => store.dispatch(ivTimeSeriesStateActions.clearIVGraphBrushOffset())); + store.dispatch(setUserInputsForSelectingTimespan('numberOfDaysFieldValue', userSpecifiedNumberOfDays)); + store.dispatch(setUserInputsForSelectingTimespan('mainTimeRangeSelectionButton', 'custom')); + store.dispatch(setSelectedDateRange(formattedPeriodQueryParameter)); + store.dispatch(retrieveHydrographData(siteno, getInputsForRetrieval(store.getState()))) + .then(() => store.dispatch(clearGraphBrushOffset())); } }); }; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index d7d224ee5..2802aeb8b 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -19,11 +19,13 @@ import {drawLoadingIndicator} from 'd3render/loading-indicator'; //import {hasAnyVariables, getCurrentVariableID, getCurrentParmCd, getVariables} from 'ml/selectors/time-series-selector'; import {retrieveHydrographData} from 'ml/store/hydrograph-data'; +import {setSelectedParameterCode} from 'ml/store/hydrograph-state'; + //import {Actions as ivTimeSeriesDataActions} from 'ml/store/instantaneous-value-time-series-data'; //import {Actions as ivTimeSeriesStateActions} from 'ml/store/instantaneous-value-time-series-state'; //import {Actions as statisticsDataActions} from 'ml/store/statistics-data'; //import {Actions as timeZoneActions} from 'ml/store/time-zone'; -//import {Actions as floodDataActions} from 'ml/store/flood-inundation'; +import {Actions as floodDataActions} from 'ml/store/flood-inundation'; //import {drawDateRangeControls} from './date-controls'; //import {drawDataTables} from './data-table'; @@ -56,8 +58,6 @@ export const attachToNode = function(store, siteno, agencyCode, sitename, - latitude, - longitude, parameterCode, compare, period, @@ -78,9 +78,6 @@ export const attachToNode = function(store, .select('.loading-indicator-container') .call(drawLoadingIndicator, {showLoadingIndicator: true, sizeClass: 'fa-3x'}); - // Fetch waterwatch flood levels - //store.dispatch(floodDataActions.retrieveWaterwatchData(siteno)); - // Need to set default parameter code in server and insert in markup */ const fetchDataPromise = store.dispatch(retrieveHydrographData(siteno, { parameterCode: parameterCode, period: startDT && endDT ? null : period || 'P7D', @@ -89,54 +86,62 @@ export const attachToNode = function(store, loadCompare: false, loadMedian: false })); + + + // Fetch waterwatch flood levels - TODO: consider only fetching when gage height is requested + store.dispatch(floodDataActions.retrieveWaterwatchData(siteno)); + fetchDataPromise.then(() => { console.log('Finished fetching Hydrograph data'); nodeElem .select('.loading-indicator-container') .call(drawLoadingIndicator, {showLoadingIndicator: false, sizeClass: 'fa-3x'}); - // Initial data has been fetched. We can render the hydrograph elements - // Initialize method picker before rendering time series - let graphContainer = nodeElem.select('.graph-container') - .call(drawMethodPicker, store, timeSeriesId) - .call(drawTimeSeriesGraph, store, siteno, agencyCode, sitename, showMLName, !showOnlyGraph); - - if (!showOnlyGraph) { - graphContainer - .call(drawTooltipCursorSlider, store) - .call(drawGraphBrush, store); - } - graphContainer.append('div') - .classed('ts-legend-controls-container', true) - .call(drawTimeSeriesLegend, store); - // Add UI interactive elements, data table and the provisional data alert. + // Initial data has been fetched. We can render the hydrograph elements + // Initialize method picker before rendering the graph in order to set the selected method id + if (!showOnlyGraph) { + nodeElem.call(drawMethodPicker, store, timeSeriesId); + } + let graphContainer = nodeElem.select('.graph-container'); + graphContainer.call(drawTimeSeriesGraph, store, siteno, agencyCode, sitename, showMLName, !showOnlyGraph); + + if (!showOnlyGraph) { + graphContainer + .call(drawTooltipCursorSlider, store) + .call(drawGraphBrush, store); + } + const legendControlsContainer = graphContainer.append('div') + .classed('ts-legend-controls-container', true) + .call(drawTimeSeriesLegend, store); + + if (!showOnlyGraph) { + /* + nodeElem + .call(drawDateRangeControls, store, siteno); + legendControlsContainer + .call(drawGraphControls, store); + + nodeElem.select('#iv-graph-list-container') + .call(renderDownloadLinks, store, siteno); + + nodeElem.select('#iv-data-table-container') + .call(drawDataTables, store); +*/ + // Set the parameter code explictly. We may eventually set this within the parameter selection table + store.dispatch(setSelectedParameterCode(parameterCode)); /* - if (!showOnlyGraph) { - nodeElem - .call(drawMethodPicker, store) - .call(drawDateRangeControls, store, siteno); - - nodeElem.select('.ts-legend-controls-container') - .call(drawGraphControls, store); - - nodeElem.select('#iv-graph-list-container') - .call(renderDownloadLinks, store, siteno); - - nodeElem.select('#iv-data-table-container') - .call(drawDataTables, store); - //TODO: Find out why putting this before drawDataTable causes the tests to not work correctly - nodeElem.select('.select-time-series-container') - .call(link(store, plotSeriesSelectTable, createStructuredSelector({ - siteno: () => siteno, - availableParameterCodes: getAvailableParameterCodes, - lineSegmentsByParmCd: getLineSegmentsByParmCd('current', 'P7D'), - timeSeriesScalesByParmCd: getTimeSeriesScalesByParmCd('current', 'P7D', SPARK_LINE_DIM) - }), store)); - - renderTimeSeriesUrlParams(store); - } + //TODO: Find out why putting this before drawDataTable causes the tests to not work correctly + nodeElem.select('.select-time-series-container') + .call(link(store, plotSeriesSelectTable, createStructuredSelector({ + siteno: () => siteno, + availableParameterCodes: getAvailableParameterCodes, + lineSegmentsByParmCd: getLineSegmentsByParmCd('current', 'P7D'), + timeSeriesScalesByParmCd: getTimeSeriesScalesByParmCd('current', 'P7D', SPARK_LINE_DIM) + }), store)); + + renderTimeSeriesUrlParams(store); */ - + } }); }; diff --git a/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js b/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js index a671b5dbf..d2a978953 100644 --- a/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js +++ b/assets/src/scripts/monitoring-location/selectors/hydrograph-state-selector.js @@ -1,4 +1,7 @@ +import {DateTime} from 'luxon'; +import {createSelector} from 'reselect'; + export const isCompareIVDataVisible = state => state.hydrographState.showCompareIVData || false; export const isMedianDataVisible = state => state.hydrographState.showMedianData || false; @@ -10,3 +13,25 @@ export const getGraphCursorOffset = state => state.hydrographState.graphCursorOf export const getGraphBrushOffset = state => state.hydrographState.graphBrushOffset || null; export const getUserInputsForTimeRange = state => state.hydrographState.userInputsForTimeRange || null; +export const getInputsForRetrieval = createSelector( + getSelectedParameterCode, + getSelectedDateRange, + getSelectedCustomTimeRange, + isCompareIVDataVisible, + isMedianDataVisible, + (parameterCode, selectedDateRange, selectedCustomTimeRange, loadCompare, loadMedian) => { + const isCustomTime = selectedDateRange === 'custom'; + const period = isCustomTime ? null : selectedDateRange; + const startTime = isCustomTime ? DateTime.toISO(DateTime.fromMillis(selectedCustomTimeRange.start)) : null; + const endTime = isCustomTime ? DateTime.toISO(DateTime.fromMillis(selectedCustomTimeRange.end)) : null; + + return { + parameterCode, + period, + startTime, + endTime, + loadCompare, + loadMedian + }; + } +); diff --git a/wdfn-server/waterdata/templates/macros/components.html b/wdfn-server/waterdata/templates/macros/components.html index 19de1f983..4e2729d9b 100644 --- a/wdfn-server/waterdata/templates/macros/components.html +++ b/wdfn-server/waterdata/templates/macros/components.html @@ -1,6 +1,5 @@ {% macro TimeSeriesComponent(site_data, default_parameter_code) -%} <div class="wdfn-component" data-component="hydrograph" data-siteno="{{ site_data.site_no }}" - data-latitude="{{ site_data.dec_lat_va }}" data-longitude="{{ site_data.dec_long_va }}" data-agency-cd="{{ site_data.agency_cd }}" data-sitename="{{ site_data.station_nm }}" data-parameter-code="{{ default_parameter_code }}"> <div class="loading-indicator-container"></div> -- GitLab