Newer
Older
Bucknell, Mary S.
committed
import {line as d3Line, curveStepAfter} from 'd3-shape';
import {createSelector, createStructuredSelector} from 'reselect';
import {link} from 'ui/lib/d3-redux';
import {mediaQuery} from 'ui/utils';
import {addSVGAccessibility} from 'd3render/accessibility';
import {appendAxes} from 'd3render/axes';
import {renderMaskDefs} from 'd3render/data-masks';
Bucknell, Mary S.
committed
import {appendInfoTooltip} from 'd3render/info-tooltip';
Bucknell, Mary S.
committed
import {showFloodLevels} from 'ml/selectors/flood-data-selector';
Bucknell, Mary S.
committed
import {getPrimaryMedianStatisticsData} from 'ml/selectors/hydrograph-data-selector';
Bucknell, Mary S.
committed
Bucknell, Mary S.
committed
import {getAxes} from './selectors/axes';
import {getIVParameter} from 'ml/selectors/hydrograph-data-selector';
Bucknell, Mary S.
committed
import {getGroundwaterLevelPoints} from './selectors/discrete-data';
Bucknell, Mary S.
committed
import {getFloodLevelData} from './selectors/flood-level-data';
import {getIVDataSegments, HASH_ID} from './selectors/iv-data';
Bucknell, Mary S.
committed
import {getMainLayout} from './selectors/layout';
import {getMainXScale, getMainYScale, getYScale} from './selectors/scales';
import {
getTitle,
getDescription,
isVisible,
Bucknell, Mary S.
committed
Bucknell, Mary S.
committed
import {drawGroundwaterLevels} from './discrete-data';
Bucknell, Mary S.
committed
import {drawFloodLevelLines} from './flood-level-lines';
import {drawThresholdLines} from './threshold-lines';
import {drawDataSegments} from './time-series-lines';
Bucknell, Mary S.
committed
import {drawTooltipFocus, drawTooltipText} from './tooltip';
import {getThresholdsInRange} from './selectors/thresholds-data';
Bucknell, Mary S.
committed
const addDefsPatterns = function(elem) {
const patterns = [{
patternId: HASH_ID.primary,
Bucknell, Mary S.
committed
patternTransform: 'rotate(45)'
}, {
patternId: HASH_ID.secondary,
patternTransform: 'rotate(90)'
},
{
Bucknell, Mary S.
committed
patternId: HASH_ID.compare,
patternTransform: 'rotate(135)'
}];
const defs = elem.append('defs');
renderMaskDefs(defs, 'iv-graph-pattern-mask', patterns);
/**
* Plots the median points for a single median time series.
* @param {Object} elem
* @param {Function} xscale
* @param {Function} yscale
* @param {Number} modulo
* @param {Array} points
Bucknell, Mary S.
committed
const drawMedianPoints = function(elem, {xscale, yscale, modulo, points}) {
const stepFunction = d3Line()
.curve(curveStepAfter)
const medianGrp = elem.append('g')
.attr('class', 'median-stats-group');
medianGrp.append('path')
.datum(points)
.classed('median-data-series', true)
.classed('median-step', true)
.classed(`median-step-${modulo}`, true)
.attr('d', stepFunction);
};
/**
* Plots the median points for all median time series for the current variable.
* @param {Object} elem
* @param {Boolean} visible
* @param {Function} xscale
* @param {Function} yscale
* @param {Array} seriesPoints
* @param {Boolean} enableClip
Bucknell, Mary S.
committed
const drawAllMedianPoints = function(elem, {visible, xscale, yscale, seriesPoints, enableClip}) {
elem.select('#median-points').remove();
const container = elem
.append('g')
Bucknell, Mary S.
committed
if (!visible || !seriesPoints) {
return;
}
if (enableClip) {
container.attr('clip-path', 'url(#graph-clip');
}
Bucknell, Mary S.
committed
Object.values(seriesPoints).forEach((series, index) => {
Bucknell, Mary S.
committed
drawMedianPoints(container, {xscale, yscale, modulo: index % 6, points: series.values});
const drawTitle = function(elem, store, siteNo, agencyCode, sitename, showMLName, showTooltip) {
let titleDiv = elem.append('div')
.classed('time-series-graph-title', true);
if (showMLName) {
titleDiv.append('div')
.attr('class', 'monitoring-location-name-div')
.html(`${sitename}, ${agencyCode} ${siteNo}`);
}
titleDiv.append('div')
.call(link(store, (elem, {title, primaryParameter}) => {
Bucknell, Mary S.
committed
elem.html(title);
if (showTooltip) {
primaryParameter ? primaryParameter.description || 'No description available' : '',
Bucknell, Mary S.
committed
}
}, createStructuredSelector({
primaryParameter: getPrimaryParameter
titleDiv.append('div')
.attr('class', 'secondary-title')
.call(link(store, (elem, {title, secondaryParameter}) => {
if (showTooltip && secondaryParameter) {
secondaryParameter ? secondaryParameter.description || 'No description available' : '',
secondaryParameter: getIVParameter('secondary')
const drawWatermark = function(elem, store) {
// These constants will need to change if the watermark svg is updated
const watermarkHalfHeight = 87 / 2;
const watermarkHalfWidth = 235 / 2;
elem.append('img')
.classed('watermark', true)
.attr('alt', 'USGS - science for a changing world')
.attr('src', config.STATIC_URL + '/img/USGS_green_logo.svg')
Bucknell, Mary S.
committed
.call(link(store, function(elem, layout) {
const centerX = layout.margin.left + (layout.width - layout.margin.right - layout.margin.left) / 2;
const centerY = layout.margin.top + (layout.height - layout.margin.bottom - layout.margin.top) / 2;
const scale = !mediaQuery(config.USWDS_MEDIUM_SCREEN) ? 0.5 : 1;
const translateX = centerX - watermarkHalfWidth;
const translateY = centerY - watermarkHalfHeight;
const transform = `matrix(${scale}, 0, 0, ${scale}, ${translateX}, ${translateY})`;
elem.style('transform', transform);
// for Safari browser
elem.style('-webkit-transform', transform);
Bucknell, Mary S.
committed
}, getMainLayout));
/**
* Redux selector helper function that can combine the primary and secondary data titles into a single string.
* @return {String} the concatenated accessibility title when a secondary data parameter is selected, otherwise returns
* the standard primary parameter title.
*/
const combinedAccessibilityTitle = createSelector(
getTitle('primary'),
getTitle('secondary'),
(primaryTitle, secondaryTitle) => {
return `${primaryTitle}${secondaryTitle ? ` -- ${secondaryTitle}` : ''}`;
});
Bucknell, Mary S.
committed
* Initialize the time series svg and other elements but don't render any data
* @param {D3 selection} elem
* @param {Redux store} store
* @param {String} siteNo
Bucknell, Mary S.
committed
* @param {String} agencyCd
* @param {String} sitename
* @param {Boolean} showMLName - Set to true when the sitename should be rendered above the time series graph
* @param {Boolean} showTooltip
Bucknell, Mary S.
committed
export const initializeTimeSeriesGraph = function(elem, store, siteNo, agencyCode, sitename, showMLName, showTooltip) {
Bucknell, Mary S.
committed
let graphDiv;
Bucknell, Mary S.
committed
graphDiv = elem.append('div')
.attr('class', 'hydrograph-container')
.attr('ga-on', 'click')
.attr('ga-event-category', 'hydrograph-interaction')
.attr('ga-event-action', 'clickOnTimeSeriesGraph')
.call(drawWatermark, store)
.call(drawTitle, store, siteNo, agencyCode, sitename, showMLName, showTooltip);
Bucknell, Mary S.
committed
if (showTooltip) {
graphDiv.call(drawTooltipText, store);
}
Bucknell, Mary S.
committed
graphDiv.append('svg')
Bucknell, Mary S.
committed
.attr('xmlns', 'http://www.w3.org/2000/svg')
.classed('hydrograph-svg', true)
Bucknell, Mary S.
committed
.call(link(store, (elem, layout) => {
elem.select('#graph-clip').remove();
Bucknell, Mary S.
committed
elem.attr('viewBox', `0 0 ${layout.width + layout.margin.left + layout.margin.right} ${layout.height + layout.margin.top + layout.margin.bottom}`)
.attr('id', 'graph-clip')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', layout.width - layout.margin.right)
.attr('height', layout.height - layout.margin.bottom);
Bucknell, Mary S.
committed
.call(link(store, addSVGAccessibility, createStructuredSelector({
title: combinedAccessibilityTitle,
description: getDescription,
Bucknell, Mary S.
committed
isInteractive: () => true,
idPrefix: () => 'hydrograph'
})))
Bucknell, Mary S.
committed
.call(addDefsPatterns);
Bucknell, Mary S.
committed
};
/*
Bucknell, Mary S.
committed
* Sets up the event handlers to render the time series graph data and tooltips.
Bucknell, Mary S.
committed
* @param {D3 selection} elem
* @param {Redux store} store
* @param {Boolean} showTooltip - If true render the tooltip text and add the tooltip focus element
*/
export const drawTimeSeriesGraphData = function(elem, store, showTooltip) {
const graphDiv = elem.select('.hydrograph-container');
const graphSvg = graphDiv.select('.hydrograph-svg');
Bucknell, Mary S.
committed
const dataGroup = graphSvg.append('g')
.attr('class', 'plot-data-lines-group')
.call(link(store, (group, layout) => {
group.attr('transform', `translate(${layout.margin.left},${layout.margin.top})`);
}, getMainLayout))
.call(link(store, appendAxes, getAxes('MAIN')))
.call(link(store, drawDataSegments, createStructuredSelector({
visible: () => true,
Bucknell, Mary S.
committed
segments: getIVDataSegments('primary'),
dataKind: () => 'primary',
Bucknell, Mary S.
committed
xScale: getMainXScale('current'),
yScale: getMainYScale,
enableClip: () => true
Bucknell, Mary S.
committed
})))
.call(link(store, drawDataSegments, createStructuredSelector({
visible: isVisible('secondary'),
segments: getIVDataSegments('secondary'),
dataKind: () => 'secondary',
xScale: getMainXScale('current'),
.call(link(store, drawDataSegments, createStructuredSelector({
visible: isVisible('compare'),
Bucknell, Mary S.
committed
segments: getIVDataSegments('compare'),
dataKind: () => 'compare',
xScale: getMainXScale('prioryear'),
Bucknell, Mary S.
committed
yScale: getMainYScale,
enableClip: () => true
Bucknell, Mary S.
committed
})))
.call(link(store, drawGroundwaterLevels, createStructuredSelector({
levels: getGroundwaterLevelPoints,
xScale: getMainXScale('current'),
yScale: getMainYScale,
enableClip: () => true
Bucknell, Mary S.
committed
.call(link(store, drawAllMedianPoints, createStructuredSelector({
visible: isVisible('median'),
Bucknell, Mary S.
committed
xscale: getMainXScale('current'),
yscale: getMainYScale,
seriesPoints: getPrimaryMedianStatisticsData,
Bucknell, Mary S.
committed
.call(link(store, drawFloodLevelLines, createStructuredSelector({
Bucknell, Mary S.
committed
visible: showFloodLevels,
Bucknell, Mary S.
committed
xscale: getMainXScale('current'),
yscale: getMainYScale,
Bucknell, Mary S.
committed
floodLevels: getFloodLevelData
})))
.call(link(store, drawThresholdLines, createStructuredSelector({
xscale: getMainXScale('current'),
yscale: getMainYScale,
thresholds: getThresholdsInRange
Bucknell, Mary S.
committed
})));
Bucknell, Mary S.
committed
if (showTooltip) {
dataGroup.call(drawTooltipFocus, store);
}