Skip to content
Snippets Groups Projects
tooltip.js 6.24 KiB
import {DateTime} from 'luxon';
import {createSelector, createStructuredSelector} from 'reselect';

import config from 'ui/config';
import {link} from 'ui/lib/d3-redux';

import {drawFocusOverlay, drawFocusCircles, drawFocusLine} from 'd3render/graph-tooltip';

import {setGraphCursorOffset} from 'ml/store/hydrograph-state';
import {getIVParameter} from 'ml/selectors/hydrograph-data-selector';

import {getCursorTime, getIVDataCursorPoint, getIVDataTooltipPoint, getGroundwaterLevelCursorPoint,
    getGroundwaterLevelTooltipPoint
} from './selectors/cursor';
import {getMainLayout} from './selectors/layout';
import {getMainXScale, getMainYScale} from './selectors/scales';
import {getPrimaryParameter} from './selectors/time-series-data';
import {drawCursorSlider} from './cursor-slider';


const getIVDataTooltipTextInfo = function(point, dataKind, unitCode) {
    const timeLabel = DateTime.fromMillis(
            point.dateTime,
            {zone: config.locationTimeZone}
        ).toFormat('MMM dd, yyyy hh:mm:ss a ZZZZ');
    const valueLabel = point.isMasked ? point.label : `${point.value} ${unitCode}`;
    return {
        label: `${valueLabel} - ${timeLabel}`,
        classes: [`${dataKind}-tooltip-text`, point.class]
    };
};

const getGWLevelTextInfo = function(point, unitCode) {
    if (!point) {
        return null;
    }
    const valueLabel = point.value !== null ? `${point.value} ${unitCode}` : ' ';
    const timeLabel = DateTime.fromMillis(point.dateTime, {zone: config.locationTimeZone}).toFormat('MMM dd, yyyy hh:mm:ss a ZZZZ');
    return {
        label: `${valueLabel} - ${timeLabel}`,
        classes: point.classes
    };
};

const createTooltipTextGroup = function(elem, {
    currentPoint,
    secondaryParameterPoint,
    comparePoint,
    gwLevelPoint,
    parameter,
    secondaryParameter,
    layout
}, textGroup) {
    const adjustMarginOfTooltips = function(elem) {
        let marginAdjustment = layout.margin.left;
        elem.style('margin-left', marginAdjustment + 'px');
    };

    if (!textGroup) {
        textGroup = elem.append('div')
            .attr('class', 'tooltip-text-group')
            .call(adjustMarginOfTooltips);
    }
    const primaryParameterUnitCode = parameter ? parameter.unit : '';
    const secondaryParameterUnitCode = secondaryParameter ? secondaryParameter.unit : '';

    let tooltipTextData = [];
    if (currentPoint) {
        tooltipTextData.push(getIVDataTooltipTextInfo(currentPoint, 'primary', primaryParameterUnitCode));
    }
    if (secondaryParameterPoint) {
        tooltipTextData.push(getIVDataTooltipTextInfo(secondaryParameterPoint, 'secondary', secondaryParameterUnitCode));
    }
    if (comparePoint) {
        tooltipTextData.push(getIVDataTooltipTextInfo(comparePoint, 'compare', primaryParameterUnitCode));
    }
    if (gwLevelPoint) {
        tooltipTextData.push(getGWLevelTextInfo(gwLevelPoint, primaryParameterUnitCode));
    }
    const texts = textGroup
        .selectAll('div')
        .data(tooltipTextData);

    // Remove old text labels
    texts.exit()
        .remove();

    // Add new text labels
    const newTexts = texts.enter()
        .append('div');

    // Update the text and backgrounds of all tooltip labels
    const allTexts = texts.merge(newTexts);
    allTexts
        .text(textData => {
            return textData.label;
        })
        .attr('class', textData => textData.classes.join(' '));

    return textGroup;
};


/*
 * Append a group containing the tooltip text elements to elem
 * @param {Object} elem - D3 selector
 */
export const drawTooltipText = function(elem, store) {
    elem.call(link(store, createTooltipTextGroup, createStructuredSelector({
        currentPoint: getIVDataCursorPoint('primary', 'current'),
        secondaryParameterPoint: getIVDataCursorPoint('secondary', 'current'),
        comparePoint: getIVDataCursorPoint('compare', 'prioryear'),
        gwLevelPoint: getGroundwaterLevelCursorPoint,
        parameter: getPrimaryParameter,
        secondaryParameter: getIVParameter('secondary'),
        layout: getMainLayout
    })));
};

/*
 * Appends a group to elem containing a focus line and circles for the current and compare time series
 * @param {Object} elem - D3 select
 * @param {Object} store - Redux.Store
 * @param {Object} yScale - D3 Y scale for the graph
 */
export const drawTooltipFocus = function(elem, store) {
    elem.call(link(store, drawFocusLine, createStructuredSelector({
        xScale: getMainXScale('current'),
        yScale: getMainYScale,
        cursorTime: getCursorTime('current')
    })));

    elem.call(link(store, drawFocusCircles, createSelector(
        getIVDataTooltipPoint('primary', 'current'),
        getIVDataTooltipPoint('secondary', 'current'),
        getIVDataTooltipPoint('compare', 'prioryear'),
        getGroundwaterLevelTooltipPoint,
        (current, secondary, compare, gwLevel) => {
            let points = [];
            if (current) {
                points.push(current);
            }
            if (secondary) {
                points.push(secondary);
            }
            if (compare) {
                points.push(compare);
            }
            if (gwLevel) {
                points.push(gwLevel);
            }

            return points;
        }
    )));

    elem.call(link(
        store,
        drawFocusOverlay,
        createStructuredSelector({
            xScale: getMainXScale('current'),
            layout: getMainLayout
        }),
        store,
        setGraphCursorOffset)
    );
};

/*
 * Initial rendering of the tooltip slider
 * @param {D3 selection} elem
 * @param {Redux store} store
 */
export const initializeTooltipCursorSlider = function(elem, store) {
    elem.append('svg')
        .classed('cursor-slider-svg', true)
        .attr('xmlns', 'http://www.w3.org/2000/svg')
        .call(link(store, (elem, layout) => {
                elem.attr('viewBox', `0 0 ${layout.width + layout.margin.left + layout.margin.right} 25`);
            }, getMainLayout));
};

/*
 * Renders the cursor slider used to move the tooltip focus and sets up handlers
 * to update the slider as the tooltip focus changes.
 * @param {D3 selection} elem
 * @param {Redux store} store
 */
export const drawTooltipCursorSlider = function(elem, store) {
    elem.select('.cursor-slider-svg')
        .call(drawCursorSlider, store);
};