diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 76f5745b..0bea93f4 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -323,6 +323,9 @@ const resetInterface = () => (dispatch, getState) => { dispatch({ type: "reset colorscale" }); + dispatch({ + type: "reset centroid labels" + }); dispatch({ type: "clear scatterplot" }); diff --git a/client/src/components/categorical/categorical.css b/client/src/components/categorical/categorical.css index 7f6051de..683bdc1b 100644 --- a/client/src/components/categorical/categorical.css +++ b/client/src/components/categorical/categorical.css @@ -1,3 +1,4 @@ +:local(.hover), :local(.value):hover { background: rgba(167, 182, 194, 0.3); } diff --git a/client/src/components/categorical/value.js b/client/src/components/categorical/value.js index 643ea6e6..fe9a9a17 100644 --- a/client/src/components/categorical/value.js +++ b/client/src/components/categorical/value.js @@ -24,6 +24,7 @@ import { AnnotationsHelpers } from "../../util/stateManager"; annotations: state.annotations, colorScale: state.colors.scale, colorAccessor: state.colors.colorAccessor, + pointDilation: state.pointDilation, schema: state.world?.schema, world: state.world, crossfilter: state.crossfilter @@ -194,6 +195,7 @@ class CategoryValue extends React.Component { const crossfilterChange = props.isUserAnno && props.crossfilter !== nextProps.crossfilter; const editingLabel = state.editedLabelText !== nextState.editedLabelText; + const dilationChange = props.pointDilation !== nextProps.pointDilation; return ( valueSelectionChange || @@ -201,7 +203,8 @@ class CategoryValue extends React.Component { colorAccessorChange || annotationsChange || crossfilterChange || - editingLabel + editingLabel || + dilationChange ); }; @@ -288,7 +291,8 @@ class CategoryValue extends React.Component { annotations, // flippedProps is potentially brittle, their docs want {...flippedProps} on our div, // our lint doesn't like jsx spread, we are version pinned to prevent api change on their part - flippedProps + flippedProps, + pointDilation } = this.props; const { editedLabelText } = this.state; @@ -341,7 +345,15 @@ class CategoryValue extends React.Component { data-flip-config={flippedProps["data-flip-config"]} data-flip-id={flippedProps["data-flip-id"]} data-portal-key={flippedProps["data-portal-key"]} - className={styles.value} + className={ + /* This code is to change the styles on centroid label hover is causing over-rendering */ + `${styles.value}${ + pointDilation.metadataField === metadataField && + pointDilation.categoryField === displayString + ? ` ${styles.hover}` + : "" + }` + } data-testclass="categorical-row" style={{ padding: "4px 7px", diff --git a/client/src/components/graph/graph.js b/client/src/components/graph/graph.js index b72a8e8b..a5bcf6b5 100644 --- a/client/src/components/graph/graph.js +++ b/client/src/components/graph/graph.js @@ -8,10 +8,13 @@ import memoize from "memoize-one"; import * as globals from "../../globals"; import setupSVGandBrushElements from "./setupSVGandBrush"; -import setupCentroidSVG from "./setupCentroidSVG"; import _camera from "../../util/camera"; import _drawPoints from "./drawPointsRegl"; import { isTypedArray } from "../../util/typeHelpers"; +import styles from "./graph.css"; + +import GraphOverlayLayer from "./overlays/graphOverlayLayer"; +import CentroidLabels from "./overlays/centroidLabels"; /* Simple 2D transforms control all point painting. There are three: @@ -79,9 +82,10 @@ function renderThrottle(callback) { selectionTool: state.graphSelection.tool, currentSelection: state.graphSelection.selection, layoutChoice: state.layoutChoice, - centroidLabel: state.centroidLabel, + centroidLabels: state.centroidLabels, graphInteractionMode: state.controls.graphInteractionMode, - colorAccessor: state.colors.colorAccessor + colorAccessor: state.colors.colorAccessor, + pointDilation: state.pointDilation })) class Graph extends React.PureComponent { computePointPositions = memoize((X, Y, modelTF) => { @@ -121,7 +125,7 @@ class Graph extends React.PureComponent { ); computePointFlags = memoize( - (world, crossfilter, colorAccessor, centroidLabel) => { + (world, crossfilter, colorAccessor, pointDilation) => { /* We communicate with the shader using three flags: - isNaN -- the value is a NaN. Only makes sense when we have a colorAccessor @@ -146,7 +150,7 @@ class Graph extends React.PureComponent { 0 ).slice(); - const { metadataField, categoryField } = centroidLabel; + const { metadataField, categoryField } = pointDilation; const highlightData = metadataField ? world.obsAnnotations.col(metadataField)?.asArray() : null; @@ -186,9 +190,9 @@ class Graph extends React.PureComponent { }; this.state = { toolSVG: null, - centroidSVG: null, tool: null, - container: null + container: null, + cameraRender: 0 }; } @@ -245,23 +249,17 @@ class Graph extends React.PureComponent { currentSelection, layoutChoice, graphInteractionMode, - colorAccessor, - centroidLabel + pointDilation, + colorAccessor } = this.props; - const { regl, toolSVG, centroidSVG } = this.state; + const { regl, toolSVG, camera, modelTF } = this.state; let stateChanges = {}; if (regl && world) { /* update the regl and point rendering state */ const { obsLayout, nObs } = world; - const { - drawPoints, - camera, - pointBuffer, - colorBuffer, - flagBuffer, - modelTF - } = this.state; + const { drawPoints, pointBuffer, colorBuffer, flagBuffer } = this.state; + let { projectionTF } = this.state; let needsRepaint = false; @@ -305,12 +303,12 @@ class Graph extends React.PureComponent { world, crossfilter, colorAccessor, - centroidLabel + pointDilation ); if (renderCache.flags !== newFlags) { renderCache.flags = newFlags; - flagBuffer({ data: newFlags, dimension: 1 }); needsRepaint = true; + flagBuffer({ data: newFlags, dimension: 1 }); } this.count = nObs; @@ -328,26 +326,27 @@ class Graph extends React.PureComponent { } } - // Centroid SVG creation is disabled for now but should go into the - // first and third cases if enabled if ( prevProps.responsive.height !== responsive.height || prevProps.responsive.width !== responsive.width ) { // If the window size has changed we want to recreate all SVGs - stateChanges = { ...stateChanges, ...this.createToolSVG() }; + stateChanges = { + ...stateChanges, + ...this.createToolSVG() + }; } else if ( (responsive.height && responsive.width && !toolSVG) || - selectionTool !== prevProps.selectionTool || - prevProps.graphInteractionMode !== graphInteractionMode + selectionTool !== prevProps.selectionTool ) { - // first time or change of selection tool6 - stateChanges = { ...stateChanges, ...this.createToolSVG() }; - } else if ( - centroidLabel !== prevProps.centroidLabel || - (responsive.height && responsive.width && !centroidSVG) - ) { - // First time for centroid or label change + // first time or change of selection tool + stateChanges = { ...stateChanges, ...this.createToolSVG(true) }; + } else if (prevProps.graphInteractionMode !== graphInteractionMode) { + // If lasso/zoom is switched + stateChanges = { + ...stateChanges, + ...this.createToolSVG() + }; } /* @@ -365,7 +364,6 @@ class Graph extends React.PureComponent { stateChanges.container ? stateChanges.container : container ); } - if (Object.keys(stateChanges).length > 0) { this.setState(stateChanges); } @@ -376,10 +374,13 @@ class Graph extends React.PureComponent { if (e.type !== "wheel") e.preventDefault(); if (camera.handleEvent(e, projectionTF)) { this.renderCanvas(); + this.setState(state => { + return { ...state, updateOverlay: !state.updateOverlay }; + }); } }; - createToolSVG() { + createToolSVG = () => { /* Called from componentDidUpdate. Create the tool SVG, and return any state changes that should be passed to setState(). @@ -387,10 +388,14 @@ class Graph extends React.PureComponent { const { responsive, selectionTool, graphInteractionMode } = this.props; /* clear out whatever was on the div, even if nothing, but usually the brushes etc */ - d3.select("#graphAttachPoint") - .select("#tool") + + d3.select("#lasso-layer") + .selectAll(".lasso-group") .remove(); + // Don't render or recreate toolSVG if currently in zoom mode + if (graphInteractionMode !== "select") return { toolSVG: undefined }; + let handleStart; let handleDrag; let handleEnd; @@ -417,36 +422,7 @@ class Graph extends React.PureComponent { ); return { toolSVG: newToolSVG, tool, container }; - } - - createCentroidSVG() { - /* - Called from componentDidUpdate. Create the centroid SVG, and return any - state changes that should be passed to setState(). - - CURRENTLY UNUSED - */ - const { responsive, centroidLabel, colorAccessor } = this.props; - d3.select("#graphAttachPoint") - .select("#centroid-container") - .remove(); - - if (centroidLabel.metadataField === "" || !centroidLabel.centroidXY) { - return {}; - } - - const centroidScreen = this.mapPointToScreen(centroidLabel.centroidXY); - - const newCentroidSVG = setupCentroidSVG( - responsive, - this.graphPaddingRightLeft, - centroidScreen, - centroidLabel.categoryField, - colorAccessor - ); - - return { centroidSVG: newCentroidSVG }; - } + }; brushToolUpdate(tool, container) { /* @@ -745,7 +721,10 @@ class Graph extends React.PureComponent { }); render() { - const { responsive } = this.props; + const { responsive, graphInteractionMode } = this.props; + const { modelTF, projectionTF, camera } = this.state; + + const cameraTF = camera?.view(); return (