Newer
Older
/**
* Hydrograph charting module.
*/
Bucknell, Mary S.
committed
import {select} from 'd3-selection';
import {createStructuredSelector} from 'reselect';
Bucknell, Mary S.
committed
import {DateTime} from 'luxon';
import {drawWarningAlert, drawInfoAlert} from 'd3render/alerts';
import {drawLoadingIndicator} from 'd3render/loading-indicator';
import {link} from 'ui/lib/d3-redux';
Bucknell, Mary S.
committed
import {getIanaTimeZone} from 'ml/selectors/time-zone-selector';
import {hasAnyTimeSeries, getCurrentParmCd, getVariables, getCurrentDateRange, getCustomTimeRange, getShowIVTimeSeries} from 'ml/selectors/time-series-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';
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 {renderTimeSeriesUrlParams} from 'ml/url-params';
Bucknell, Mary S.
committed
import {drawDateRangeControls} from 'ivhydrograph/date-controls';
import {drawDataTable} from 'ivhydrograph/data-table';
import {drawGraphBrush} from 'ivhydrograph/graph-brush';
import {drawGraphControls} from 'ivhydrograph/graph-controls';
import {SPARK_LINE_DIM} from 'ivhydrograph/selectors/layout';
import {drawTimeSeriesLegend} from 'ivhydrograph/legend';
import {drawMethodPicker} from 'ivhydrograph/method-picker';
import {plotSeriesSelectTable} from 'ivhydrograph/parameters';
import {getLineSegmentsByParmCd} from 'ivhydrograph/selectors/drawing-data';
import {getAvailableParameterCodes} from 'ivhydrograph/selectors/parameter-data';
import {getTimeSeriesScalesByParmCd} from 'ivhydrograph/selectors/scales';
import {getQueryInformation} from 'ivhydrograph/selectors/time-series-data';
import {drawTimeSeriesGraph} from 'ivhydrograph/time-series-graph';
import {drawTooltipCursorSlider} from 'ivhydrograph/tooltip';
import {isPeriodWithinAcceptableRange, isPeriodCustom} from 'ivhydrograph/hydrograph-utils';
Bucknell, Mary S.
committed
/**
Bucknell, Mary S.
committed
* Modify styling to hide or display the elem.
Bucknell, Mary S.
committed
*
* @param elem
Bucknell, Mary S.
committed
* @param {Boolean} showElem
Bucknell, Mary S.
committed
*/
const controlDisplay = function(elem, showElem) {
Bucknell, Mary S.
committed
elem.attr('hidden', showElem ? null : true);
Bucknell, Mary S.
committed
};
Bucknell, Mary S.
committed
/*
* Renders the hydrograph on the node element using the Redux store for state information. The siteno, latitude, and
* longitude are required parameters. All others are optional and are used to set the initial state of the hydrograph.
Bucknell, Mary S.
committed
* @param {Redux store} store
* @param {DOM node} node
* @param {Object} - string properties to set initial state information. The property siteno is required
Bucknell, Mary S.
committed
*/
node,
{
siteno,
latitude,
longitude,
parameterCode,
compare,
period,
startDT,
endDT,
timeSeriesId, // This must be converted to an integer
showOnlyGraph = false,
showMLName = false
} = {}) {
Bucknell, Mary S.
committed
const nodeElem = select(node);
/*
* Converts a numerical time representation to a calendar date format
* @param {Object} customTimeRange - A numerical time indicator than can be converted to date format
* @param {String} ianaTimeZone - A geographical reference to the user's timezone
* @return {Object} Contains the start and end calendar dates
*/
const convertedTimeToDate = function(customTimeRange, ianaTimeZone) {
return {
start: DateTime.fromMillis(customTimeRange.start, {zone: ianaTimeZone.timeZone}).toFormat('yyyy-LL-dd'),
end: DateTime.fromMillis(customTimeRange.end, {zone: ianaTimeZone.timeZone}).toFormat('yyyy-LL-dd')
};
};
/*
* Assembles the href/URL needed to contact WaterServices and return data related to the currently showing hydrograph
* @param {String} currentIVDateRange - The string will be in the form of P{number of days}D (like P7D) or P1Y or 'custom'
* If the value is 'custom', it means that the user selected the timespan in calendar days
* @param {Object} customTimeRange - A numerical time indicator than can be converted to date format
* This will only have a value if the currentIVDateRange is custom
* @param {String} ianaTimeZone - A geographical reference to the user's timezone
* @param {String} parameterCode - the five digit code for the current hydrograph parameter
* @ return a URL formatted to return data from WaterServices that matches the currently displayed hydrograph
*/
const stationDataDownloadURL = function(currentIVDateRange, customTimeRange, ianaTimeZone, parameterCode) {
console.log('currentIVDateRange ', currentIVDateRange)
if (currentIVDateRange === 'custom') {
const convertedTimeDate = convertedTimeToDate(customTimeRange, ianaTimeZone);
return `${config.WATER_SERVICES}/?format=rdb&sites=${siteno}&startDT=${convertedTimeDate.start}&endDT=${convertedTimeDate.end}¶meterCd=${parameterCode}&siteStatus=all`;
} else {
return `${config.WATER_SERVICES}/?format=rdb&sites=${siteno}&period=${currentIVDateRange}¶meterCd=${parameterCode}&siteStatus=all`;
}
if (!siteno) {
Bucknell, Mary S.
committed
select(node).call(drawWarningAlert, {title: 'Hydrograph Alert', body: 'No data is available.'});
Bucknell, Mary S.
committed
}
Bucknell, Mary S.
committed
// Show the loading indicator
Bucknell, Mary S.
committed
nodeElem
Naab, Daniel James
committed
.select('.loading-indicator-container')
.call(drawLoadingIndicator, {showLoadingIndicator: true, sizeClass: 'fa-3x'});
Bucknell, Mary S.
committed
// Fetch time zone
Bucknell, Mary S.
committed
const fetchTimeZonePromise = store.dispatch(timeZoneActions.retrieveIanaTimeZone(latitude, longitude));
// Fetch waterwatch flood levels
store.dispatch(floodDataActions.retrieveWaterwatchData(siteno));
Bucknell, Mary S.
committed
let fetchDataPromise;
Bucknell, Mary S.
committed
if (showOnlyGraph) {
// Only fetch what is needed
if (parameterCode && period) {
fetchDataPromise = store.dispatch(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries(siteno, parameterCode, period));
Bucknell, Mary S.
committed
} else if (parameterCode && startDT && endDT) {
Bucknell, Mary S.
committed
// Don't fetch until time zone is available
fetchDataPromise = fetchTimeZonePromise.then(() => {
return store.dispatch(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange(siteno, startDT, endDT, parameterCode));
Bucknell, Mary S.
committed
});
Bucknell, Mary S.
committed
} else {
fetchDataPromise = store.dispatch(ivTimeSeriesDataActions.retrieveIVTimeSeries(siteno, parameterCode ? [parameterCode] : null));
Bucknell, Mary S.
committed
}
Bucknell, Mary S.
committed
} else {
Bucknell, Mary S.
committed
// Retrieve all parameter codes for 7 days and median statistics
fetchDataPromise = store.dispatch(ivTimeSeriesDataActions.retrieveIVTimeSeries(siteno))
Bucknell, Mary S.
committed
.then(() => {
// Fetch any extended data needed to set initial state
const currentParamCode = parameterCode ? parameterCode : getCurrentParmCd(store.getState());
// If there are query parameters present, use them but restrict them to the form of PxD or P1Y
if (isPeriodWithinAcceptableRange(period)) {
isPeriodCustom(period) ?
store.dispatch(ivTimeSeriesDataActions.retrieveCustomTimePeriodIVTimeSeries(siteno, parameterCode, period)) :
store.dispatch(ivTimeSeriesDataActions.retrieveExtendedIVTimeSeries(siteno, period, currentParamCode));
Bucknell, Mary S.
committed
} else if (startDT && endDT) {
Bucknell, Mary S.
committed
fetchTimeZonePromise.then(() => {
store.dispatch(ivTimeSeriesDataActions.retrieveUserRequestedIVDataForDateRange(siteno, startDT, endDT, currentParamCode));
Bucknell, Mary S.
committed
});
Bucknell, Mary S.
committed
}
});
store.dispatch(statisticsDataActions.retrieveMedianStatistics(siteno));
Bucknell, Mary S.
committed
}
fetchDataPromise.then(() => {
// Hide the loading indicator
nodeElem
.select('.loading-indicator-container')
.call(drawLoadingIndicator, {showLoadingIndicator: false, sizeClass: 'fa-3x'});
if (!hasAnyTimeSeries(store.getState())) {
drawInfoAlert(nodeElem, {body: 'No time series data available for this site'});
Bucknell, Mary S.
committed
if (!showOnlyGraph) {
document.getElementById('classic-page-link')
.setAttribute('href', `${config.NWIS_INVENTORY_ENDPOINT}?site_no=${siteno}`);
}
//Update time series state
if (parameterCode) {
const isThisParamCode = function(variable) {
return variable.variableCode.value === parameterCode;
};
const thisVariable = Object.values(getVariables(store.getState())).find(isThisParamCode);
store.dispatch(ivTimeSeriesStateActions.setCurrentIVVariable(thisVariable.oid));
}
if (compare) {
store.dispatch(ivTimeSeriesStateActions.setIVTimeSeriesVisibility('compare', true));
}
if (timeSeriesId) {
store.dispatch(ivTimeSeriesStateActions.setCurrentIVMethodID(parseInt(timeSeriesId)));
}
// Initial data has been fetched and initial state set. We can render the hydrograph elements
// Set up rendering functions for the graph-container
let graphContainer = nodeElem.select('.graph-container')
.call(link(store, controlDisplay, hasAnyTimeSeries))
.call(drawTimeSeriesGraph, store, siteno, 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.
if (!showOnlyGraph) {
nodeElem
.call(drawMethodPicker, store)
.call(drawDateRangeControls, store, siteno);
nodeElem.select('.ts-legend-controls-container')
.call(drawGraphControls, store);
// Construct and add the hrefs needed so users can download the data corresponding to the currently displayed hydrograph with the 'download data' links
nodeElem.select('#station-data-download-link').call(link(store, (container, {currentIVDateRange, customTimeRange, ianaTimeZone, parameterCode, showIVTimeSeries, queryInformation}) => {
container.attr('href', stationDataDownloadURL(currentIVDateRange, customTimeRange, ianaTimeZone, parameterCode, showIVTimeSeries, queryInformation));
nodeElem.select('#station-compare-data-download-link').text('').attr('href', '');
nodeElem.select('#median-data-download-link').text('').attr('href', '');
if (showIVTimeSeries.compare) {
nodeElem.select('#station-compare-data-download-link')
.text('Station - compare to last year')
.attr('href', '//waterservices.usgs.gov/nwis/iv/?format=rdb&sites=01646500¶meterCd=00060&siteStatus=all');
}
if (showIVTimeSeries.median) {
nodeElem.select('#median-data-download-link')
.text('Median')
.attr('href', '//waterservices.usgs.gov/nwis/iv/?format=rdb&sites=01646500¶meterCd=00060&siteStatus=all');
}
}, createStructuredSelector({
currentIVDateRange: getCurrentDateRange,
customTimeRange: getCustomTimeRange,
ianaTimeZone: getIanaTimeZone,
showIVTimeSeries: getShowIVTimeSeries,
queryInformation: getQueryInformation
nodeElem.select('#metadata-download-link').attr('href', '//waterservices.usgs.gov/nwis/iv/?format=rdb&sites=01646500¶meterCd=00060&siteStatus=all');
nodeElem.select('#iv-data-table-container')
.call(drawDataTable, 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);
}
Bucknell, Mary S.
committed
});