"README.Rmd" did not exist on "798bd7dbc3d386696ae8f77660f4ab73fd27eb17"
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: d3Line } = require('d3-shape');
const { 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');
Naab, Daniel James
committed
const { MARGIN, CIRCLE_RADIUS, CIRCLE_RADIUS_SINGLE_PT, SPARK_LINE_DIM, layoutSelector } = require('./layout');
const { drawSimpleLegend, legendMarkerRowsSelector } = require('./legend');
const { plotSeriesSelectTable, availableTimeseriesSelector } = require('./parameters');
const { xScaleSelector, yScaleSelector, timeSeriesScalesByParmCdSelector } = require('./scales');
Bucknell, Mary S.
committed
const { Actions, configureStore } = require('../../store');
const { currentVariableLineSegmentsSelector, currentVariableSelector, currentVariableTimeseries, pointsSelector,
methodsSelector, pointsTableDataSelector, isVisibleSelector, titleSelector,
descriptionSelector, lineSegmentsByParmCdSelector, currentVariableTimeSeriesSelector, 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')
const plotDataLine = function (elem, {visible, lines, tsKey, xScale, yScale}) {
Naab, Daniel James
committed
if (!visible) {
Naab, Daniel James
committed
for (let line of lines) {
Naab, Daniel James
committed
// If this is a single point line, then represent it as a circle.
// Otherwise, render as a line.
if (line.points.length === 1) {
elem.append('circle')
.data(line.points)
.classed('line-segment', true)
.classed('approved', line.classes.approved)
.classed('estimated', line.classes.estimated)
.attr('r', CIRCLE_RADIUS_SINGLE_PT)
.attr('cx', d => xScale(d.dateTime))
.attr('cy', d => yScale(d.value));
} else {
const tsLine = d3Line()
.x(d => xScale(d.dateTime))
.y(d => yScale(d.value));
elem.append('path')
.datum(line.points)
.classed('line-segment', true)
.classed('approved', line.classes.approved)
.classed('estimated', line.classes.estimated)
.classed(`ts-${tsKey}`, true)
.attr('d', tsLine);
}
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', `${tsKey}-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[tsKey] ? `url(#${HASH_ID[tsKey]})` : '';
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, tsLinesMap, tsKey, xScale, yScale}) {
const elemId = `ts-${tsKey}-group`;
Naab, Daniel James
committed
elem.selectAll(`#${elemId}`).remove();
const tsLineGroup = elem
.append('g')
.attr('id', elemId)
.classed('tsKey', true);
Naab, Daniel James
committed
Naab, Daniel James
committed
for (const lines of Object.values(tsLinesMap)) {
plotDataLine(tsLineGroup, {visible, lines, tsKey, xScale, yScale});
Naab, Daniel James
committed
}
};
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 timeSeriesLegend = function(elem) {
elem.append('div')
.attr('class', 'hydrograph-container')
.append('svg')
.call(link(drawSimpleLegend, createStructuredSelector({
legendMarkerRows: legendMarkerRowsSelector,
layout: layoutSelector
})));
/**
* Plots the median points for a single median time series.
* @param {Object} elem
* @param {Function} options.xscale
* @param {Function} options.yscale
* @param {Number} options.modulo
* @param {Array} options.points
* @param {Boolean} options.showLabel
* @param {Object} options.variable
*/
const plotMedianPoints = function (elem, {xscale, yscale, modulo, points, showLabel, variable}) {
elem.selectAll('medianPoint')
.data(points)
.enter()
.append('circle')
.classed('median-data-series', true)
.classed(`median-modulo-${modulo}`, true)
.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) {
elem.selectAll('medianPointText')
.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
}
/**
* Plots the median points for all median time series for the current variable.
* @param {Object} elem
* @param {Boolean} options.visible
* @param {Function} options.xscale
* @param {Function} options.yscale
* @param {Array} options.pointsList
* @param {Boolean} options.showLabel
* @param {Object} options.variable
*/
Naab, Daniel James
committed
const plotAllMedianPoints = function (elem, {visible, xscale, yscale, seriesMap, showLabel, variable}) {
elem.select('#median-points').remove();
if (!visible) {
return;
}
const container = elem
.append('g')
.attr('id', 'median-points');
Naab, Daniel James
committed
for (const [index, seriesID] of Object.keys(seriesMap).entries()) {
const points = seriesMap[seriesID].points;
plotMedianPoints(container, {xscale, yscale, modulo: index % 6, points, showLabel, variable});
}
};
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
const plotSROnlyTable = function (elem, {tsKey, variable, methods, visible, dataByTsID, timeSeries}) {
elem.selectAll(`sr-only-${tsKey}`).remove();
if (!visible) {
return;
}
const container = elem.append('div')
.attr('id', `sr-only-${tsKey}`);
for (const seriesID of Object.keys(timeSeries)) {
const series = timeSeries[seriesID];
const method = methods[series.method].methodDescription;
let title = variable.variableName;
if (method) {
title += ` (${method})`;
}
if (tsKey === 'median') {
title = `Median ${title}`;
}
addSROnlyTable(container, {
columnNames: [title, 'Time', 'Qualifiers'],
data: dataByTsID[seriesID],
describeById: `${seriesID}-time-series-sr-desc`,
describeByText: `${seriesID} time series data in tabular format`
});
}
};
Naab, Daniel James
committed
const timeSeriesGraph = function (elem) {
elem.append('div')
.attr('class', 'hydrograph-container')
.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)
.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
tsLinesMap: currentVariableLineSegmentsSelector('current'),
Naab, Daniel James
committed
xScale: xScaleSelector('current'),
yScale: yScaleSelector,
tsKey: () => 'current'
Naab, Daniel James
committed
})))
Naab, Daniel James
committed
.call(link(plotDataLines, createStructuredSelector({
Naab, Daniel James
committed
visible: isVisibleSelector('compare'),
Naab, Daniel James
committed
tsLinesMap: currentVariableLineSegmentsSelector('compare'),
Naab, Daniel James
committed
xScale: xScaleSelector('compare'),
yScale: yScaleSelector,
tsKey: () => '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(plotAllMedianPoints, createStructuredSelector({
visible: isVisibleSelector('median'),
Bucknell, Mary S.
committed
xscale: xScaleSelector('current'),
yscale: yScaleSelector,
Naab, Daniel James
committed
seriesMap: currentVariableTimeseries('median'),
variable: currentVariableSelector,
Bucknell, Mary S.
committed
showLabel: (state) => state.showMedianStatsLabel
Naab, Daniel James
committed
})));
elem.call(link(plotSeriesSelectTable, createStructuredSelector({
availableTimeseries: availableTimeseriesSelector,
lineSegmentsByParmCd: lineSegmentsByParmCdSelector('current'),
timeSeriesScalesByParmCd: timeSeriesScalesByParmCdSelector('current')(SPARK_LINE_DIM),
})));
.call(link(plotSROnlyTable, createStructuredSelector({
tsKey: () => 'compare',
variable: currentVariableSelector,
methods: methodsSelector,
visible: isVisibleSelector('compare'),
dataByTsID: pointsTableDataSelector('compare'),
timeSeries: currentVariableTimeSeriesSelector('compare')
})));
elem.append('div')
.call(link(plotSROnlyTable, createStructuredSelector({
tsKey: () => 'compare',
variable: currentVariableSelector,
methods: methodsSelector,
visible: isVisibleSelector('compare'),
dataByTsID: pointsTableDataSelector('compare'),
timeSeries: currentVariableTimeSeriesSelector('compare')
Bucknell, Mary S.
committed
})));
.call(link(plotSROnlyTable, createStructuredSelector({
tsKey: () => 'median',
variable: currentVariableSelector,
methods: methodsSelector,
visible: isVisibleSelector('median'),
dataByTsID: pointsTableDataSelector('median'),
timeSeries: currentVariableTimeSeriesSelector('median')
Naab, Daniel James
committed
})));
Bucknell, Mary S.
committed
const attachToNode = function (store, node, {siteno} = {}) {
if (!siteno) {
select(node).call(drawMessage, 'No data is available.');
return;
Bucknell, Mary S.
committed
}
Bucknell, Mary S.
committed
store.dispatch(Actions.resizeUI(window.innerWidth, node.offsetWidth));
select(node)
.call(provide(store))
Naab, Daniel James
committed
.call(timeSeriesGraph)
.call(timeSeriesLegend)
.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.resizeUI(window.innerWidth, node.offsetWidth));
Bucknell, Mary S.
committed
};
store.dispatch(Actions.retrieveTimeseries(siteno));
};
module.exports = {attachToNode, timeSeriesLegend, timeSeriesGraph};