Skip to content
Snippets Groups Projects
legend.js 6.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • // functions to facilitate legend creation for a d3 plot
    const { createSelector } = require('reselect');
    
    const { defineLineMarker, defineCircleMarker, defineRectangleMarker, rectangleMarker } = require('./markers');
    
    const { CIRCLE_RADIUS } = require('./layout');
    
    const { MASK_DESC } = require('./points');
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
    /**
     * Create a simple horizontal legend
     *
     * @param svg
     * @param legendMarkers
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
     * @param startingXPosition
     * @param markerYPosition
     * @param textYPosition
     * @param markerGroupOffset
     * @param markerTextOffset
     */
    function drawSimpleLegend(svg,
                              legendMarkers,
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
                              startingXPosition=0,
                              markerYPosition=-4,
                              textYPosition=0,
                              markerGroupOffset=40,
                              markerTextOffset=10) {
    
        const verticalRowOffset = 20;
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
        const svgBBox = svg.node().getBBox();
    
        const svgWidth = width ? width : svgBBox.width;
        let rowCounter = 0;
    
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
        let legend = svg
            .append('g')
            .attr('class', 'legend');
    
        let previousMarkerGroup;
    
        for (let legendMarker of legendMarkers) {
            let xPosition;
            let previousMarkerGroupBox;
            let detachedMarker;
            if (previousMarkerGroup == null) {
                xPosition = startingXPosition;
            }
            else {
                previousMarkerGroupBox = previousMarkerGroup.node().getBBox();
                xPosition = previousMarkerGroupBox.x + previousMarkerGroupBox.width + markerGroupOffset;
            }
    
            let legendGroup = legend.append('g')
                .attr('class', 'legend-marker');
    
            if (legendMarker.groupId) {
                legendGroup.attr('id', legendMarker.groupId);
            }
    
            let markerType = legendMarker.type;
            let yPosition;
            if (markerType === rectangleMarker) {
                yPosition = markerYPosition * 2.5 + verticalRowOffset * rowCounter;
            }
            else {
                yPosition = markerYPosition + verticalRowOffset * rowCounter
            }
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
            let markerArgs = {
                r: legendMarker.r ? legendMarker.r : null,
                x: xPosition,
    
                y: yPosition,
                width: 20,
                height: 10,
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
                length: 20,
                domId: legendMarker.domId,
    
                domClass: legendMarker.domClass,
                fill: legendMarker.fill
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
            };
            // add the marker to the svg
            detachedMarker = markerType(markerArgs);
            legendGroup.node().appendChild(detachedMarker.node());
            // add text for the legend marker
            let detachedMarkerBBox = detachedMarker.node().getBBox();
            legendGroup.append('text')
                .attr('x', detachedMarkerBBox.x + detachedMarkerBBox.width + markerTextOffset)
    
                .attr('y', textYPosition + verticalRowOffset * rowCounter)
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
                .text(legendMarker.text);
    
            let legendGroupBBox = legendGroup.node().getBBox();
            let legendGroupRightXCoordinate = legendGroupBBox.x + legendGroupBBox.width;
            if (legendGroupRightXCoordinate/svgWidth >= 0.60) {
                rowCounter += 1;
                previousMarkerGroup = null;
            }
            else {
                previousMarkerGroup = legendGroup;
            }
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
        }
        // center the legend group in the svg
        let legendBBox = legend.node().getBBox();
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
        const legendXPosition = (svgWidth - legendBBox.width) / 2;
    
        legend.attr('transform', `translate(${legendXPosition}, ${svgBBox.height-60})`);
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
    }
    
    
    /**
     * create elements for the legend in the svg
     *
     * @param dataPlotElements
    
     * @param lineSegments
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
    const createLegendMarkers = function(dataPlotElements, lineSegments=[]) {
    
        let text;
        let marker;
        let legendMarkers = [];
    
        // create legend markers for data series
    
        for (let dataItem of dataPlotElements.dataItems) {
    
            if (dataItem === 'compare' || dataItem === 'current') {
                let domId = `ts-${dataItem}`;
                let svgGroup = `${dataItem}-line-marker`;
                if (dataItem === 'compare') {
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
                    hashMarker = defineRectangleMarker(null, 'mask', 'Compare Timeseries Mask', null, 'url(#hash-135)');
    
                    text = 'Last Year';
                }
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
                    hashMarker = defineRectangleMarker(null, 'mask', 'Current Timeseries Mask', null, 'url(#hash-45)');
    
                    text = 'Current Year';
                }
    
                marker = defineLineMarker(domId, 'line', text, svgGroup);
            }
            else if (dataItem === 'medianStatistics') {
                text = 'Median Discharge';
                let beginYear = dataPlotElements.metadata.statistics.beginYear;
                let endYear = dataPlotElements.metadata.statistics.endYear;
                if (beginYear && endYear) {
                    text = `${text} ${beginYear} - ${endYear}`;
                }
                marker = defineCircleMarker(CIRCLE_RADIUS, null, 'median-data-series', text, 'median-circle-marker');
            }
            else {
                marker = null;
            }
            if (marker) {
                legendMarkers.push(marker);
            }
    
            if (hashMarker) {
                legendMarkers.push(hashMarker);
            }
    
        // create markers for data masks for different components of data series
    
        let masks = [];
        lineSegments.map(segment => masks.push(segment.classes.dataMask));
        let uniqueMasks = new Set(masks.filter(x => x !== null));
        for (let uniqueMask of uniqueMasks) {
    
            let maskDisplayName = MASK_DESC[uniqueMask];
            let maskClass = `${maskDisplayName.replace(' ', '-').toLowerCase()}-mask`;
            marker = defineRectangleMarker(null, maskClass, maskDisplayName, null);
    
            legendMarkers.push(marker);
        }
    
        return legendMarkers;
    };
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
    
    
    /**
     * Select attributes from the state useful for legend creation
     */
    
    const legendDisplaySelector = createSelector(
        (state) => state.showSeries,
        (state) => state.statisticalMetaData,
        (showSeries, statisticalMetaData) => {
            let shownSeries = [];
    
            let dataPlotElements = {};
    
            let text;
            let marker;
            for (let key in showSeries) {
                if (showSeries.hasOwnProperty(key)) {
    
    Yan, Andrew N.'s avatar
    Yan, Andrew N. committed
                    if (showSeries[key]) {
                        shownSeries.push(key);
    
    
            dataPlotElements.dataItems = shownSeries;
            dataPlotElements.metadata = {
                statistics: {
                    beginYear: statisticalMetaData.beginYear ? statisticalMetaData.beginYear : undefined,
                    endYear: statisticalMetaData.endYear ? statisticalMetaData.endYear : undefined
    
            };
            return dataPlotElements;
    
    module.exports = {drawSimpleLegend, createLegendMarkers, legendDisplaySelector};