brushable histogram brush selection responds to state updates (#670)

* add continuousSelection reducer and make histo respond to state changes

* correctly handle uninitialized state for brush move

* PR review tweaks
This commit is contained in:
Bruce Martin
2019-03-25 15:02:39 -07:00
committed by GitHub
parent df46db965a
commit 153f240c43
3 changed files with 91 additions and 16 deletions
@@ -13,12 +13,13 @@ import memoize from "memoize-one";
import * as globals from "../../globals";
import actions from "../../actions";
import finiteExtent from "../../util/finiteExtent";
import { makeContinuousDimensionName } from "../../util/nameCreators";
@connect(state => ({
world: state.world,
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
crossfilter: state.crossfilter,
continuousSelection: state.continuousSelection,
differential: state.differential,
colorAccessor: state.colors.colorAccessor,
obsAnnotations: _.get(state.world, "obsAnnotations", null)
@@ -86,12 +87,52 @@ class HistogramBrush extends React.Component {
}
componentDidUpdate(prevProps) {
const { field, obsAnnotations } = this.props;
const { field, obsAnnotations, continuousSelection } = this.props;
const { x, y, bins, numValues, svgRef } = this._histogram;
if (obsAnnotations !== prevProps.obsAnnotations) {
this.renderAxesBrushBins(x, y, bins, numValues, svgRef, field);
}
/*
if the selection has changed, ensure that the brush correctly reflects
the underlying selection.
*/
if (continuousSelection !== prevProps.continuousSelection) {
const { isObs, isUserDefined, isDiffExp } = this.props;
const myName = makeContinuousDimensionName(
{ isObs, isUserDefined, isDiffExp },
field
);
const range = continuousSelection[myName];
const { brushXselection, brushX } = this.state;
if (brushXselection) {
const selection = d3.brushSelection(brushXselection.node());
if (!range && selection) {
/* no active selection - clear brush */
brushXselection.call(brushX.move, null);
} else if (range && !selection) {
/* there is an active selection, but no brush - set the brush */
const x0 = x(range[0]);
const x1 = x(range[1]);
brushXselection.call(brushX.move, [x0, x1]);
} else if (range && selection) {
/* there is an active selection and a brush - make sure they match */
const moveDeltaThreshold = 1;
const x0 = x(range[0]);
const x1 = x(range[1]);
const dX0 = Math.abs(x0 - selection[0]);
const dX1 = Math.abs(x1 - selection[1]);
/*
only update the brush if it is grossly incorrect,
as defined by the moveDeltaThreshold
*/
if (dX0 > moveDeltaThreshold || dX1 > moveDeltaThreshold) {
brushXselection.call(brushX.move, [x0, x1]);
}
}
}
}
}
onBrush(selection, x, eventType) {
@@ -99,6 +140,9 @@ class HistogramBrush extends React.Component {
return () => {
const { dispatch, field, isObs, isUserDefined, isDiffExp } = this.props;
// ignore programmatically generated events
if (!d3.event.sourceEvent) return;
if (d3.event.selection) {
dispatch({
type,
@@ -128,10 +172,13 @@ class HistogramBrush extends React.Component {
onBrushEnd(selection, x) {
return () => {
const { dispatch, field, isObs, isUserDefined, isDiffExp } = this.props;
const { brushX } = this.state;
const { brushXselection } = this.state;
const minAllowedBrushSize = 10;
const smallAmountToAvoidInfiniteLoop = 0.1;
// ignore programmatically generated events
if (!d3.event.sourceEvent) return;
if (d3.event.selection) {
let _range;
@@ -151,7 +198,7 @@ class HistogramBrush extends React.Component {
_range = [x(d3.event.selection[0]), x(procedurallyResizedBrushWidth)];
d3.event.target.move(brushX, [
d3.event.target.move(brushXselection, [
d3.event.selection[0],
procedurallyResizedBrushWidth
]);
@@ -283,21 +330,20 @@ class HistogramBrush extends React.Component {
/* BRUSH */
const brushX = d3
.brushX()
/*
emit start so that the Undoable history can save an undo point
upon drag start, and ignore the subsequent intermediate drag events.
*/
.on("start", this.onBrush(field, x.invert, "start").bind(this))
.on("brush", this.onBrush(field, x.invert, "brush").bind(this))
.on("end", this.onBrushEnd(field, x.invert).bind(this));
const brushXselection = d3
.select(svgRef)
.append("g")
.attr("class", "brush")
.attr("data-testid", `${svgRef.id}-brush`)
.call(
d3
.brushX()
/*
emit start so that the Undoable history can save an undo point
upon drag start, and ignore the subsequent intermediate drag events.
*/
.on("start", this.onBrush(field, x.invert, "start").bind(this))
.on("brush", this.onBrush(field, x.invert, "brush").bind(this))
.on("end", this.onBrushEnd(field, x.invert).bind(this))
);
.call(brushX);
/* AXIS */
d3.select(svgRef)
@@ -318,7 +364,7 @@ class HistogramBrush extends React.Component {
.selectAll(".axis--x line")
.style("stroke", "rgb(230,230,230)");
this.setState({ brushX });
this.setState({ brushX, brushXselection });
}
render() {
@@ -0,0 +1,26 @@
import { makeContinuousDimensionName } from "../util/nameCreators";
const ContinuousSelection = (state = {}, action) => {
switch (action.type) {
case "reset World to eq Universe": {
return {};
}
case "continuous metadata histogram start":
case "continuous metadata histogram brush":
case "continuous metadata histogram end": {
const name = makeContinuousDimensionName(
action.continuousNamespace,
action.selection
);
return {
...state,
[name]: action.range
};
}
default: {
return state;
}
}
};
export default ContinuousSelection;
+3
View File
@@ -7,6 +7,7 @@ import config from "./config";
import universe from "./universe";
import world from "./world";
import categoricalSelection from "./categoricalSelection";
import continuousSelection from "./continuousSelection";
import crossfilter from "./crossfilter";
import colors from "./colors";
import differential from "./differential";
@@ -63,6 +64,7 @@ const Reducer = undoable(
["universe", universe],
["world", world],
["categoricalSelection", categoricalSelection],
["continuousSelection", continuousSelection],
["crossfilter", crossfilter],
["colors", colors],
["controls", controls],
@@ -73,6 +75,7 @@ const Reducer = undoable(
[
"world",
"categoricalSelection",
"continuousSelection",
"crossfilter",
"colors",
"controls",