From 919532295db2a4779516b16bf49eead9a0fb906d Mon Sep 17 00:00:00 2001 From: Darius Williams <dswilliams@contractor.usgs.gov> Date: Fri, 29 Jul 2022 15:59:43 -0500 Subject: [PATCH] Moving graph controls within the legend component --- .../components/hydrograph/index.js | 24 ++- .../hydrograph/vue-components/legend.vue | 140 +++++++++++++++--- 2 files changed, 130 insertions(+), 34 deletions(-) diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index b3c046270..314c6a777 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -29,7 +29,6 @@ import {getPreferredIVMethodID} from './selectors/time-series-data'; import {showDataIndicators} from './data-indicator'; -import {drawTimeSeriesLegend} from './legend'; import {initializeTimeSeriesGraph, drawTimeSeriesGraphData} from './time-series-graph'; @@ -156,21 +155,20 @@ export const attachToNode = function(store, mapStateToPropsFactory: createStructuredSelector }); hydrographApp.provide('store', store); + hydrographApp.provide('siteno', siteno); hydrographApp.provide('initialLoadingComplete', initialLoadingComplete); hydrographApp.mount('#hydrograph-brush-container'); } - const legendControlsContainer = graphContainer.append('div') - .classed('ts-legend-controls-container', true); if (!showOnlyGraph) { - const graphControlsApp = createApp(GraphControlsApp, {}); - graphControlsApp.use(ReduxConnectVue, { - store, - mapDispatchToPropsFactory: (actionCreators) => (dispatch) => bindActionCreators(actionCreators, dispatch), - mapStateToPropsFactory: createStructuredSelector - }); - graphControlsApp.provide('store', store); - graphControlsApp.provide('siteno', siteno); - graphControlsApp.mount('.ts-legend-controls-container'); + // const graphControlsApp = createApp(GraphControlsApp, {}); + // graphControlsApp.use(ReduxConnectVue, { + // store, + // mapDispatchToPropsFactory: (actionCreators) => (dispatch) => bindActionCreators(actionCreators, dispatch), + // mapStateToPropsFactory: createStructuredSelector + // }); + // graphControlsApp.provide('store', store); + // graphControlsApp.provide('siteno', siteno); + // graphControlsApp.mount('.ts-legend-controls-container'); const parameterSelectionApp = createApp(ParameterSelectionApp, {}); parameterSelectionApp.use(ReduxConnectVue, { @@ -195,8 +193,6 @@ export const attachToNode = function(store, showDataIndicators(false, store); graphContainer.call(drawTimeSeriesGraphData, store, !thisShowOnlyGraph); - legendControlsContainer.call(drawTimeSeriesLegend, store); - if (!thisShowOnlyGraph) { // eslint-disable-next-line vue/one-component-per-file const dataTablesApp = createApp(DataTablesApp, {}); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/legend.vue b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/legend.vue index cf365ceea..00006944a 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/legend.vue +++ b/assets/src/scripts/monitoring-location/components/hydrograph/vue-components/legend.vue @@ -1,51 +1,151 @@ <template> - <div class="hydrograph-container"> - <svg + <div class="ts-legend-controls-container"> + <div class="hydrograph-container"> + <svg v-if="legendMarkerRows.length" class="legend-svg" + :viewBox="svgViewBox" > <g + ref="legend" class="legend" :transform="legendTransform" - > - - </g> + /> </svg> + </div> + <div class="graph-controls-container"> + <GraphControls /> + </div> </div> </template> <script> -import {useActions, useState} from 'redux-connect-vue'; -import {computed, inject, ref, watchEffect} from 'vue'; - -import {createStructuredSelector} from 'reselect'; +import GraphControls from './graph-controls.vue'; -import {drawSimpleLegend} from 'd3render/legend'; +import {useState} from 'redux-connect-vue'; +import {computed, ref, watchEffect} from 'vue'; +import {select} from 'd3-selection'; -import {getMainLayout} from './selectors/layout'; -import {getLegendMarkerRows} from './selectors/legend-data'; +import {getMainLayout} from '../selectors/layout'; +import {getLegendMarkerRows} from '../selectors/legend-data'; import {mediaQuery} from 'ui/utils'; +import config from 'ui/config'; export default { name: 'Legend', + components: { + GraphControls + }, setup() { + const legend = ref(null); + + const RECTANGLE_MARKER_WIDTH = 20; + const RECTANGLE_MARKER_HEIGHT = 10; + const LINE_MARKER_WIDTH = 20; + const MARKER_GROUP_X_OFFSET = 15; + const VERTICAL_ROW_OFFSET = 18; + + let yPosition = VERTICAL_ROW_OFFSET; + const state = useState({ legendMarkerRows: getLegendMarkerRows, layout: getMainLayout }); + // Set the size of the containing svg node to the size of the legend. + const bBox = computed(() => { + try { + return select(legend.value).node().getBBox(); + } catch (error) { + return null; + } + }) + const legendTransform = computed(() => { - return `translate(${mediaQuery(config.USWDS_MEDIUM_SCREEN) ? layout.margin.left : 0}, 0)` + return `translate(${mediaQuery(config.USWDS_MEDIUM_SCREEN) ? state.layout.value.margin.left : 0}, 0)`; + }); + + const svgViewBox = computed(() => { + if (bBox.value) { + return `0 0 ${state.layout.value.width} ${bBox.value.height + 10}`; + } + return '' + }); + + watchEffect(() => { + state.legendMarkerRows.value.forEach((rowMarkers) => { + let xPosition = 0; + let markerArgs; + let markerGroup; + let lastMarker; + + const getNewXPosition = function(markerGroup, lastXPosition) { + // 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 { + const markerGroupBBox = markerGroup.node().getBBox(); + return markerGroupBBox.x + markerGroupBBox.width + MARKER_GROUP_X_OFFSET; + } catch (error) { + // See above explanation + return lastXPosition; + } + }; + + const repositionLastMarkerWhenNeeded = function() { + if (xPosition - MARKER_GROUP_X_OFFSET > state.layout.value.width && legend.value) { + // Need to remove the last marker and draw it on the next row. + markerGroup.remove(); + xPosition = 0; + yPosition = yPosition + VERTICAL_ROW_OFFSET; + markerArgs.x = xPosition; + markerArgs.y = yPosition; + markerGroup = lastMarker.type(legend.value, markerArgs); + xPosition = getNewXPosition(markerGroup, xPosition); + } + }; + + rowMarkers.forEach((marker) => { + if (legend.value) { + repositionLastMarkerWhenNeeded(); + + markerArgs = { + x: xPosition, + y: yPosition, + text: marker.text, + domId: marker.domId, + domClass: marker.domClass, + width: RECTANGLE_MARKER_WIDTH, + height: RECTANGLE_MARKER_HEIGHT, + length: LINE_MARKER_WIDTH, + r: marker.radius, + fill: marker.fill + }; + + lastMarker = marker; + markerGroup = marker.type(select(legend.value), markerArgs); + xPosition = getNewXPosition(markerGroup, xPosition); + } + + }); + repositionLastMarkerWhenNeeded(); + //start new row + yPosition = yPosition = yPosition + VERTICAL_ROW_OFFSET; + }); }); return { ...state, - legendTransform - } + legendTransform, + svgViewBox, + legend + }; } } -</script> - -<style> - -</style> \ No newline at end of file +</script> \ No newline at end of file -- GitLab