mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-01 05:28:11 +08:00
Graph selection state management and history bug fixes (#679)
* save graph selection in redux state * fix old graph brush select regressions * refactor graph brush selection to work with undo/redo * update tests to match new crossfilter spatial select API * graph selection state now in redux * remove dead code * sync graph selection with redux state; improvements to undoable machinery * fix regression in undoable * differentiate graph selection cancel from deselect action * simplify calculation * remove debugging code * fix responsive repaint bug in graph selection tool * undoable debugging and code cleanliness * undoable action filter state now merges, rather than replaces * improve comments * add debounce to undoable action filter; improve comments and debug sanity check code * comments * fix undoable bug with clear scatterplot actions * disable undoable debug flag * cleanup API and comments around statemachine * add test id attribute to lasso * add better error handling for gene fetch requests
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
Very simple FSM for use in reducer, etc.
|
||||
|
||||
To create a state machine:
|
||||
new StateMachine(initialState, transitions, onErrorCallback) -> statemachine
|
||||
|
||||
Where:
|
||||
* initialState - a caller-specified value that represents the initial state of
|
||||
the FSM.
|
||||
* transitions - an array of objects, representing FSM transitions (graph edges),
|
||||
having the form:
|
||||
{
|
||||
to: state_name_transitioning_to,
|
||||
from: state_name_transitioning_from,
|
||||
event: value_that_will_cause_transition,
|
||||
action: optional_callback_upon_transition
|
||||
}
|
||||
The transition will be provided to the action callback, so other data
|
||||
may be stored in the transition object for use by the action callback.
|
||||
* onErrorCallback - a callback function called if the FSM receives an event
|
||||
for which it has no defined transition.
|
||||
|
||||
|
||||
Interface:
|
||||
* states - property containing the state names. A Set(), contianing the
|
||||
union of to: and from: values.
|
||||
* events - property containing all of the accepted event values. Set().
|
||||
* graph - a Map of Maps, organized as graph[eventValue][fromStateValue]
|
||||
* clone() - clone the entire statemachine.
|
||||
* next(eventValue) - drive the FSM to the next state. If the event
|
||||
matches a transition with a defined action, the action callback is
|
||||
called, and the action return value is returned by next(). If no
|
||||
transition is defined, onErrorCallback is called.
|
||||
|
||||
Example:
|
||||
|
||||
const transitions = [
|
||||
{ from: "A", to: "B", event: "yo", action: () => 42 }
|
||||
];
|
||||
const fsm = new StateMachine("A", transitions, () => { throw new Error("oops") });
|
||||
fsm.next("yo"); // returns 42
|
||||
|
||||
|
||||
*/
|
||||
export default class StateMachine {
|
||||
constructor(initState, transitions, onError) {
|
||||
this.onError = onError || (() => undefined);
|
||||
this.state = initState;
|
||||
|
||||
// all states
|
||||
this.states = new Set(
|
||||
transitions.reduce((names, tsn) => {
|
||||
names.push(tsn.from);
|
||||
names.push(tsn.to);
|
||||
return names;
|
||||
}, [])
|
||||
);
|
||||
|
||||
// all transition names (aka events)
|
||||
this.events = new Set(transitions.map(tsn => tsn.event));
|
||||
|
||||
// the transition graph.
|
||||
// graph[event][from] -> transition
|
||||
this.graph = transitions.reduce((graph, tsn) => {
|
||||
const { event, from } = tsn;
|
||||
if (!graph.has(event)) graph.set(event, new Map());
|
||||
const tsnMap = graph.get(event);
|
||||
tsnMap.set(from, tsn);
|
||||
return graph;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
clone(initState) {
|
||||
const fsm = new StateMachine(initState, []);
|
||||
fsm.onError = this.onError;
|
||||
fsm.states = this.states;
|
||||
fsm.events = this.events;
|
||||
fsm.graph = this.graph;
|
||||
return fsm;
|
||||
}
|
||||
|
||||
next(event, data) {
|
||||
const { graph, state } = this;
|
||||
const tsnMap = graph.get(event);
|
||||
if (!tsnMap) return this.onError(this, event, state, undefined);
|
||||
|
||||
const transition = tsnMap.get(state);
|
||||
if (!transition) return this.onError(this, event, state, undefined);
|
||||
|
||||
this.state = transition.to;
|
||||
return transition.action
|
||||
? transition.action(this, transition, data)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
@@ -477,16 +477,16 @@ class ImmutableSpatialDimension extends _ImmutableBaseDimension {
|
||||
|
||||
selectWithinRect(spec) {
|
||||
/*
|
||||
{ mode: "within-rect", x0: 1, y0: 0, x1: 3, y1: 9 }
|
||||
{ mode: "within-rect", minX: 1, minY: 0, maxX: 3, maxY: 9 }
|
||||
*/
|
||||
const { x0, y0, x1, y1 } = spec;
|
||||
const { minX, minY, maxX, maxY } = spec;
|
||||
const { X, Y } = this;
|
||||
const ranges = [];
|
||||
let start = -1;
|
||||
for (let i = 0, l = X.length; i < l; i += 1) {
|
||||
const x = X[i];
|
||||
const y = Y[i];
|
||||
const inside = x0 <= x && x < x1 && y0 <= y && y < y1;
|
||||
const inside = minX <= x && x < maxX && minY <= y && y < maxY;
|
||||
if (inside && start === -1) start = i;
|
||||
if (!inside && start !== -1) {
|
||||
ranges.push([start, i]);
|
||||
|
||||
Reference in New Issue
Block a user