Undo selection appends genes from differential expression to user gene list (#1183)

* Undo selection appends diffExp genes to user gene list

Fixes https://github.com/chanzuckerberg/cellxgene/issues/1171

Need: When a user performs a differential expression from within
a sub-selection (world) of the data and then resets the selection to all
cells (universe), the differential expression results are no longer
valid.

Approach:
* When the selection is reset, move the top (maxUserDefinedGenes
- len(userDefinedGenes) from the differential expression results to the
list of user defined genes
* Raise maxUserDefinedGenes to 25 to give users more room and
accommodate the extra genes transferred in from differential expression

Other commits:
* Choose different button icons
* Add diff exp genes to user defined genes on subset too
* Respond to feedback from @liaprins-czi and @bkmartinjr
This commit is contained in:
Matt Weiden
2020-03-02 12:07:12 -08:00
committed by GitHub
parent 87efcc0e99
commit 05323ae643
15 changed files with 231 additions and 246 deletions
@@ -182,9 +182,9 @@ class GeneExpression extends React.Component {
const gene = g.target;
if (userDefinedGenes.indexOf(gene) !== -1) {
postUserErrorToast("That gene already exists");
} else if (userDefinedGenes.length > 15) {
} else if (userDefinedGenes.length > globals.maxUserDefinedGenes) {
postUserErrorToast(
"That's too many genes, you can have at most 15 user defined genes"
`That's too many genes, you can have at most ${globals.maxUserDefinedGenes} user defined genes`
);
} else if (
world.varAnnotations.col(varIndexName).indexOf(gene) === undefined
+31 -90
View File
@@ -10,22 +10,20 @@ import {
Position,
RadioGroup,
Radio,
Icon
} from "@blueprintjs/core";
import { World } from "../../util/stateManager";
import * as globals from "../../globals";
import actions from "../../actions";
import CellSetButton from "./cellSetButtons";
import InformationMenu from "./infoMenu";
import UndoRedoReset from "./undoRedoReset";
import Clip from "./clip";
import * as globals from "../../globals";
import InformationMenu from "./infoMenu";
import Subset from "./subset";
import UndoRedoReset from "./undoRedo";
@connect(state => ({
universe: state.universe,
world: state.world,
crossfilter: state.crossfilter,
differential: state.differential,
resettingInterface: state.controls.resettingInterface,
layoutChoice: state.layoutChoice,
graphInteractionMode: state.controls.graphInteractionMode,
clipPercentileMin: Math.round(100 * (state.world?.clipQuantiles?.min ?? 0)),
@@ -99,61 +97,6 @@ class MenuBar extends React.Component {
return isDisabled;
};
isResetDisabled = () => {
/*
Reset should be disabled when all of the following are true:
* nothing is selected in the crossfilter
* world EQ universe
* nothing is colored by
* there are no userDefinedGenes or diffexpGenes displayed
* scatterplot is not displayed
* nothing in cellset1 or cellset2
* clip percentiles are [0,100]
*/
const {
crossfilter,
world,
universe,
userDefinedGenes,
diffexpGenes,
colorAccessor,
scatterplotXXaccessor,
scatterplotYYaccessor,
celllist1,
celllist2,
clipPercentileMin,
clipPercentileMax
} = this.props;
if (!crossfilter || !world || !universe) {
return false;
}
const nothingSelected = crossfilter.countSelected() === crossfilter.size();
const nothingColoredBy = !colorAccessor;
const noGenes = userDefinedGenes.length === 0 && diffexpGenes.length === 0;
const scatterNotDpl = !scatterplotXXaccessor || !scatterplotYYaccessor;
const nothingInCellsets = !celllist1 && !celllist2;
return (
nothingSelected &&
World.worldEqUniverse(world, universe) &&
nothingColoredBy &&
noGenes &&
scatterNotDpl &&
nothingInCellsets &&
clipPercentileMax === 100 &&
clipPercentileMin === 0
);
};
resetInterface = () => {
const { dispatch } = this.props;
dispatch({
type: "interface reset started"
});
dispatch(actions.resetInterface());
};
handleClipOnKeyPress = e => {
/*
allow only numbers, plus other critical keys which
@@ -268,6 +211,20 @@ class MenuBar extends React.Component {
});
};
subsetPossible = () => {
const { crossfilter } = this.props;
return (
crossfilter.countSelected() !== 0 &&
crossfilter.countSelected() !== crossfilter.size()
);
};
subsetResetPossible = () => {
const { world, universe } = this.props;
return world.nObs !== universe.nObs;
};
renderDiffExp() {
/* diffexp-related buttons may be disabled */
const { disableDiffexp, differential, diffexpMayBeSlow } = this.props;
@@ -332,8 +289,6 @@ class MenuBar extends React.Component {
render() {
const {
dispatch,
crossfilter,
resettingInterface,
libraryVersions,
undoDisabled,
redoDisabled,
@@ -368,30 +323,19 @@ class MenuBar extends React.Component {
}}
>
{this.renderDiffExp()}
<Tooltip
content="Show only metadata and cells which are currently selected"
position="bottom"
hoverOpenDelay={globals.tooltipHoverOpenDelay}
>
<AnchorButton
data-testid="subset-button"
disabled={
crossfilter &&
(crossfilter.countSelected() === 0 ||
crossfilter.countSelected() === crossfilter.size())
}
style={{
marginRight: 10
}}
onClick={() => {
dispatch(actions.regraph());
dispatch({ type: "increment graph render counter" });
}}
>
<Icon icon="double-chevron-down" />
</AnchorButton>
</Tooltip>
<ButtonGroup style={{ marginRight: "10px" }}>
<Subset
subsetPossible={this.subsetPossible()}
subsetResetPossible={this.subsetResetPossible()}
handleSubset={() => {
dispatch(actions.setWorldToSelection());
dispatch({ type: "increment graph render counter" });
}}
handleSubsetReset={() => {
dispatch(actions.resetWorldToUniverse());
dispatch({ type: "increment graph render counter" });
}}
/>
<ButtonGroup style={{marginRight: "10px"}}>
<Tooltip
content={selectionTooltip}
position="bottom"
@@ -514,9 +458,6 @@ class MenuBar extends React.Component {
/>
<UndoRedoReset
dispatch={dispatch}
isResetDisabled={this.isResetDisabled}
resetInterface={this.resetInterface}
resettingInterface={resettingInterface}
undoDisabled={undoDisabled}
redoDisabled={redoDisabled}
/>
+48
View File
@@ -0,0 +1,48 @@
import React from "react";
import {AnchorButton, ButtonGroup, Tooltip} from "@blueprintjs/core";
import * as globals from "../../globals";
function Subset(props) {
const {
subsetPossible,
subsetResetPossible,
handleSubset,
handleSubsetReset,
} = props;
return (
<ButtonGroup style={{marginRight: "10px"}}>
<Tooltip
content="Subset to currently selected cells and associated metadata"
position="bottom"
hoverOpenDelay={globals.tooltipHoverOpenDelay}
>
<AnchorButton
data-testid="subset-button"
active={!subsetPossible}
icon="pie-chart"
onClick={() => {
if (subsetPossible) handleSubset();
}}
/>
</Tooltip>
<Tooltip
content="Undo subset and show all cells and associated metadata"
position="bottom"
hoverOpenDelay={globals.tooltipHoverOpenDelay}
>
<AnchorButton
data-testid="reset-subset-button"
active={!subsetResetPossible}
icon="full-circle"
onClick={() => {
if (subsetResetPossible) handleSubsetReset();
}}
/>
</Tooltip>
</ButtonGroup>
);
}
export default Subset;
@@ -5,11 +5,8 @@ import { tooltipHoverOpenDelay } from "../../globals";
function InformationMenu(props) {
const {
resettingInterface,
undoDisabled,
redoDisabled,
resetInterface,
isResetDisabled,
dispatch
} = props;
return (
@@ -50,23 +47,6 @@ function InformationMenu(props) {
data-testid="redo"
/>
</Tooltip>
<Tooltip
content="Reset cellxgene, clearing all selections"
position="bottom"
hoverOpenDelay={tooltipHoverOpenDelay}
>
<AnchorButton
disabled={isResetDisabled()}
style={{ marginLeft: 10 }}
type="button"
loading={resettingInterface}
intent="none"
icon="refresh"
onClick={resetInterface}
data-testid="reset"
data-testclass={`resetting-${resettingInterface}`}
/>
</Tooltip>
</div>
);
}