Newer
Older
/**
* Hydrograph charting module.
*/
const { select } = require('d3-selection');
Yan, Andrew N.
committed
const { extent } = require('d3-array');
Naab, Daniel James
committed
const { line } = require('d3-shape');
Naab, Daniel James
committed
const { createSelector, createStructuredSelector } = require('reselect');
const { addSVGAccessibility, addSROnlyTable } = require('../../accessibility');
Naab, Daniel James
committed
const { dispatch, link, provide } = require('../../lib/redux');
Naab, Daniel James
committed
const { appendAxes, axesSelector } = require('./axes');
Yan, Andrew N.
committed
const { ASPECT_RATIO_PERCENT, MARGIN, CIRCLE_RADIUS, layoutSelector } = require('./layout');
const { drawSimpleLegend, legendDisplaySelector, createLegendMarkers } = require('./legend');
const { plotSeriesSelectTable, availableTimeseriesSelector } = require('./parameters');
const { xScaleSelector, yScaleSelector } = require('./scales');
const { Actions, configureStore } = require('./store');
const { currentVariableSelector, pointsSelector, lineSegmentsSelector, pointsTableDataSelector, isVisibleSelector, titleSelector, descriptionSelector, MASK_DESC, HASH_ID } = require('./timeseries');
Bucknell, Mary S.
committed
const { createTooltipFocus, createTooltipText } = require('./tooltip');
const drawMessage = function (elem, message) {
// Set up parent element and SVG
elem.innerHTML = '';
const alertBox = elem
.append('div')
.attr('class', 'usa-alert usa-alert-warning')
.append('div')
.attr('class', 'usa-alert-body');
alertBox
.append('h3')
.attr('class', 'usa-alert-heading')
.html('Hydrograph Alert');
alertBox
.append('p')
Naab, Daniel James
committed
const plotDataLine = function (elem, {visible, lines, tsDataKey, xScale, yScale}) {
Naab, Daniel James
committed
if (!visible) {
Naab, Daniel James
committed
const tsLine = line()
Naab, Daniel James
committed
.x(d => xScale(d.dateTime))
Naab, Daniel James
committed
.y(d => yScale(d.value));
for (let line of lines) {
elem.append('path')
.datum(line.points)
.classed('line', true)
.classed('approved', line.classes.approved)
.classed('estimated', line.classes.estimated)
Naab, Daniel James
committed
.classed(`ts-${tsDataKey}`, true)
const maskCode = line.classes.dataMask.toLowerCase();
const maskDisplayName = MASK_DESC[maskCode].replace(' ', '-').toLowerCase();
Naab, Daniel James
committed
const [xDomainStart, xDomainEnd] = extent(line.points, d => d.dateTime);
const [yRangeStart, yRangeEnd] = yScale.domain();
let maskGroup = elem.append('g')
.attr('class', `${tsDataKey}-mask-group`);
const xSpan = xScale(xDomainEnd) - xScale(xDomainStart);
const rectWidth = xSpan > 0 ? xSpan : 1;
maskGroup.append('rect')
.attr('x', xScale(xDomainStart))
.attr('y', yScale(yRangeEnd))
.attr('width', rectWidth)
.attr('height', Math.abs(yScale(yRangeEnd)- yScale(yRangeStart)))
.attr('class', `mask ${maskDisplayName}-mask`);
const patternId = HASH_ID[tsDataKey] ? `url(#${HASH_ID[tsDataKey]})` : '';
maskGroup.append('rect')
.attr('x', xScale(xDomainStart))
.attr('y', yScale(yRangeEnd))
.attr('width', rectWidth)
.attr('height', Math.abs(yScale(yRangeEnd) - yScale(yRangeStart)))
.attr('fill', patternId);
Naab, Daniel James
committed
}
Naab, Daniel James
committed
const plotDataLines = function (elem, {visible, tsLines, tsDataKey, xScale, yScale}) {
const elemId = `ts-${tsDataKey}-group`;
elem.selectAll(`#${elemId}`).remove();
const tsLineGroup = elem
.append('g')
.attr('id', elemId)
.classed('tsDataKey', true);
for (const lines of tsLines) {
plotDataLine(tsLineGroup, {visible, lines, tsDataKey, xScale, yScale});
}
};
let defs = elem.append('defs');
defs.append('mask')
.attr('id', 'display-mask')
.attr('maskUnits', 'userSpaceOnUse')
.append('rect')
.attr('x', '0')
.attr('y', '0')
.attr('width', '100%')
.attr('height', '100%')
.attr('fill', '#0000ff');
.attr('id', HASH_ID.current)
.attr('width', '8')
.attr('height', '8')
.attr('patternUnits', 'userSpaceOnUse')
.attr('patternTransform', 'rotate(45)')
.append('rect')
.attr('width', '4')
.attr('height', '8')
.attr('transform', 'translate(0, 0)')
.attr('mask', 'url(#display-mask)');
.attr('id', HASH_ID.compare)
.attr('width', '8')
.attr('height', '8')
.attr('patternUnits', 'userSpaceOnUse')
.attr('patternTransform', 'rotate(135)')
.append('rect')
.attr('width', '4')
.attr('height', '8')
.attr('transform', 'translate(0, 0)')
.attr('mask', 'url(#display-mask)');
const plotLegend = function(elem, {displayItems, layout}) {
elem.select('.legend').remove();
let plotMarkers = createLegendMarkers(displayItems);
Bucknell, Mary S.
committed
drawSimpleLegend(elem, plotMarkers, layout);
const plotMedianPoints = function (elem, {visible, xscale, yscale, points, showLabel, variable}) {
elem.select('#median-points').remove();
Naab, Daniel James
committed
if (!visible) {
return;
}
const container = elem
.append('g')
.attr('id', 'median-points');
container.selectAll('medianPoint')
.data(points)
.enter()
.append('circle')
.attr('class', 'median-data-series')
.attr('r', CIRCLE_RADIUS)
.attr('cx', function(d) {
Naab, Daniel James
committed
return xscale(d.dateTime);
})
.attr('cy', function(d) {
Bucknell, Mary S.
committed
.on('click', dispatch(function() {
return Actions.showMedianStatsLabel(!showLabel);
}));
Bucknell, Mary S.
committed
if (showLabel) {
.data(points)
.enter()
.append('text')
.text(function(d) {
return `${d.value} ${variable.unit.unitCode}`;
Naab, Daniel James
committed
return xscale(d.dateTime) + 5;
})
.attr('y', function(d) {
return yscale(d.value);
});
Bucknell, Mary S.
committed
}
Naab, Daniel James
committed
const timeSeriesGraph = function (elem) {
elem.append('div')
.attr('class', 'hydrograph-container')
.style('padding-bottom', ASPECT_RATIO_PERCENT)
.append('svg')
Bucknell, Mary S.
committed
.call(link((elem, layout) => elem.attr('viewBox', `0 0 ${layout.width} ${layout.height}`), layoutSelector))
Naab, Daniel James
committed
.call(link(addSVGAccessibility, createStructuredSelector({
Naab, Daniel James
committed
titleSelector,
descriptionSelector,
Naab, Daniel James
committed
isInteractive: () => true
})))
.call(createTooltipText)
displayItems: legendDisplaySelector,
Bucknell, Mary S.
committed
layout: layoutSelector
.append('g')
.attr('transform', `translate(${MARGIN.left},${MARGIN.top})`)
Naab, Daniel James
committed
.call(link(appendAxes, axesSelector))
Naab, Daniel James
committed
.call(link(plotDataLines, createStructuredSelector({
Naab, Daniel James
committed
visible: isVisibleSelector('current'),
Naab, Daniel James
committed
tsLines: lineSegmentsSelector('current'),
Naab, Daniel James
committed
xScale: xScaleSelector('current'),
yScale: yScaleSelector,
Naab, Daniel James
committed
tsDataKey: () => 'current'
Naab, Daniel James
committed
})))
Naab, Daniel James
committed
.call(link(plotDataLines, createStructuredSelector({
Naab, Daniel James
committed
visible: isVisibleSelector('compare'),
Naab, Daniel James
committed
tsLines: lineSegmentsSelector('compare'),
Naab, Daniel James
committed
xScale: xScaleSelector('compare'),
yScale: yScaleSelector,
Naab, Daniel James
committed
tsDataKey: () => 'compare'
Naab, Daniel James
committed
})))
Bucknell, Mary S.
committed
.call(link(createTooltipFocus, createStructuredSelector({
Naab, Daniel James
committed
xScale: xScaleSelector('current'),
yScale: yScaleSelector,
compareXScale: xScaleSelector('compare'),
currentTsData: pointsSelector('current'),
compareTsData: pointsSelector('compare'),
isCompareVisible: isVisibleSelector('compare')
Bucknell, Mary S.
committed
})))
.call(link(plotMedianPoints, createStructuredSelector({
visible: isVisibleSelector('median'),
Bucknell, Mary S.
committed
xscale: xScaleSelector('current'),
yscale: yScaleSelector,
points: pointsSelector('median'),
variable: currentVariableSelector,
Bucknell, Mary S.
committed
showLabel: (state) => state.showMedianStatsLabel
Naab, Daniel James
committed
})));
elem.call(link(plotSeriesSelectTable, createStructuredSelector({
availableTimeseries: availableTimeseriesSelector
})));
elem.append('div')
.call(link(addSROnlyTable, createStructuredSelector({
columnNames: createSelector(
Naab, Daniel James
committed
titleSelector,
Bucknell, Mary S.
committed
(title) => [title, 'Time', 'Qualifiers']
Bucknell, Mary S.
committed
data: pointsTableDataSelector('current'),
Bucknell, Mary S.
committed
describeById: () => 'current-time-series-sr-desc',
describeByText: () => 'current time series data in tabular format'
})));
elem.append('div')
.call(link(addSROnlyTable, createStructuredSelector({
columnNames: createSelector(
Naab, Daniel James
committed
titleSelector,
Bucknell, Mary S.
committed
(title) => [title, 'Time', 'Qualifiers']
Bucknell, Mary S.
committed
data: pointsTableDataSelector('compare'),
Bucknell, Mary S.
committed
describeById: () => 'compare-time-series-sr-desc',
describeByText: () => 'previous year time series data in tabular format'
})));
.call(link(addSROnlyTable, createStructuredSelector({
columnNames: createSelector(
Naab, Daniel James
committed
titleSelector,
(title) => [`Median ${title}`, 'Time']
),
data: pointsTableDataSelector('median'),
describeById: () => 'median-statistics-sr-desc',
describeByText: () => 'median statistical data in tabular format'
Naab, Daniel James
committed
})));
};
const attachToNode = function (node, {siteno} = {}) {
if (!siteno) {
select(node).call(drawMessage, 'No data is available.');
return;
Bucknell, Mary S.
committed
}
let store = configureStore({
width: node.offsetWidth
});
select(node)
.call(provide(store))
Naab, Daniel James
committed
.call(timeSeriesGraph)
.select('.hydrograph-last-year-input')
.on('change', dispatch(function () {
return Actions.toggleTimeseries('compare', this.checked);
}));
Bucknell, Mary S.
committed
window.onresize = function() {
store.dispatch(Actions.resizeTimeseriesPlot(node.offsetWidth));
};
store.dispatch(Actions.retrieveTimeseries(siteno));
};
module.exports = {attachToNode, timeSeriesGraph};