From 05323ae643274b6bca1e44edda138c17bd683ea9 Mon Sep 17 00:00:00 2001
From: Matt Weiden <538456+mweiden@users.noreply.github.com>
Date: Mon, 2 Mar 2020 12:07:12 -0800
Subject: [PATCH] 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
---
client/__tests__/e2e/e2e.test.js | 106 +++++++--------
client/__tests__/e2e/e2eAnnotations.test.js | 18 +--
client/__tests__/e2e/puppeteerUtils.js | 63 ++++++---
.../util/stateManager/controlsHelpers.test.js | 24 ++++
client/src/actions/index.js | 43 ++-----
client/src/components/geneExpression/index.js | 4 +-
client/src/components/menubar/index.js | 121 +++++-------------
client/src/components/menubar/subset.js | 48 +++++++
.../menubar/{undoRedoReset.js => undoRedo.js} | 20 ---
client/src/globals.js | 3 +
client/src/reducers/controls.js | 12 +-
client/src/reducers/undoableConfig.js | 2 -
client/src/util/dataframe/dataframe.js | 2 +-
.../src/util/stateManager/controlsHelpers.js | 7 +
client/src/util/stateManager/world.js | 4 +-
15 files changed, 231 insertions(+), 246 deletions(-)
create mode 100644 client/__tests__/util/stateManager/controlsHelpers.test.js
create mode 100644 client/src/components/menubar/subset.js
rename client/src/components/menubar/{undoRedoReset.js => undoRedo.js} (69%)
diff --git a/client/__tests__/e2e/e2e.test.js b/client/__tests__/e2e/e2e.test.js
index 1c21c939..cf5cbba1 100644
--- a/client/__tests__/e2e/e2e.test.js
+++ b/client/__tests__/e2e/e2e.test.js
@@ -20,9 +20,7 @@ beforeEach(async () => {
});
afterAll(() => {
- if (!DEBUG) {
- browser.close();
- }
+ browser.close();
});
describe("did launch", () => {
@@ -111,16 +109,10 @@ describe("cell selection", () => {
describe("gene entry", () => {
test("search for single gene", async () => {
- // blueprint's typeahead is treating typing weird, clicking & waiting first solves this
- await utils.typeInto("gene-search", data.genes.search);
- await page.keyboard.press("Enter");
- await page.waitForSelector(
- `[data-testid='histogram-${data.genes.search}']`
- );
+ await cxgActions.addGeneToSearch(data.genes.search);
});
test("bulk add genes", async () => {
- await cxgActions.reset();
const testGenes = data.genes.bulkadd;
await utils.clickOn("section-bulk-add");
await utils.typeInto("input-bulk-add", testGenes.join(","));
@@ -135,21 +127,9 @@ describe("gene entry", () => {
});
});
-describe("diffexp", () => {
+describe("differential expression", () => {
test("selects cells, saves them and performs diffexp", async () => {
- for (const select of data.diffexp.cellset1) {
- if (select.kind === "categorical") {
- await cxgActions.selectCategory(select.metadata, select.values, true);
- }
- }
- await cxgActions.cellSet(1);
- for (const select of data.diffexp.cellset2) {
- if (select.kind === "categorical") {
- await cxgActions.selectCategory(select.metadata, select.values, true);
- }
- }
- await cxgActions.cellSet(2);
- await utils.clickOn("diffexp-button");
+ await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
const allHistograms = await cxgActions.getAllHistograms(
"histogram-diffexp",
data.diffexp["gene-results"]
@@ -161,7 +141,7 @@ describe("diffexp", () => {
});
});
-describe("subset/reset", () => {
+describe("subset", () => {
test("subset - cell count matches", async () => {
for (const select of data.subset.cellset1) {
if (select.kind === "categorical") {
@@ -180,39 +160,6 @@ describe("subset/reset", () => {
}
});
- test("reset after subset", async () => {
- for (const select of data.subset.cellset1) {
- if (select.kind === "categorical") {
- await cxgActions.selectCategory(select.metadata, select.values, true);
- }
- }
- await utils.clickOn("subset-button");
- for (const label in data.subset.categorical) {
- const categories = await cxgActions.getAllCategoriesAndCounts(label);
- expect(Object.keys(categories)).toMatchObject(
- Object.keys(data.subset.categorical[label])
- );
- expect(Object.values(categories)).toMatchObject(
- Object.values(data.subset.categorical[label])
- );
- }
- await cxgActions.reset();
- for (const label in data.categorical) {
- await utils.waitByID(`category-${label}`);
- const categoryName = await utils.getOneElementInnerText(
- `[data-testid="category-${label}"]`
- );
- expect(categoryName).toMatch(label);
- const categories = await cxgActions.getAllCategoriesAndCounts(label);
- expect(Object.keys(categories)).toMatchObject(
- Object.keys(data.categorical[label])
- );
- expect(Object.values(categories)).toMatchObject(
- Object.values(data.categorical[label])
- );
- }
- });
-
test("lasso after subset", async () => {
for (const select of data.subset.cellset1) {
if (select.kind === "categorical") {
@@ -233,11 +180,52 @@ describe("subset/reset", () => {
const cellCount = await cxgActions.cellSet(1);
expect(cellCount).toBe(data.subset.lasso.count);
});
+
+ test("undo selection appends the top diff exp genes to user defined genes", async () => {
+ const userDefinedGenes = ["ACD", "AAR2", "AATF", "ARSG"];
+ const diffExpGenes = data.diffexp["gene-results"];
+ for (const userDefinedGene of userDefinedGenes) {
+ await cxgActions.addGeneToSearch(userDefinedGene);
+ }
+ const userDefinedHistograms = await cxgActions.getAllHistograms("histogram-user-gene", userDefinedGenes);
+ expect(userDefinedHistograms).toStrictEqual(userDefinedGenes);
+ await cxgActions.subset({x1: 0.15, y1: 0.10, x2: 0.98, y2: 0.98});
+ await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
+ const diffExpHistograms = await cxgActions.getAllHistograms("histogram-diffexp", diffExpGenes);
+ expect(diffExpHistograms).toStrictEqual(diffExpGenes);
+ await utils.clickOn("reset-subset-button");
+ const expected = [].concat(userDefinedGenes, diffExpGenes);
+ const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms(
+ "histogram-user-gene",
+ expected
+ );
+ expect(userDefinedHistogramsAfterSubset).toStrictEqual(expected);
+ });
+
+ test("subset selection appends the top diff exp genes to user defined genes", async () => {
+ const userDefinedGenes = ["ACD", "AAR2", "AATF", "ARSG"];
+ const diffExpGenes = data.diffexp["gene-results"];
+ for (const userDefinedGene of userDefinedGenes) {
+ await cxgActions.addGeneToSearch(userDefinedGene);
+ }
+ const userDefinedHistograms = await cxgActions.getAllHistograms("histogram-user-gene", userDefinedGenes);
+ expect(userDefinedHistograms).toStrictEqual(userDefinedGenes);
+ await cxgActions.subset({x1: 0.15, y1: 0.10, x2: 0.98, y2: 0.98});
+ await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
+ const diffExpHistograms = await cxgActions.getAllHistograms("histogram-diffexp", diffExpGenes);
+ expect(diffExpHistograms).toStrictEqual(diffExpGenes);
+ await cxgActions.subset({x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97});
+ const expected = [].concat(userDefinedGenes, diffExpGenes);
+ const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms(
+ "histogram-user-gene",
+ expected
+ );
+ expect(userDefinedHistogramsAfterSubset).toStrictEqual(expected);
+ });
});
describe("scatter plot", () => {
test("scatter plot appears", async () => {
- await cxgActions.reset();
const testGenes = data.scatter.genes;
await utils.clickOn("section-bulk-add");
await utils.typeInto("input-bulk-add", Object.values(testGenes).join(","));
diff --git a/client/__tests__/e2e/e2eAnnotations.test.js b/client/__tests__/e2e/e2eAnnotations.test.js
index 94db26b7..b74e55d9 100644
--- a/client/__tests__/e2e/e2eAnnotations.test.js
+++ b/client/__tests__/e2e/e2eAnnotations.test.js
@@ -22,22 +22,6 @@ describe.each([
{withSubset: false, tag: "whole"}
])("annotations", (config) => {
- async function subset() {
- const lassoSelection = await cxgActions.calcDragCoordinates(
- "layout-graph",
- {x1: 0.10, y1: 0.10, x2: 0.80, y2: 0.80}
- );
- await cxgActions.drag(
- "layout-graph",
- lassoSelection.start,
- lassoSelection.end,
- true
- );
- await utils.clickOn("subset-button");
- const coordinate = await cxgActions.calcCoordinate("layout-graph", 0.9, 0.9);
- await cxgActions.clickOnCoordinate("layout-graph", coordinate);
- }
-
const perTestCategoryName = "per-test-category";
const perTestLabelName = "per-test-label";
@@ -48,7 +32,7 @@ describe.each([
// setup the test fixtures
await cxgActions.createCategory(perTestCategoryName);
await cxgActions.createLabel(perTestCategoryName, perTestLabelName);
- if (config.withSubset) await subset();
+ if (config.withSubset) await cxgActions.subset({x1: 0.10, y1: 0.10, x2: 0.80, y2: 0.80});
await utils.waitByClass("autosave-complete");
});
diff --git a/client/__tests__/e2e/puppeteerUtils.js b/client/__tests__/e2e/puppeteerUtils.js
index 90873357..15404ad3 100644
--- a/client/__tests__/e2e/puppeteerUtils.js
+++ b/client/__tests__/e2e/puppeteerUtils.js
@@ -1,5 +1,6 @@
import {DEBUG, DEV} from "./config";
import puppeteer from "puppeteer";
+import { strict as assert } from "assert";
export const puppeteerUtils = puppeteerPage => ({
async waitByID(testid, props = {}) {
@@ -16,7 +17,7 @@ export const puppeteerUtils = puppeteerPage => ({
);
},
- async waitForAllByIds(testids, props = {}) {
+ async waitForAllByIds(testids) {
await Promise.all(
testids.map(testid =>
puppeteerPage.waitForSelector(`[data-testid='${testid}']`)
@@ -24,19 +25,16 @@ export const puppeteerUtils = puppeteerPage => ({
);
},
- async getAllByClass(testclass, props = {}) {
+ async getAllByClass(testclass) {
const elements = await puppeteerPage.$$eval(
`[data-testclass=${testclass}]`,
- els => {
- return els.map(el => {
- return el.dataset.testid;
- });
- }
+ eles => eles.map(ele => ele.dataset.testid)
);
return elements;
},
async typeInto(testid, text) {
+ // blueprint's typeahead is treating typing weird, clicking & waiting first solves this
// only works for text without special characters
await this.waitByID(testid);
const selector = `[data-testid='${testid}']`;
@@ -80,6 +78,7 @@ export const puppeteerUtils = puppeteerPage => ({
});
export const cellxgeneActions = puppeteerPage => ({
+
async drag(testid, start, end, lasso = false) {
const layout = await puppeteerUtils(puppeteerPage).waitByID(testid);
const elBox = await layout.boxModel();
@@ -116,7 +115,7 @@ export const cellxgeneActions = puppeteerPage => ({
testclass
);
return allHistograms.map(hist =>
- hist.substr("histogram_".length, hist.length)
+ hist.substr("histogram-".length, hist.length)
);
},
@@ -196,9 +195,7 @@ export const cellxgeneActions = puppeteerPage => ({
await puppeteerUtils(puppeteerPage).clickOn(`${category}:category-expand`);
await puppeteerUtils(puppeteerPage).clickOn(`${category}:category-select`);
for (const val of values) {
- await puppeteerUtils(puppeteerPage).clickOn(
- `categorical-value-select-${category}-${val}`
- );
+ await puppeteerUtils(puppeteerPage).clickOn(`categorical-value-select-${category}-${val}`);
}
},
@@ -210,12 +207,6 @@ export const cellxgeneActions = puppeteerPage => ({
}
},
- async reset() {
- await puppeteerUtils(puppeteerPage).clickOn("reset");
- // loading state never actually happens, reset is too fast
- await page.waitFor(200);
- },
-
async clip(min = 0, max = 100) {
await puppeteerUtils(puppeteerPage).clickOn("visualization-settings");
await puppeteerUtils(puppeteerPage).clearInputAndTypeInto(
@@ -269,6 +260,44 @@ export const cellxgeneActions = puppeteerPage => ({
newLabelName
);
await puppeteerUtils(puppeteerPage).clickOn(`${categoryName}:${oldLabelName}:submit-label-edit`);
+ },
+
+ async addGeneToSearch(geneName) {
+ await puppeteerUtils(puppeteerPage).typeInto("gene-search", geneName);
+ await puppeteerPage.keyboard.press("Enter");
+ await puppeteerPage.waitForSelector(
+ `[data-testid='histogram-${geneName}']`
+ );
+ },
+
+ async subset(coordinatesAsPercent) {
+ // In order to deselect the selection after the subset, make sure we have some clear part
+ // of the scatterplot we can click on
+ assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
+ const lassoSelection = await this.calcDragCoordinates( "layout-graph", coordinatesAsPercent);
+ await this.drag("layout-graph", lassoSelection.start, lassoSelection.end, true );
+ await puppeteerUtils(puppeteerPage).clickOn("subset-button");
+ const clearCoordinate = await this.calcCoordinate(
+ "layout-graph",
+ 0.5,
+ 0.99
+ );
+ await this.clickOnCoordinate("layout-graph", clearCoordinate);
+ },
+
+ async setSellSet(cellSet, cellSetNum) {
+ for (const selection of cellSet) {
+ if (selection.kind === "categorical") {
+ await this.selectCategory(selection.metadata, selection.values, true);
+ }
+ }
+ await this.cellSet(cellSetNum);
+ },
+
+ async runDiffExp(cellSet1, cellSet2) {
+ await this.setSellSet(cellSet1, 1);
+ await this.setSellSet(cellSet2, 2);
+ await puppeteerUtils(puppeteerPage).clickOn("diffexp-button");
}
});
diff --git a/client/__tests__/util/stateManager/controlsHelpers.test.js b/client/__tests__/util/stateManager/controlsHelpers.test.js
new file mode 100644
index 00000000..1e7b1a7a
--- /dev/null
+++ b/client/__tests__/util/stateManager/controlsHelpers.test.js
@@ -0,0 +1,24 @@
+/*
+test controls helpers
+*/
+import { subsetAndResetGeneLists } from "../../../src/util/stateManager/controlsHelpers";
+import * as globals from "../../../src/globals";
+
+describe("controls helpers", () => {
+
+ test("subsetAndResetGeneLists", () => {
+ const geneList = [...Array(150).keys()].map(() =>
+ Math.random().toString(36).substring(2, 6) // random string of 4 characters
+ );
+ const state = {
+ userDefinedGenes: geneList.slice(0, 20),
+ diffexpGenes: geneList.slice(20),
+ };
+ const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists(state);
+ expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes);
+ expect(geneList.length).toBeGreaterThan(globals.maxGenes);
+ expect(newUserDefinedGenes).toHaveLength(globals.maxGenes);
+ expect(newUserDefinedGenes).toStrictEqual(geneList.slice(0, globals.maxGenes));
+ expect(newDiffExpGenes).toStrictEqual([]);
+ });
+});
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 992304a3..fb640e5f 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -149,7 +149,7 @@ const doInitialDataLoad = () =>
Set the view (world) to current selection. Placeholder for an async action
which also does re-layout.
*/
-const regraph = () => (dispatch, getState) => {
+const setWorldToSelection = () => (dispatch, getState) => {
const { universe, world, crossfilter } = getState();
dispatch({
type: "set World to current selection",
@@ -167,7 +167,7 @@ const dispatchExpressionErrors = (dispatch, res) => {
};
/*
-Fetch expression vectors for each gene in genes. This is NOT an action
+Fetch expression vectors for each gene in genes. This is NOT an action
function, but rather a helper to be called from an action helper that
needs expression data.
@@ -385,37 +385,12 @@ const requestDifferentialExpression = (set1, set2, num_genes = 10) => async (
}
};
-const resetInterface = () => (dispatch, getState) => {
+const resetWorldToUniverse = () => (dispatch, getState) => {
const { universe } = getState();
-
- dispatch({
- type: "user reset start"
- });
- dispatch({
- type: "clear all user defined genes"
- });
- dispatch({
- type: "clear differential expression"
- });
- dispatch({
- type: "reset colorscale"
- });
- dispatch({
- type: "reset centroid labels"
- });
- dispatch({
- type: "clear scatterplot"
- });
dispatch({
type: "reset World to eq Universe",
universe
});
- dispatch({
- type: "increment graph render counter"
- });
- dispatch({
- type: "user reset end"
- });
};
const saveObsAnnotations = () => async (dispatch, getState) => {
@@ -472,11 +447,11 @@ const saveObsAnnotations = () => async (dispatch, getState) => {
};
export default {
- regraph,
- resetInterface,
- requestSingleGeneExpressionCountsForColoringPOST,
- requestDifferentialExpression,
- requestUserDefinedGene,
doInitialDataLoad,
- saveObsAnnotations
+ requestDifferentialExpression,
+ requestSingleGeneExpressionCountsForColoringPOST,
+ requestUserDefinedGene,
+ resetWorldToUniverse,
+ saveObsAnnotations,
+ setWorldToSelection,
};
diff --git a/client/src/components/geneExpression/index.js b/client/src/components/geneExpression/index.js
index dcfcb46b..4a9448bf 100644
--- a/client/src/components/geneExpression/index.js
+++ b/client/src/components/geneExpression/index.js
@@ -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
diff --git a/client/src/components/menubar/index.js b/client/src/components/menubar/index.js
index bc461cb1..6d7e2d1f 100644
--- a/client/src/components/menubar/index.js
+++ b/client/src/components/menubar/index.js
@@ -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()}
-
- {
- dispatch(actions.regraph());
- dispatch({ type: "increment graph render counter" });
- }}
- >
-
-
-
-
+ {
+ dispatch(actions.setWorldToSelection());
+ dispatch({ type: "increment graph render counter" });
+ }}
+ handleSubsetReset={() => {
+ dispatch(actions.resetWorldToUniverse());
+ dispatch({ type: "increment graph render counter" });
+ }}
+ />
+
diff --git a/client/src/components/menubar/subset.js b/client/src/components/menubar/subset.js
new file mode 100644
index 00000000..aa09374f
--- /dev/null
+++ b/client/src/components/menubar/subset.js
@@ -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 (
+
+
+ {
+ if (subsetPossible) handleSubset();
+ }}
+ />
+
+
+ {
+ if (subsetResetPossible) handleSubsetReset();
+ }}
+ />
+
+
+ );
+}
+
+export default Subset;
diff --git a/client/src/components/menubar/undoRedoReset.js b/client/src/components/menubar/undoRedo.js
similarity index 69%
rename from client/src/components/menubar/undoRedoReset.js
rename to client/src/components/menubar/undoRedo.js
index 9f54db07..523d6e7e 100644
--- a/client/src/components/menubar/undoRedoReset.js
+++ b/client/src/components/menubar/undoRedo.js
@@ -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"
/>
-
-
-
);
}
diff --git a/client/src/globals.js b/client/src/globals.js
index 8e4219b8..81d43c4c 100644
--- a/client/src/globals.js
+++ b/client/src/globals.js
@@ -74,6 +74,9 @@ export const leftSidebarSectionPadding = 10;
export const categoryLabelDisplayStringLongLength = 27;
export const categoryLabelDisplayStringShortLength = 11;
+export const maxUserDefinedGenes = 25;
+export const maxGenes = 100;
+
/* various timing-related behaviors */
export const tooltipHoverOpenDelay = 1000; /* ms delay before a tooltip displays */
export const tooltipHoverOpenDelayQuick = 500;
diff --git a/client/src/reducers/controls.js b/client/src/reducers/controls.js
index 8625ed40..d38b3dac 100644
--- a/client/src/reducers/controls.js
+++ b/client/src/reducers/controls.js
@@ -1,6 +1,8 @@
// jshint esversion: 6
import _ from "lodash";
+import * as globals from "../globals";
+import { subsetAndResetGeneLists } from "../util/stateManager/controlsHelpers";
const Controls = (
state = {
@@ -74,16 +76,22 @@ const Controls = (
};
}
case "reset World to eq Universe": {
+ const [ newUserDefinedGenes, newDiffExpGenes ] = subsetAndResetGeneLists(state);
return {
...state,
- resettingInterface: false
+ resettingInterface: false,
+ userDefinedGenes: newUserDefinedGenes,
+ diffexpGenes: newDiffExpGenes
};
}
case "set World to current selection": {
+ const [ newUserDefinedGenes, newDiffExpGenes ] = subsetAndResetGeneLists(state);
return {
...state,
loading: false,
- error: null
+ error: null,
+ userDefinedGenes: newUserDefinedGenes,
+ diffexpGenes: newDiffExpGenes
};
}
case "request user defined gene started": {
diff --git a/client/src/reducers/undoableConfig.js b/client/src/reducers/undoableConfig.js
index b514a0da..2d08f4ca 100644
--- a/client/src/reducers/undoableConfig.js
+++ b/client/src/reducers/undoableConfig.js
@@ -16,7 +16,6 @@ const skipOnActions = new Set([
"configuration load complete",
"increment graph render counter",
"window resize",
- "user reset start",
"reset colorscale",
"reset centroid labels",
@@ -65,7 +64,6 @@ const clearOnActions = new Set([
"initial data load complete (universe exists)",
"reset World to eq Universe",
"initial data load error",
- "user reset end"
]);
/*
diff --git a/client/src/util/dataframe/dataframe.js b/client/src/util/dataframe/dataframe.js
index f01c3fa8..74dc0aa4 100644
--- a/client/src/util/dataframe/dataframe.js
+++ b/client/src/util/dataframe/dataframe.js
@@ -586,7 +586,7 @@ class Dataframe {
withRowIndex allows assignment of new row index during subset operation.
If withRowIndex === null, it will reset the index to identity (offset)
- indexing. if withRowIndex is a label index object, it will be used
+ indexing. If withRowIndex is a label index object, it will be used
for the new dataframe.
*/
return this.__subset(rowOffsets, colOffsets, withRowIndex);
diff --git a/client/src/util/stateManager/controlsHelpers.js b/client/src/util/stateManager/controlsHelpers.js
index 397730e3..a8d1cd56 100644
--- a/client/src/util/stateManager/controlsHelpers.js
+++ b/client/src/util/stateManager/controlsHelpers.js
@@ -200,3 +200,10 @@ export function pruneVarDataCache(varData, needed) {
}
return varData;
}
+
+export function subsetAndResetGeneLists(state) {
+ const { userDefinedGenes, diffexpGenes } = state;
+ const newUserDefinedGenes = [].concat(userDefinedGenes, diffexpGenes).slice(0, globals.maxGenes);
+ const newDiffExpGenes = [];
+ return [newUserDefinedGenes, newDiffExpGenes];
+}
diff --git a/client/src/util/stateManager/world.js b/client/src/util/stateManager/world.js
index 0419814f..19acca5a 100644
--- a/client/src/util/stateManager/world.js
+++ b/client/src/util/stateManager/world.js
@@ -5,8 +5,8 @@ import { isContinuousAnnotation } from "./annotationsHelpers";
/*
-World is a subset of universe. Most code should use world, and should
-(generally) not use Universe. World contains any per-obs or per-var data
+World is a subset of universe. Most code should use world, and should
+(generally) not use Universe. World contains any per-obs or per-var data
that must be consistent acorss the app when we view/manipulate subsets
of Universe.