diff --git a/assets/src/scripts/components/dailyValueHydrograph/index.js b/assets/src/scripts/components/dailyValueHydrograph/index.js index a20c3744f2875eb7b22b5ae0d213a25af64e59b6..25b883699f6a57a8d26df531b540384dd4311b19 100644 --- a/assets/src/scripts/components/dailyValueHydrograph/index.js +++ b/assets/src/scripts/components/dailyValueHydrograph/index.js @@ -9,6 +9,7 @@ import {drawErrorAlert, drawInfoAlert} from '../../d3-rendering/alerts'; import {drawLoadingIndicator} from '../../d3-rendering/loading-indicator'; import {drawTimeSeriesGraph} from './time-series-graph'; +import {drawTimeSeriesLegend} from "./legend"; const TEMP_TIME_SERIES_ID = '36307c899ac14d2eac6956b1bf5ceb69'; @@ -52,4 +53,10 @@ export const attachToNode = function (store, }, hasCurrentObservationsTimeSeries)) .call(drawTimeSeriesGraph, store); + // Add DV legend + let graphContainer = nodeElem.select('.graph-container') + graphContainer.append('div') + .classed('dv-legend-controls-container', true) + .call(drawTimeSeriesLegend, store); + }; \ No newline at end of file diff --git a/assets/src/scripts/components/dailyValueHydrograph/legend.js b/assets/src/scripts/components/dailyValueHydrograph/legend.js new file mode 100644 index 0000000000000000000000000000000000000000..02ff542724ef69165145b5980623ee195b443263 --- /dev/null +++ b/assets/src/scripts/components/dailyValueHydrograph/legend.js @@ -0,0 +1,185 @@ +// functions to facilitate legend creation for a d3 plot +import { set } from 'd3-collection'; +import memoize from 'fast-memoize'; +import {createSelector, createStructuredSelector} from 'reselect'; + +import {getLayout} from './selectors/layout'; +import { defineLineMarker, defineTextOnlyMarker, defineRectangleMarker } from '../hydrograph/markers'; +import { currentVariableLineSegmentsSelector, HASH_ID, MASK_DESC } from '../hydrograph/drawing-data'; +import config from '../../config'; +import { mediaQuery } from '../../utils'; +import {link} from '../../lib/d3-redux'; + +const TS_LABEL = { + 'current': 'Current: ' +}; + +const tsLineMarkers = function(tsKey, lineClasses) { + let result = []; + + console.log('tsKey:'+tsKey); + + if (lineClasses.default) { + result.push(defineLineMarker(null, `line-segment ts-${tsKey}`, 'Provisional')); + } + if (lineClasses.approved) { + result.push(defineLineMarker(null, `line-segment approved ts-${tsKey}`, 'Approved')); + } + if (lineClasses.estimated) { + result.push(defineLineMarker(null, `line-segment estimated ts-${tsKey}`, 'Estimated')); + } + return result; +}; + +/** + * create elements for the legend in the svg + * + * @param {Object} displayItems - Object containing keys for each ts. The current and compare will contain an + * object that has a masks property containing the Set of masks that are currently displayed. + * The median property will contain the metadata for the median statistics + * @return {Object} - Each key represents a ts and contains an array of markers to show. + */ +const createLegendMarkers = function(displayItems) { + const legendMarkers = []; + + if (displayItems.current) { + const currentMarkers = [ + ...tsLineMarkers('current', displayItems.current), + ]; + if (currentMarkers.length) { + legendMarkers.push([ + defineTextOnlyMarker(TS_LABEL.current, null, 'ts-legend-current-text'), + ...currentMarkers + ]); + } + } + + return legendMarkers; +}; + +/** + * Create a simple legend + * + * @param {Object} div - d3 selector where legend should be created + * @param {Object} legendMarkerRows - Array of rows. Each row should be an array of legend markers. + * @param {Object} layout - width and height of svg. + */ +export const drawSimpleLegend = function(div, {legendMarkerRows, layout}) { + div.selectAll('.legend-svg').remove(); + + if (!legendMarkerRows.length || !layout) { + return; + } + + const markerGroupXOffset = 15; + const verticalRowOffset = 18; + + let svg = div.append('svg') + .attr('class', 'legend-svg'); + let legend = svg + .append('g') + .attr('class', 'legend') + .attr('transform', `translate(${mediaQuery(config.USWDS_MEDIUM_SCREEN) ? layout.margin.left : 0}, 0)`); + + legendMarkerRows.forEach((rowMarkers, rowIndex) => { + let xPosition = 0; + let yPosition = verticalRowOffset * (rowIndex + 1); + + rowMarkers.forEach((marker) => { + let markerArgs = { + x: xPosition, + y: yPosition, + text: marker.text, + domId: marker.domId, + domClass: marker.domClass, + width: 20, + height: 10, + length: 20, + r: marker.r, + fill: marker.fill + }; + console.log('DV: marker text:'+marker.text+ ' xPosition:'+xPosition+ ' yPosition:'+yPosition ); + + let markerGroup = marker.type(legend, markerArgs); + let markerGroupBBox; + // Long story short, firefox is unable to get the bounding box if + // the svg element isn't actually taking up space and visible. Folks on the + // internet seem to have gotten around this by setting `visibility:hidden` + // to hide things, but that would still mean the elements will take up space. + // which we don't want. So, here's some error handling for getBBox failures. + // This handling ends up not creating the legend, but that's okay because the + // graph is being shown anyway. A more detailed discussion of this can be found at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=612118 and + // https://stackoverflow.com/questions/28282295/getbbox-of-svg-when-hidden. + try { + markerGroupBBox = markerGroup.node().getBBox(); + xPosition = markerGroupBBox.x + markerGroupBBox.width + markerGroupXOffset; + + console.log('DV: markerGroupBBox.x:'+markerGroupBBox.x+ ' markerGroupBBox.width:'+markerGroupBBox.width+ ' markerGroupXOffset:'+markerGroupXOffset); + console.log('DV: xPosition:'+xPosition); + + } catch(error) { + // See above explanation + } + }); + }); + + // Set the size of the containing svg node to the size of the legend. + let bBox; + try { + bBox = legend.node().getBBox(); + } catch(error) { + return; + } + svg.attr('viewBox', `-4 0 ${layout.width} ${bBox.height + 20}`); +}; + +const uniqueClassesSelector = memoize(tsKey => createSelector( + currentVariableLineSegmentsSelector(tsKey), + (tsLineSegments) => { + let classes = [].concat(...Object.values(tsLineSegments)).map((line) => line.classes); + return { + default: classes.some((cls) => !cls.approved && !cls.estimated), + approved: classes.some((cls) => cls.approved), + estimated: classes.some((cls) => cls.estimated), + }; + } +)); + + + +/** + * Select attributes from the state useful for legend creation + */ +const legendDisplaySelector = createSelector( + (state) => state.timeSeriesState.showSeries, + uniqueClassesSelector('current'), + uniqueClassesSelector('compare'), + (showSeries, medianSeries, currentClasses, compareClasses) => { + return { + current: showSeries.current ? currentClasses : undefined, + }; + } +); + + +/* + * Factory function that returns an array of array of markers to be used for the + * time series graph legend + * @return {Array of Array} of markers + */ +export const legendMarkerRowsSelector = createSelector( + legendDisplaySelector, + displayItems => createLegendMarkers(displayItems) +); + + +export const drawTimeSeriesLegend = function(elem, store) { + elem.append('div') + .classed('hydrograph-container', true) + .call(link(store, drawSimpleLegend, createStructuredSelector({ + legendMarkerRows: legendMarkerRowsSelector, + layout: getLayout + }))); +}; + diff --git a/assets/src/scripts/components/hydrograph/legend.js b/assets/src/scripts/components/hydrograph/legend.js index 558dd8846ef9d6bd6f6173d5b0a1e6257b809821..0b14c6f08cf6b12e7f109dd0efd57a717435e190 100644 --- a/assets/src/scripts/components/hydrograph/legend.js +++ b/assets/src/scripts/components/hydrograph/legend.js @@ -48,7 +48,7 @@ const tsLineMarkers = function(tsKey, lineClasses) { * @param {Object} displayItems - Object containing keys for each ts. The current and compare will contain an * object that has a masks property containing the Set of masks that are currently displayed. * The median property will contain the metadata for the median statistics - * @return {Object} - Each key respresnts a ts and contains an array of markers to show. + * @return {Object} - Each key represents a ts and contains an array of markers to show. */ const createLegendMarkers = function(displayItems) { const legendMarkers = []; @@ -143,9 +143,12 @@ export const drawSimpleLegend = function(div, {legendMarkerRows, layout}) { width: 20, height: 10, length: 20, - r: marker.r , + r: marker.r, fill: marker.fill }; + + //console.log('UV: marker text:'+marker.text+ ' xPosition:'+xPosition+ ' yPosition:'+yPosition ); + let markerGroup = marker.type(legend, markerArgs); let markerGroupBBox; // Long story short, firefox is unable to get the bounding box if @@ -161,6 +164,9 @@ export const drawSimpleLegend = function(div, {legendMarkerRows, layout}) { markerGroupBBox = markerGroup.node().getBBox(); xPosition = markerGroupBBox.x + markerGroupBBox.width + markerGroupXOffset; + //console.log('UV: markerGroupBBox.x:'+markerGroupBBox.x+ ' markerGroupBBox.width:'+markerGroupBBox.width+ ' markerGroupXOffset:'+markerGroupXOffset); + //console.log('UV: xPosition:'+xPosition); + } catch(error) { // See above explanation } diff --git a/assets/src/styles/components/hydrograph/_app.scss b/assets/src/styles/components/hydrograph/_app.scss index 099418317b91789e4bb298ec8ca9fb466a20f7c9..ee37f1a4dcc6097fda6288736a7d01ab0353b8d6 100644 --- a/assets/src/styles/components/hydrograph/_app.scss +++ b/assets/src/styles/components/hydrograph/_app.scss @@ -212,6 +212,17 @@ } } +.dv-legend-controls-container { + display: inline-block; + height: 85px; + + font-size: .7em; + @include at-media('tablet') { + font-size: 1em; + } + +} + .provisional-data-alert { p { height: 6em;