draw labels marking the centroids of category value clusters (#809)

* Connect mouse over events to reducer actions

* Change Styling on hover

* Rename reducer actions to be more descriptive

* Reorder reducer in cascade

* Create centroid calculation util

* Whitespace

* Typo fix, use correct action

* Create centroid calc util

* Create centroid svg setup

* Refactor existing svg layer to toolSVG

* Change calcCentroid signature and centroidXY to match mapPointToScreen

* Add id and styling

* Run prettier

* Set z-index to 999

* Draw the label

* Introduce the centroid SVG, refactor code to allow both SVG layers

* Add text label and compute radius based on population

* Implement optional chaining

* Update font family

* Optimize calcMeanCentroid()

* Create and utilize calcMedianCentroid()

* Remove mass circle from label

* Remove styling change on hover

* Remove reducer action logs

* Prettier

* Swap out binds for arrow functions

* Style text

* switch from selectAll() to select()

* Reflect centroid container's purpose in id

* Remove mass from the output

* Swap to obj

* Add finite check

* Don't draw centroid if no finite values

* Fix finite check

* Remove log

* Toggle label coloring based on colorBy state

* Pass cursor events through centroid svg
This commit is contained in:
Severiano Badajoz
2019-06-13 15:40:57 -07:00
committed by GitHub
parent 6aeefb0fe6
commit 334b8bb8da
8 changed files with 265 additions and 59 deletions
+25 -7
View File
@@ -13,23 +13,41 @@ import * as globals from "../../globals";
world: state.world
}))
class CategoryValue extends React.Component {
toggleOff() {
toggleOff = () => {
const { dispatch, metadataField, categoryIndex } = this.props;
dispatch({
type: "categorical metadata filter deselect",
metadataField,
categoryIndex
});
}
};
toggleOn() {
toggleOn = () => {
const { dispatch, metadataField, categoryIndex } = this.props;
dispatch({
type: "categorical metadata filter select",
metadataField,
categoryIndex
});
}
};
handleMouseEnter = () => {
const { dispatch, metadataField, categoryIndex } = this.props;
dispatch({
type: "category value mouse hover start",
metadataField,
categoryIndex
});
};
handleMouseExit = () => {
const { dispatch, metadataField, categoryIndex } = this.props;
dispatch({
type: "category value mouse hover end",
metadataField,
categoryIndex
});
};
render() {
const {
@@ -79,6 +97,8 @@ class CategoryValue extends React.Component {
justifyContent: "space-between"
}}
data-testclass="categorical-row"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseExit}
>
<div
style={{
@@ -92,9 +112,7 @@ class CategoryValue extends React.Component {
>
<label className="bp3-control bp3-checkbox">
<input
onChange={
selected ? this.toggleOff.bind(this) : this.toggleOn.bind(this)
}
onChange={selected ? this.toggleOff : this.toggleOn}
data-testclass="categorical-value-select"
data-testid={`categorical-value-select-${metadataField}-${displayString}`}
checked={selected}
+65 -22
View File
@@ -8,6 +8,8 @@ import memoize from "memoize-one";
import * as globals from "../../globals";
import setupSVGandBrushElements from "./setupSVGandBrush";
import setupCentroidSVG from "./setupCentroidSVG";
import actions from "../../actions";
import _camera from "../../util/camera";
import _drawPoints from "./drawPointsRegl";
import scaleLinear from "../../util/scaleLinear";
@@ -22,7 +24,9 @@ import scaleLinear from "../../util/scaleLinear";
selectionTool: state.graphSelection.tool,
currentSelection: state.graphSelection.selection,
layoutChoice: state.layoutChoice,
graphInteractionMode: state.controls.graphInteractionMode
centroidLabel: state.centroidLabel,
graphInteractionMode: state.controls.graphInteractionMode,
colorAccessor: state.colors.colorAccessor
}))
class Graph extends React.Component {
computePointPositions = memoize((X, Y, scaleX, scaleY) => {
@@ -70,7 +74,8 @@ class Graph extends React.Component {
sizes: null
};
this.state = {
svg: null,
toolSVG: null,
centroidSVG: null,
tool: null,
container: null
};
@@ -139,9 +144,11 @@ class Graph extends React.Component {
selectionTool,
currentSelection,
layoutChoice,
graphInteractionMode
graphInteractionMode,
colorAccessor,
centroidLabel
} = this.props;
const { reglRender, regl, svg } = this.state;
const { reglRender, mode, regl, toolSVG, centroidSVG } = this.state;
let stateChanges = {};
if (reglRender) {
@@ -216,16 +223,10 @@ class Graph extends React.Component {
);
}
if (
prevProps.responsive.height !== responsive.height ||
prevProps.responsive.width !== responsive.width ||
/* first time */
(responsive.height && responsive.width && !svg) ||
selectionTool !== prevProps.selectionTool
) {
const createToolSVG = () => {
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */
d3.select("#graphAttachPoint")
.selectAll("svg")
.select("#tool")
.remove();
let handleStart;
@@ -241,16 +242,63 @@ class Graph extends React.Component {
handleEnd = this.handleLassoEnd.bind(this);
handleCancel = this.handleLassoCancel.bind(this);
}
const { svg: newSvg, tool, container } = setupSVGandBrushElements(
const { svg: newToolSVG, tool, container } = setupSVGandBrushElements(
selectionTool,
handleStart,
handleDrag,
handleEnd,
handleCancel,
responsive,
this.graphPaddingRight
this.graphPaddingRight,
graphInteractionMode
);
stateChanges = { ...stateChanges, svg: newSvg, tool, container };
stateChanges = { ...stateChanges, toolSVG: newToolSVG, tool, container };
};
const createCentroidSVG = () => {
d3.select("#graphAttachPoint")
.select("#centroid-container")
.remove();
if (centroidLabel.metadataField === "" || !centroidLabel.centroidXY) {
return;
}
const centroidScreen = this.mapPointToScreen(centroidLabel.centroidXY);
const newCentroidSVG = setupCentroidSVG(
responsive,
this.graphPaddingRight,
centroidScreen,
centroidLabel.categoryField,
colorAccessor
);
stateChanges = { ...stateChanges, centroidSVG: newCentroidSVG };
};
if (
prevProps.responsive.height !== responsive.height ||
prevProps.responsive.width !== responsive.width
) {
// If the window size has changed we want to recreate all SVGs
createToolSVG();
createCentroidSVG();
} else if (
(responsive.height && responsive.width && !toolSVG) ||
selectionTool !== prevProps.selectionTool ||
prevProps.graphInteractionMode !== graphInteractionMode
) {
// first time or change of selection tool6
createToolSVG();
} else if (
centroidLabel !== prevProps.centroidLabel ||
(responsive.height && responsive.width && !centroidSVG)
) {
// First time for centroid or label change
createCentroidSVG();
}
/*
@@ -260,7 +308,7 @@ class Graph extends React.Component {
if (
currentSelection !== prevProps.currentSelection ||
graphInteractionMode !== prevProps.graphInteractionMode ||
stateChanges.svg
stateChanges.toolSVG
) {
const { tool, container } = this.state;
this.selectionToolUpdate(
@@ -588,12 +636,7 @@ class Graph extends React.Component {
right: 0
}}
>
<div
style={{
display: graphInteractionMode === "select" ? "inherit" : "none"
}}
id="graphAttachPoint"
/>
<div id="graphAttachPoint" />
<div style={{ padding: 0, margin: 0 }}>
<canvas
width={responsive.width - this.graphPaddingRight}
@@ -0,0 +1,34 @@
import * as d3 from "d3";
import styles from "./graph.css";
export default (responsive, graphPaddingRight, xy, text, colorBy) => {
const containerWidth = responsive.width - graphPaddingRight;
const svg = d3
.select("#graphAttachPoint")
.append("svg")
.attr("id", "centroid-container")
.attr("data-testid", "centroid-overlay")
.attr("width", containerWidth)
.attr("height", responsive.height)
.attr("class", `${styles.graphSVG}`)
.style("z-index", 998)
.style("pointer-events", "none");
// TODO: Create own styles, ask Colin for an explanation on the css
// For now I'm going to put centroid z-index at 998 and lasso on 999
const label = svg
.append("g")
.attr("transform", `translate(${xy[0]}, ${xy[1]})`);
label
.append("text")
.attr("text-anchor", "middle")
.text(text)
.style("font-family", "Roboto Condensed")
.style("font-size", "18px")
.style("font-weight", "700")
.style("fill", colorBy ? "black" : "rgb(32, 178, 212)");
return svg;
};
@@ -16,15 +16,19 @@ export default (
handleEndAction,
handleCancelAction,
responsive,
graphPaddingRight
graphPaddingRight,
graphInteractionMode
) => {
const svg = d3
.select("#graphAttachPoint")
.append("svg")
.attr("id", "tool")
.attr("data-testid", "layout-overlay")
.attr("width", responsive.width - graphPaddingRight)
.attr("height", responsive.height)
.attr("class", `${styles.graphSVG}`);
.attr("class", `${styles.graphSVG}`)
.style("z-index", 999)
.style("display", graphInteractionMode === "select" ? "inherit" : "none");
if (selectionToolType === "brush") {
const brush = d3