centroid labels (#872)

* refactor reducer to no longer support hover state and hold many labels

* refactor to generate centroidCoordinates for all values of a category

* create hash for function and memoize export

* create button to display all labels for a category

* clear state

* create label for each thing

* calculate on each value

* change to in place modification of map

* switch to for loop with iterator instead of forEach

* use map from centroidLabel instead of creating copy

* adapt for map

* utilize tarrays

* begin documentation

* disable centroids if in zoom mode

* clean up

* persist uncalc coordinates

* document

* clean up and document

* cleanup and document

* fix

* fix undefined labels and document changes

* fix first element skip

* fix conditional recalc

* rename centroidLabel -> centroidLabels

* break out dilation on hover to new reducer

* numerous styling changes for readability

* change centroid icon

* remove colorAccessor from parameters

* recalc centroids on world change

* make label toggle undoable

* remove unused import

* highlight labels on hover

* remove special characters from svg id

* lighten backdrop

* only generate new centroids if they pre-exist

* fix issue with spaces in catagorical value name

* add label buttons to menubar

* change reducer to use colorAccessor and have single toggle

* fix check to see if svg should be rendered

* move svg overlays onto a single svg layer

* dilate on label hover

* remove logs

* allow centroid to update along side regl renders

* allow actions to pass through svg if in zoom mode

* remove artifact from circle

* remove comment

* remove disabling of centroid button

* fix conditional map to screen

* make styling label conditions stricter

* prettier

* refactor onto master

* refactor computePointFlags() to use pointDilation store

* notify when viewport changes

* move svg attributes out of lasso setup and prevent rerenders/writes

* begin playing with transform matrix

* first solution for camera interaction

* create transform using nested groups

* semi-working method using nested groups with transforms

* inversely scale text

* properly do final transform

* cleanup dead / test code

* reinstate original functionality

* breakout centroid labels labels into separate component

* default toggle on for testing

* separate lasso and centroid layers

* remove unnecessary attributes, working hover

* dilation on label hover

* fix dilation on scatterplot

* add dilation on label hover

* break overlay into separate component

* make overlay agnostic to children

* move label mouse actions to centroidlabels component, add overlay state

* remove lasso on switch to camera

* disallow user selection

* fix reducer

* fix subset with continuous color error

* reset labels on color by continuous

* revert centroids on by default

* refactor for nested restructuring

* remove update checking

* remove unused method

* readd deleted hover delay

* remove old centroid setup

* remove centroid from undoable

* cleanup dead code

* remove dead code

* rollback unnecessary changes

* begin adding annotation functionality

* add annotation functionality

* add reset and undo functionality

* change centroids on layout change

* don't create label for unassigned

* add comment pointing out POI for performance

* touch up matrix transform comment

* add comment explaining coordinate space and children's assumed space

* remove dead code

* switch to pure component

* connect centroidLabels to redux

* clean up camera check and null result

* tool tip change

* rename centroid toggle and the like

* fix the misalignment of buttons, also make blueprint use consistent

* fix comment spelling mistakes

* introduce variable for cleaner logic expressions and state assignment

* alter tooltip text to back color by interaction

* remove manual iterator manipulation for forEach()

* remove debounce

* nit fix

* tooltip wording fix

* lint
This commit is contained in:
Severiano Badajoz
2020-01-22 13:40:22 -08:00
committed by GitHub
parent 69c639b14d
commit 8d725b1ad9
18 changed files with 541 additions and 233 deletions
+70 -68
View File
@@ -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 (
<div id="graphWrapper">
@@ -757,7 +736,30 @@ class Graph extends React.PureComponent {
right: globals.leftSidebarWidth
}}
>
<div id="graphAttachPoint" />
<div id="graphAttachPoint">
<GraphOverlayLayer
cameraTF={cameraTF}
modelTF={modelTF}
projectionTF={projectionTF}
graphPaddingRightLeft={this.graphPaddingRightLeft}
graphPaddingTop={this.graphPaddingTop}
responsive={responsive}
>
<CentroidLabels />
</GraphOverlayLayer>
<svg
id="lasso-layer"
data-testid="layout-overlay"
className={styles.graphSVG}
width={responsive.width - this.graphPaddingRightLeft}
height={responsive.height}
pointerEvents={
graphInteractionMode === "select" ? "auto" : "none"
}
style={{ zIndex: 89 }}
/>
</div>
<div style={{ padding: 0, margin: 0 }}>
<canvas
width={responsive.width - this.graphPaddingRightLeft}