Skip to content
Snippets Groups Projects
time-series-graph.js 7.27 KiB
Newer Older
  • Learn to ignore specific revisions
  • import {line as d3Line, curveStepAfter} from 'd3-shape';
    
    import {createStructuredSelector} from 'reselect';
    
    
    import config from '../../config';
    
    
    import {addSVGAccessibility} from '../../d3-rendering/accessibility';
    import {appendAxes} from '../../d3-rendering/axes';
    
    import {renderMaskDefs} from '../../d3-rendering/data-masks';
    
    import {link} from '../../lib/d3-redux';
    import {getAgencyCode, getMonitoringLocationName} from '../../selectors/time-series-selector';
    
    import {mediaQuery}  from '../../utils';
    
    import {getAxes}  from './axes';
    
    import {
        currentVariableLineSegmentsSelector,
        getCurrentVariableMedianStatPoints,
    
    } from './drawing-data';
    
    import {getMainLayout} from './layout';
    import {getMainXScale, getMainYScale} from './scales';
    import {descriptionSelector, isVisibleSelector, titleSelector} from './time-series';
    import {drawDataLines} from './time-series-data';
    
    import {drawTooltipFocus, drawTooltipText}  from './tooltip';
    
    const addDefsPatterns = function(elem) {
        const patterns = [{
            patternId: HASH_ID.current,
            patternTransform: 'rotate(45)'
        }, {
            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
     */
    const plotMedianPoints = function(elem, {xscale, yscale, modulo, points}) {
        const stepFunction = d3Line()
            .curve(curveStepAfter)
    
                return yscale(d.value);
            });
    
        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
    
    const plotAllMedianPoints = function (elem, {visible, xscale, yscale, seriesPoints, enableClip}) {
    
        elem.select('#median-points').remove();
        if (!visible) {
            return;
        }
        const container = elem
            .append('g')
                .attr('id', 'median-points');
    
        if (enableClip) {
            container.attr('clip-path', 'url(#graph-clip');
        }
    
        seriesPoints.forEach((points, index) => {
            plotMedianPoints(container, {xscale, yscale, modulo: index % 6, points: points});
        });
    };
    
    
    const createTitle = function(elem, store, siteNo, showMLName) {
    
        let titleDiv = elem.append('div')
            .classed('time-series-graph-title', true);
    
        if (showMLName) {
            titleDiv.append('div')
    
                .call(link(store,(elem, {mlName, agencyCode}) => {
    
                    elem.attr('class', 'monitoring-location-name-div')
    
    Bucknell, Mary S.'s avatar
    Bucknell, Mary S. committed
                        .html(`${mlName}, ${agencyCode} ${siteNo}`);
    
                }, createStructuredSelector({
                    mlName: getMonitoringLocationName(siteNo),
                    agencyCode: getAgencyCode(siteNo)
                })));
        }
        titleDiv.append('div')
    
                elem.html(title);
            }, titleSelector));
    };
    
    
    const watermark = 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')
    
                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);
    
    
    export const drawTimeSeriesGraph = function(elem, store, siteNo, showMLName, showTooltip) {
    
            .attr('class', 'hydrograph-container')
    
            .call(createTitle, store, siteNo, showMLName);
        if (showTooltip) {
            graphDiv.call(drawTooltipText, store);
        }
    
            .attr('xmlns', 'http://www.w3.org/2000/svg')
            .classed('hydrograph-svg', true)
    
                elem.select('#graph-clip').remove();
    
                elem.attr('viewBox', `0 0 ${layout.width + layout.margin.left + layout.margin.right} ${layout.height + layout.margin.top + layout.margin.bottom}`)
    
                    .append('clipPath')
                        .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);
    
            .call(link(store, addSVGAccessibility, createStructuredSelector({
    
                title: titleSelector,
                description: descriptionSelector,
                isInteractive: () => true,
                idPrefix: () => 'hydrograph'
            })))
    
    
        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()))
            .call(link(store, drawDataLines, createStructuredSelector({
                visible: isVisibleSelector('current'),
                tsLinesMap: currentVariableLineSegmentsSelector('current'),
                xScale: getMainXScale('current'),
                yScale: getMainYScale,
    
                tsKey: () => 'current',
                enableClip: () => true
    
            })))
            .call(link(store, drawDataLines, createStructuredSelector({
                visible: isVisibleSelector('compare'),
                tsLinesMap: currentVariableLineSegmentsSelector('compare'),
                xScale: getMainXScale('compare'),
                yScale: getMainYScale,
    
                tsKey: () => 'compare',
                enableClip: () => true
    
            })))
            .call(link(store, plotAllMedianPoints, createStructuredSelector({
                visible: isVisibleSelector('median'),
                xscale: getMainXScale('current'),
                yscale: getMainYScale,
                seriesPoints: getCurrentVariableMedianStatPoints
            })));
        if (showTooltip) {
            dataGroup.call(drawTooltipFocus, store);
        }