run prettier(2.0.5) (#1438)

This commit is contained in:
Severiano Badajoz
2020-05-04 10:26:42 -07:00
committed by GitHub
parent b255e32548
commit 6cccc41c0f
118 changed files with 1979 additions and 1584 deletions
+53 -26
View File
@@ -1,7 +1,6 @@
import { strict as assert } from "assert"; import { strict as assert } from "assert";
export const cellxgeneActions = (page, utils) => ({ export const cellxgeneActions = (page, utils) => ({
async drag(testId, start, end, lasso = false) { async drag(testId, start, end, lasso = false) {
const layout = await utils.waitByID(testId); const layout = await utils.waitByID(testId);
const elBox = await layout.boxModel(); const elBox = await layout.boxModel();
@@ -31,22 +30,29 @@ export const cellxgeneActions = (page, utils) => ({
}, },
async getAllHistograms(testclass, testIds) { async getAllHistograms(testclass, testIds) {
const histTestIds = testIds.map(tid => `histogram-${tid}`); const histTestIds = testIds.map((tid) => `histogram-${tid}`);
// these load asynchronously, so we need to wait for each histogram individually // these load asynchronously, so we need to wait for each histogram individually
await utils.waitForAllByIds(histTestIds); await utils.waitForAllByIds(histTestIds);
const allHistograms = await utils.getAllByClass(testclass); const allHistograms = await utils.getAllByClass(testclass);
return allHistograms.map(hist => hist.replace(/^histogram-/, "")); return allHistograms.map((hist) => hist.replace(/^histogram-/, ""));
}, },
async getAllCategoriesAndCounts(category) { async getAllCategoriesAndCounts(category) {
await utils.waitByClass("categorical-row"); await utils.waitByClass("categorical-row");
return page.$$eval( return page.$$eval(
`[data-testid="category-${category}"] [data-testclass='categorical-row']`, `[data-testid="category-${category}"] [data-testclass='categorical-row']`,
rows => Object.fromEntries(rows.map(row => { (rows) =>
const cat = row.querySelector("[data-testclass='categorical-value']").innerText; Object.fromEntries(
const count = row.querySelector("[data-testclass='categorical-value-count']").innerText; rows.map((row) => {
return [cat, count]; const cat = row.querySelector(
})) "[data-testclass='categorical-value']"
).innerText;
const count = row.querySelector(
"[data-testclass='categorical-value-count']"
).innerText;
return [cat, count];
})
)
); );
}, },
@@ -60,12 +66,14 @@ export const cellxgeneActions = (page, utils) => ({
await utils.waitByID(checkboxId); await utils.waitByID(checkboxId);
const checkedPseudoclass = await page.$eval( const checkedPseudoclass = await page.$eval(
`[data-testid='${checkboxId}']`, `[data-testid='${checkboxId}']`,
el => el.matches(":checked") (el) => el.matches(":checked")
); );
if (!checkedPseudoclass) await utils.clickOn(checkboxId); if (!checkedPseudoclass) await utils.clickOn(checkboxId);
try { try {
const categoryRow = await utils.waitByID(`${category}:category-expand`); const categoryRow = await utils.waitByID(`${category}:category-expand`);
const isExpanded = await categoryRow.$("[data-testclass='category-expand-is-expanded']"); const isExpanded = await categoryRow.$(
"[data-testclass='category-expand-is-expanded']"
);
if (isExpanded) await utils.clickOn(`${category}:category-expand`); if (isExpanded) await utils.clickOn(`${category}:category-expand`);
} catch {} } catch {}
}, },
@@ -75,14 +83,22 @@ export const cellxgeneActions = (page, utils) => ({
const size = await el.boxModel(); const size = await el.boxModel();
return { return {
x: Math.floor(size.width * xAsPercent), x: Math.floor(size.width * xAsPercent),
y: Math.floor(size.height * yAsPercent) y: Math.floor(size.height * yAsPercent),
} };
}, },
async calcDragCoordinates(testId, coordinateAsPercent) { async calcDragCoordinates(testId, coordinateAsPercent) {
return { return {
start: await this.calcCoordinate(testId, coordinateAsPercent.x1, coordinateAsPercent.y1), start: await this.calcCoordinate(
end: await this.calcCoordinate(testId, coordinateAsPercent.x2, coordinateAsPercent.y2) testId,
coordinateAsPercent.x1,
coordinateAsPercent.y1
),
end: await this.calcCoordinate(
testId,
coordinateAsPercent.x2,
coordinateAsPercent.y2
),
}; };
}, },
@@ -97,7 +113,9 @@ export const cellxgeneActions = (page, utils) => ({
async expandCategory(category) { async expandCategory(category) {
const expand = await utils.waitByID(`${category}:category-expand`); const expand = await utils.waitByID(`${category}:category-expand`);
const notExpanded = await expand.$("[data-testclass='category-expand-is-not-expanded']"); const notExpanded = await expand.$(
"[data-testclass='category-expand-is-not-expanded']"
);
if (notExpanded) await utils.clickOn(`${category}:category-expand`); if (notExpanded) await utils.clickOn(`${category}:category-expand`);
}, },
@@ -117,7 +135,10 @@ export const cellxgeneActions = (page, utils) => ({
async renameCategory(oldCatgoryName, newCategoryName) { async renameCategory(oldCatgoryName, newCategoryName) {
await utils.clickOn(`${oldCatgoryName}:see-actions`); await utils.clickOn(`${oldCatgoryName}:see-actions`);
await utils.clickOn(`${oldCatgoryName}:edit-category-mode`); await utils.clickOn(`${oldCatgoryName}:edit-category-mode`);
await utils.clearInputAndTypeInto(`${oldCatgoryName}:edit-category-name-text`, newCategoryName); await utils.clearInputAndTypeInto(
`${oldCatgoryName}:edit-category-name-text`,
newCategoryName
);
await utils.clickOn(`${oldCatgoryName}:submit-category-edit`); await utils.clickOn(`${oldCatgoryName}:submit-category-edit`);
}, },
@@ -136,7 +157,7 @@ export const cellxgeneActions = (page, utils) => ({
async deleteLabel(categoryName, labelName) { async deleteLabel(categoryName, labelName) {
await this.expandCategory(categoryName); await this.expandCategory(categoryName);
await utils.clickOn(`${categoryName}:${labelName}:see-actions`); await utils.clickOn(`${categoryName}:${labelName}:see-actions`);
await utils.clickOn( `${categoryName}:${labelName}:delete-label`); await utils.clickOn(`${categoryName}:${labelName}:delete-label`);
}, },
async renameLabel(categoryName, oldLabelName, newLabelName) { async renameLabel(categoryName, oldLabelName, newLabelName) {
@@ -153,17 +174,23 @@ export const cellxgeneActions = (page, utils) => ({
async addGeneToSearch(geneName) { async addGeneToSearch(geneName) {
await utils.typeInto("gene-search", geneName); await utils.typeInto("gene-search", geneName);
await page.keyboard.press("Enter"); await page.keyboard.press("Enter");
await page.waitForSelector( await page.waitForSelector(`[data-testid='histogram-${geneName}']`);
`[data-testid='histogram-${geneName}']`
);
}, },
async subset(coordinatesAsPercent) { async subset(coordinatesAsPercent) {
// In order to deselect the selection after the subset, make sure we have some clear part // In order to deselect the selection after the subset, make sure we have some clear part
// of the scatterplot we can click on // of the scatterplot we can click on
assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99); assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
const lassoSelection = await this.calcDragCoordinates( "layout-graph", coordinatesAsPercent); const lassoSelection = await this.calcDragCoordinates(
await this.drag("layout-graph", lassoSelection.start, lassoSelection.end, true ); "layout-graph",
coordinatesAsPercent
);
await this.drag(
"layout-graph",
lassoSelection.start,
lassoSelection.end,
true
);
await utils.clickOn("subset-button"); await utils.clickOn("subset-button");
const clearCoordinate = await this.calcCoordinate( const clearCoordinate = await this.calcCoordinate(
"layout-graph", "layout-graph",
@@ -174,7 +201,9 @@ export const cellxgeneActions = (page, utils) => ({
}, },
async setSellSet(cellSet, cellSetNum) { async setSellSet(cellSet, cellSetNum) {
for (const selection of cellSet.filter(sel => sel.kind === "categorical")) { for (const selection of cellSet.filter(
(sel) => sel.kind === "categorical"
)) {
await this.selectCategory(selection.metadata, selection.values, true); await this.selectCategory(selection.metadata, selection.values, true);
} }
await this.cellSet(cellSetNum); await this.cellSet(cellSetNum);
@@ -190,7 +219,5 @@ export const cellxgeneActions = (page, utils) => ({
await utils.clickOn("section-bulk-add"); await utils.clickOn("section-bulk-add");
await utils.typeInto("input-bulk-add", geneNames.join(",")); await utils.typeInto("input-bulk-add", geneNames.join(","));
await page.keyboard.press("Enter"); await page.keyboard.press("Enter");
} },
}); });
+2 -1
View File
@@ -1,6 +1,7 @@
export const jest_env = process.env.JEST_ENV; export const jest_env = process.env.JEST_ENV;
export const appPort = process.env.CXG_SERVER_PORT; export const appPort = process.env.CXG_SERVER_PORT;
export const appUrlBase = process.env.CXG_URL_BASE || `http://localhost:${appPort}`; export const appUrlBase =
process.env.CXG_URL_BASE || `http://localhost:${appPort}`;
export const DEV = jest_env === "dev"; export const DEV = jest_env === "dev";
export const DEBUG = jest_env === "debug"; export const DEBUG = jest_env === "debug";
export const DATASET = "pbmc3k"; export const DATASET = "pbmc3k";
+37 -37
View File
@@ -4,7 +4,7 @@ export const datasets = {
dataframe: { dataframe: {
nObs: "2638", nObs: "2638",
nVar: "1838", nVar: "1838",
type: "float32" type: "float32",
}, },
categorical: { categorical: {
louvain: { louvain: {
@@ -15,47 +15,47 @@ export const datasets = {
"Dendritic cells": "37", "Dendritic cells": "37",
"FCGR3A+ Monocytes": "150", "FCGR3A+ Monocytes": "150",
Megakaryocytes: "15", Megakaryocytes: "15",
"NK cells": "154" "NK cells": "154",
} },
}, },
continuous: { continuous: {
n_genes: "int32", n_genes: "int32",
percent_mito: "float32", percent_mito: "float32",
n_counts: "float32" n_counts: "float32",
}, },
cellsets: { cellsets: {
lasso: [ lasso: [
{ {
"coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 }, "coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 },
count: "1181" count: "1181",
} },
], ],
categorical: [ categorical: [
{ {
metadata: "louvain", metadata: "louvain",
values: ["B cells", "Megakaryocytes"], values: ["B cells", "Megakaryocytes"],
count: "357" count: "357",
} },
], ],
continuous: [ continuous: [
{ {
metadata: "n_genes", metadata: "n_genes",
"coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 }, "coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 },
count: "1537" count: "1537",
} },
] ],
}, },
diffexp: { diffexp: {
cellset1: [ cellset1: [
{ kind: "categorical", metadata: "louvain", values: ["B cells"] } { kind: "categorical", metadata: "louvain", values: ["B cells"] },
], ],
cellset2: [ cellset2: [
{ {
kind: "categorical", kind: "categorical",
metadata: "louvain", metadata: "louvain",
values: ["CD4 T cells", "NK cells"] values: ["CD4 T cells", "NK cells"],
} },
], ],
"gene-results": [ "gene-results": [
"HLA-DRB1", "HLA-DRB1",
@@ -67,21 +67,21 @@ export const datasets = {
"HLA-DQB1", "HLA-DQB1",
"MS4A1", "MS4A1",
"IL32", "IL32",
"CD37" "CD37",
] ],
}, },
genes: { genes: {
bulkadd: ["S100A8", "FCGR3A", "LGALS2", "GSTP1"], bulkadd: ["S100A8", "FCGR3A", "LGALS2", "GSTP1"],
search: "ACD" search: "ACD",
}, },
subset: { subset: {
cellset1: [ cellset1: [
{ {
kind: "categorical", kind: "categorical",
metadata: "louvain", metadata: "louvain",
values: ["B cells", "Megakaryocytes"] values: ["B cells", "Megakaryocytes"],
} },
], ],
count: "357", count: "357",
categorical: { categorical: {
@@ -93,27 +93,27 @@ export const datasets = {
"Dendritic cells": "0", "Dendritic cells": "0",
"FCGR3A+ Monocytes": "0", "FCGR3A+ Monocytes": "0",
Megakaryocytes: "15", Megakaryocytes: "15",
"NK cells": "0" "NK cells": "0",
} },
}, },
lasso: { lasso: {
"coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 }, "coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 },
count: "329" count: "329",
} },
}, },
scatter: { scatter: {
genes: { x: "S100A8", y: "FCGR3A" } genes: { x: "S100A8", y: "FCGR3A" },
}, },
pan: { pan: {
"coordinates-as-percent": { x1: 0.75, y1: 0.75, x2: 0.35, y2: 0.35 } "coordinates-as-percent": { x1: 0.75, y1: 0.75, x2: 0.35, y2: 0.35 },
}, },
features: { features: {
panzoom: { panzoom: {
lasso: { lasso: {
"coordinates-as-percent": { x1: 0.3, y1: 0.3, x2: 0.5, y2: 0.5 }, "coordinates-as-percent": { x1: 0.3, y1: 0.3, x2: 0.5, y2: 0.5 },
count: "24" count: "24",
} },
} },
}, },
categoryLabel: { categoryLabel: {
lasso: { lasso: {
@@ -122,17 +122,17 @@ export const datasets = {
newCount: { newCount: {
bySubsetConfig: { bySubsetConfig: {
false: "600", false: "600",
true: "591" true: "591",
} },
} },
}, },
annotationsFromFile: { annotationsFromFile: {
count: { count: {
bySubsetConfig: { bySubsetConfig: {
false: "1161", false: "1161",
true: "856" true: "856",
} },
} },
}, },
clip: { clip: {
min: "30", min: "30",
@@ -141,7 +141,7 @@ export const datasets = {
gene: "S100A8", gene: "S100A8",
"coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 }, "coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 },
count: "386", count: "386",
"gene-cell-count": "416" "gene-cell-count": "416",
} },
} },
}; };
+46 -17
View File
@@ -24,7 +24,9 @@ afterAll(() => {
describe("did launch", () => { describe("did launch", () => {
test("page launched", async () => { test("page launched", async () => {
const element = await utils.getOneElementInnerHTML("[data-testid='header']"); const element = await utils.getOneElementInnerHTML(
"[data-testid='header']"
);
expect(element).toBe(data.title); expect(element).toBe(data.title);
}); });
}); });
@@ -32,7 +34,9 @@ describe("did launch", () => {
describe("metadata loads", () => { describe("metadata loads", () => {
test("categories and values from dataset appear", async () => { test("categories and values from dataset appear", async () => {
for (const label in data.categorical) { for (const label in data.categorical) {
const categoryName = await utils.getOneElementInnerText(`[data-testid="category-${label}"]`); const categoryName = await utils.getOneElementInnerText(
`[data-testid="category-${label}"]`
);
expect(categoryName).toMatch(label); expect(categoryName).toMatch(label);
await utils.clickOn(`${label}:category-expand`); await utils.clickOn(`${label}:category-expand`);
const categories = await cxgActions.getAllCategoriesAndCounts(label); const categories = await cxgActions.getAllCategoriesAndCounts(label);
@@ -50,7 +54,6 @@ describe("metadata loads", () => {
await utils.waitByID(`histogram-${label}`); await utils.waitByID(`histogram-${label}`);
} }
}); });
}); });
describe("cell selection", () => { describe("cell selection", () => {
@@ -81,7 +84,9 @@ describe("cell selection", () => {
await utils.clickOn(`${cellset.metadata}:category-expand`); await utils.clickOn(`${cellset.metadata}:category-expand`);
await utils.clickOn(`${cellset.metadata}:category-select`); await utils.clickOn(`${cellset.metadata}:category-select`);
for (const val of cellset.values) { for (const val of cellset.values) {
await utils.clickOn(`categorical-value-select-${cellset.metadata}-${val}`); await utils.clickOn(
`categorical-value-select-${cellset.metadata}-${val}`
);
} }
const cellCount = await cxgActions.cellSet(1); const cellCount = await cxgActions.cellSet(1);
expect(cellCount).toBe(cellset.count); expect(cellCount).toBe(cellset.count);
@@ -103,12 +108,16 @@ describe("cell selection", () => {
}); });
describe("gene entry", () => { describe("gene entry", () => {
test("search for single gene", async () => cxgActions.addGeneToSearch(data.genes.search)); test("search for single gene", async () =>
cxgActions.addGeneToSearch(data.genes.search));
test("bulk add genes", async () => { test("bulk add genes", async () => {
const testGenes = data.genes.bulkadd; const testGenes = data.genes.bulkadd;
await cxgActions.bulkAddGenes(testGenes); await cxgActions.bulkAddGenes(testGenes);
const allHistograms = await cxgActions.getAllHistograms("histogram-user-gene", testGenes); const allHistograms = await cxgActions.getAllHistograms(
"histogram-user-gene",
testGenes
);
expect(allHistograms).toEqual(expect.arrayContaining(testGenes)); expect(allHistograms).toEqual(expect.arrayContaining(testGenes));
expect(allHistograms.length).toEqual(testGenes.length); expect(allHistograms.length).toEqual(testGenes.length);
}); });
@@ -172,11 +181,19 @@ describe("subset", () => {
const userDefinedGenes = data.genes.bulkadd; const userDefinedGenes = data.genes.bulkadd;
const diffExpGenes = data.diffexp["gene-results"]; const diffExpGenes = data.diffexp["gene-results"];
await cxgActions.bulkAddGenes(userDefinedGenes); await cxgActions.bulkAddGenes(userDefinedGenes);
const userDefinedHistograms = await cxgActions.getAllHistograms("histogram-user-gene", userDefinedGenes); const userDefinedHistograms = await cxgActions.getAllHistograms(
expect(userDefinedHistograms).toEqual(expect.arrayContaining(userDefinedGenes)); "histogram-user-gene",
await cxgActions.subset({x1: 0.15, y1: 0.10, x2: 0.98, y2: 0.98}); userDefinedGenes
);
expect(userDefinedHistograms).toEqual(
expect.arrayContaining(userDefinedGenes)
);
await cxgActions.subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 });
await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
const diffExpHistograms = await cxgActions.getAllHistograms("histogram-diffexp", diffExpGenes); const diffExpHistograms = await cxgActions.getAllHistograms(
"histogram-diffexp",
diffExpGenes
);
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes)); expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
await utils.clickOn("reset-subset-button"); await utils.clickOn("reset-subset-button");
const expected = [].concat(userDefinedGenes, diffExpGenes); const expected = [].concat(userDefinedGenes, diffExpGenes);
@@ -184,26 +201,38 @@ describe("subset", () => {
"histogram-user-gene", "histogram-user-gene",
expected expected
); );
expect(userDefinedHistogramsAfterSubset).toEqual(expect.arrayContaining(expected)); expect(userDefinedHistogramsAfterSubset).toEqual(
expect.arrayContaining(expected)
);
}); });
test("subset selection appends the top diff exp genes to user defined genes", async () => { test("subset selection appends the top diff exp genes to user defined genes", async () => {
const userDefinedGenes = data.genes.bulkadd; const userDefinedGenes = data.genes.bulkadd;
const diffExpGenes = data.diffexp["gene-results"]; const diffExpGenes = data.diffexp["gene-results"];
await cxgActions.bulkAddGenes(userDefinedGenes); await cxgActions.bulkAddGenes(userDefinedGenes);
const userDefinedHistograms = await cxgActions.getAllHistograms("histogram-user-gene", userDefinedGenes); const userDefinedHistograms = await cxgActions.getAllHistograms(
expect(userDefinedHistograms).toEqual(expect.arrayContaining(userDefinedGenes)); "histogram-user-gene",
await cxgActions.subset({x1: 0.15, y1: 0.10, x2: 0.98, y2: 0.98}); userDefinedGenes
);
expect(userDefinedHistograms).toEqual(
expect.arrayContaining(userDefinedGenes)
);
await cxgActions.subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 });
await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
const diffExpHistograms = await cxgActions.getAllHistograms("histogram-diffexp", diffExpGenes); const diffExpHistograms = await cxgActions.getAllHistograms(
"histogram-diffexp",
diffExpGenes
);
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes)); expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
await cxgActions.subset({x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97}); await cxgActions.subset({ x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97 });
const expected = [].concat(userDefinedGenes, diffExpGenes); const expected = [].concat(userDefinedGenes, diffExpGenes);
const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms( const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms(
"histogram-user-gene", "histogram-user-gene",
expected expected
); );
expect(userDefinedHistogramsAfterSubset).toEqual(expect.arrayContaining(expected)); expect(userDefinedHistogramsAfterSubset).toEqual(
expect.arrayContaining(expected)
);
}); });
}); });
+43 -21
View File
@@ -13,14 +13,13 @@ beforeAll(async () => {
}); });
afterAll(() => { afterAll(() => {
if (browser !== undefined) browser.close() if (browser !== undefined) browser.close();
}); });
describe.each([ describe.each([
{withSubset: true, tag: "subset"}, { withSubset: true, tag: "subset" },
{withSubset: false, tag: "whole"} { withSubset: false, tag: "whole" },
])("annotations", (config) => { ])("annotations", (config) => {
const perTestCategoryName = "per-test-category"; const perTestCategoryName = "per-test-category";
const perTestLabelName = "per-test-label"; const perTestLabelName = "per-test-label";
@@ -31,7 +30,8 @@ describe.each([
// setup the test fixtures // setup the test fixtures
await actions.createCategory(perTestCategoryName); await actions.createCategory(perTestCategoryName);
await actions.createLabel(perTestCategoryName, perTestLabelName); await actions.createLabel(perTestCategoryName, perTestLabelName);
if (config.withSubset) await actions.subset({x1: 0.10, y1: 0.10, x2: 0.80, y2: 0.80}); if (config.withSubset)
await actions.subset({ x1: 0.1, y1: 0.1, x2: 0.8, y2: 0.8 });
await utils.waitByClass("autosave-complete"); await utils.waitByClass("autosave-complete");
}); });
@@ -74,7 +74,11 @@ describe.each([
test("rename a label", async () => { test("rename a label", async () => {
const newLabelName = "my-cool-new-label"; const newLabelName = "my-cool-new-label";
await assertLabelDoesNotExist(perTestCategoryName, newLabelName); await assertLabelDoesNotExist(perTestCategoryName, newLabelName);
await actions.renameLabel(perTestCategoryName, perTestLabelName, newLabelName); await actions.renameLabel(
perTestCategoryName,
perTestLabelName,
newLabelName
);
await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName); await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName);
await assertLabelExists(perTestCategoryName, newLabelName); await assertLabelExists(perTestCategoryName, newLabelName);
}); });
@@ -83,8 +87,10 @@ describe.each([
const categoryName = "cluster-test"; const categoryName = "cluster-test";
const labelName = "four"; const labelName = "four";
await actions.expandCategory(categoryName); await actions.expandCategory(categoryName);
const result = await utils.waitByID(`categorical-value-count-${categoryName}-${labelName}`); const result = await utils.waitByID(
expect(await result.evaluate(node => node.innerText)).toBe( `categorical-value-count-${categoryName}-${labelName}`
);
expect(await result.evaluate((node) => node.innerText)).toBe(
data.annotationsFromFile.count.bySubsetConfig[config.withSubset] data.annotationsFromFile.count.bySubsetConfig[config.withSubset]
); );
}); });
@@ -101,11 +107,17 @@ describe.each([
lassoSelection.end, lassoSelection.end,
true true
); );
await utils.waitByID("lasso-element", {visible: true}); await utils.waitByID("lasso-element", { visible: true });
await utils.clickOn(`${perTestCategoryName}:${perTestLabelName}:see-actions`); await utils.clickOn(
await utils.clickOn(`${perTestCategoryName}:${perTestLabelName}:add-current-selection-to-this-label`); `${perTestCategoryName}:${perTestLabelName}:see-actions`
const result = await utils.waitByID(`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`); );
expect(await result.evaluate(node => node.innerText)).toBe( await utils.clickOn(
`${perTestCategoryName}:${perTestLabelName}:add-current-selection-to-this-label`
);
const result = await utils.waitByID(
`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`
);
expect(await result.evaluate((node) => node.innerText)).toBe(
data.categoryLabel.newCount.bySubsetConfig[config.withSubset] data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
); );
}); });
@@ -170,7 +182,11 @@ describe.each([
test("undo/redo label rename", async () => { test("undo/redo label rename", async () => {
const newLabelName = `label-renamed-undo-${config.tag}`; const newLabelName = `label-renamed-undo-${config.tag}`;
await assertLabelDoesNotExist(perTestCategoryName, newLabelName); await assertLabelDoesNotExist(perTestCategoryName, newLabelName);
await actions.renameLabel(perTestCategoryName, perTestLabelName, newLabelName); await actions.renameLabel(
perTestCategoryName,
perTestLabelName,
newLabelName
);
await assertLabelExists(perTestCategoryName, newLabelName); await assertLabelExists(perTestCategoryName, newLabelName);
await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName); await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName);
await utils.clickOn("undo"); await utils.clickOn("undo");
@@ -183,14 +199,16 @@ describe.each([
async function assertCategoryExists(categoryName) { async function assertCategoryExists(categoryName) {
const handle = await utils.waitByID(`${categoryName}:category-expand`); const handle = await utils.waitByID(`${categoryName}:category-expand`);
const result = await handle.evaluate(node => node.innerText); const result = await handle.evaluate((node) => node.innerText);
// slice beginning and end of category name result to account for truncation of long names // slice beginning and end of category name result to account for truncation of long names
expect(result.slice(0, 10)).toBe(categoryName.slice(0, 10)); expect(result.slice(0, 10)).toBe(categoryName.slice(0, 10));
expect(result.slice(-10)).toBe(categoryName.slice(-10)); expect(result.slice(-10)).toBe(categoryName.slice(-10));
} }
async function assertCategoryDoesNotExist(categoryName) { async function assertCategoryDoesNotExist(categoryName) {
const result = await page.$(`[data-testid='${categoryName}:category-expand']`); const result = await page.$(
`[data-testid='${categoryName}:category-expand']`
);
expect(result).toBeNull(); expect(result).toBeNull();
} }
@@ -198,13 +216,17 @@ describe.each([
const category = await utils.waitByID(`${categoryName}:category-expand`); const category = await utils.waitByID(`${categoryName}:category-expand`);
expect(category).not.toBeNull(); expect(category).not.toBeNull();
await actions.expandCategory(categoryName); await actions.expandCategory(categoryName);
const previous = await utils.waitByID(`categorical-value-${categoryName}-${labelName}`); const previous = await utils.waitByID(
expect(await previous.evaluate(node => node.innerText)).toBe(labelName); `categorical-value-${categoryName}-${labelName}`
);
expect(await previous.evaluate((node) => node.innerText)).toBe(labelName);
} }
async function assertLabelDoesNotExist(categoryName, labelName) { async function assertLabelDoesNotExist(categoryName, labelName) {
await actions.expandCategory(categoryName); await actions.expandCategory(categoryName);
const result = await page.$(`[data-testid='categorical-value-${categoryName}-${labelName}']`); const result = await page.$(
`[data-testid='categorical-value-${categoryName}-${labelName}']`
);
expect(result).toBeNull(); expect(result).toBeNull();
} }
@@ -212,10 +234,10 @@ describe.each([
try { try {
const category = await page.waitForSelector( const category = await page.waitForSelector(
`[data-testid='${categoryName}:category-expand']`, `[data-testid='${categoryName}:category-expand']`,
{timeout: 200} { timeout: 200 }
); );
if (category !== null) return await actions.deleteCategory(categoryName); if (category !== null) return await actions.deleteCategory(categoryName);
} catch {} } catch {}
return null return null;
} }
}); });
+2 -6
View File
@@ -1,9 +1,5 @@
{ {
"preset": "jest-puppeteer", "preset": "jest-puppeteer",
"testMatch": [ "testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"],
"**/__tests__/**/?(*.)(spec|test).js?(x)" "setupFiles": ["../setupMissingGlobals.js"]
],
"setupFiles": [
"../setupMissingGlobals.js"
]
} }
+2 -2
View File
@@ -29,9 +29,9 @@ beforeAll(async () => {
page = await browser.newPage(); page = await browser.newPage();
await page.setViewport(browserViewport); await page.setViewport(browserViewport);
if (DEV || DEBUG) { if (DEV || DEBUG) {
page.on("console", msg => console.log(`PAGE LOG: ${msg.text()}`)); page.on("console", (msg) => console.log(`PAGE LOG: ${msg.text()}`));
} }
page.on("pageerror", err => { page.on("pageerror", (err) => {
throw new Error(`Console error: ${err}`); throw new Error(`Console error: ${err}`);
}); });
utils = puppeteerUtils(page); utils = puppeteerUtils(page);
+8 -8
View File
@@ -1,4 +1,4 @@
export const puppeteerUtils = page => ({ export const puppeteerUtils = (page) => ({
async waitByID(testId, props = {}) { async waitByID(testId, props = {}) {
return page.waitForSelector(`[data-testid='${testId}']`, props); return page.waitForSelector(`[data-testid='${testId}']`, props);
}, },
@@ -9,13 +9,13 @@ export const puppeteerUtils = page => ({
async waitForAllByIds(testIds) { async waitForAllByIds(testIds) {
await Promise.all( await Promise.all(
testIds.map(testId => page.waitForSelector(`[data-testid='${testId}']`)) testIds.map((testId) => page.waitForSelector(`[data-testid='${testId}']`))
); );
}, },
async getAllByClass(testClass) { async getAllByClass(testClass) {
return page.$$eval(`[data-testclass=${testClass}]`, eles => return page.$$eval(`[data-testclass=${testClass}]`, (eles) =>
eles.map(ele => ele.dataset.testid) eles.map((ele) => ele.dataset.testid)
); );
}, },
@@ -52,18 +52,18 @@ export const puppeteerUtils = page => ({
async getOneElementInnerHTML(selector) { async getOneElementInnerHTML(selector) {
await page.waitForSelector(selector); await page.waitForSelector(selector);
return page.$eval(selector, el => el.innerHTML); return page.$eval(selector, (el) => el.innerHTML);
}, },
async getOneElementInnerText(selector) { async getOneElementInnerText(selector) {
await page.waitForSelector(selector); await page.waitForSelector(selector);
return page.$eval(selector, el => el.innerText); return page.$eval(selector, (el) => el.innerText);
}, },
async getElementCoordinates(testid) { async getElementCoordinates(testid) {
return page.$eval(`[data-testid='${testid}']`, elem => { return page.$eval(`[data-testid='${testid}']`, (elem) => {
const { left, top } = elem.getBoundingClientRect(); const { left, top } = elem.getBoundingClientRect();
return [left, top]; return [left, top];
}); });
} },
}); });
+20 -13
View File
@@ -7,41 +7,48 @@ export async function setupTestBrowser() {
const browserViewport = { width: 1280, height: 960 }; const browserViewport = { width: 1280, height: 960 };
const browserParams = DEV const browserParams = DEV
? { ? {
headless: false, headless: false,
slowMo: 5, slowMo: 5,
args: [ `--window-size=${browserViewport.width},${browserViewport.height}`] args: [
} `--window-size=${browserViewport.width},${browserViewport.height}`,
],
}
: DEBUG : DEBUG
? { ? {
headless: false, headless: false,
slowMo: 100, slowMo: 100,
devtools: true, devtools: true,
args: [ `--window-size=${browserViewport.width + 560},${browserViewport.height}`] args: [
`--window-size=${browserViewport.width + 560},${
browserViewport.height
}`,
],
} }
: { : {
args: [ `--window-size=${browserViewport.width},${browserViewport.height}`] args: [
`--window-size=${browserViewport.width},${browserViewport.height}`,
],
}; };
const browser = await puppeteer.launch(browserParams); const browser = await puppeteer.launch(browserParams);
const page = await browser.pages().then(pages => pages[0]); const page = await browser.pages().then((pages) => pages[0]);
await page.setViewport(browserViewport); await page.setViewport(browserViewport);
if (DEV || DEBUG) { if (DEV || DEBUG) {
page.on("console", async msg => { page.on("console", async (msg) => {
// If there is a console.error but an error is not thrown, this will ensure the test fails // If there is a console.error but an error is not thrown, this will ensure the test fails
if (msg.type() === "error") { if (msg.type() === "error") {
const errorMsgText = await Promise.all( const errorMsgText = await Promise.all(
// TODO can we do this without internal properties? // TODO can we do this without internal properties?
msg.args().map(arg => arg._remoteObject.description) msg.args().map((arg) => arg._remoteObject.description)
); );
throw new Error(`Console error: ${errorMsgText}`); throw new Error(`Console error: ${errorMsgText}`);
} }
console.log(`PAGE LOG: ${msg.text()}`); console.log(`PAGE LOG: ${msg.text()}`);
}); });
} }
page.on("pageerror", err => { page.on("pageerror", (err) => {
throw new Error(`Console error: ${err}`); throw new Error(`Console error: ${err}`);
}); });
const utils = puppeteerUtils(page); const utils = puppeteerUtils(page);
const cxgActions = cellxgeneActions(page, utils); const cxgActions = cellxgeneActions(page, utils);
return [browser, page, utils, cxgActions]; return [browser, page, utils, cxgActions];
} }
+3 -3
View File
@@ -26,7 +26,7 @@ describe("cascade", () => {
expect(nextSharedState).toStrictEqual({}); expect(nextSharedState).toStrictEqual({});
expect(prevSharedState).toBe(topLevelState); expect(prevSharedState).toBe(topLevelState);
return 0; return 0;
} },
], ],
[ [
"bar", "bar",
@@ -36,8 +36,8 @@ describe("cascade", () => {
expect(nextSharedState).toStrictEqual({ foo: 0 }); expect(nextSharedState).toStrictEqual({ foo: 0 });
expect(prevSharedState).toBe(topLevelState); expect(prevSharedState).toBe(topLevelState);
return 99; return 99;
} },
] ],
]); ]);
const nextState = reducer(topLevelState, topLevelAction); const nextState = reducer(topLevelState, topLevelAction);
+3 -3
View File
@@ -23,7 +23,7 @@ describe("create", () => {
describe("undo", () => { describe("undo", () => {
test("expected state modifications", () => { test("expected state modifications", () => {
const initialState = { a: 0, b: 1000 }; const initialState = { a: 0, b: 1000 };
const reducer = state => { const reducer = (state) => {
return { a: state.a + 1, b: state.b + 1 }; return { a: state.a + 1, b: state.b + 1 };
}; };
const undoableReducer = undoable(reducer, ["a"]); const undoableReducer = undoable(reducer, ["a"]);
@@ -43,7 +43,7 @@ describe("undo", () => {
describe("redo", () => { describe("redo", () => {
const initialState = { a: 0, b: 1000 }; const initialState = { a: 0, b: 1000 };
const reducer = state => { const reducer = (state) => {
return { a: state.a + 1, b: state.b + 1 }; return { a: state.a + 1, b: state.b + 1 };
}; };
let UR; let UR;
@@ -58,7 +58,7 @@ describe("redo", () => {
// verify undo->redo reverts state. // verify undo->redo reverts state.
const s2 = UR(UR(s1, { type: "@@undoable/undo" }), { const s2 = UR(UR(s1, { type: "@@undoable/undo" }), {
type: "@@undoable/redo" type: "@@undoable/redo",
}); });
expect(s2).toMatchObject({ a: 1, b: 1001 }); expect(s2).toMatchObject({ a: 1, b: 1001 });
+9 -6
View File
@@ -13,16 +13,16 @@ describe("rangeEncodeIndices", () => {
expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([ expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([
1, 1,
9, 9,
432 432,
]); ]);
expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([ expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([
1, 1,
9, 9,
432 432,
]); ]);
expect(rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)).toMatchObject( expect(
[[0, 3], [9, 10], 432] rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)
); ).toMatchObject([[0, 3], [9, 10], 432]);
expect( expect(
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false) rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false)
).toMatchObject([[0, 3], [9, 10], 432]); ).toMatchObject([[0, 3], [9, 10], 432]);
@@ -34,7 +34,10 @@ describe("rangeEncodeIndices", () => {
).toMatchObject([[3, 7], [10, 12], 99]); ).toMatchObject([[3, 7], [10, 12], 99]);
expect( expect(
rangeEncodeIndices([3, 4, 5, 6, 7, 10, 11, 12], 3, false) rangeEncodeIndices([3, 4, 5, 6, 7, 10, 11, 12], 3, false)
).toMatchObject([[3, 7], [10, 12]]); ).toMatchObject([
[3, 7],
[10, 12],
]);
expect( expect(
rangeEncodeIndices([0, 3, 4, 5, 6, 7, 10, 11, 12], 3, false) rangeEncodeIndices([0, 3, 4, 5, 6, 7, 10, 11, 12], 3, false)
).toMatchObject([0, [3, 7], [10, 12]]); ).toMatchObject([0, [3, 7], [10, 12]]);
+5 -5
View File
@@ -31,7 +31,7 @@ describe("centroid", () => {
...Universe.addObsLayout( ...Universe.addObsLayout(
universe, universe,
Universe.matrixFBSToDataframe(REST.layoutObs) Universe.matrixFBSToDataframe(REST.layoutObs)
) ),
}; };
world = World.createWorldFromEntireUniverse(universe); world = World.createWorldFromEntireUniverse(universe);
@@ -61,10 +61,10 @@ describe("centroid", () => {
// This expected result assumes that all cells belong in all categorical values inside of sample response // This expected result assumes that all cells belong in all categorical values inside of sample response
const expectedResult = [ const expectedResult = [
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0], quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0] quantile([0.5], world.obsLayout.col("umap_1").asArray())[0],
]; ];
centroidResult.forEach(coordinate => { centroidResult.forEach((coordinate) => {
expect(coordinate).toEqual(expectedResult); expect(coordinate).toEqual(expectedResult);
}); });
}); });
@@ -86,10 +86,10 @@ describe("centroid", () => {
// This expected result assumes that all cells belong in all categorical values inside of sample response // This expected result assumes that all cells belong in all categorical values inside of sample response
const expectedResult = [ const expectedResult = [
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0], quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0] quantile([0.5], world.obsLayout.col("umap_1").asArray())[0],
]; ];
centroidResult.forEach(coordinate => { centroidResult.forEach((coordinate) => {
expect(coordinate).toEqual(expectedResult); expect(coordinate).toEqual(expectedResult);
}); });
}); });
@@ -53,7 +53,7 @@ describe("simple data access", () => {
[4, 2], [4, 2],
[ [
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]), new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
["red", "blue", "green", "nan"] ["red", "blue", "green", "nan"],
], ],
new Dataframe.DenseInt32Index([3, 2, 1, 0]), new Dataframe.DenseInt32Index([3, 2, 1, 0]),
new Dataframe.KeyIndex(["numbers", "colors"]) new Dataframe.KeyIndex(["numbers", "colors"])
@@ -136,7 +136,7 @@ describe("dataframe subsetting", () => {
new Int32Array([0, 1, 2]), new Int32Array([0, 1, 2]),
["A", "B", "C"], ["A", "B", "C"],
new Float32Array([4.4, 5.5, 6.6]), new Float32Array([4.4, 5.5, 6.6]),
["red", "green", "blue"] ["red", "green", "blue"],
], ],
null, null,
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
@@ -258,7 +258,7 @@ describe("dataframe subsetting", () => {
new Int32Array([0, 1, 2]), new Int32Array([0, 1, 2]),
["A", "B", "C"], ["A", "B", "C"],
new Float32Array([4.4, 5.5, 6.6]), new Float32Array([4.4, 5.5, 6.6]),
["red", "green", "blue"] ["red", "green", "blue"],
], ],
new Dataframe.DenseInt32Index([2, 4, 6]), new Dataframe.DenseInt32Index([2, 4, 6]),
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"]) new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
@@ -283,7 +283,7 @@ describe("dataframe factories", () => {
[ [
new Array(3).fill(0), new Array(3).fill(0),
new Int16Array(3).fill(99), new Int16Array(3).fill(99),
new Float64Array(3).fill(1.1) new Float64Array(3).fill(1.1),
] ]
); );
@@ -323,7 +323,7 @@ describe("dataframe factories", () => {
[2, 2], [2, 2],
[ [
["red", "blue"], ["red", "blue"],
[true, false] [true, false],
], ],
null, null,
new Dataframe.KeyIndex(["colors", "bools"]) new Dataframe.KeyIndex(["colors", "bools"])
@@ -346,7 +346,7 @@ describe("dataframe factories", () => {
[2, 2], [2, 2],
[ [
["red", "blue"], ["red", "blue"],
[true, false] [true, false],
], ],
null, null,
new Dataframe.DenseInt32Index([74, 75]) new Dataframe.DenseInt32Index([74, 75])
@@ -371,7 +371,7 @@ describe("dataframe factories", () => {
[2, 2], [2, 2],
[ [
["red", "blue"], ["red", "blue"],
[true, false] [true, false],
], ],
null, null,
new Dataframe.DenseInt32Index([74, 75]) new Dataframe.DenseInt32Index([74, 75])
@@ -396,7 +396,7 @@ describe("dataframe factories", () => {
[2, 2], [2, 2],
[ [
["red", "blue"], ["red", "blue"],
[true, false] [true, false],
], ],
null, null,
null null
@@ -421,7 +421,7 @@ describe("dataframe factories", () => {
[2, 2], [2, 2],
[ [
["red", "blue"], ["red", "blue"],
[true, false] [true, false],
], ],
null, null,
null null
@@ -479,7 +479,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
@@ -553,7 +553,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
@@ -595,7 +595,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
@@ -618,7 +618,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.KeyIndex(["colors", "bools", "numbers"]) new Dataframe.KeyIndex(["colors", "bools", "numbers"])
@@ -641,7 +641,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
null null
@@ -665,7 +665,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
null null
@@ -689,7 +689,7 @@ describe("dataframe factories", () => {
[ [
["red", "blue"], ["red", "blue"],
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.DenseInt32Index([102, 101, 100]) new Dataframe.DenseInt32Index([102, 101, 100])
@@ -715,7 +715,7 @@ describe("dataframe factories", () => {
[ [
new Array(3).fill(0), new Array(3).fill(0),
new Int16Array(3).fill(99), new Int16Array(3).fill(99),
new Float64Array(3).fill(1.1) new Float64Array(3).fill(1.1),
] ]
); );
const dfB = dfA.mapColumns((col, idx) => { const dfB = dfA.mapColumns((col, idx) => {
@@ -760,7 +760,7 @@ describe("dataframe factories", () => {
[2, 2], [2, 2],
[ [
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
@@ -781,7 +781,7 @@ describe("dataframe col", () => {
[2, 2], [2, 2],
[ [
[true, false], [true, false],
[1, 0] [1, 0],
], ],
null, null,
new Dataframe.KeyIndex(["A", "B"]) new Dataframe.KeyIndex(["A", "B"])
@@ -14,7 +14,7 @@ describe("Dataframe column histogram", () => {
new Map([ new Map([
["n1", new Map([["c1", 1]])], ["n1", new Map([["c1", 1]])],
["n2", new Map([["c2", 1]])], ["n2", new Map([["c2", 1]])],
["n3", new Map([["c3", 1]])] ["n3", new Map([["c3", 1]])],
]) ])
); );
// memoized? // memoized?
@@ -31,7 +31,11 @@ describe("Dataframe column histogram", () => {
const h1 = df.col("value").histogram(3, [0, 2], df.col("name")); const h1 = df.col("value").histogram(3, [0, 2], df.col("name"));
expect(h1).toMatchObject( expect(h1).toMatchObject(
new Map([["n1", [1, 0, 0]], ["n2", [0, 1, 0]], ["n3", [0, 0, 1]]]) new Map([
["n1", [1, 0, 0]],
["n2", [0, 1, 0]],
["n3", [0, 0, 1]],
])
); );
// memoized? // memoized?
expect(df.col("value").histogram(3, [0, 2], df.col("name"))).toMatchObject( expect(df.col("value").histogram(3, [0, 2], df.col("name"))).toMatchObject(
@@ -48,7 +52,13 @@ describe("Dataframe column histogram", () => {
); );
const h1 = df.col("cat").histogram(); const h1 = df.col("cat").histogram();
expect(h1).toMatchObject(new Map([["c1", 1], ["c2", 1], ["c3", 1]])); expect(h1).toMatchObject(
new Map([
["c1", 1],
["c2", 1],
["c3", 1],
])
);
// memoized? // memoized?
expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1); expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1);
}); });
@@ -87,7 +97,7 @@ describe("Dataframe column histogram", () => {
0, 0,
0, 0,
0, 0,
2 2,
]); ]);
}); });
}); });
@@ -13,7 +13,7 @@ describe("Dataframe column summary", () => {
categorical: true, categorical: true,
categories: [], categories: [],
categoryCounts: new Map(), categoryCounts: new Map(),
numCategories: 0 numCategories: 0,
}) })
); );
}); });
@@ -27,7 +27,7 @@ describe("Dataframe column summary", () => {
[true], [true],
new Float32Array([39.3]), new Float32Array([39.3]),
new Int32Array([99]), new Int32Array([99]),
[1] [1],
], ],
null, null,
new Dataframe.KeyIndex([ new Dataframe.KeyIndex([
@@ -36,7 +36,7 @@ describe("Dataframe column summary", () => {
"nameBoolean", "nameBoolean",
"nameFloat32", "nameFloat32",
"nameInt32", "nameInt32",
"nameCategorical" "nameCategorical",
]) ])
); );
@@ -45,7 +45,7 @@ describe("Dataframe column summary", () => {
categorical: true, categorical: true,
categories: ["n1"], categories: ["n1"],
categoryCounts: new Map([["n1", 1]]), categoryCounts: new Map([["n1", 1]]),
numCategories: 1 numCategories: 1,
}) })
); );
expect(df.icol(1).summarize()).toEqual( expect(df.icol(1).summarize()).toEqual(
@@ -53,7 +53,7 @@ describe("Dataframe column summary", () => {
categorical: true, categorical: true,
categories: ["hi"], categories: ["hi"],
categoryCounts: new Map([["hi", 1]]), categoryCounts: new Map([["hi", 1]]),
numCategories: 1 numCategories: 1,
}) })
); );
expect(df.icol(2).summarize()).toEqual( expect(df.icol(2).summarize()).toEqual(
@@ -61,7 +61,7 @@ describe("Dataframe column summary", () => {
categorical: true, categorical: true,
categories: [true], categories: [true],
categoryCounts: new Map([[true, 1]]), categoryCounts: new Map([[true, 1]]),
numCategories: 1 numCategories: 1,
}) })
); );
expect(df.icol(3).summarize()).toEqual( expect(df.icol(3).summarize()).toEqual(
@@ -71,7 +71,7 @@ describe("Dataframe column summary", () => {
max: float32Conversion(39.3), max: float32Conversion(39.3),
nan: 0, nan: 0,
ninf: 0, ninf: 0,
pinf: 0 pinf: 0,
}) })
); );
expect(df.icol(4).summarize()).toEqual( expect(df.icol(4).summarize()).toEqual(
@@ -81,7 +81,7 @@ describe("Dataframe column summary", () => {
max: 99, max: 99,
nan: 0, nan: 0,
ninf: 0, ninf: 0,
pinf: 0 pinf: 0,
}) })
); );
expect(df.icol(5).summarize()).toEqual( expect(df.icol(5).summarize()).toEqual(
@@ -89,7 +89,7 @@ describe("Dataframe column summary", () => {
categorical: true, categorical: true,
categories: [1], categories: [1],
categoryCounts: new Map([[1, 1]]), categoryCounts: new Map([[1, 1]]),
numCategories: 1 numCategories: 1,
}) })
); );
}); });
@@ -103,7 +103,7 @@ describe("Dataframe column summary", () => {
[false, true, true], [false, true, true],
new Float32Array([39.3, 39.3, 0]), new Float32Array([39.3, 39.3, 0]),
new Int32Array([99, 99, 99]), new Int32Array([99, 99, 99]),
[1, false, "0"] [1, false, "0"],
], ],
null, null,
new Dataframe.KeyIndex([ new Dataframe.KeyIndex([
@@ -112,7 +112,7 @@ describe("Dataframe column summary", () => {
"nameBoolean", "nameBoolean",
"nameFloat32", "nameFloat32",
"nameInt32", "nameInt32",
"nameCategorical" "nameCategorical",
]) ])
); );
@@ -120,24 +120,34 @@ describe("Dataframe column summary", () => {
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]), categories: expect.arrayContaining(["n0", "n1", "n2"]),
categoryCounts: new Map([["n0", 1], ["n1", 1], ["n2", 1]]), categoryCounts: new Map([
numCategories: 3 ["n0", 1],
["n1", 1],
["n2", 1],
]),
numCategories: 3,
}) })
); );
expect(df.icol(1).summarize()).toEqual( expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["hi", "bye"]), categories: expect.arrayContaining(["hi", "bye"]),
categoryCounts: new Map([["hi", 2], ["bye", 1]]), categoryCounts: new Map([
numCategories: 2 ["hi", 2],
["bye", 1],
]),
numCategories: 2,
}) })
); );
expect(df.icol(2).summarize()).toEqual( expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([true, false]), categories: expect.arrayContaining([true, false]),
categoryCounts: new Map([[true, 2], [false, 1]]), categoryCounts: new Map([
numCategories: 2 [true, 2],
[false, 1],
]),
numCategories: 2,
}) })
); );
expect(df.icol(3).summarize()).toEqual( expect(df.icol(3).summarize()).toEqual(
@@ -147,7 +157,7 @@ describe("Dataframe column summary", () => {
max: float32Conversion(39.3), max: float32Conversion(39.3),
nan: 0, nan: 0,
ninf: 0, ninf: 0,
pinf: 0 pinf: 0,
}) })
); );
expect(df.icol(4).summarize()).toEqual( expect(df.icol(4).summarize()).toEqual(
@@ -157,15 +167,19 @@ describe("Dataframe column summary", () => {
max: 99, max: 99,
nan: 0, nan: 0,
ninf: 0, ninf: 0,
pinf: 0 pinf: 0,
}) })
); );
expect(df.icol(5).summarize()).toEqual( expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([1, false, "0"]), categories: expect.arrayContaining([1, false, "0"]),
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]), categoryCounts: new Map([
numCategories: 3 [1, 1],
[false, 1],
["0", 1],
]),
numCategories: 3,
}) })
); );
}); });
@@ -181,10 +195,10 @@ describe("Dataframe column summary", () => {
39.3, 39.3,
Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY,
Number.NaN, Number.NaN,
Number.POSITIVE_INFINITY Number.POSITIVE_INFINITY,
]), ]),
new Int32Array([99, 99, 99, 99]), new Int32Array([99, 99, 99, 99]),
[1, false, "0", "0"] [1, false, "0", "0"],
], ],
null, null,
new Dataframe.KeyIndex([ new Dataframe.KeyIndex([
@@ -193,7 +207,7 @@ describe("Dataframe column summary", () => {
"nameBoolean", "nameBoolean",
"nameFloat32", "nameFloat32",
"nameInt32", "nameInt32",
"nameCategorical" "nameCategorical",
]) ])
); );
@@ -201,24 +215,34 @@ describe("Dataframe column summary", () => {
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]), categories: expect.arrayContaining(["n0", "n1", "n2"]),
categoryCounts: new Map([["n0", 1], ["n1", 1], ["n2", 2]]), categoryCounts: new Map([
numCategories: 3 ["n0", 1],
["n1", 1],
["n2", 2],
]),
numCategories: 3,
}) })
); );
expect(df.icol(1).summarize()).toEqual( expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["hi", "bye"]), categories: expect.arrayContaining(["hi", "bye"]),
categoryCounts: new Map([["hi", 2], ["bye", 1]]), categoryCounts: new Map([
numCategories: 2 ["hi", 2],
["bye", 1],
]),
numCategories: 2,
}) })
); );
expect(df.icol(2).summarize()).toEqual( expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([true, false]), categories: expect.arrayContaining([true, false]),
categoryCounts: new Map([[true, 2], [false, 1]]), categoryCounts: new Map([
numCategories: 2 [true, 2],
[false, 1],
]),
numCategories: 2,
}) })
); );
expect(df.icol(3).summarize()).toEqual( expect(df.icol(3).summarize()).toEqual(
@@ -228,7 +252,7 @@ describe("Dataframe column summary", () => {
max: float32Conversion(39.3), max: float32Conversion(39.3),
nan: 1, nan: 1,
ninf: 1, ninf: 1,
pinf: 1 pinf: 1,
}) })
); );
expect(df.icol(4).summarize()).toEqual( expect(df.icol(4).summarize()).toEqual(
@@ -238,15 +262,19 @@ describe("Dataframe column summary", () => {
max: 99, max: 99,
nan: 0, nan: 0,
ninf: 0, ninf: 0,
pinf: 0 pinf: 0,
}) })
); );
expect(df.icol(5).summarize()).toEqual( expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([1, false, "0"]), categories: expect.arrayContaining([1, false, "0"]),
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]), categoryCounts: new Map([
numCategories: 3 [1, 1],
[false, 1],
["0", 1],
]),
numCategories: 3,
}) })
); );
}); });
+8 -8
View File
@@ -6,7 +6,7 @@ import {
obsAnnoDimensionName, obsAnnoDimensionName,
diffexpDimensionName, diffexpDimensionName,
userDefinedDimensionName, userDefinedDimensionName,
makeContinuousDimensionName makeContinuousDimensionName,
} from "../../src/util/nameCreators"; } from "../../src/util/nameCreators";
describe("nameCreators", () => { describe("nameCreators", () => {
@@ -14,25 +14,25 @@ describe("nameCreators", () => {
layoutDimensionName, layoutDimensionName,
obsAnnoDimensionName, obsAnnoDimensionName,
diffexpDimensionName, diffexpDimensionName,
userDefinedDimensionName userDefinedDimensionName,
]; ];
test("check for namespace isolation", () => { test("check for namespace isolation", () => {
const foo = "foo"; const foo = "foo";
nameCreators.forEach(fn => expect(fn(foo)).not.toBe(foo)); nameCreators.forEach((fn) => expect(fn(foo)).not.toBe(foo));
const bar = "bar"; const bar = "bar";
nameCreators.forEach(fn => expect(fn(foo)).not.toBe(fn(bar))); nameCreators.forEach((fn) => expect(fn(foo)).not.toBe(fn(bar)));
nameCreators.forEach(fn => { nameCreators.forEach((fn) => {
const allOtherFn = nameCreators.filter(elmnt => elmnt !== fn); const allOtherFn = nameCreators.filter((elmnt) => elmnt !== fn);
allOtherFn.forEach(otherFn => expect(fn(foo)).not.toBe(otherFn(foo))); allOtherFn.forEach((otherFn) => expect(fn(foo)).not.toBe(otherFn(foo)));
}); });
}); });
test("check for legal keys", () => { test("check for legal keys", () => {
/* need namespace creators to return strings only */ /* need namespace creators to return strings only */
nameCreators.forEach(fn => expect(fn("X")).toMatch(/X/)); nameCreators.forEach((fn) => expect(fn("X")).toMatch(/X/));
}); });
}); });
+61 -61
View File
@@ -1,73 +1,73 @@
import { PromiseLimit } from "../../src/util/promiseLimit"; import { PromiseLimit } from "../../src/util/promiseLimit";
import { range } from "../../src/util/range"; import { range } from "../../src/util/range";
const delay = t => new Promise((resolve, reject) => setTimeout(resolve, t)); const delay = (t) => new Promise((resolve, reject) => setTimeout(resolve, t));
describe("PromiseLimit", () => { describe("PromiseLimit", () => {
test("simple evaluation, concurrency 1", async () => { test("simple evaluation, concurrency 1", async () => {
const plimit = new PromiseLimit(1); const plimit = new PromiseLimit(1);
const result = await Promise.all([ const result = await Promise.all([
plimit.add(() => Promise.resolve(1)), plimit.add(() => Promise.resolve(1)),
plimit.add(() => Promise.resolve(2)), plimit.add(() => Promise.resolve(2)),
plimit.add(() => Promise.resolve(3)), plimit.add(() => Promise.resolve(3)),
plimit.add(() => Promise.resolve(4)) plimit.add(() => Promise.resolve(4)),
]); ]);
expect(result).toEqual([1, 2, 3, 4]); expect(result).toEqual([1, 2, 3, 4]);
}); });
test("simple evaluation, concurrency > 1", async () => { test("simple evaluation, concurrency > 1", async () => {
const plimit = new PromiseLimit(100); const plimit = new PromiseLimit(100);
const result = await Promise.all([ const result = await Promise.all([
plimit.add(() => Promise.resolve(1)), plimit.add(() => Promise.resolve(1)),
plimit.add(() => Promise.resolve(2)), plimit.add(() => Promise.resolve(2)),
plimit.add(() => Promise.resolve(3)), plimit.add(() => Promise.resolve(3)),
plimit.add(() => Promise.resolve(4)) plimit.add(() => Promise.resolve(4)),
]); ]);
expect(result).toEqual([1, 2, 3, 4]); expect(result).toEqual([1, 2, 3, 4]);
}); });
test("eval in order of insertion", async () => { test("eval in order of insertion", async () => {
const plimit = new PromiseLimit(100); const plimit = new PromiseLimit(100);
let counter = 0; let counter = 0;
const result = await Promise.all([ const result = await Promise.all([
plimit.add(() => Promise.resolve((counter += 1))), plimit.add(() => Promise.resolve((counter += 1))),
plimit.add(() => Promise.resolve((counter += 1))), plimit.add(() => Promise.resolve((counter += 1))),
plimit.add(() => Promise.resolve((counter += 1))), plimit.add(() => Promise.resolve((counter += 1))),
plimit.add(() => Promise.resolve((counter += 1))) plimit.add(() => Promise.resolve((counter += 1))),
]); ]);
expect(result).toEqual([1, 2, 3, 4]); expect(result).toEqual([1, 2, 3, 4]);
}); });
test("obeys concurrency limit", async () => { test("obeys concurrency limit", async () => {
const plimit = new PromiseLimit(2); const plimit = new PromiseLimit(2);
let running = 0; let running = 0;
let maxRunning = 0; let maxRunning = 0;
const cbfn = async i => { const cbfn = async (i) => {
running = running + 1; running = running + 1;
maxRunning = running > maxRunning ? running : maxRunning; maxRunning = running > maxRunning ? running : maxRunning;
await delay(100); await delay(100);
running = running - 1; running = running - 1;
}; };
const result = await Promise.all( const result = await Promise.all(
range(10).map(i => plimit.add(() => cbfn(i))) range(10).map((i) => plimit.add(() => cbfn(i)))
); );
expect(maxRunning).toEqual(2); expect(maxRunning).toEqual(2);
}); });
test("rejection", async () => { test("rejection", async () => {
const plimit = new PromiseLimit(2); const plimit = new PromiseLimit(2);
const result = await Promise.all([ const result = await Promise.all([
plimit.add(() => Promise.resolve("OK")), plimit.add(() => Promise.resolve("OK")),
plimit.add(() => Promise.reject("not OK")).catch(e => e), plimit.add(() => Promise.reject("not OK")).catch((e) => e),
plimit.add(() => Promise.resolve("OK")), plimit.add(() => Promise.resolve("OK")),
plimit plimit
.add(() => { .add(() => {
throw new Error("not OK"); throw new Error("not OK");
}) })
.catch(e => e.message) .catch((e) => e.message),
]); ]);
expect(result).toEqual(["OK", "not OK", "OK", "not OK"]); expect(result).toEqual(["OK", "not OK", "OK", "not OK"]);
}); });
}); });
+24 -24
View File
@@ -1,29 +1,29 @@
import quantile from "../../src/util/quantile"; import quantile from "../../src/util/quantile";
describe("quantile", () => { describe("quantile", () => {
test("single q", () => { test("single q", () => {
const arr = new Float32Array([9, 3, 5, 6, 0]); const arr = new Float32Array([9, 3, 5, 6, 0]);
expect(quantile([1.0], arr)).toMatchObject([9]); expect(quantile([1.0], arr)).toMatchObject([9]);
expect(quantile([0.9], arr)).toMatchObject([9]); expect(quantile([0.9], arr)).toMatchObject([9]);
expect(quantile([0.8], arr)).toMatchObject([9]); expect(quantile([0.8], arr)).toMatchObject([9]);
expect(quantile([0.7], arr)).toMatchObject([6]); expect(quantile([0.7], arr)).toMatchObject([6]);
expect(quantile([0.6], arr)).toMatchObject([6]); expect(quantile([0.6], arr)).toMatchObject([6]);
expect(quantile([0.5], arr)).toMatchObject([5]); expect(quantile([0.5], arr)).toMatchObject([5]);
expect(quantile([0.4], arr)).toMatchObject([5]); expect(quantile([0.4], arr)).toMatchObject([5]);
expect(quantile([0.3], arr)).toMatchObject([3]); expect(quantile([0.3], arr)).toMatchObject([3]);
expect(quantile([0.2], arr)).toMatchObject([3]); expect(quantile([0.2], arr)).toMatchObject([3]);
expect(quantile([0.1], arr)).toMatchObject([0]); expect(quantile([0.1], arr)).toMatchObject([0]);
expect(quantile([0], arr)).toMatchObject([0]); expect(quantile([0], arr)).toMatchObject([0]);
}); });
test("multi q", () => { test("multi q", () => {
const arr = new Float32Array([9, 3, 5, 6, 0]); const arr = new Float32Array([9, 3, 5, 6, 0]);
expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([ expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([
0, 0,
3, 3,
5, 5,
6, 6,
9 9,
]); ]);
}); });
}); });
+35 -37
View File
@@ -1,50 +1,48 @@
import { range, rangeFill, linspace } from "../../src/util/range"; import { range, rangeFill, linspace } from "../../src/util/range";
describe("range", () => { describe("range", () => {
test("no defaults", () => { test("no defaults", () => {
expect(range(0, 3, 1)).toMatchObject([0, 1, 2]); expect(range(0, 3, 1)).toMatchObject([0, 1, 2]);
}); });
test("range(stop)", () => { test("range(stop)", () => {
expect(range(3)).toMatchObject([0, 1, 2]); expect(range(3)).toMatchObject([0, 1, 2]);
expect(range(0)).toMatchObject([]); expect(range(0)).toMatchObject([]);
expect(range(1)).toMatchObject([0]); expect(range(1)).toMatchObject([0]);
}); });
test("range(start,stop)", () => { test("range(start,stop)", () => {
expect(range(0, 0)).toMatchObject([]); expect(range(0, 0)).toMatchObject([]);
expect(range(0, 2)).toMatchObject([0, 1]); expect(range(0, 2)).toMatchObject([0, 1]);
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]); expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
}); });
test("range(start, stop, step", () => { test("range(start, stop, step", () => {
expect(range(4, 0, -1)).toMatchObject([4, 3, 2, 1]); expect(range(4, 0, -1)).toMatchObject([4, 3, 2, 1]);
expect(range(0, 4, 2)).toMatchObject([0, 2]); expect(range(0, 4, 2)).toMatchObject([0, 2]);
}); });
}); });
describe("rangefill", () => { describe("rangefill", () => {
test("rangeFill(arr)", () => { test("rangeFill(arr)", () => {
expect(rangeFill(new Int32Array(3))).toMatchObject( expect(rangeFill(new Int32Array(3))).toMatchObject(
new Int32Array([0, 1, 2]) new Int32Array([0, 1, 2])
); );
}); });
test("rangeFill(arr, start)", () => { test("rangeFill(arr, start)", () => {
expect(rangeFill(new Int32Array(2), 1)).toMatchObject( expect(rangeFill(new Int32Array(2), 1)).toMatchObject(
new Int32Array([1, 2]) new Int32Array([1, 2])
); );
}); });
test("rangeFill(arr, start, step)", () => { test("rangeFill(arr, start, step)", () => {
expect(rangeFill(new Int32Array(3), 2, -1)).toMatchObject( expect(rangeFill(new Int32Array(3), 2, -1)).toMatchObject(
new Int32Array([2, 1, 0]) new Int32Array([2, 1, 0])
); );
}); });
}); });
describe("linspace", () => { describe("linspace", () => {
test("linspace(arr, start, step)", () => { test("linspace(arr, start, step)", () => {
expect(linspace(0.0, 2.0, 5)).toMatchObject( expect(linspace(0.0, 2.0, 5)).toMatchObject([0.0, 0.5, 1.0, 1.5, 2.0]);
[0.0, 0.5, 1.0, 1.5, 2.0] });
);
});
}); });
@@ -5,20 +5,23 @@ import { subsetAndResetGeneLists } from "../../../src/util/stateManager/controls
import * as globals from "../../../src/globals"; import * as globals from "../../../src/globals";
describe("controls helpers", () => { describe("controls helpers", () => {
test("subsetAndResetGeneLists", () => { test("subsetAndResetGeneLists", () => {
const geneList = [...Array(150).keys()].map(() => const geneList = [...Array(150).keys()].map(
Math.random().toString(36).substring(2, 6) // random string of 4 characters () => Math.random().toString(36).substring(2, 6) // random string of 4 characters
); );
const state = { const state = {
userDefinedGenes: geneList.slice(0, 20), userDefinedGenes: geneList.slice(0, 20),
diffexpGenes: geneList.slice(20), diffexpGenes: geneList.slice(20),
}; };
const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists(state); const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists(
state
);
expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes); expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes);
expect(geneList.length).toBeGreaterThan(globals.maxGenes); expect(geneList.length).toBeGreaterThan(globals.maxGenes);
expect(newUserDefinedGenes).toHaveLength(globals.maxGenes); expect(newUserDefinedGenes).toHaveLength(globals.maxGenes);
expect(newUserDefinedGenes).toStrictEqual(geneList.slice(0, globals.maxGenes)); expect(newUserDefinedGenes).toStrictEqual(
geneList.slice(0, globals.maxGenes)
);
expect(newDiffExpGenes).toStrictEqual([]); expect(newDiffExpGenes).toStrictEqual([]);
}); });
}); });
+23 -23
View File
@@ -3,32 +3,32 @@ test FBS encode/decode API
*/ */
import { Dataframe, KeyIndex } from "../../../src/util/dataframe"; import { Dataframe, KeyIndex } from "../../../src/util/dataframe";
import { import {
decodeMatrixFBS, decodeMatrixFBS,
encodeMatrixFBS encodeMatrixFBS,
} from "../../../src/util/stateManager/matrix"; } from "../../../src/util/stateManager/matrix";
describe("encode/decode", () => { describe("encode/decode", () => {
test("round trip", () => { test("round trip", () => {
const columns = [ const columns = [
["red", "green", "blue"], ["red", "green", "blue"],
new Int32Array(3).fill(0), new Int32Array(3).fill(0),
new Uint32Array(3).fill(1), new Uint32Array(3).fill(1),
new Float32Array(3).fill(2) new Float32Array(3).fill(2),
]; ];
const dfNoColIdx = new Dataframe([3, 4], columns); const dfNoColIdx = new Dataframe([3, 4], columns);
const dfA = decodeMatrixFBS(encodeMatrixFBS(dfNoColIdx)); const dfA = decodeMatrixFBS(encodeMatrixFBS(dfNoColIdx));
expect([dfA.nRows, dfA.nCols]).toEqual(dfNoColIdx.dims); expect([dfA.nRows, dfA.nCols]).toEqual(dfNoColIdx.dims);
expect(dfA.colIdx).toBeNull(); expect(dfA.colIdx).toBeNull();
expect(dfA.rowIdx).toBeNull(); expect(dfA.rowIdx).toBeNull();
expect(dfA.columns).toEqual(columns); expect(dfA.columns).toEqual(columns);
const colIndex = new KeyIndex(["a", "b", "c", "d"]); const colIndex = new KeyIndex(["a", "b", "c", "d"]);
const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex); const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex);
const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx)); const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims); expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);
expect(dfB.colIdx).toEqual(colIndex.keys()); expect(dfB.colIdx).toEqual(colIndex.keys());
expect(dfB.rowIdx).toBeNull(); expect(dfB.rowIdx).toBeNull();
expect(dfB.columns).toEqual(columns); expect(dfB.columns).toEqual(columns);
}); });
}); });
@@ -17,13 +17,13 @@ const aConfigResponse = {
{ method: "POST", path: "/cluster/", available: false }, { method: "POST", path: "/cluster/", available: false },
{ method: "POST", path: "/layout/", available: false }, { method: "POST", path: "/layout/", available: false },
{ method: "POST", path: "/diffexp/", available: false }, { method: "POST", path: "/diffexp/", available: false },
{ method: "POST", path: "/saveLocal/", available: false } { method: "POST", path: "/saveLocal/", available: false },
], ],
displayNames: { displayNames: {
engine: "the little engine that could", engine: "the little engine that could",
dataset: "all your zeros are mine" dataset: "all your zeros are mine",
} },
} },
}; };
const aSchemaResponse = { const aSchemaResponse = {
@@ -31,7 +31,7 @@ const aSchemaResponse = {
dataframe: { dataframe: {
nObs, nObs,
nVar, nVar,
type: "float32" type: "float32",
}, },
annotations: { annotations: {
obs: { obs: {
@@ -44,9 +44,9 @@ const aSchemaResponse = {
{ {
name: "field4", name: "field4",
type: "categorical", type: "categorical",
categories: field4Categories categories: field4Categories,
} },
] ],
}, },
var: { var: {
index: "name", index: "name",
@@ -58,46 +58,46 @@ const aSchemaResponse = {
{ {
name: "fieldD", name: "fieldD",
type: "categorical", type: "categorical",
categories: fieldDCategories categories: fieldDCategories,
} },
] ],
} },
}, },
layout: { layout: {
obs: [{ name: "umap", type: "float32", dims: ["umap_0", "umap_1"] }], obs: [{ name: "umap", type: "float32", dims: ["umap_0", "umap_1"] }],
var: [] var: [],
} },
} },
}; };
const anAnnotationsObsJSONResponse = { const anAnnotationsObsJSONResponse = {
names: ["name", "field1", "field2", "field3", "field4"], names: ["name", "field1", "field2", "field3", "field4"],
data: _() data: _()
.range(nObs) .range(nObs)
.map(idx => [ .map((idx) => [
idx, idx,
`obs${idx}`, `obs${idx}`,
2 * idx, 2 * idx,
idx + 0.0133, idx + 0.0133,
!!(idx & 1), !!(idx & 1),
field4Categories[idx % field4Categories.length] field4Categories[idx % field4Categories.length],
]) ])
.value() .value(),
}; };
const anAnnotationsVarJSONResponse = { const anAnnotationsVarJSONResponse = {
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"], names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
data: _() data: _()
.range(nVar) .range(nVar)
.map(idx => [ .map((idx) => [
idx, idx,
10 * idx, 10 * idx,
idx + 2.90143, idx + 2.90143,
!!(idx & 1), !!(idx & 1),
fieldDCategories[idx % fieldDCategories.length], fieldDCategories[idx % fieldDCategories.length],
`var${idx}` `var${idx}`,
]) ])
.value() .value(),
}; };
function encodeTypedArray(builder, uType, uData) { function encodeTypedArray(builder, uType, uData) {
@@ -120,7 +120,7 @@ function encodeMatrix(columns, colIndex = undefined) {
*/ */
const utf8Encoder = new TextEncoder("utf-8"); const utf8Encoder = new TextEncoder("utf-8");
const builder = new flatbuffers.Builder(1024); const builder = new flatbuffers.Builder(1024);
const cols = _.map(columns, carr => { const cols = _.map(columns, (carr) => {
let uType; let uType;
let tarr; let tarr;
if (_.every(carr, _.isNumber)) { if (_.every(carr, _.isNumber)) {
@@ -178,7 +178,7 @@ const anAnnotationsVarFBSResponse = (() => {
const aLayoutFBSResponse = (() => { const aLayoutFBSResponse = (() => {
const coords = [ const coords = [
new Float32Array(nObs).fill(Math.random()), new Float32Array(nObs).fill(Math.random()),
new Float32Array(nObs).fill(Math.random()) new Float32Array(nObs).fill(Math.random()),
]; ];
return encodeMatrix(coords, ["umap_0", "umap_1"]); return encodeMatrix(coords, ["umap_0", "umap_1"]);
})(); })();
@@ -187,8 +187,8 @@ const aDataObsResponse = {
var: [2, 4, 29], var: [2, 4, 29],
obs: _() obs: _()
.range(nObs) .range(nObs)
.map(idx => [idx, Math.random(), Math.random(), Math.random()]) .map((idx) => [idx, Math.random(), Math.random(), Math.random()])
.value() .value(),
}; };
export { export {
@@ -197,5 +197,5 @@ export {
anAnnotationsVarFBSResponse as annotationsVar, anAnnotationsVarFBSResponse as annotationsVar,
anAnnotationsObsFBSResponse as annotationsObs, anAnnotationsObsFBSResponse as annotationsObs,
aSchemaResponse as schema, aSchemaResponse as schema,
aConfigResponse as config aConfigResponse as config,
}; };
@@ -43,7 +43,7 @@ describe("createUniverseFromResponse", () => {
obsAnnotations: expect.any(Dataframe.Dataframe), obsAnnotations: expect.any(Dataframe.Dataframe),
varAnnotations: expect.any(Dataframe.Dataframe), varAnnotations: expect.any(Dataframe.Dataframe),
obsLayout: expect.any(Dataframe.Dataframe), obsLayout: expect.any(Dataframe.Dataframe),
varData: expect.any(Dataframe.Dataframe) varData: expect.any(Dataframe.Dataframe),
}) })
); );
@@ -60,7 +60,7 @@ describe("createUniverseFromResponse", () => {
...Universe.addObsLayout( ...Universe.addObsLayout(
universe, universe,
Universe.matrixFBSToDataframe(REST.layoutObs) Universe.matrixFBSToDataframe(REST.layoutObs)
) ),
}; };
expect(universe).toMatchObject( expect(universe).toMatchObject(
@@ -71,13 +71,13 @@ describe("createUniverseFromResponse", () => {
obsAnnotations: expect.any(Dataframe.Dataframe), obsAnnotations: expect.any(Dataframe.Dataframe),
varAnnotations: expect.any(Dataframe.Dataframe), varAnnotations: expect.any(Dataframe.Dataframe),
obsLayout: expect.any(Dataframe.Dataframe), obsLayout: expect.any(Dataframe.Dataframe),
varData: expect.any(Dataframe.Dataframe) varData: expect.any(Dataframe.Dataframe),
}) })
); );
expect(universe.obsAnnotations.dims).toEqual([ expect(universe.obsAnnotations.dims).toEqual([
nObs, nObs,
REST.schema.schema.annotations.obs.columns.length REST.schema.schema.annotations.obs.columns.length,
]); ]);
expect(universe.obsLayout.dims).toEqual([nObs, 2]); expect(universe.obsLayout.dims).toEqual([nObs, 2]);
expect(universe.obsLayout.colIndex.keys()).toEqual( expect(universe.obsLayout.colIndex.keys()).toEqual(
@@ -85,7 +85,7 @@ describe("createUniverseFromResponse", () => {
); );
expect(universe.varAnnotations.dims).toEqual([ expect(universe.varAnnotations.dims).toEqual([
nVar, nVar,
REST.schema.schema.annotations.var.columns.length REST.schema.schema.annotations.var.columns.length,
]); ]);
expect(universe.varData.isEmpty()).toBeTruthy(); expect(universe.varData.isEmpty()).toBeTruthy();
}); });
@@ -7,7 +7,7 @@ import { DimTypes } from "../../../src/util/typedCrossfilter/crossfilter";
import * as REST from "./sampleResponses"; import * as REST from "./sampleResponses";
import { import {
obsAnnoDimensionName, obsAnnoDimensionName,
layoutDimensionName layoutDimensionName,
} from "../../../src/util/nameCreators"; } from "../../../src/util/nameCreators";
/* /*
@@ -35,7 +35,7 @@ const defaultBigBang = () => {
...Universe.addObsLayout( ...Universe.addObsLayout(
universe, universe,
Universe.matrixFBSToDataframe(REST.layoutObs) Universe.matrixFBSToDataframe(REST.layoutObs)
) ),
}; };
/* create world */ /* create world */
@@ -50,7 +50,7 @@ const defaultBigBang = () => {
return { return {
universe, universe,
world, world,
crossfilter crossfilter,
}; };
}; };
@@ -80,8 +80,8 @@ describe("createWorldFromEntireUniverse", () => {
clipQuantiles: { min: 0, max: 1 }, clipQuantiles: { min: 0, max: 1 },
unclipped: { unclipped: {
obsAnnotations: expect.any(Dataframe.Dataframe), obsAnnotations: expect.any(Dataframe.Dataframe),
varData: expect.any(Dataframe.Dataframe) varData: expect.any(Dataframe.Dataframe),
} },
}) })
); );
}); });
@@ -92,7 +92,7 @@ describe("createWorldFromCurrentSelection", () => {
const { const {
universe, universe,
world: originalWorld, world: originalWorld,
crossfilter: originalCrossfilter crossfilter: originalCrossfilter,
} = defaultBigBang(); } = defaultBigBang();
/* mock a selection */ /* mock a selection */
@@ -100,7 +100,7 @@ describe("createWorldFromCurrentSelection", () => {
.select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 }) .select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 })
.select(obsAnnoDimensionName("field3"), { .select(obsAnnoDimensionName("field3"), {
mode: "exact", mode: "exact",
values: [false] values: [false],
}); });
/* create the world from the selection */ /* create the world from the selection */
@@ -124,7 +124,7 @@ describe("createWorldFromCurrentSelection", () => {
}; };
const matchingIndices = _() const matchingIndices = _()
.range(universe.nObs) .range(universe.nObs)
.filter(idx => matchFilter(universe.obsAnnotations, idx)) .filter((idx) => matchFilter(universe.obsAnnotations, idx))
.value(); .value();
expect(world).toMatchObject( expect(world).toMatchObject(
@@ -139,8 +139,8 @@ describe("createWorldFromCurrentSelection", () => {
varData: expect.any(Dataframe.Dataframe), varData: expect.any(Dataframe.Dataframe),
unclipped: { unclipped: {
obsAnnotations: expect.any(Dataframe.Dataframe), obsAnnotations: expect.any(Dataframe.Dataframe),
varData: expect.any(Dataframe.Dataframe) varData: expect.any(Dataframe.Dataframe),
} },
}) })
); );
@@ -170,7 +170,7 @@ describe("createObsDimensionMap", () => {
const { crossfilter } = defaultBigBang(); const { crossfilter } = defaultBigBang();
const annotationNames = _.map( const annotationNames = _.map(
REST.schema.schema.annotations.obs.columns, REST.schema.schema.annotations.obs.columns,
c => c.name (c) => c.name
); );
const obsIndexColName = REST.schema.schema.annotations.obs.index; const obsIndexColName = REST.schema.schema.annotations.obs.index;
const schemaByObsName = _.keyBy( const schemaByObsName = _.keyBy(
@@ -178,7 +178,7 @@ describe("createObsDimensionMap", () => {
"name" "name"
); );
expect(crossfilter).toBeDefined(); expect(crossfilter).toBeDefined();
annotationNames.forEach(name => { annotationNames.forEach((name) => {
const dim = crossfilter.dimensions[obsAnnoDimensionName(name)]; const dim = crossfilter.dimensions[obsAnnoDimensionName(name)];
if (name === obsIndexColName) { if (name === obsIndexColName) {
expect(dim).toBeUndefined(); expect(dim).toBeUndefined();
@@ -187,7 +187,7 @@ describe("fillBySelection", () => {
describe("wide bitarray", () => { describe("wide bitarray", () => {
test.each([9, 30, 31, 32, 33, 54, 63, 64, 65, 127, 128, 129])( test.each([9, 30, 31, 32, 33, 54, 63, 64, 65, 127, 128, 129])(
"more than %d dimensions", "more than %d dimensions",
ndim => { (ndim) => {
/* ensure we move across the uint boundary correctly */ /* ensure we move across the uint boundary correctly */
const ba = new BitArray(defaultTestLength); const ba = new BitArray(defaultTestLength);
@@ -11,7 +11,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["001"], productIDs: ["001"],
coords: [0, 0], coords: [0, 0],
nonFinite: 0.0 nonFinite: 0.0,
}, },
{ {
date: "2011-11-14T16:20:19Z", date: "2011-11-14T16:20:19Z",
@@ -21,7 +21,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["001", "005"], productIDs: ["001", "005"],
coords: [0.4, 0.4], coords: [0.4, 0.4],
nonFinite: Number.NaN nonFinite: Number.NaN,
}, },
{ {
date: "2011-11-14T16:28:54Z", date: "2011-11-14T16:28:54Z",
@@ -31,7 +31,7 @@ const someData = [
type: "visa", type: "visa",
productIDs: ["004", "005"], productIDs: ["004", "005"],
coords: [0.3, 0.1], coords: [0.3, 0.1],
nonFinite: Number.POSITIVE_INFINITY nonFinite: Number.POSITIVE_INFINITY,
}, },
{ {
date: "2011-11-14T16:30:43Z", date: "2011-11-14T16:30:43Z",
@@ -41,7 +41,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["001", "002"], productIDs: ["001", "002"],
coords: [0.392, 0.1], coords: [0.392, 0.1],
nonFinite: Number.NEGATIVE_INFINITY nonFinite: Number.NEGATIVE_INFINITY,
}, },
{ {
date: "2011-11-14T16:48:46Z", date: "2011-11-14T16:48:46Z",
@@ -51,7 +51,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["005"], productIDs: ["005"],
coords: [0.7, 0.0482], coords: [0.7, 0.0482],
nonFinite: 1.0 nonFinite: 1.0,
}, },
{ {
date: "2011-11-14T16:53:41Z", date: "2011-11-14T16:53:41Z",
@@ -61,7 +61,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["001", "004", "005"], productIDs: ["001", "004", "005"],
coords: [0.9999, 1.0], coords: [0.9999, 1.0],
nonFinite: Number.NaN nonFinite: Number.NaN,
}, },
{ {
date: "2011-11-14T16:54:06Z", date: "2011-11-14T16:54:06Z",
@@ -71,7 +71,7 @@ const someData = [
type: "cash", type: "cash",
productIDs: ["001", "002", "003", "004", "005"], productIDs: ["001", "002", "003", "004", "005"],
coords: [0.384, 0.6938], coords: [0.384, 0.6938],
nonFinite: 99.0 nonFinite: 99.0,
}, },
{ {
date: "2011-11-14T16:58:03Z", date: "2011-11-14T16:58:03Z",
@@ -81,7 +81,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["001"], productIDs: ["001"],
coords: [0.4822, 0.482], coords: [0.4822, 0.482],
nonFinite: Number.NaN nonFinite: Number.NaN,
}, },
{ {
date: "2011-11-14T17:07:21Z", date: "2011-11-14T17:07:21Z",
@@ -91,7 +91,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["004", "005"], productIDs: ["004", "005"],
coords: [0.2234, 0], coords: [0.2234, 0],
nonFinite: Number.NaN nonFinite: Number.NaN,
}, },
{ {
date: "2011-11-14T17:22:59Z", date: "2011-11-14T17:22:59Z",
@@ -101,7 +101,7 @@ const someData = [
type: "tab", type: "tab",
productIDs: ["001", "002", "004", "005"], productIDs: ["001", "002", "004", "005"],
coords: [0.382, 0.38485], coords: [0.382, 0.38485],
nonFinite: -1 nonFinite: -1,
}, },
{ {
date: "2011-11-14T17:25:45Z", date: "2011-11-14T17:25:45Z",
@@ -111,7 +111,7 @@ const someData = [
type: "cash", type: "cash",
productIDs: ["002"], productIDs: ["002"],
coords: [0.998, 0.8472], coords: [0.998, 0.8472],
nonFinite: 0.0 nonFinite: 0.0,
}, },
{ {
date: "2011-11-14T17:29:52Z", date: "2011-11-14T17:29:52Z",
@@ -121,8 +121,8 @@ const someData = [
type: "visa", type: "visa",
productIDs: ["004"], productIDs: ["004"],
coords: [0.8273, 0.3384], coords: [0.8273, 0.3384],
nonFinite: 0.0 nonFinite: 0.0,
} },
]; ];
let payments = null; let payments = null;
@@ -248,16 +248,21 @@ describe("ImmutableTypedCrossfilter", () => {
test("none", () => { test("none", () => {
expect(p.select("quantity", { mode: "none" }).countSelected()).toEqual(0); expect(p.select("quantity", { mode: "none" }).countSelected()).toEqual(0);
}); });
test.each([[[]], [[2]], [[2, 1]], [[9, 82]], [[0, 1]]])("exact: %p", v => test.each([[[]], [[2]], [[2, 1]], [[9, 82]], [[0, 1]]])("exact: %p", (v) =>
expect( expect(
p.select("quantity", { mode: "exact", values: v }).countSelected() p.select("quantity", { mode: "exact", values: v }).countSelected()
).toEqual(_.filter(someData, d => v.includes(d.quantity)).length) ).toEqual(_.filter(someData, (d) => v.includes(d.quantity)).length)
); );
test.each([[0, 1], [1, 2], [0, 99], [99, 100000]])("range %p", (lo, hi) => test.each([
[0, 1],
[1, 2],
[0, 99],
[99, 100000],
])("range %p", (lo, hi) =>
expect( expect(
p.select("quantity", { mode: "range", lo, hi }).countSelected() p.select("quantity", { mode: "range", lo, hi }).countSelected()
).toEqual( ).toEqual(
_.filter(someData, d => d.quantity >= lo && d.quantity < hi).length _.filter(someData, (d) => d.quantity >= lo && d.quantity < hi).length
) )
); );
test("bad mode", () => { test("bad mode", () => {
@@ -284,11 +289,11 @@ describe("ImmutableTypedCrossfilter", () => {
[["tab"]], [["tab"]],
[["visa"]], [["visa"]],
[["visa", "tab"]], [["visa", "tab"]],
[["cash", "tab", "visa"]] [["cash", "tab", "visa"]],
])("exact: %p", v => ])("exact: %p", (v) =>
expect( expect(
p.select("type", { mode: "exact", values: v }).countSelected() p.select("type", { mode: "exact", values: v }).countSelected()
).toEqual(_.filter(someData, d => v.includes(d.type)).length) ).toEqual(_.filter(someData, (d) => v.includes(d.type)).length)
); );
test("range", () => { test("range", () => {
expect(() => p.select("type", { mode: "range", lo: 0, hi: 9 })).toThrow( expect(() => p.select("type", { mode: "range", lo: 0, hi: 9 })).toThrow(
@@ -303,8 +308,8 @@ describe("ImmutableTypedCrossfilter", () => {
describe("spatial dimension", () => { describe("spatial dimension", () => {
let p; let p;
beforeEach(() => { beforeEach(() => {
const X = someData.map(r => r.coords[0]); const X = someData.map((r) => r.coords[0]);
const Y = someData.map(r => r.coords[1]); const Y = someData.map((r) => r.coords[1]);
p = payments.addDimension("coords", "spatial", X, Y); p = payments.addDimension("coords", "spatial", X, Y);
}); });
@@ -316,34 +321,76 @@ describe("ImmutableTypedCrossfilter", () => {
test("none", () => { test("none", () => {
expect(p.select("coords", { mode: "none" }).countSelected()).toEqual(0); expect(p.select("coords", { mode: "none" }).countSelected()).toEqual(0);
}); });
test.each([[0, 0, 1, 1], [0, 0, 0.5, 0.5], [0.5, 0.5, 1, 1]])( test.each([
"within-rect %d %d %d %d", [0, 0, 1, 1],
(minX, minY, maxX, maxY) => { [0, 0, 0.5, 0.5],
expect( [0.5, 0.5, 1, 1],
p ])("within-rect %d %d %d %d", (minX, minY, maxX, maxY) => {
.select("coords", { mode: "within-rect", minX, minY, maxX, maxY }) expect(
.allSelected() p
).toEqual( .select("coords", { mode: "within-rect", minX, minY, maxX, maxY })
_.filter(someData, d => { .allSelected()
const [x, y] = d.coords; ).toEqual(
return minX <= x && x < maxX && minY <= y && y < maxY; _.filter(someData, (d) => {
}) const [x, y] = d.coords;
); return minX <= x && x < maxX && minY <= y && y < maxY;
} })
); );
});
test.each([ test.each([
[ [
[[0, 0], [0, 1], [1, 1], [1, 0]], [
[true, true, true, true, true, false, true, true, true, true, true, true] [0, 0],
[0, 1],
[1, 1],
[1, 0],
],
[
true,
true,
true,
true,
true,
false,
true,
true,
true,
true,
true,
true,
],
], ],
[ [
[[0, 0], [0, 0.5], [0.5, 0.5], [0.5, 0]], [
[true, true, true, true, false, false, false, true, true, true, false, false] [0, 0],
] [0, 0.5],
[0.5, 0.5],
[0.5, 0],
],
[
true,
true,
true,
true,
false,
false,
false,
true,
true,
true,
false,
false,
],
],
])("within-polygon %p", (polygon, expected) => { ])("within-polygon %p", (polygon, expected) => {
expect(p.select("coords", { mode: "within-polygon", polygon }).allSelected()) expect(
.toEqual(_.zip(someData, expected).filter(x => x[1]).map(x => x[0])); p.select("coords", { mode: "within-polygon", polygon }).allSelected()
).toEqual(
_.zip(someData, expected)
.filter((x) => x[1])
.map((x) => x[0])
);
}); });
}); });
@@ -381,7 +428,7 @@ describe("ImmutableTypedCrossfilter", () => {
p p
.select("nonFinite", { .select("nonFinite", {
mode: "exact", mode: "exact",
values: [Number.POSITIVE_INFINITY] values: [Number.POSITIVE_INFINITY],
}) })
.countSelected() .countSelected()
).toEqual(1); ).toEqual(1);
@@ -389,7 +436,7 @@ describe("ImmutableTypedCrossfilter", () => {
p p
.select("nonFinite", { .select("nonFinite", {
mode: "exact", mode: "exact",
values: [Number.NEGATIVE_INFINITY] values: [Number.NEGATIVE_INFINITY],
}) })
.countSelected() .countSelected()
).toEqual(1); ).toEqual(1);
@@ -402,7 +449,7 @@ describe("ImmutableTypedCrossfilter", () => {
p p
.select("nonFinite", { .select("nonFinite", {
mode: "exact", mode: "exact",
values: [Number.POSITIVE_INFINITY, 0, 1, 99] values: [Number.POSITIVE_INFINITY, 0, 1, 99],
}) })
.countSelected() .countSelected()
).toEqual(6); ).toEqual(6);
@@ -414,7 +461,7 @@ describe("ImmutableTypedCrossfilter", () => {
.select("nonFinite", { .select("nonFinite", {
mode: "range", mode: "range",
lo: 0, lo: 0,
hi: Number.POSITIVE_INFINITY hi: Number.POSITIVE_INFINITY,
}) })
.countSelected() .countSelected()
).toEqual(5); ).toEqual(5);
@@ -423,7 +470,7 @@ describe("ImmutableTypedCrossfilter", () => {
.select("nonFinite", { .select("nonFinite", {
mode: "range", mode: "range",
lo: 0, lo: 0,
hi: Number.NaN hi: Number.NaN,
}) })
.countSelected() .countSelected()
).toEqual(6); ).toEqual(6);
@@ -432,7 +479,7 @@ describe("ImmutableTypedCrossfilter", () => {
.select("nonFinite", { .select("nonFinite", {
mode: "range", mode: "range",
lo: Number.NEGATIVE_INFINITY, lo: Number.NEGATIVE_INFINITY,
hi: Number.POSITIVE_INFINITY hi: Number.POSITIVE_INFINITY,
}) })
.countSelected() .countSelected()
).toEqual(7); ).toEqual(7);
@@ -10,15 +10,30 @@ describe("canonicalize", () => {
test("simple, already correct", () => { test("simple, already correct", () => {
expect(PositiveIntervals.canonicalize([[0, 1]])).toEqual([[0, 1]]); expect(PositiveIntervals.canonicalize([[0, 1]])).toEqual([[0, 1]]);
expect(PositiveIntervals.canonicalize([[0, 1], [2, 3]])).toEqual([ expect(
PositiveIntervals.canonicalize([
[0, 1],
[2, 3],
])
).toEqual([
[0, 1], [0, 1],
[2, 3] [2, 3],
]); ]);
}); });
test("non-canonical, need to be canonicalized", () => { test("non-canonical, need to be canonicalized", () => {
expect(PositiveIntervals.canonicalize([[0, 1], [1, 2]])).toEqual([[0, 2]]); expect(
expect(PositiveIntervals.canonicalize([[1, 2], [2, 3]])).toEqual([[1, 3]]); PositiveIntervals.canonicalize([
[0, 1],
[1, 2],
])
).toEqual([[0, 2]]);
expect(
PositiveIntervals.canonicalize([
[1, 2],
[2, 3],
])
).toEqual([[1, 3]]);
}); });
}); });
@@ -26,14 +41,30 @@ describe("union", () => {
test("empty range", () => { test("empty range", () => {
expect(PositiveIntervals.union([], [])).toEqual([]); expect(PositiveIntervals.union([], [])).toEqual([]);
expect(PositiveIntervals.union([], [[1, 2]])).toEqual([[1, 2]]); expect(PositiveIntervals.union([], [[1, 2]])).toEqual([[1, 2]]);
expect(PositiveIntervals.union([], [[1, 2], [3, 4]])).toEqual([ expect(
PositiveIntervals.union(
[],
[
[1, 2],
[3, 4],
]
)
).toEqual([
[1, 2], [1, 2],
[3, 4] [3, 4],
]); ]);
expect(PositiveIntervals.union([[3, 4]], [])).toEqual([[3, 4]]); expect(PositiveIntervals.union([[3, 4]], [])).toEqual([[3, 4]]);
expect(PositiveIntervals.union([[1, 2], [3, 4]], [])).toEqual([ expect(
PositiveIntervals.union(
[
[1, 2],
[3, 4],
],
[]
)
).toEqual([
[1, 2], [1, 2],
[3, 4] [3, 4],
]); ]);
expect(PositiveIntervals.union([[3, 3]], [])).toEqual([[3, 3]]); expect(PositiveIntervals.union([[3, 3]], [])).toEqual([[3, 3]]);
expect(PositiveIntervals.union([], [[3, 3]])).toEqual([[3, 3]]); expect(PositiveIntervals.union([], [[3, 3]])).toEqual([[3, 3]]);
@@ -44,17 +75,37 @@ describe("union", () => {
expect(PositiveIntervals.union([[2, 3]], [[1, 2]])).toEqual([[1, 3]]); expect(PositiveIntervals.union([[2, 3]], [[1, 2]])).toEqual([[1, 3]]);
expect(PositiveIntervals.union([[1, 2]], [[3, 4]])).toEqual([ expect(PositiveIntervals.union([[1, 2]], [[3, 4]])).toEqual([
[1, 2], [1, 2],
[3, 4] [3, 4],
]); ]);
expect( expect(
PositiveIntervals.union([[1, 2], [3, 4]], [[6, 7], [19, 40]]) PositiveIntervals.union(
).toEqual([[1, 2], [3, 4], [6, 7], [19, 40]]); [
expect(PositiveIntervals.union([[1, 4]], [[1, 1], [3, 4]])).toEqual([ [1, 2],
[1, 4] [3, 4],
],
[
[6, 7],
[19, 40],
]
)
).toEqual([
[1, 2],
[3, 4],
[6, 7],
[19, 40],
]); ]);
expect(
PositiveIntervals.union(
[[1, 4]],
[
[1, 1],
[3, 4],
]
)
).toEqual([[1, 4]]);
expect(PositiveIntervals.union([[3, 3]], [[4, 4]])).toEqual([ expect(PositiveIntervals.union([[3, 3]], [[4, 4]])).toEqual([
[3, 3], [3, 3],
[4, 4] [4, 4],
]); ]);
}); });
}); });
@@ -70,34 +121,43 @@ describe("intersection", () => {
expect(PositiveIntervals.intersection([[1, 2]], [[2, 3]])).toEqual([]); expect(PositiveIntervals.intersection([[1, 2]], [[2, 3]])).toEqual([]);
expect(PositiveIntervals.intersection([[2, 3]], [[1, 2]])).toEqual([]); expect(PositiveIntervals.intersection([[2, 3]], [[1, 2]])).toEqual([]);
expect(PositiveIntervals.intersection([[1, 10]], [[1, 10]])).toEqual([ expect(PositiveIntervals.intersection([[1, 10]], [[1, 10]])).toEqual([
[1, 10] [1, 10],
]); ]);
expect(PositiveIntervals.intersection([[1, 10]], [[2, 8]])).toEqual([ expect(PositiveIntervals.intersection([[1, 10]], [[2, 8]])).toEqual([
[2, 8] [2, 8],
]); ]);
expect(PositiveIntervals.intersection([[2, 8]], [[1, 10]])).toEqual([ expect(PositiveIntervals.intersection([[2, 8]], [[1, 10]])).toEqual([
[2, 8] [2, 8],
]); ]);
expect(PositiveIntervals.intersection([[1, 10]], [[2, 12]])).toEqual([ expect(PositiveIntervals.intersection([[1, 10]], [[2, 12]])).toEqual([
[2, 10] [2, 10],
]); ]);
expect(PositiveIntervals.intersection([[2, 12]], [[1, 10]])).toEqual([ expect(PositiveIntervals.intersection([[2, 12]], [[1, 10]])).toEqual([
[2, 10] [2, 10],
]); ]);
expect(PositiveIntervals.intersection([[1, 10]], [[1, 8]])).toEqual([ expect(PositiveIntervals.intersection([[1, 10]], [[1, 8]])).toEqual([
[1, 8] [1, 8],
]); ]);
expect(PositiveIntervals.intersection([[1, 8]], [[1, 10]])).toEqual([ expect(PositiveIntervals.intersection([[1, 8]], [[1, 10]])).toEqual([
[1, 8] [1, 8],
]); ]);
expect(PositiveIntervals.intersection([[1, 10]], [[1, 2], [6, 9]])).toEqual( expect(
[[1, 2], [6, 9]] PositiveIntervals.intersection(
); [[1, 10]],
expect(PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])).toEqual( [
[[1363, 2638]] [1, 2],
); [6, 9],
]
)
).toEqual([
[1, 2],
[6, 9],
]);
expect(
PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])
).toEqual([[1363, 2638]]);
expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([ expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([
[1, 2] [1, 2],
]); ]);
}); });
}); });
@@ -110,32 +170,66 @@ describe("difference", () => {
}); });
test("simple", () => { test("simple", () => {
expect(PositiveIntervals.difference([[1, 2], [3, 4]], [])).toEqual([ expect(
PositiveIntervals.difference(
[
[1, 2],
[3, 4],
],
[]
)
).toEqual([
[1, 2], [1, 2],
[3, 4] [3, 4],
]);
expect(PositiveIntervals.difference([[1, 2], [3, 10]], [[5, 10]])).toEqual([
[1, 2],
[3, 5]
]);
expect(PositiveIntervals.difference([[1, 2], [3, 10]], [[0, 5]])).toEqual([
[5, 10]
]); ]);
expect( expect(
PositiveIntervals.difference([[0, 2638]], [[0, 1363], [2055, 2638]]) PositiveIntervals.difference(
[
[1, 2],
[3, 10],
],
[[5, 10]]
)
).toEqual([
[1, 2],
[3, 5],
]);
expect(
PositiveIntervals.difference(
[
[1, 2],
[3, 10],
],
[[0, 5]]
)
).toEqual([[5, 10]]);
expect(
PositiveIntervals.difference(
[[0, 2638]],
[
[0, 1363],
[2055, 2638],
]
)
).toEqual([[1363, 2055]]); ).toEqual([[1363, 2055]]);
expect( expect(
PositiveIntervals.difference([[0, 1363], [2055, 2638]], [[0, 2638]]) PositiveIntervals.difference(
[
[0, 1363],
[2055, 2638],
],
[[0, 2638]]
)
).toEqual([]); ).toEqual([]);
expect(PositiveIntervals.difference([[0, 10]], [[0, 1]])).toEqual([ expect(PositiveIntervals.difference([[0, 10]], [[0, 1]])).toEqual([
[1, 10] [1, 10],
]); ]);
expect(PositiveIntervals.difference([[0, 10]], [[1, 2]])).toEqual([ expect(PositiveIntervals.difference([[0, 10]], [[1, 2]])).toEqual([
[0, 1], [0, 1],
[2, 10] [2, 10],
]); ]);
expect(PositiveIntervals.difference([[0, 10]], [[9, 10]])).toEqual([ expect(PositiveIntervals.difference([[0, 10]], [[9, 10]])).toEqual([
[0, 9] [0, 9],
]); ]);
}); });
}); });
@@ -4,7 +4,7 @@ import {
lowerBound, lowerBound,
upperBound, upperBound,
lowerBoundIndirect, lowerBoundIndirect,
upperBoundIndirect upperBoundIndirect,
} from "../../../src/util/typedCrossfilter/sort"; } from "../../../src/util/typedCrossfilter/sort";
/* /*
@@ -40,7 +40,7 @@ describe("sortArray", () => {
["a", "b", "0", "1"], ["a", "b", "0", "1"],
[0, "a", true, null, undefined, 3.1415], [0, "a", true, null, undefined, 3.1415],
fillRand(new Array(1000)), fillRand(new Array(1000)),
["a", NaN, null, pInf] ["a", NaN, null, pInf],
].map((val, idx) => ].map((val, idx) =>
test(`JS vals ${idx}`, () => { test(`JS vals ${idx}`, () => {
expect(sortArray(val)).toMatchObject(val.sort()); expect(sortArray(val)).toMatchObject(val.sort());
@@ -49,7 +49,7 @@ describe("sortArray", () => {
}); });
describe("finite numbers", () => { describe("finite numbers", () => {
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map(Type => [Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
test(Type.name, () => { test(Type.name, () => {
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject( expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject(
Type.from([0, 1, 2, 3, 4, 5, 6]) Type.from([0, 1, 2, 3, 4, 5, 6])
@@ -131,7 +131,7 @@ describe("sortArray", () => {
describe("sortIndex", () => { describe("sortIndex", () => {
describe("finite numbers", () => { describe("finite numbers", () => {
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map(Type => [Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
test(Type.name, () => { test(Type.name, () => {
const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]); const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]);
const index1 = fillRange(new Uint32Array(source1.length)); const index1 = fillRange(new Uint32Array(source1.length));
@@ -1,6 +1,6 @@
import { import {
sliceByIndex, sliceByIndex,
makeSortIndex makeSortIndex,
} from "../../../src/util/typedCrossfilter/util"; } from "../../../src/util/typedCrossfilter/util";
import { rangeFill as fillRange } from "../../../src/util/range"; import { rangeFill as fillRange } from "../../../src/util/range";
+3 -3
View File
@@ -3,7 +3,7 @@ module.exports = {
cacheDirectory: true, cacheDirectory: true,
presets: [ presets: [
["modern-browsers", { loose: true, modules: false }], ["modern-browsers", { loose: true, modules: false }],
"@babel/preset-react" "@babel/preset-react",
], ],
plugins: [ plugins: [
"@babel/plugin-proposal-function-bind", "@babel/plugin-proposal-function-bind",
@@ -11,6 +11,6 @@ module.exports = {
["@babel/plugin-proposal-class-properties", { loose: true }], ["@babel/plugin-proposal-class-properties", { loose: true }],
"@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator" "@babel/plugin-proposal-nullish-coalescing-operator",
] ],
}; };
+3 -3
View File
@@ -2,7 +2,7 @@ module.exports = {
babelrc: false, babelrc: false,
presets: [ presets: [
["modern-browsers", { loose: true, modules: false }], ["modern-browsers", { loose: true, modules: false }],
"@babel/preset-react" "@babel/preset-react",
], ],
plugins: [ plugins: [
"@babel/plugin-proposal-function-bind", "@babel/plugin-proposal-function-bind",
@@ -12,6 +12,6 @@ module.exports = {
"@babel/plugin-transform-react-constant-elements", "@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator" "@babel/plugin-proposal-nullish-coalescing-operator",
] ],
}; };
+9 -9
View File
@@ -9,8 +9,8 @@ module.exports = {
sourceType: "module", sourceType: "module",
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
generators: true generators: true,
} },
}, },
rules: { rules: {
"no-magic-numbers": "off", "no-magic-numbers": "off",
@@ -26,7 +26,7 @@ module.exports = {
"operator-linebreak": [ "operator-linebreak": [
"error", "error",
"after", "after",
{ overrides: { "?": "before", ":": "before" } } { overrides: { "?": "before", ":": "before" } },
], ],
"no-console": "off", "no-console": "off",
"spaced-comment": ["error", "always", { exceptions: ["*"] }], "spaced-comment": ["error", "always", { exceptions: ["*"] }],
@@ -35,13 +35,13 @@ module.exports = {
"react/prop-types": [0], "react/prop-types": [0],
"space-before-function-paren": "off", "space-before-function-paren": "off",
"function-paren-newline": "off", "function-paren-newline": "off",
"prefer-destructuring": ["error", { object: true, array: false }] "prefer-destructuring": ["error", { object: true, array: false }],
}, },
overrides: [ overrides: [
{ {
files: ["**/*.test.js"], files: ["**/*.test.js"],
env: { env: {
jest: true // now **/*.test.js files' env has both es6 *and* jest jest: true, // now **/*.test.js files' env has both es6 *and* jest
}, },
// Can't extend in overrides: https://github.com/eslint/eslint/issues/8813 // Can't extend in overrides: https://github.com/eslint/eslint/issues/8813
// "extends": ["plugin:jest/recommended"] // "extends": ["plugin:jest/recommended"]
@@ -51,8 +51,8 @@ module.exports = {
"jest/no-focused-tests": "error", "jest/no-focused-tests": "error",
"jest/no-identical-title": "error", "jest/no-identical-title": "error",
"jest/prefer-to-have-length": "warn", "jest/prefer-to-have-length": "warn",
"jest/valid-expect": "error" "jest/valid-expect": "error",
} },
} },
] ],
}; };
@@ -18,7 +18,7 @@ module.exports = {
path: path.resolve("build"), path: path.resolve("build"),
pathinfo: true, pathinfo: true,
filename: "static/js/bundle.js", filename: "static/js/bundle.js",
publicPath: "/" publicPath: "/",
}, },
module: { module: {
rules: [ rules: [
@@ -26,7 +26,7 @@ module.exports = {
test: /\.js$/, test: /\.js$/,
include: src, include: src,
loader: "babel-loader", loader: "babel-loader",
options: babelOptions options: babelOptions,
}, },
{ {
test: /\.css$/, test: /\.css$/,
@@ -34,29 +34,29 @@ module.exports = {
exclude: [path.resolve(src, "index.css")], exclude: [path.resolve(src, "index.css")],
loader: [ loader: [
{ {
loader: "style-loader" loader: "style-loader",
}, },
{ {
loader: "css-loader", loader: "css-loader",
options: { options: {
modules: { modules: {
localIdentName: "[name]__[local]___[hash:base64:5]" localIdentName: "[name]__[local]___[hash:base64:5]",
} },
} },
} },
] ],
}, },
{ {
test: /index\.css$/, test: /index\.css$/,
include: [path.resolve(src, "index.css")], include: [path.resolve(src, "index.css")],
loader: [ loader: [
{ {
loader: "style-loader" loader: "style-loader",
}, },
{ {
loader: "css-loader" loader: "css-loader",
} },
] ],
}, },
{ test: /\.json$/, include: [src, nodeModules], loader: "json-loader" }, { test: /\.json$/, include: [src, nodeModules], loader: "json-loader" },
@@ -64,14 +64,14 @@ module.exports = {
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i, test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
loader: "file-loader", loader: "file-loader",
include: [nodeModules, fonts], include: [nodeModules, fonts],
query: { name: "static/assets/[name].[ext]" } query: { name: "static/assets/[name].[ext]" },
} },
] ],
}, },
plugins: [ plugins: [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: true, inject: true,
template: path.resolve("index.html") template: path.resolve("index.html"),
}), }),
new FaviconsWebpackPlugin({ new FaviconsWebpackPlugin({
logo: "./favicon.png", logo: "./favicon.png",
@@ -84,16 +84,18 @@ module.exports = {
coast: false, coast: false,
firefox: false, firefox: false,
windows: false, windows: false,
yandex: false yandex: false,
} },
} },
}), }),
new webpack.NoEmitOnErrorsPlugin(), new webpack.NoEmitOnErrorsPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__REACT_DEVTOOLS_GLOBAL_HOOK__: "({ isDisabled: true })" __REACT_DEVTOOLS_GLOBAL_HOOK__: "({ isDisabled: true })",
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
"process.env.CXG_SERVER_PORT": JSON.stringify(process.env.CXG_SERVER_PORT) "process.env.CXG_SERVER_PORT": JSON.stringify(
}) process.env.CXG_SERVER_PORT
] ),
}),
],
}; };
+24 -6
View File
@@ -1,12 +1,27 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>cell&times;gene</title> <title>cell&times;gene</title>
<style> <style>
html, body, p, h1, h2, h3, h4, h5, h6, span, button, input, label, text, div { html,
font-family: 'Roboto Condensed','Helvetica Neue','Helvetica','Arial',sans-serif; body,
p,
h1,
h2,
h3,
h4,
h5,
h6,
span,
button,
input,
label,
text,
div {
font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", "Arial",
sans-serif;
font-size: 14px; font-size: 14px;
} }
body { body {
@@ -20,9 +35,12 @@
</style> </style>
</head> </head>
<body> <body>
<noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript> <noscript
>If you're seeing this message, that means
<strong>JavaScript has been disabled on your browser</strong>, please
<strong>enable JS</strong> to make this app work.</noscript
>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>
+55 -54
View File
@@ -1,60 +1,61 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>cell&times;gene</title> <title>cell&times;gene</title>
<style> <style>
html, html,
body, body,
p, p,
h1, h1,
h2, h2,
h3, h3,
h4, h4,
h5, h5,
h6, h6,
span, span,
button, button,
input, input,
label, label,
text, text,
div { div {
font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", "Arial",
"Arial", sans-serif; sans-serif;
font-size: 14px; font-size: 14px;
} }
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
</style> </style>
</head> </head>
<body> <body>
<script type="text/javascript"> <script type="text/javascript">
window.CELLXGENE = {}; window.CELLXGENE = {};
window.CELLXGENE.API = { window.CELLXGENE.API = {
prefix: window.location.href + "api/", prefix: window.location.href + "api/",
version: "v0.2/" version: "v0.2/",
}; };
</script> </script>
<noscript <noscript
>If you're seeing this message, that means >If you're seeing this message, that means
<strong>JavaScript has been disabled on your browser</strong>, <strong>JavaScript has been disabled on your browser</strong>, please
please <strong>enable JS</strong> to make this app work. <strong>enable JS</strong> to make this app work.
</noscript> </noscript>
<div id="root"></div> <div id="root"></div>
{% for script in SCRIPTS %} {% for script in SCRIPTS %}
<script type="text/javascript" src="{{script | safe}}"></script> <script type="text/javascript" src="{{script | safe}}"></script>
{% endfor %} {% endfor %} {% for ils in INLINE_SCRIPTS %}
{% for ils in INLINE_SCRIPTS %} <script type="text/javascript">
<script type="text/javascript">{% include ils %}</script> {% include ils %}
{% endfor %} </script>
</body> {% endfor %}
</body>
</html> </html>
+3 -3
View File
@@ -21,7 +21,7 @@ compiler.plugin("invalid", () => {
console.log("Compiling..."); console.log("Compiling...");
}); });
compiler.plugin("done", stats => { compiler.plugin("done", (stats) => {
utils.formatStats(stats, CLIENT_PORT); utils.formatStats(stats, CLIENT_PORT);
}); });
@@ -33,7 +33,7 @@ app.use(historyApiFallback({ verbose: false }));
app.use( app.use(
require("webpack-dev-middleware")(compiler, { require("webpack-dev-middleware")(compiler, {
logLevel: "warn", logLevel: "warn",
publicPath: config.output.publicPath publicPath: config.output.publicPath,
}) })
); );
@@ -43,7 +43,7 @@ app.get("*", (req, res) => {
res.sendFile(path.resolve("index.html")); res.sendFile(path.resolve("index.html"));
}); });
app.listen(CLIENT_PORT, err => { app.listen(CLIENT_PORT, (err) => {
if (err) { if (err) {
console.log(err); console.log(err);
return; return;
+4 -4
View File
@@ -36,10 +36,10 @@ var formatStats = (stats, port) => {
var json = stats.toJson(); var json = stats.toJson();
var formattedErrors = json.errors.map( var formattedErrors = json.errors.map(
message => "Error in " + formatMessage(message) (message) => "Error in " + formatMessage(message)
); );
var formattedWarnings = json.warnings.map( var formattedWarnings = json.warnings.map(
message => "Warning in " + formatMessage(message) (message) => "Warning in " + formatMessage(message)
); );
if (hasErrors) { if (hasErrors) {
@@ -48,7 +48,7 @@ var formatStats = (stats, port) => {
if (formattedErrors.some(isLikelyASyntaxError)) { if (formattedErrors.some(isLikelyASyntaxError)) {
formattedErrors = formattedErrors.filter(isLikelyASyntaxError); formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
} }
formattedErrors.forEach(message => { formattedErrors.forEach((message) => {
console.log(message); console.log(message);
console.log(); console.log();
}); });
@@ -58,7 +58,7 @@ var formatStats = (stats, port) => {
if (hasWarnings) { if (hasWarnings) {
console.log(chalk.yellow("Compiled with warnings.")); console.log(chalk.yellow("Compiled with warnings."));
console.log(); console.log();
formattedWarnings.forEach(message => { formattedWarnings.forEach((message) => {
console.log(message); console.log(message);
console.log(); console.log();
}); });
+37 -27
View File
@@ -24,15 +24,17 @@ async function obsAnnotationFetchAndLoad(dispatch, schema) {
const plimit = new PromiseLimit(5); const plimit = new PromiseLimit(5);
return Promise.all( return Promise.all(
columns.map(col => columns.map((col) =>
plimit.add(() => plimit.add(() =>
fetchBinary(`annotations/obs?annotation-name=${encodeURIComponent(col.name)}`) fetchBinary(
.then(buffer => Universe.matrixFBSToDataframe(buffer)) `annotations/obs?annotation-name=${encodeURIComponent(col.name)}`
.then(df => )
.then((buffer) => Universe.matrixFBSToDataframe(buffer))
.then((df) =>
dispatch({ dispatch({
type: "universe: column load success", type: "universe: column load success",
dim: "obsAnnotations", dim: "obsAnnotations",
dataframe: df dataframe: df,
}) })
) )
) )
@@ -48,14 +50,14 @@ async function varAnnotationFetchAndLoad(dispatch, schema) {
const index = varAnnotations.index ?? false; const index = varAnnotations.index ?? false;
const names = index ? [index] : []; const names = index ? [index] : [];
return Promise.all( return Promise.all(
names.map(name => names.map((name) =>
fetchBinary(`annotations/var?annotation-name=${encodeURIComponent(name)}`) fetchBinary(`annotations/var?annotation-name=${encodeURIComponent(name)}`)
.then(buffer => Universe.matrixFBSToDataframe(buffer)) .then((buffer) => Universe.matrixFBSToDataframe(buffer))
.then(df => .then((df) =>
dispatch({ dispatch({
type: "universe: column load success", type: "universe: column load success",
dim: "varAnnotations", dim: "varAnnotations",
dataframe: df dataframe: df,
}) })
) )
) )
@@ -71,17 +73,18 @@ function layoutFetchAndLoad(dispatch, schema) {
const plimit = new PromiseLimit(5); const plimit = new PromiseLimit(5);
return Promise.all( return Promise.all(
embNames.map(e => embNames.map((e) =>
plimit.add(() => plimit.add(() =>
fetchBinary(`layout/obs?layout-name=${encodeURIComponent(e)}`) fetchBinary(
.then(buffer => Universe.matrixFBSToDataframe(buffer)) `layout/obs?layout-name=${encodeURIComponent(e)}`
).then((buffer) => Universe.matrixFBSToDataframe(buffer))
) )
) )
).then(dfs => ).then((dfs) =>
dispatch({ dispatch({
type: "universe: column load success", type: "universe: column load success",
dim: "obsLayout", dim: "obsLayout",
dataframe: Dataframe.Dataframe.empty().withColsFromAll(dfs) dataframe: Dataframe.Dataframe.empty().withColsFromAll(dfs),
}) })
); );
} }
@@ -90,13 +93,12 @@ function layoutFetchAndLoad(dispatch, schema) {
return promise fetching user-configured colors return promise fetching user-configured colors
*/ */
async function userColorsFetchAndLoad(dispatch) { async function userColorsFetchAndLoad(dispatch) {
return fetchJson("colors") return fetchJson("colors").then((response) =>
.then(response => dispatch({
dispatch({ type: "universe: user color load success",
type: "universe: user color load success", userColors: loadUserColorConfig(response),
userColors: loadUserColorConfig(response) })
}) );
);
} }
/* /*
@@ -185,11 +187,15 @@ async function _doRequestExpressionData(dispatch, getState, genes) {
/* helper for this function only */ /* helper for this function only */
const fetchData = async (geneNames) => { const fetchData = async (geneNames) => {
const query = geneNames const query = geneNames
.map(g => `var:${dubEncURIComponent(varIndexName)}=${dubEncURIComponent(g)}`) .map(
(g) =>
`var:${dubEncURIComponent(varIndexName)}=${dubEncURIComponent(g)}`
)
.join("&"); .join("&");
// TODO: why convert to an Object and not a Dataframe? // TODO: why convert to an Object and not a Dataframe?
return fetchBinary(`data/var?${query}`) return fetchBinary(`data/var?${query}`).then((buffer) =>
.then(buffer => Universe.convertDataFBStoObject(universe, buffer)); Universe.convertDataFBStoObject(universe, buffer)
);
}; };
/* preload data already in cache */ /* preload data already in cache */
@@ -351,7 +357,7 @@ const requestDifferentialExpression = (set1, set2, num_genes = 10) => async (
*/ */
const plimit = new PromiseLimit(5); const plimit = new PromiseLimit(5);
await Promise.all( await Promise.all(
topNGenes.map(gene => topNGenes.map((gene) =>
plimit.add(() => _doRequestExpressionData(dispatch, getState, [gene])) plimit.add(() => _doRequestExpressionData(dispatch, getState, [gene]))
) )
); );
@@ -432,11 +438,15 @@ const saveObsAnnotations = () => async (dispatch, getState) => {
}; };
function fetchJson(pathAndQuery) { function fetchJson(pathAndQuery) {
return doJsonRequest(`${globals.API.prefix}${globals.API.version}${pathAndQuery}`); return doJsonRequest(
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
);
} }
function fetchBinary(pathAndQuery) { function fetchBinary(pathAndQuery) {
return doBinaryRequest(`${globals.API.prefix}${globals.API.version}${pathAndQuery}`); return doBinaryRequest(
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
);
} }
export default { export default {
+10 -10
View File
@@ -3,7 +3,7 @@ import { Universe } from "../util/stateManager";
import { import {
postNetworkErrorToast, postNetworkErrorToast,
postAsyncSuccessToast, postAsyncSuccessToast,
postAsyncFailureToast postAsyncFailureToast,
} from "../components/framework/toasters"; } from "../components/framework/toasters";
function abortableFetch(request, opts, timeout = 0) { function abortableFetch(request, opts, timeout = 0) {
@@ -18,7 +18,7 @@ function abortableFetch(request, opts, timeout = 0) {
setTimeout(() => controller.abort(), timeout); setTimeout(() => controller.abort(), timeout);
} }
return fetch(request, { ...opts, signal }); return fetch(request, { ...opts, signal });
} },
}; };
} }
@@ -38,19 +38,19 @@ async function doReembedFetch(dispatch, getState) {
method: "PUT", method: "PUT",
headers: new Headers({ headers: new Headers({
Accept: "application/octet-stream", Accept: "application/octet-stream",
"Content-Type": "application/json" "Content-Type": "application/json",
}), }),
body: JSON.stringify({ body: JSON.stringify({
method: "umap", method: "umap",
filter: { obs: { index: cells } } filter: { obs: { index: cells } },
}), }),
credentials: "include" credentials: "include",
}, },
60000 // 1 minute timeout 60000 // 1 minute timeout
); );
dispatch({ dispatch({
type: "reembed: request start", type: "reembed: request start",
abortableFetch: af abortableFetch: af,
}); });
const res = await af.ready(); const res = await af.ready();
@@ -82,17 +82,17 @@ export function requestReembed() {
const buffer = await res.arrayBuffer(); const buffer = await res.arrayBuffer();
const df = Universe.matrixFBSToDataframe(buffer); const df = Universe.matrixFBSToDataframe(buffer);
dispatch({ dispatch({
type: "reembed: request completed" type: "reembed: request completed",
}); });
dispatch({ dispatch({
type: "reembed: add reembedding", type: "reembed: add reembedding",
embedding: df, embedding: df,
schema schema,
}); });
postAsyncSuccessToast("Re-embedding has completed."); postAsyncSuccessToast("Re-embedding has completed.");
} catch (error) { } catch (error) {
dispatch({ dispatch({
type: "reembed: request aborted" type: "reembed: request aborted",
}); });
if (error.name === "AbortError") { if (error.name === "AbortError") {
postAsyncFailureToast("Re-embedding calculation was aborted."); postAsyncFailureToast("Re-embedding calculation was aborted.");
@@ -108,6 +108,6 @@ export function reembedResetWorldToUniverse(dispatch, getState) {
const { reembedController } = getState(); const { reembedController } = getState();
if (reembedController.pendingFetch) reembedController.pendingFetch.abort(); if (reembedController.pendingFetch) reembedController.pendingFetch.abort();
dispatch({ dispatch({
type: "reembed: clear all reembeddings" type: "reembed: clear all reembeddings",
}); });
} }
+8 -8
View File
@@ -14,10 +14,10 @@ import TermsOfServicePrompt from "./termsPrompt";
import actions from "../actions"; import actions from "../actions";
@connect(state => ({ @connect((state) => ({
loading: state.controls.loading, loading: state.controls.loading,
error: state.controls.error, error: state.controls.error,
graphRenderCounter: state.controls.graphRenderCounter graphRenderCounter: state.controls.graphRenderCounter,
})) }))
class App extends React.Component { class App extends React.Component {
constructor(props) { constructor(props) {
@@ -40,16 +40,16 @@ class App extends React.Component {
type: "window resize", type: "window resize",
data: { data: {
height: window.innerHeight, height: window.innerHeight,
width: window.innerWidth width: window.innerWidth,
} },
}); });
}); });
dispatch({ dispatch({
type: "window resize", type: "window resize",
data: { data: {
height: window.innerHeight, height: window.innerHeight,
width: window.innerWidth width: window.innerWidth,
} },
}); });
} }
@@ -70,7 +70,7 @@ class App extends React.Component {
position: "fixed", position: "fixed",
fontWeight: 500, fontWeight: 500,
top: window.innerHeight / 2, top: window.innerHeight / 2,
left: window.innerWidth / 2 - 50 left: window.innerWidth / 2 - 50,
}} }}
> >
loading cellxgene loading cellxgene
@@ -82,7 +82,7 @@ class App extends React.Component {
position: "fixed", position: "fixed",
fontWeight: 500, fontWeight: 500,
top: window.innerHeight / 2, top: window.innerHeight / 2,
left: window.innerWidth / 2 - 50 left: window.innerWidth / 2 - 50,
}} }}
> >
error loading error loading
@@ -7,10 +7,10 @@ import {
InputGroup, InputGroup,
Dialog, Dialog,
Classes, Classes,
Colors Colors,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
@connect(state => ({ @connect((state) => ({
universe: state.universe, universe: state.universe,
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null, idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
annotations: state.annotations, annotations: state.annotations,
@@ -18,13 +18,13 @@ import {
saveInProgress: state.autosave?.saveInProgress ?? false, saveInProgress: state.autosave?.saveInProgress ?? false,
lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations, lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations,
error: state.autosave?.error, error: state.autosave?.error,
writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false,
})) }))
class FilenameDialog extends React.Component { class FilenameDialog extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
filenameText: "" filenameText: "",
}; };
} }
@@ -36,7 +36,7 @@ class FilenameDialog extends React.Component {
dispatch({ dispatch({
type: "set annotations collection name", type: "set annotations collection name",
data: filenameText data: filenameText,
}); });
}; };
@@ -71,7 +71,7 @@ class FilenameDialog extends React.Component {
fontStyle: "italic", fontStyle: "italic",
fontSize: 12, fontSize: 12,
marginTop: 5, marginTop: 5,
color: Colors.ORANGE3 color: Colors.ORANGE3,
}} }}
> >
Name cannot be blank Name cannot be blank
@@ -84,7 +84,7 @@ class FilenameDialog extends React.Component {
fontStyle: "italic", fontStyle: "italic",
fontSize: 12, fontSize: 12,
marginTop: 5, marginTop: 5,
color: Colors.ORANGE3 color: Colors.ORANGE3,
}} }}
> >
Only alphanumeric and underscore allowed Only alphanumeric and underscore allowed
@@ -108,7 +108,7 @@ class FilenameDialog extends React.Component {
onClose={this.dismissFilenameDialog} onClose={this.dismissFilenameDialog}
> >
<form <form
onSubmit={e => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
this.handleCreateFilename(); this.handleCreateFilename();
}} }}
@@ -120,7 +120,9 @@ class FilenameDialog extends React.Component {
autoFocus autoFocus
value={filenameText} value={filenameText}
intent={this.filenameError(filenameText) ? "warning" : "none"} intent={this.filenameError(filenameText) ? "warning" : "none"}
onChange={e => this.setState({ filenameText: e.target.value })} onChange={(e) =>
this.setState({ filenameText: e.target.value })
}
leftIcon="tag" leftIcon="tag"
/> />
<p <p
@@ -129,7 +131,7 @@ class FilenameDialog extends React.Component {
visibility: this.filenameError(filenameText) visibility: this.filenameError(filenameText)
? "visible" ? "visible"
: "hidden", : "hidden",
color: Colors.ORANGE3 color: Colors.ORANGE3,
}} }}
> >
{this.filenameErrorMessage(filenameText)} {this.filenameErrorMessage(filenameText)}
+12 -8
View File
@@ -4,7 +4,7 @@ import * as globals from "../../globals";
import actions from "../../actions"; import actions from "../../actions";
import FilenameDialog from "./filenameDialog"; import FilenameDialog from "./filenameDialog";
@connect(state => ({ @connect((state) => ({
universe: state.universe, universe: state.universe,
annotations: state.annotations, annotations: state.annotations,
obsAnnotations: state.universe.obsAnnotations, obsAnnotations: state.universe.obsAnnotations,
@@ -12,13 +12,13 @@ import FilenameDialog from "./filenameDialog";
lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations, lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations,
error: state.autosave?.error, error: state.autosave?.error,
writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false, writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false,
initialDataLoadComplete: state.autosave?.initialDataLoadComplete initialDataLoadComplete: state.autosave?.initialDataLoadComplete,
})) }))
class Autosave extends React.Component { class Autosave extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
timer: null timer: null,
}; };
} }
@@ -64,22 +64,26 @@ class Autosave extends React.Component {
} }
render() { render() {
const { writableCategoriesEnabled, saveInProgress, initialDataLoadComplete } = this.props; const {
writableCategoriesEnabled,
saveInProgress,
initialDataLoadComplete,
} = this.props;
return writableCategoriesEnabled ? ( return writableCategoriesEnabled ? (
<div <div
id="autosave" id="autosave"
data-testclass={ data-testclass={
!initialDataLoadComplete !initialDataLoadComplete
? "autosave-init" ? "autosave-init"
: (this.needToSave() || saveInProgress) : this.needToSave() || saveInProgress
? "autosave-incomplete" ? "autosave-incomplete"
: "autosave-complete" : "autosave-complete"
} }
style={{ style={{
position: "fixed", position: "fixed",
display: "inherit", display: "inherit",
right: globals.leftSidebarWidth + 5, right: globals.leftSidebarWidth + 5,
bottom: 5 bottom: 5,
}} }}
> >
{this.statusMessage()} {this.statusMessage()}
@@ -2,11 +2,11 @@ import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core"; import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
@connect(state => ({ @connect((state) => ({
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
categoricalSelection: state.categoricalSelection, categoricalSelection: state.categoricalSelection,
annotations: state.annotations, annotations: state.annotations,
universe: state.universe universe: state.universe,
})) }))
class AnnoDialog extends React.PureComponent { class AnnoDialog extends React.PureComponent {
constructor(props) { constructor(props) {
@@ -30,13 +30,13 @@ class AnnoDialog extends React.PureComponent {
primaryButtonText, primaryButtonText,
secondaryButtonText, secondaryButtonText,
handleSecondaryButtonSubmit, handleSecondaryButtonSubmit,
primaryButtonProps primaryButtonProps,
} = this.props; } = this.props;
return ( return (
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}> <Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
<form <form
onSubmit={e => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
}} }}
> >
@@ -48,7 +48,7 @@ class AnnoDialog extends React.PureComponent {
style={{ style={{
marginTop: 7, marginTop: 7,
visibility: validationError ? "visible" : "hidden", visibility: validationError ? "visible" : "hidden",
color: Colors.ORANGE3 color: Colors.ORANGE3,
}} }}
> >
{errorMessage} {errorMessage}
@@ -3,11 +3,11 @@ import { connect } from "react-redux";
import { Button, MenuItem } from "@blueprintjs/core"; import { Button, MenuItem } from "@blueprintjs/core";
import { Select } from "@blueprintjs/select"; import { Select } from "@blueprintjs/select";
@connect(state => ({ @connect((state) => ({
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
categoricalSelection: state.categoricalSelection, categoricalSelection: state.categoricalSelection,
annotations: state.annotations, annotations: state.annotations,
universe: state.universe universe: state.universe,
})) }))
class DuplicateCategorySelect extends React.PureComponent { class DuplicateCategorySelect extends React.PureComponent {
constructor(props) { constructor(props) {
@@ -19,7 +19,7 @@ class DuplicateCategorySelect extends React.PureComponent {
const { const {
allCategoryNames, allCategoryNames,
categoryToDuplicate, categoryToDuplicate,
handleModalDuplicateCategorySelection handleModalDuplicateCategorySelection,
} = this.props; } = this.props;
return ( return (
<div> <div>
@@ -37,7 +37,7 @@ class DuplicateCategorySelect extends React.PureComponent {
return <MenuItem onClick={handleClick} key={d} text={d} />; return <MenuItem onClick={handleClick} key={d} text={d} />;
}} }}
noResults={<MenuItem disabled text="No results." />} noResults={<MenuItem disabled text="No results." />}
onItemSelect={d => { onItemSelect={(d) => {
handleModalDuplicateCategorySelection(d); handleModalDuplicateCategorySelection(d);
}} }}
> >
@@ -6,11 +6,11 @@ import {
MenuItem, MenuItem,
Popover, Popover,
Position, Position,
PopoverInteractionKind PopoverInteractionKind,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
@connect(state => ({ @connect((state) => ({
annotations: state.annotations annotations: state.annotations,
})) }))
class AnnoMenuCategory extends React.PureComponent { class AnnoMenuCategory extends React.PureComponent {
constructor(props) { constructor(props) {
@@ -22,7 +22,7 @@ class AnnoMenuCategory extends React.PureComponent {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "annotation: activate add new label mode", type: "annotation: activate add new label mode",
data: metadataField data: metadataField,
}); });
}; };
@@ -31,7 +31,7 @@ class AnnoMenuCategory extends React.PureComponent {
dispatch({ dispatch({
type: "annotation: activate category edit mode", type: "annotation: activate category edit mode",
data: metadataField data: metadataField,
}); });
}; };
@@ -39,7 +39,7 @@ class AnnoMenuCategory extends React.PureComponent {
const { dispatch, metadataField } = this.props; const { dispatch, metadataField } = this.props;
dispatch({ dispatch({
type: "annotation: delete category", type: "annotation: delete category",
metadataField metadataField,
}); });
}; };
@@ -50,7 +50,7 @@ class AnnoMenuCategory extends React.PureComponent {
isUserAnno, isUserAnno,
createText, createText,
editText, editText,
deleteText deleteText,
} = this.props; } = this.props;
return ( return (
+14 -12
View File
@@ -31,7 +31,7 @@ export default class LabelInput extends React.PureComponent {
const queryResults = this.filterLabels(query); const queryResults = this.filterLabels(query);
this.state = { this.state = {
query, query,
queryResults queryResults,
}; };
} }
@@ -42,7 +42,7 @@ export default class LabelInput extends React.PureComponent {
const queryResults = this.filterLabels(query); const queryResults = this.filterLabels(query);
this.setState({ this.setState({
query, query,
queryResults queryResults,
}); });
const { onChange } = this.props; const { onChange } = this.props;
@@ -57,7 +57,7 @@ export default class LabelInput extends React.PureComponent {
if (target !== query && onSelect) onSelect(target, event); if (target !== query && onSelect) onSelect(target, event);
}; };
handleKeyDown = e => { handleKeyDown = (e) => {
/* /*
prevent these events from propagating to containing form/dialog prevent these events from propagating to containing form/dialog
and causing further side effects (eg, closing dialog, submitting and causing further side effects (eg, closing dialog, submitting
@@ -72,7 +72,7 @@ export default class LabelInput extends React.PureComponent {
} }
}; };
handleChange = e => { handleChange = (e) => {
const { onChange } = this.props; const { onChange } = this.props;
if (onChange) onChange(e.target.value); if (onChange) onChange(e.target.value);
}; };
@@ -112,16 +112,18 @@ export default class LabelInput extends React.PureComponent {
/* empty query is wildcard */ /* empty query is wildcard */
if (query === "") { if (query === "") {
return labelSuggestions.slice(0, LabelInput.QueryResultLimit).map(l => ({ return labelSuggestions
target: l, .slice(0, LabelInput.QueryResultLimit)
score: -10000 .map((l) => ({
})); target: l,
score: -10000,
}));
} }
/* else, do a fuzzy query */ /* else, do a fuzzy query */
const options = { const options = {
limit: LabelInput.QueryResultLimit, limit: LabelInput.QueryResultLimit,
threshold: -10000 // don't return bad results threshold: -10000, // don't return bad results
}; };
let queryResults = fuzzysort.go(query, labelSuggestions, options); let queryResults = fuzzysort.go(query, labelSuggestions, options);
/* exact match will always be first in list */ /* exact match will always be first in list */
@@ -149,18 +151,18 @@ export default class LabelInput extends React.PureComponent {
const popoverProps = { const popoverProps = {
minimal: true, minimal: true,
...props.popoverProps ...props.popoverProps,
}; };
const inputProps = { const inputProps = {
...props.inputProps, ...props.inputProps,
autoFocus: false autoFocus: false,
}; };
const { queryResults } = this.state; const { queryResults } = this.state;
return ( return (
<> <>
<Suggest <Suggest
fill fill
inputValueRenderer={i => i.target} inputValueRenderer={(i) => i.target}
items={queryResults} items={queryResults}
itemRenderer={this.renderLabelSuggestion} itemRenderer={this.renderLabelSuggestion}
onItemSelect={this.handleItemSelect} onItemSelect={this.handleItemSelect}
+42 -42
View File
@@ -4,60 +4,60 @@ import { Colors } from "@blueprintjs/core";
import { AnnotationsHelpers } from "../../util/stateManager"; import { AnnotationsHelpers } from "../../util/stateManager";
export function isLabelErroneous(label, metadataField, ontology, schema) { export function isLabelErroneous(label, metadataField, ontology, schema) {
/* /*
return false if this is a LEGAL/acceptable category name or NULL/empty string, return false if this is a LEGAL/acceptable category name or NULL/empty string,
or return an error type. or return an error type.
*/ */
/* allow empty string */ /* allow empty string */
if (label === "") return false; if (label === "") return false;
/* check for label syntax errors, but allow terms in ontology */ /* check for label syntax errors, but allow terms in ontology */
const termInOntology = ontology?.termSet.has(label) ?? false; const termInOntology = ontology?.termSet.has(label) ?? false;
const error = AnnotationsHelpers.annotationNameIsErroneous(label); const error = AnnotationsHelpers.annotationNameIsErroneous(label);
if (error && !termInOntology) return error; if (error && !termInOntology) return error;
/* disallow duplicates */ /* disallow duplicates */
const { obsByName } = schema.annotations; const { obsByName } = schema.annotations;
if (obsByName[metadataField].categories.indexOf(label) !== -1) if (obsByName[metadataField].categories.indexOf(label) !== -1)
return "duplicate"; return "duplicate";
/* otherwise, no error */ /* otherwise, no error */
return false; return false;
} }
/* all other errors - map code to human error message */ /* all other errors - map code to human error message */
const errorMessageMap = { const errorMessageMap = {
"empty-string": "Blank names not allowed", "empty-string": "Blank names not allowed",
duplicate: "Name must be unique", duplicate: "Name must be unique",
"trim-spaces": "Leading and trailing spaces not allowed", "trim-spaces": "Leading and trailing spaces not allowed",
"illegal-characters": "illegal-characters":
"Only alphanumeric and special characters (-_.) allowed", "Only alphanumeric and special characters (-_.) allowed",
"multi-space-run": "Multiple consecutive spaces not allowed" "multi-space-run": "Multiple consecutive spaces not allowed",
}; };
export function labelPrompt(err, prolog, epilog) { export function labelPrompt(err, prolog, epilog) {
let errPrompt = null; let errPrompt = null;
if (err) { if (err) {
let errMsg = errorMessageMap[err] ?? "error"; let errMsg = errorMessageMap[err] ?? "error";
errMsg = errMsg[0].toLowerCase() + errMsg.slice(1); errMsg = errMsg[0].toLowerCase() + errMsg.slice(1);
errPrompt = ( errPrompt = (
<span <span
style={{ style={{
marginTop: 7, marginTop: 7,
color: Colors.ORANGE3 color: Colors.ORANGE3,
}} }}
> >
{errMsg} {errMsg}
</span> </span>
); );
} }
return ( return (
<span> <span>
{prolog} {prolog}
{err ? " - " : null} {err ? " - " : null}
{errPrompt} {errPrompt}
{epilog} {epilog}
</span> </span>
); );
} }
@@ -33,7 +33,7 @@ const setupParallelCoordinates = (width, height, margin) => {
return { return {
svg, svg,
ctx ctx,
}; };
}; };
+3 -3
View File
@@ -13,7 +13,7 @@ export const innerHeight = height - 2;
export const devicePixelRatio = window.devicePixelRatio || 1; export const devicePixelRatio = window.devicePixelRatio || 1;
export const createDimensions = data => { export const createDimensions = (data) => {
const newArr = []; const newArr = [];
_.each(data, (value, key) => { _.each(data, (value, key) => {
if (value.range) { if (value.range) {
@@ -21,12 +21,12 @@ export const createDimensions = data => {
key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */, key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */,
type: { type: {
within: (d, extent, dim) => within: (d, extent, dim) =>
extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1] extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1],
}, },
scale: d3 scale: d3
.scaleSqrt() .scaleSqrt()
.range([innerHeight, 0]) .range([innerHeight, 0])
.domain([0, value.range.max]) .domain([0, value.range.max]),
}); });
} }
}); });
@@ -38,12 +38,12 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
.range([1, legendheight - margin.top - margin.bottom]) .range([1, legendheight - margin.top - margin.bottom])
.domain([ .domain([
colorscale.domain()[1], colorscale.domain()[1],
colorscale.domain()[0] colorscale.domain()[0],
]); /* we flip this to make viridis colors dark if high in the color scale */ ]); /* we flip this to make viridis colors dark if high in the color scale */
// image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5 // image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5
const image = ctx.createImageData(1, legendheight); const image = ctx.createImageData(1, legendheight);
d3.range(legendheight).forEach(i => { d3.range(legendheight).forEach((i) => {
const c = d3.rgb(colorscale(legendscale.invert(i))); const c = d3.rgb(colorscale(legendscale.invert(i)));
image.data[4 * i] = c.r; image.data[4 * i] = c.r;
image.data[4 * i + 1] = c.g; image.data[4 * i + 1] = c.g;
@@ -62,11 +62,7 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
}); });
*/ */
const legendaxis = d3 const legendaxis = d3.axisRight().scale(legendscale).tickSize(6).ticks(8);
.axisRight()
.scale(legendscale)
.tickSize(6)
.ticks(8);
const svg = d3 const svg = d3
.select(selectorId) .select(selectorId)
@@ -98,10 +94,10 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
.text(colorAccessor); .text(colorAccessor);
}; };
@connect(state => ({ @connect((state) => ({
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
colorScale: state.colors.scale, colorScale: state.colors.scale,
responsive: state.responsive responsive: state.responsive,
})) }))
class ContinuousLegend extends React.Component { class ContinuousLegend extends React.Component {
constructor(props) { constructor(props) {
@@ -118,9 +114,7 @@ class ContinuousLegend extends React.Component {
prevProps.responsive.width !== responsive.width prevProps.responsive.width !== responsive.width
) { ) {
/* always remove it, if it's not continuous we don't put it back. */ /* always remove it, if it's not continuous we don't put it back. */
d3.select("#continuous_legend") d3.select("#continuous_legend").selectAll("*").remove();
.selectAll("*")
.remove();
} }
if (colorAccessor && colorScale && colorScale.range) { if (colorAccessor && colorScale && colorScale.range) {
@@ -144,7 +138,7 @@ class ContinuousLegend extends React.Component {
position: "fixed", position: "fixed",
display: colorAccessor ? "inherit" : "none", display: colorAccessor ? "inherit" : "none",
right: globals.leftSidebarWidth, right: globals.leftSidebarWidth,
top: responsive.height / 2 top: responsive.height / 2,
}} }}
/> />
); );
+1 -1
View File
@@ -3,7 +3,7 @@ import React from "react";
import styles from "./container.css"; import styles from "./container.css";
const Container = props => { const Container = (props) => {
const { children } = props; const { children } = props;
return <div className={styles.container}>{children}</div>; return <div className={styles.container}>{children}</div>;
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import * as globals from "../../globals"; import * as globals from "../../globals";
const Logo = props => { const Logo = (props) => {
const { size } = props; const { size } = props;
return ( return (
<svg width={size} height={size} viewBox="0 0 48 48" fill="none"> <svg width={size} height={size} viewBox="0 0 48 48" fill="none">
+19 -17
View File
@@ -11,7 +11,7 @@ import {
Button, Button,
FormGroup, FormGroup,
InputGroup, InputGroup,
ControlGroup ControlGroup,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import { Suggest } from "@blueprintjs/select"; import { Suggest } from "@blueprintjs/select";
import HistogramBrush from "../brushableHistogram"; import HistogramBrush from "../brushableHistogram";
@@ -19,7 +19,7 @@ import * as globals from "../../globals";
import actions from "../../actions"; import actions from "../../actions";
import { import {
postUserErrorToast, postUserErrorToast,
keepAroundErrorToast keepAroundErrorToast,
} from "../framework/toasters"; } from "../framework/toasters";
import { memoize } from "../../util/dataframe/util"; import { memoize } from "../../util/dataframe/util";
@@ -40,7 +40,7 @@ const renderGene = (fuzzySortResult, { handleClick, modifiers }) => {
// See https://github.com/chanzuckerberg/cellxgene/issues/483 // See https://github.com/chanzuckerberg/cellxgene/issues/483
// label={gene.n_counts} // label={gene.n_counts}
key={geneName} key={geneName}
onClick={g => onClick={(g) =>
/* this fires when user clicks a menu item */ /* this fires when user clicks a menu item */
handleClick(g) handleClick(g)
} }
@@ -53,17 +53,17 @@ const filterGenes = (query, genes) =>
/* fires on load, once, and then for each character typed into the input */ /* fires on load, once, and then for each character typed into the input */
fuzzysort.go(query, genes, { fuzzysort.go(query, genes, {
limit: 5, limit: 5,
threshold: -10000 // don't return bad results threshold: -10000, // don't return bad results
}); });
@connect(state => { @connect((state) => {
return { return {
obsAnnotations: state.world?.obsAnnotations, obsAnnotations: state.world?.obsAnnotations,
userDefinedGenes: state.controls.userDefinedGenes, userDefinedGenes: state.controls.userDefinedGenes,
userDefinedGenesLoading: state.controls.userDefinedGenesLoading, userDefinedGenesLoading: state.controls.userDefinedGenesLoading,
world: state.world, world: state.world,
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
differential: state.differential differential: state.differential,
}; };
}) })
class GeneExpression extends React.Component { class GeneExpression extends React.Component {
@@ -72,11 +72,11 @@ class GeneExpression extends React.Component {
this.state = { this.state = {
bulkAdd: "", bulkAdd: "",
tab: "autosuggest", tab: "autosuggest",
activeItem: null activeItem: null,
}; };
} }
_genesToUpper = listGenes => { _genesToUpper = (listGenes) => {
// Has to be a Map to preserve index // Has to be a Map to preserve index
const upperGenes = new Map(); const upperGenes = new Map();
for (let i = 0, { length } = listGenes; i < length; i += 1) { for (let i = 0, { length } = listGenes; i < length; i += 1) {
@@ -87,7 +87,7 @@ class GeneExpression extends React.Component {
}; };
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
_memoGenesToUpper = memoize(this._genesToUpper, arr => arr); _memoGenesToUpper = memoize(this._genesToUpper, (arr) => arr);
handleBulkAddClick = () => { handleBulkAddClick = () => {
const { world, dispatch, userDefinedGenes } = this.props; const { world, dispatch, userDefinedGenes } = this.props;
@@ -115,7 +115,7 @@ class GeneExpression extends React.Component {
dispatch({ type: "bulk user defined gene start" }); dispatch({ type: "bulk user defined gene start" });
Promise.all( Promise.all(
[...upperGenes.keys()].map(upperGene => { [...upperGenes.keys()].map((upperGene) => {
if (upperUserDefinedGenes.get(upperGene) !== undefined) { if (upperUserDefinedGenes.get(upperGene) !== undefined) {
return keepAroundErrorToast("That gene already exists"); return keepAroundErrorToast("That gene already exists");
} }
@@ -204,7 +204,7 @@ class GeneExpression extends React.Component {
world, world,
userDefinedGenes, userDefinedGenes,
userDefinedGenesLoading, userDefinedGenesLoading,
differential differential,
} = this.props; } = this.props;
const varIndexName = world?.schema?.annotations?.var?.index; const varIndexName = world?.schema?.annotations?.var?.index;
const varIndex = world?.varAnnotations?.col(varIndexName)?.asArray(); const varIndex = world?.varAnnotations?.col(varIndexName)?.asArray();
@@ -218,7 +218,7 @@ class GeneExpression extends React.Component {
<div> <div>
<div <div
style={{ style={{
padding: globals.leftSidebarSectionPadding padding: globals.leftSidebarSectionPadding,
}} }}
> >
<Button <Button
@@ -250,7 +250,7 @@ class GeneExpression extends React.Component {
<ControlGroup <ControlGroup
style={{ style={{
paddingLeft: globals.leftSidebarSectionPadding, paddingLeft: globals.leftSidebarSectionPadding,
paddingBottom: globals.leftSidebarSectionPadding paddingBottom: globals.leftSidebarSectionPadding,
}} }}
> >
<Suggest <Suggest
@@ -261,7 +261,7 @@ class GeneExpression extends React.Component {
userDefinedGenesLoading ? () => true : () => false userDefinedGenesLoading ? () => true : () => false
} }
noResults={<MenuItem disabled text="No matching genes." />} noResults={<MenuItem disabled text="No matching genes." />}
onItemSelect={g => { onItemSelect={(g) => {
/* this happens on 'enter' */ /* this happens on 'enter' */
this.handleClick(g); this.handleClick(g);
}} }}
@@ -271,7 +271,9 @@ class GeneExpression extends React.Component {
return ""; return "";
}} }}
itemListPredicate={filterGenes} itemListPredicate={filterGenes}
onActiveItemChange={item => this.setState({ activeItem: item })} onActiveItemChange={(item) =>
this.setState({ activeItem: item })
}
itemRenderer={renderGene.bind(this)} itemRenderer={renderGene.bind(this)}
items={varIndex || ["No genes"]} items={varIndex || ["No genes"]}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
@@ -289,7 +291,7 @@ class GeneExpression extends React.Component {
{tab === "bulkadd" ? ( {tab === "bulkadd" ? (
<div style={{ paddingLeft: globals.leftSidebarSectionPadding }}> <div style={{ paddingLeft: globals.leftSidebarSectionPadding }}>
<form <form
onSubmit={e => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
this.handleBulkAddClick(); this.handleBulkAddClick();
}} }}
@@ -300,7 +302,7 @@ class GeneExpression extends React.Component {
> >
<ControlGroup> <ControlGroup>
<InputGroup <InputGroup
onChange={e => { onChange={(e) => {
this.setState({ bulkAdd: e.target.value }); this.setState({ bulkAdd: e.target.value });
}} }}
id="text-input-bulk-add" id="text-input-bulk-add"
@@ -1,6 +1,6 @@
import { glPointFlags, glPointSize } from "../../util/glHelpers"; import { glPointFlags, glPointSize } from "../../util/glHelpers";
export default function(regl) { export default function (regl) {
return regl({ return regl({
vert: ` vert: `
precision mediump float; precision mediump float;
@@ -54,18 +54,18 @@ export default function(regl) {
attributes: { attributes: {
position: regl.prop("position"), position: regl.prop("position"),
color: regl.prop("color"), color: regl.prop("color"),
flag: regl.prop("flag") flag: regl.prop("flag"),
}, },
uniforms: { uniforms: {
distance: regl.prop("distance"), distance: regl.prop("distance"),
projView: regl.prop("projView"), projView: regl.prop("projView"),
nPoints: regl.prop("nPoints"), nPoints: regl.prop("nPoints"),
minViewportDimension: regl.prop("minViewportDimension") minViewportDimension: regl.prop("minViewportDimension"),
}, },
count: regl.prop("count"), count: regl.prop("count"),
primitive: "points" primitive: "points",
}); });
} }
+32 -34
View File
@@ -34,12 +34,12 @@ function createProjectionTF(viewportWidth, viewportHeight) {
const minDim = Math.min(viewportWidth, heightMinusGutter); const minDim = Math.min(viewportWidth, heightMinusGutter);
const aspectScale = [ const aspectScale = [
(fractionToUse * minDim) / viewportWidth, (fractionToUse * minDim) / viewportWidth,
(fractionToUse * minDim) / viewportHeight (fractionToUse * minDim) / viewportHeight,
]; ];
const m = mat3.create(); const m = mat3.create();
mat3.fromTranslation(m, [ mat3.fromTranslation(m, [
0, 0,
-topGutterSizePx / viewportHeight / aspectScale[1] -topGutterSizePx / viewportHeight / aspectScale[1],
]); ]);
mat3.scale(m, m, aspectScale); mat3.scale(m, m, aspectScale);
return m; return m;
@@ -73,7 +73,7 @@ function renderThrottle(callback) {
}; };
} }
@connect(state => ({ @connect((state) => ({
universe: state.universe, universe: state.universe,
world: state.world, world: state.world,
crossfilter: state.crossfilter, crossfilter: state.crossfilter,
@@ -85,7 +85,7 @@ function renderThrottle(callback) {
centroidLabels: state.centroidLabels, centroidLabels: state.centroidLabels,
graphInteractionMode: state.controls.graphInteractionMode, graphInteractionMode: state.controls.graphInteractionMode,
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
pointDilation: state.pointDilation pointDilation: state.pointDilation,
})) }))
class Graph extends React.Component { class Graph extends React.Component {
computePointPositions = memoize((X, Y, modelTF) => { computePointPositions = memoize((X, Y, modelTF) => {
@@ -102,7 +102,7 @@ class Graph extends React.Component {
return positions; return positions;
}); });
computePointColors = memoize(rgb => { computePointColors = memoize((rgb) => {
/* /*
compute webgl colors for each point compute webgl colors for each point
*/ */
@@ -186,13 +186,13 @@ class Graph extends React.Component {
positions: null, positions: null,
colors: null, colors: null,
sizes: null, sizes: null,
flags: null flags: null,
}; };
this.state = { this.state = {
toolSVG: null, toolSVG: null,
tool: null, tool: null,
container: null, container: null,
cameraRender: 0 cameraRender: 0,
}; };
} }
@@ -234,7 +234,7 @@ class Graph extends React.Component {
camera, camera,
modelTF, modelTF,
modelInvTF: mat3.invert([], modelTF), modelInvTF: mat3.invert([], modelTF),
projectionTF projectionTF,
}); });
} }
@@ -250,7 +250,7 @@ class Graph extends React.Component {
layoutChoice, layoutChoice,
graphInteractionMode, graphInteractionMode,
pointDilation, pointDilation,
colorAccessor colorAccessor,
} = this.props; } = this.props;
const { regl, toolSVG, camera, modelTF } = this.state; const { regl, toolSVG, camera, modelTF } = this.state;
let stateChanges = {}; let stateChanges = {};
@@ -274,7 +274,7 @@ class Graph extends React.Component {
needsRepaint = true; needsRepaint = true;
stateChanges = { stateChanges = {
...stateChanges, ...stateChanges,
projectionTF projectionTF,
}; };
} }
@@ -333,7 +333,7 @@ class Graph extends React.Component {
// If the window size has changed we want to recreate all SVGs // If the window size has changed we want to recreate all SVGs
stateChanges = { stateChanges = {
...stateChanges, ...stateChanges,
...this.createToolSVG() ...this.createToolSVG(),
}; };
} else if ( } else if (
(responsive.height && responsive.width && !toolSVG) || (responsive.height && responsive.width && !toolSVG) ||
@@ -345,7 +345,7 @@ class Graph extends React.Component {
// If lasso/zoom is switched // If lasso/zoom is switched
stateChanges = { stateChanges = {
...stateChanges, ...stateChanges,
...this.createToolSVG() ...this.createToolSVG(),
}; };
} }
@@ -369,12 +369,12 @@ class Graph extends React.Component {
} }
} }
handleCanvasEvent = e => { handleCanvasEvent = (e) => {
const { camera, projectionTF } = this.state; const { camera, projectionTF } = this.state;
if (e.type !== "wheel") e.preventDefault(); if (e.type !== "wheel") e.preventDefault();
if (camera.handleEvent(e, projectionTF)) { if (camera.handleEvent(e, projectionTF)) {
this.renderCanvas(); this.renderCanvas();
this.setState(state => { this.setState((state) => {
return { ...state, updateOverlay: !state.updateOverlay }; return { ...state, updateOverlay: !state.updateOverlay };
}); });
} }
@@ -389,9 +389,7 @@ class Graph extends React.Component {
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */ /* clear out whatever was on the div, even if nothing, but usually the brushes etc */
d3.select("#lasso-layer") d3.select("#lasso-layer").selectAll(".lasso-group").remove();
.selectAll(".lasso-group")
.remove();
// Don't render or recreate toolSVG if currently in zoom mode // Don't render or recreate toolSVG if currently in zoom mode
if (graphInteractionMode !== "select") { if (graphInteractionMode !== "select") {
@@ -443,7 +441,7 @@ class Graph extends React.Component {
*/ */
const screenCoords = [ const screenCoords = [
this.mapPointToScreen(currentSelection.brushCoords.northwest), this.mapPointToScreen(currentSelection.brushCoords.northwest),
this.mapPointToScreen(currentSelection.brushCoords.southeast) this.mapPointToScreen(currentSelection.brushCoords.southeast),
]; ];
if (!toolCurrentSelection) { if (!toolCurrentSelection) {
/* tool is not selected, so just move the brush */ /* tool is not selected, so just move the brush */
@@ -480,7 +478,7 @@ class Graph extends React.Component {
/* /*
if there is a current selection, make sure the lasso tool matches if there is a current selection, make sure the lasso tool matches
*/ */
const polygon = currentSelection.polygon.map(p => const polygon = currentSelection.polygon.map((p) =>
this.mapPointToScreen(p) this.mapPointToScreen(p)
); );
tool.move(polygon); tool.move(polygon);
@@ -551,7 +549,7 @@ class Graph extends React.Component {
), ),
Math.round( Math.round(
-((xy[1] + 1) / 2 - 1) * (responsive.height - this.graphPaddingTop) -((xy[1] + 1) / 2 - 1) * (responsive.height - this.graphPaddingTop)
) ),
]; ];
return pin; return pin;
} }
@@ -571,12 +569,12 @@ class Graph extends React.Component {
const s = d3.event.selection; const s = d3.event.selection;
const brushCoords = { const brushCoords = {
northwest: this.mapScreenToPoint([s[0][0], s[0][1]]), northwest: this.mapScreenToPoint([s[0][0], s[0][1]]),
southeast: this.mapScreenToPoint([s[1][0], s[1][1]]) southeast: this.mapScreenToPoint([s[1][0], s[1][1]]),
}; };
dispatch({ dispatch({
type: "graph brush change", type: "graph brush change",
brushCoords brushCoords,
}); });
} }
@@ -601,15 +599,15 @@ class Graph extends React.Component {
if (s) { if (s) {
const brushCoords = { const brushCoords = {
northwest: this.mapScreenToPoint(s[0]), northwest: this.mapScreenToPoint(s[0]),
southeast: this.mapScreenToPoint(s[1]) southeast: this.mapScreenToPoint(s[1]),
}; };
dispatch({ dispatch({
type: "graph brush end", type: "graph brush end",
brushCoords brushCoords,
}); });
} else { } else {
dispatch({ dispatch({
type: "graph brush deselect" type: "graph brush deselect",
}); });
} }
} }
@@ -617,14 +615,14 @@ class Graph extends React.Component {
handleBrushDeselectAction() { handleBrushDeselectAction() {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: "graph brush deselect" type: "graph brush deselect",
}); });
} }
handleLassoStart() { handleLassoStart() {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: "graph lasso start" type: "graph lasso start",
}); });
} }
@@ -642,7 +640,7 @@ class Graph extends React.Component {
} else { } else {
dispatch({ dispatch({
type: "graph lasso end", type: "graph lasso end",
polygon: polygon.map(xy => this.mapScreenToPoint(xy)) // transform the polygon polygon: polygon.map((xy) => this.mapScreenToPoint(xy)), // transform the polygon
}); });
} }
} }
@@ -667,7 +665,7 @@ class Graph extends React.Component {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: "change opacity deselected cells in 2d graph background", type: "change opacity deselected cells in 2d graph background",
data: e.target.value data: e.target.value,
}); });
} }
@@ -688,7 +686,7 @@ class Graph extends React.Component {
regl.poll(); regl.poll();
regl.clear({ regl.clear({
depth: 1, depth: 1,
color: [1, 1, 1, 1] color: [1, 1, 1, 1],
}); });
drawPoints({ drawPoints({
distance: camera.distance(), distance: camera.distance(),
@@ -698,7 +696,7 @@ class Graph extends React.Component {
count: this.count, count: this.count,
projView, projView,
nPoints: universe.nObs, nPoints: universe.nObs,
minViewportDimension: Math.min(width || 800, height || 600) minViewportDimension: Math.min(width || 800, height || 600),
}); });
regl._gl.flush(); regl._gl.flush();
} }
@@ -711,7 +709,7 @@ class Graph extends React.Component {
pointBuffer, pointBuffer,
flagBuffer, flagBuffer,
camera, camera,
projectionTF projectionTF,
} = this.state; } = this.state;
this.renderPoints( this.renderPoints(
regl, regl,
@@ -737,7 +735,7 @@ class Graph extends React.Component {
zIndex: -9999, zIndex: -9999,
position: "fixed", position: "fixed",
top: this.graphPaddingTop, top: this.graphPaddingTop,
right: globals.leftSidebarWidth right: globals.leftSidebarWidth,
}} }}
> >
<div id="graphAttachPoint"> <div id="graphAttachPoint">
@@ -774,7 +772,7 @@ class Graph extends React.Component {
width={responsive.width - this.graphPaddingRightLeft} width={responsive.width - this.graphPaddingRightLeft}
height={responsive.height - this.graphPaddingTop} height={responsive.height - this.graphPaddingTop}
data-testid="layout-graph" data-testid="layout-graph"
ref={canvas => { ref={(canvas) => {
this.reglCanvas = canvas; this.reglCanvas = canvas;
}} }}
onMouseDown={this.handleCanvasEvent} onMouseDown={this.handleCanvasEvent}
@@ -6,14 +6,14 @@ import { connect } from "react-redux";
import { categoryLabelDisplayStringLongLength } from "../../../globals"; import { categoryLabelDisplayStringLongLength } from "../../../globals";
export default export default
@connect(state => ({ @connect((state) => ({
colorAccessor: state.colors.colorAccessor, colorAccessor: state.colors.colorAccessor,
dilatedValue: state.pointDilation.categoryField, dilatedValue: state.pointDilation.categoryField,
labels: state.centroidLabels.labels labels: state.centroidLabels.labels,
})) }))
class CentroidLabels extends PureComponent { class CentroidLabels extends PureComponent {
// Check to see if centroids have either just been displayed or removed from the overlay // Check to see if centroids have either just been displayed or removed from the overlay
componentDidUpdate = prevProps => { componentDidUpdate = (prevProps) => {
const { labels, overlayToggled } = this.props; const { labels, overlayToggled } = this.props;
const prevSize = prevProps.labels.size; const prevSize = prevProps.labels.size;
const { size } = labels; const { size } = labels;
@@ -33,7 +33,7 @@ class CentroidLabels extends PureComponent {
inverseTransform, inverseTransform,
dilatedValue, dilatedValue,
dispatch, dispatch,
colorAccessor colorAccessor,
} = this.props; } = this.props;
const labelSVGS = []; const labelSVGS = [];
@@ -74,20 +74,20 @@ class CentroidLabels extends PureComponent {
fontSize, fontSize,
fontWeight, fontWeight,
fill: "black", fill: "black",
userSelect: "none" userSelect: "none",
}} }}
onMouseEnter={e => onMouseEnter={(e) =>
dispatch({ dispatch({
type: "category value mouse hover start", type: "category value mouse hover start",
metadataField: colorAccessor, metadataField: colorAccessor,
categoryField: e.target.getAttribute("data-label") categoryField: e.target.getAttribute("data-label"),
}) })
} }
onMouseOut={e => onMouseOut={(e) =>
dispatch({ dispatch({
type: "category value mouse hover end", type: "category value mouse hover end",
metadataField: colorAccessor, metadataField: colorAccessor,
categoryField: e.target.getAttribute("data-label") categoryField: e.target.getAttribute("data-label"),
}) })
} }
pointerEvents="visiblePainted" pointerEvents="visiblePainted"
@@ -4,8 +4,8 @@ import { connect } from "react-redux";
import styles from "../graph.css"; import styles from "../graph.css";
export default export default
@connect(state => ({ @connect((state) => ({
responsive: state.responsive responsive: state.responsive,
})) }))
class GraphOverlayLayer extends PureComponent { class GraphOverlayLayer extends PureComponent {
/* /*
@@ -18,11 +18,11 @@ class GraphOverlayLayer extends PureComponent {
super(props); super(props);
this.state = { this.state = {
display: {} display: {},
}; };
} }
matrixToTransformString = m => { matrixToTransformString = (m) => {
/* /*
Translates the gl-matrix mat3 to SVG matrix transform style Translates the gl-matrix mat3 to SVG matrix transform style
@@ -34,13 +34,13 @@ class GraphOverlayLayer extends PureComponent {
return `matrix(${m[0]} ${m[1]} ${m[3]} ${m[4]} ${m[6]} ${m[7]})`; return `matrix(${m[0]} ${m[1]} ${m[3]} ${m[4]} ${m[6]} ${m[7]})`;
}; };
reverseMatrixScaleTransformString = m => { reverseMatrixScaleTransformString = (m) => {
return `matrix(${1 / m[0]} 0 0 ${1 / m[4]} 0 0)`; return `matrix(${1 / m[0]} 0 0 ${1 / m[4]} 0 0)`;
}; };
// This is passed to all children, should be called when an overlay's display state is toggled along with the overlay name and its new display state in boolean form // This is passed to all children, should be called when an overlay's display state is toggled along with the overlay name and its new display state in boolean form
overlayToggled = (overlay, displaying) => { overlayToggled = (overlay, displaying) => {
this.setState(state => { this.setState((state) => {
return { ...state, display: { ...state.display, [overlay]: displaying } }; return { ...state, display: { ...state.display, [overlay]: displaying } };
}); });
}; };
@@ -54,13 +54,13 @@ class GraphOverlayLayer extends PureComponent {
graphPaddingRightLeft, graphPaddingRightLeft,
graphPaddingTop, graphPaddingTop,
children, children,
handleCanvasEvent handleCanvasEvent,
} = this.props; } = this.props;
if (!cameraTF) return null; if (!cameraTF) return null;
const { display } = this.state; const { display } = this.state;
const displaying = Object.values(display).some(value => value); // check to see if at least one overlay is currently displayed const displaying = Object.values(display).some((value) => value); // check to see if at least one overlay is currently displayed
const inverseTransform = `${this.reverseMatrixScaleTransformString( const inverseTransform = `${this.reverseMatrixScaleTransformString(
modelTF modelTF
@@ -68,15 +68,15 @@ class GraphOverlayLayer extends PureComponent {
cameraTF cameraTF
)} ${this.reverseMatrixScaleTransformString( )} ${this.reverseMatrixScaleTransformString(
projectionTF projectionTF
)} scale(1 2) scale(1 ${1 / )} scale(1 2) scale(1 ${
-(responsive.height - graphPaddingTop)}) scale(2 1) scale(${1 / 1 / -(responsive.height - graphPaddingTop)
(responsive.width - graphPaddingRightLeft)} 1)`; }) scale(2 1) scale(${1 / (responsive.width - graphPaddingRightLeft)} 1)`;
// Copy the children passed with the overlay and add the inverse transform and onDisplayChange props // Copy the children passed with the overlay and add the inverse transform and onDisplayChange props
const newChildren = React.Children.map(children, child => const newChildren = React.Children.map(children, (child) =>
cloneElement(child, { cloneElement(child, {
inverseTransform, inverseTransform,
overlayToggled: this.overlayToggled overlayToggled: this.overlayToggled,
}) })
); );
@@ -88,15 +88,16 @@ class GraphOverlayLayer extends PureComponent {
pointerEvents="none" pointerEvents="none"
style={{ style={{
zIndex: 99, zIndex: 99,
backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : "" backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : "",
}} }}
onMouseMove={handleCanvasEvent} onMouseMove={handleCanvasEvent}
onWheel={handleCanvasEvent} onWheel={handleCanvasEvent}
> >
<g <g
id="canvas-transformation-group-x" id="canvas-transformation-group-x"
transform={`scale(${responsive.width - transform={`scale(${
graphPaddingRightLeft} 1) scale(.5 1) translate(1 0)`} responsive.width - graphPaddingRightLeft
} 1) scale(.5 1) translate(1 0)`}
> >
<g <g
id="canvas-transformation-group-y" id="canvas-transformation-group-y"
+5 -8
View File
@@ -5,8 +5,8 @@ import * as d3 from "d3";
const Lasso = () => { const Lasso = () => {
const dispatch = d3.dispatch("start", "end", "cancel"); const dispatch = d3.dispatch("start", "end", "cancel");
const polygonToPath = polygon => const polygonToPath = (polygon) =>
`M${polygon.map(d => d.join(",")).join("L")}`; `M${polygon.map((d) => d.join(",")).join("L")}`;
const distance = (pt1, pt2) => const distance = (pt1, pt2) =>
Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2); Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2);
@@ -14,7 +14,7 @@ const Lasso = () => {
// distance last point has to be to first point before it auto closes when mouse is released // distance last point has to be to first point before it auto closes when mouse is released
const closeDistance = 75; const closeDistance = 75;
const lasso = svg => { const lasso = (svg) => {
let lassoPolygon; let lassoPolygon;
let lassoPath; let lassoPath;
let closePath; let closePath;
@@ -55,10 +55,7 @@ const Lasso = () => {
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) < distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
closeDistance closeDistance
) { ) {
closePath closePath.attr("x1", point[0]).attr("y1", point[1]).attr("opacity", 1);
.attr("x1", point[0])
.attr("y1", point[1])
.attr("opacity", 1);
} else { } else {
closePath.attr("opacity", 0); closePath.attr("opacity", 0);
} }
@@ -116,7 +113,7 @@ const Lasso = () => {
} }
}; };
lasso.move = polygon => { lasso.move = (polygon) => {
if (polygon !== lassoPolygon || polygon.length !== lassoPolygon.length) { if (polygon !== lassoPolygon || polygon.length !== lassoPolygon.length) {
lasso.reset(); lasso.reset();
@@ -24,7 +24,7 @@ export default (
.brush() .brush()
.extent([ .extent([
[0, 0], [0, 0],
[responsive.width - graphPaddingRight, responsive.height] [responsive.width - graphPaddingRight, responsive.height],
]) ])
.on("start", handleStartAction) .on("start", handleStartAction)
.on("brush", handleDragAction) .on("brush", handleDragAction)
@@ -4,12 +4,12 @@ import { connect } from "react-redux";
import * as globals from "../../globals"; import * as globals from "../../globals";
import Logo from "../framework/logo"; import Logo from "../framework/logo";
@connect(state => ({ @connect((state) => ({
responsive: state.responsive, responsive: state.responsive,
datasetTitle: state.config?.displayNames?.dataset ?? "", datasetTitle: state.config?.displayNames?.dataset ?? "",
aboutURL: state.config?.links?.["about-dataset"], aboutURL: state.config?.links?.["about-dataset"],
scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
scatterplotYYaccessor: state.controls.scatterplotYYaccessor scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
})) }))
class LeftSideBar extends React.Component { class LeftSideBar extends React.Component {
render() { render() {
@@ -35,7 +35,7 @@ class LeftSideBar extends React.Component {
width: globals.leftSidebarWidth - paddingToAvoidScrollBar, width: globals.leftSidebarWidth - paddingToAvoidScrollBar,
position: "absolute", position: "absolute",
backgroundColor: "white", backgroundColor: "white",
zIndex: 8888 zIndex: 8888,
/* x y blur spread color */ /* x y blur spread color */
// boxShadow: "-5px -1px 4px 2px rgba(225,225,225,0.4)" // boxShadow: "-5px -1px 4px 2px rgba(225,225,225,0.4)"
}} }}
@@ -49,7 +49,7 @@ class LeftSideBar extends React.Component {
fontWeight: "bold", fontWeight: "bold",
marginLeft: 5, marginLeft: 5,
color: globals.logoColor, color: globals.logoColor,
userSelect: "none" userSelect: "none",
}} }}
> >
cell cell
@@ -58,7 +58,7 @@ class LeftSideBar extends React.Component {
position: "relative", position: "relative",
top: 1, top: 1,
fontWeight: 300, fontWeight: 300,
fontSize: 24 fontSize: 24,
}} }}
> >
× ×
@@ -76,11 +76,17 @@ class LeftSideBar extends React.Component {
marginLeft: "7px", marginLeft: "7px",
height: "1.2em", height: "1.2em",
overflow: "hidden", overflow: "hidden",
wordBreak: "break-all" wordBreak: "break-all",
}} }}
title={datasetTitle} title={datasetTitle}
> >
{aboutURL ? <a href={aboutURL} target="_blank">{displayTitle}</a> : displayTitle} {aboutURL ? (
<a href={aboutURL} target="_blank">
{displayTitle}
</a>
) : (
displayTitle
)}
</div> </div>
</div> </div>
); );
@@ -12,7 +12,7 @@ class CellSetButton extends React.PureComponent {
differential, differential,
crossfilter, crossfilter,
dispatch, dispatch,
eitherCellSetOneOrTwo eitherCellSetOneOrTwo,
} = this.props; } = this.props;
// Reducer and components assume that value will be null if // Reducer and components assume that value will be null if
@@ -25,7 +25,7 @@ class CellSetButton extends React.PureComponent {
/* diffexp needs to be cleared before we store a new set */ /* diffexp needs to be cleared before we store a new set */
dispatch({ dispatch({
type: `store current cell selection as differential set ${eitherCellSetOneOrTwo}`, type: `store current cell selection as differential set ${eitherCellSetOneOrTwo}`,
data: set data: set,
}); });
} }
} }
+7 -7
View File
@@ -6,7 +6,7 @@ import {
Popover, Popover,
NumericInput, NumericInput,
Icon, Icon,
Tooltip Tooltip,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import { tooltipHoverOpenDelay } from "../../globals"; import { tooltipHoverOpenDelay } from "../../globals";
@@ -21,7 +21,7 @@ function Clip(props) {
isClipDisabled, isClipDisabled,
handleClipOnKeyPress, handleClipOnKeyPress,
handleClipPercentileMaxValueChange, handleClipPercentileMaxValueChange,
handleClipPercentileMinValueChange handleClipPercentileMinValueChange,
} = props; } = props;
const clipMin = const clipMin =
@@ -37,7 +37,7 @@ function Clip(props) {
<div <div
className="bp3-button-group" className="bp3-button-group"
style={{ style={{
marginRight: 10 marginRight: 10,
}} }}
> >
<Popover <Popover
@@ -52,7 +52,7 @@ function Clip(props) {
data-testid="visualization-settings" data-testid="visualization-settings"
className={`bp3-button bp3-icon-timeline-bar-chart ${activeClipClass}`} className={`bp3-button bp3-icon-timeline-bar-chart ${activeClipClass}`}
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
/> />
</Tooltip> </Tooltip>
@@ -67,7 +67,7 @@ function Clip(props) {
justifyContent: "flex-start", justifyContent: "flex-start",
alignItems: "flex-start", alignItems: "flex-start",
flexDirection: "column", flexDirection: "column",
padding: 10 padding: 10,
}} }}
> >
<div>Clip all continuous values to percentile range</div> <div>Clip all continuous values to percentile range</div>
@@ -77,7 +77,7 @@ function Clip(props) {
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
paddingTop: 5, paddingTop: 5,
paddingBottom: 5 paddingBottom: 5,
}} }}
> >
<NumericInput <NumericInput
@@ -121,7 +121,7 @@ function Clip(props) {
style={{ style={{
cursor: "pointer", cursor: "pointer",
marginRight: 5, marginRight: 5,
marginLeft: 5 marginLeft: 5,
}} }}
onClick={handleClipCommit} onClick={handleClipCommit}
> >
@@ -7,26 +7,26 @@ import {
ButtonGroup, ButtonGroup,
AnchorButton, AnchorButton,
Tooltip, Tooltip,
Position Position,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import * as globals from "../../globals"; import * as globals from "../../globals";
import actions from "../../actions"; import actions from "../../actions";
import CellSetButton from "./cellSetButtons"; import CellSetButton from "./cellSetButtons";
@connect(state => ({ @connect((state) => ({
config: state.config, config: state.config,
crossfilter: state.crossfilter, crossfilter: state.crossfilter,
differential: state.differential, differential: state.differential,
celllist1: state.differential?.celllist1, celllist1: state.differential?.celllist1,
celllist2: state.differential?.celllist2, celllist2: state.differential?.celllist2,
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false, diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
diffexpCellcountMax: state.config?.limits?.diffexp_cellcount_max diffexpCellcountMax: state.config?.limits?.diffexp_cellcount_max,
})) }))
class DiffexpButtons extends React.Component { class DiffexpButtons extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
userDismissedPopover: false userDismissedPopover: false,
}; };
} }
@@ -46,16 +46,16 @@ class DiffexpButtons extends React.Component {
const { dispatch, differential } = this.props; const { dispatch, differential } = this.props;
dispatch({ dispatch({
type: "clear differential expression", type: "clear differential expression",
diffExp: differential.diffExp diffExp: differential.diffExp,
}); });
dispatch({ dispatch({
type: "clear scatterplot" type: "clear scatterplot",
}); });
}; };
handlePopoverDismiss = () => { handlePopoverDismiss = () => {
this.setState({ this.setState({
userDismissedPopover: true userDismissedPopover: true,
}); });
}; };
@@ -124,7 +124,7 @@ class DiffexpButtons extends React.Component {
alignItems: "flex-end", alignItems: "flex-end",
flexDirection: "column", flexDirection: "column",
padding: 10, padding: 10,
maxWidth: 310 maxWidth: 310,
}} }}
> >
<p> <p>
+10 -10
View File
@@ -7,26 +7,26 @@ import {
Radio, Radio,
RadioGroup, RadioGroup,
Tooltip, Tooltip,
Position Position,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import { connect } from "react-redux"; import { connect } from "react-redux";
import * as globals from "../../globals"; import * as globals from "../../globals";
import { World } from "../../util/stateManager"; import { World } from "../../util/stateManager";
import actions from "../../actions"; import actions from "../../actions";
@connect(state => ({ @connect((state) => ({
universe: state.universe, universe: state.universe,
world: state.world, world: state.world,
layoutChoice: state.layoutChoice, layoutChoice: state.layoutChoice,
reembedController: state.reembedController, reembedController: state.reembedController,
enableReembedding: state.config?.parameters?.["enable-reembedding"] ?? false enableReembedding: state.config?.parameters?.["enable-reembedding"] ?? false,
})) }))
class Embedding extends React.PureComponent { class Embedding extends React.PureComponent {
handleLayoutChoiceChange = e => { handleLayoutChoiceChange = (e) => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ dispatch({
type: "set layout choice", type: "set layout choice",
layoutChoice: e.currentTarget.value layoutChoice: e.currentTarget.value,
}); });
}; };
@@ -36,7 +36,7 @@ class Embedding extends React.PureComponent {
world, world,
universe, universe,
dispatch, dispatch,
reembedController reembedController,
} = this.props; } = this.props;
if (!enableReembedding) return null; if (!enableReembedding) return null;
@@ -70,7 +70,7 @@ class Embedding extends React.PureComponent {
return ( return (
<ButtonGroup <ButtonGroup
style={{ style={{
marginRight: 10 marginRight: 10,
}} }}
> >
<Popover <Popover
@@ -85,7 +85,7 @@ class Embedding extends React.PureComponent {
data-testid="layout-choice" data-testid="layout-choice"
icon="heatmap" icon="heatmap"
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
/> />
</Tooltip> </Tooltip>
@@ -98,7 +98,7 @@ class Embedding extends React.PureComponent {
justifyContent: "flex-start", justifyContent: "flex-start",
alignItems: "flex-start", alignItems: "flex-start",
flexDirection: "column", flexDirection: "column",
padding: 10 padding: 10,
}} }}
> >
<RadioGroup <RadioGroup
@@ -106,7 +106,7 @@ class Embedding extends React.PureComponent {
onChange={this.handleLayoutChoiceChange} onChange={this.handleLayoutChoiceChange}
selectedValue={layoutChoice.current} selectedValue={layoutChoice.current}
> >
{layoutChoice.available.map(name => ( {layoutChoice.available.map((name) => (
<Radio label={name} value={name} key={name} /> <Radio label={name} value={name} key={name} />
))} ))}
</RadioGroup> </RadioGroup>
+19 -19
View File
@@ -11,7 +11,7 @@ import Subset from "./subset";
import UndoRedoReset from "./undoRedo"; import UndoRedoReset from "./undoRedo";
import DiffexpButtons from "./diffexpButtons"; import DiffexpButtons from "./diffexpButtons";
@connect(state => ({ @connect((state) => ({
universe: state.universe, universe: state.universe,
world: state.world, world: state.world,
crossfilter: state.crossfilter, crossfilter: state.crossfilter,
@@ -34,7 +34,7 @@ import DiffexpButtons from "./diffexpButtons";
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false, diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
showCentroidLabels: state.centroidLabels.showLabels, showCentroidLabels: state.centroidLabels.showLabels,
tosURL: state.config?.parameters?.about_legal_tos, tosURL: state.config?.parameters?.about_legal_tos,
privacyURL: state.config?.parameters?.about_legal_privacy privacyURL: state.config?.parameters?.about_legal_privacy,
})) }))
class MenuBar extends React.Component { class MenuBar extends React.Component {
static isValidDigitKeyEvent(e) { static isValidDigitKeyEvent(e) {
@@ -64,7 +64,7 @@ class MenuBar extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
pendingClipPercentiles: null pendingClipPercentiles: null,
}; };
} }
@@ -90,7 +90,7 @@ class MenuBar extends React.Component {
return isDisabled; return isDisabled;
}; };
handleClipOnKeyPress = e => { handleClipOnKeyPress = (e) => {
/* /*
allow only numbers, plus other critical keys which allow only numbers, plus other critical keys which
may be required to make a number may be required to make a number
@@ -100,7 +100,7 @@ class MenuBar extends React.Component {
} }
}; };
handleClipPercentileMinValueChange = v => { handleClipPercentileMinValueChange = (v) => {
/* /*
Ignore anything that isn't a legit number Ignore anything that isn't a legit number
*/ */
@@ -116,11 +116,11 @@ class MenuBar extends React.Component {
if (v > 100) v = 100; if (v > 100) v = 100;
const clipPercentileMin = Math.round(v); // paranoia const clipPercentileMin = Math.round(v); // paranoia
this.setState({ this.setState({
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax } pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
}); });
}; };
handleClipPercentileMaxValueChange = v => { handleClipPercentileMaxValueChange = (v) => {
/* /*
Ignore anything that isn't a legit number Ignore anything that isn't a legit number
*/ */
@@ -137,7 +137,7 @@ class MenuBar extends React.Component {
const clipPercentileMax = Math.round(v); // paranoia const clipPercentileMax = Math.round(v); // paranoia
this.setState({ this.setState({
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax } pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
}); });
}; };
@@ -149,14 +149,14 @@ class MenuBar extends React.Component {
const max = clipPercentileMax / 100; const max = clipPercentileMax / 100;
dispatch({ dispatch({
type: "set clip quantiles", type: "set clip quantiles",
clipQuantiles: { min, max } clipQuantiles: { min, max },
}); });
}; };
handleClipOpening = () => { handleClipOpening = () => {
const { clipPercentileMin, clipPercentileMax } = this.props; const { clipPercentileMin, clipPercentileMax } = this.props;
this.setState({ this.setState({
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax } pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
}); });
}; };
@@ -169,7 +169,7 @@ class MenuBar extends React.Component {
dispatch({ dispatch({
type: "show centroid labels for category", type: "show centroid labels for category",
showLabels: !showCentroidLabels showLabels: !showCentroidLabels,
}); });
}; };
@@ -200,7 +200,7 @@ class MenuBar extends React.Component {
aboutLink, aboutLink,
showCentroidLabels, showCentroidLabels,
privacyURL, privacyURL,
tosURL tosURL,
} = this.props; } = this.props;
const { pendingClipPercentiles } = this.state; const { pendingClipPercentiles } = this.state;
@@ -221,10 +221,10 @@ class MenuBar extends React.Component {
position: "fixed", position: "fixed",
right: globals.leftSidebarWidth + 8, right: globals.leftSidebarWidth + 8,
top: 8, top: 8,
display: "flex" display: "flex",
}} }}
> >
{disableDiffexp ? null : <DiffexpButtons/>} {disableDiffexp ? null : <DiffexpButtons />}
<Subset <Subset
subsetPossible={this.subsetPossible()} subsetPossible={this.subsetPossible()}
subsetResetPossible={this.subsetResetPossible()} subsetResetPossible={this.subsetResetPossible()}
@@ -251,11 +251,11 @@ class MenuBar extends React.Component {
onClick={() => { onClick={() => {
dispatch({ dispatch({
type: "change graph interaction mode", type: "change graph interaction mode",
data: "select" data: "select",
}); });
}} }}
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
/> />
</Tooltip> </Tooltip>
@@ -272,11 +272,11 @@ class MenuBar extends React.Component {
onClick={() => { onClick={() => {
dispatch({ dispatch({
type: "change graph interaction mode", type: "change graph interaction mode",
data: "zoom" data: "zoom",
}); });
}} }}
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
/> />
</Tooltip> </Tooltip>
@@ -294,7 +294,7 @@ class MenuBar extends React.Component {
active={showCentroidLabels} active={showCentroidLabels}
intent={showCentroidLabels ? "primary" : "none"} intent={showCentroidLabels ? "primary" : "none"}
style={{ style={{
marginRight: 10 marginRight: 10,
}} }}
/> />
</Tooltip> </Tooltip>
+11 -11
View File
@@ -47,16 +47,16 @@ function InformationMenu(props) {
}`} }`}
/> />
<MenuItem text="MIT License" /> <MenuItem text="MIT License" />
{tosURL ? <MenuItem {tosURL ? (
href={tosURL} <MenuItem href={tosURL} target="_blank" text="Terms of Service" />
target="_blank" ) : null}
text="Terms of Service" {privacyURL ? (
/> : null } <MenuItem
{privacyURL ? <MenuItem href={privacyURL}
href={privacyURL} target="_blank"
target="_blank" text="Privacy Policy"
text="Privacy Policy" />
/> : null } ) : null}
</Menu> </Menu>
} }
position={Position.BOTTOM_RIGHT} position={Position.BOTTOM_RIGHT}
@@ -65,7 +65,7 @@ function InformationMenu(props) {
type="button" type="button"
className="bp3-button bp3-icon-info-sign" className="bp3-button bp3-icon-info-sign"
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
/> />
</Popover> </Popover>
+1 -1
View File
@@ -7,7 +7,7 @@ function Subset(props) {
subsetPossible, subsetPossible,
subsetResetPossible, subsetResetPossible,
handleSubset, handleSubset,
handleSubsetReset handleSubsetReset,
} = props; } = props;
return ( return (
+3 -7
View File
@@ -4,11 +4,7 @@ import { AnchorButton, Tooltip } from "@blueprintjs/core";
import { tooltipHoverOpenDelay } from "../../globals"; import { tooltipHoverOpenDelay } from "../../globals";
function InformationMenu(props) { function InformationMenu(props) {
const { const { undoDisabled, redoDisabled, dispatch } = props;
undoDisabled,
redoDisabled,
dispatch
} = props;
return ( return (
<div style={{ marginRight: 10 }} className="bp3-button-group"> <div style={{ marginRight: 10 }} className="bp3-button-group">
<Tooltip <Tooltip
@@ -24,7 +20,7 @@ function InformationMenu(props) {
dispatch({ type: "@@undoable/undo" }); dispatch({ type: "@@undoable/undo" });
}} }}
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
data-testid="undo" data-testid="undo"
/> />
@@ -42,7 +38,7 @@ function InformationMenu(props) {
dispatch({ type: "@@undoable/redo" }); dispatch({ type: "@@undoable/redo" });
}} }}
style={{ style={{
cursor: "pointer" cursor: "pointer",
}} }}
data-testid="redo" data-testid="redo"
/> />
+4 -4
View File
@@ -5,10 +5,10 @@ import Continuous from "../continuous/continuous";
import GeneExpression from "../geneExpression"; import GeneExpression from "../geneExpression";
import * as globals from "../../globals"; import * as globals from "../../globals";
@connect(state => ({ @connect((state) => ({
responsive: state.responsive, responsive: state.responsive,
scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
scatterplotYYaccessor: state.controls.scatterplotYYaccessor scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
})) }))
class RightSidebar extends React.Component { class RightSidebar extends React.Component {
render() { render() {
@@ -21,7 +21,7 @@ class RightSidebar extends React.Component {
right: 0, right: 0,
backgroundColor: "white", backgroundColor: "white",
/* x y blur spread color */ /* x y blur spread color */
borderLeft: `1px solid ${globals.lightGrey}` borderLeft: `1px solid ${globals.lightGrey}`,
}} }}
> >
<div <div
@@ -29,7 +29,7 @@ class RightSidebar extends React.Component {
height: responsive.height, height: responsive.height,
width: globals.leftSidebarWidth, width: globals.leftSidebarWidth,
overflowY: "auto", overflowY: "auto",
overflowX: "hidden" overflowX: "hidden",
}} }}
> >
<GeneExpression /> <GeneExpression />
@@ -1,6 +1,6 @@
import { glPointFlags, glPointSize } from "../../util/glHelpers"; import { glPointFlags, glPointSize } from "../../util/glHelpers";
export default function(regl) { export default function (regl) {
return regl({ return regl({
vert: ` vert: `
precision mediump float; precision mediump float;
@@ -52,17 +52,17 @@ export default function(regl) {
attributes: { attributes: {
position: regl.prop("position"), position: regl.prop("position"),
color: regl.prop("color"), color: regl.prop("color"),
flag: regl.prop("flag") flag: regl.prop("flag"),
}, },
uniforms: { uniforms: {
projection: regl.prop("projection"), projection: regl.prop("projection"),
nPoints: regl.prop("nPoints"), nPoints: regl.prop("nPoints"),
minViewportDimension: regl.prop("minViewportDimension") minViewportDimension: regl.prop("minViewportDimension"),
}, },
count: regl.prop("count"), count: regl.prop("count"),
primitive: "points" primitive: "points",
}); });
} }
@@ -22,7 +22,7 @@ function createProjectionTF(viewportWidth, viewportHeight) {
return mat3.projection(m, viewportWidth, viewportHeight); return mat3.projection(m, viewportWidth, viewportHeight);
} }
@connect(state => { @connect((state) => {
const { world, crossfilter, universe } = state; const { world, crossfilter, universe } = state;
const { scatterplotXXaccessor, scatterplotYYaccessor } = state.controls; const { scatterplotXXaccessor, scatterplotYYaccessor } = state.controls;
const expressionX = scatterplotXXaccessor const expressionX = scatterplotXXaccessor
@@ -54,7 +54,7 @@ function createProjectionTF(viewportWidth, viewportHeight) {
crossfilter, crossfilter,
responsive: state.responsive responsive: state.responsive,
}; };
}) })
class Scatterplot extends React.PureComponent { class Scatterplot extends React.PureComponent {
@@ -67,7 +67,7 @@ class Scatterplot extends React.PureComponent {
return positions; return positions;
}); });
computePointColors = memoize(rgb => { computePointColors = memoize((rgb) => {
/* /*
compute webgl colors for each point compute webgl colors for each point
*/ */
@@ -135,11 +135,11 @@ class Scatterplot extends React.PureComponent {
colors: null, colors: null,
flags: null, flags: null,
xScale: null, xScale: null,
yScale: null yScale: null,
}; };
this.state = { this.state = {
svg: null, svg: null,
minimized: null minimized: null,
}; };
} }
@@ -184,7 +184,7 @@ class Scatterplot extends React.PureComponent {
colorBuffer, colorBuffer,
svg, svg,
drawPoints, drawPoints,
projectionTF projectionTF,
}); });
} }
@@ -198,7 +198,7 @@ class Scatterplot extends React.PureComponent {
expressionY, expressionY,
colorRGB, colorRGB,
colorAccessor, colorAccessor,
pointDilation pointDilation,
} = this.props; } = this.props;
const { const {
regl, regl,
@@ -207,7 +207,7 @@ class Scatterplot extends React.PureComponent {
flagBuffer, flagBuffer,
svg, svg,
drawPoints, drawPoints,
projectionTF projectionTF,
} = this.state; } = this.state;
if ( if (
@@ -284,7 +284,7 @@ class Scatterplot extends React.PureComponent {
return { return {
xScale, xScale,
yScale yScale,
}; };
} }
@@ -294,15 +294,9 @@ class Scatterplot extends React.PureComponent {
// the axes are much cleaner and easier now. No need to rotate and orient // the axes are much cleaner and easier now. No need to rotate and orient
// the axis, just call axisBottom, axisLeft etc. // the axis, just call axisBottom, axisLeft etc.
const xAxis = d3 const xAxis = d3.axisBottom().ticks(7).scale(xScale);
.axisBottom()
.ticks(7)
.scale(xScale);
const yAxis = d3 const yAxis = d3.axisLeft().ticks(7).scale(yScale);
.axisLeft()
.ticks(7)
.scale(yScale);
// adding axes is also simpler now, just translate x-axis to (0,height) // adding axes is also simpler now, just translate x-axis to (0,height)
// and it's alread defined to be a bottom axis. // and it's alread defined to be a bottom axis.
@@ -356,7 +350,7 @@ class Scatterplot extends React.PureComponent {
regl.poll(); regl.poll();
regl.clear({ regl.clear({
depth: 1, depth: 1,
color: [1, 1, 1, 1] color: [1, 1, 1, 1],
}); });
drawPoints({ drawPoints({
flag: flagBuffer, flag: flagBuffer,
@@ -368,7 +362,7 @@ class Scatterplot extends React.PureComponent {
minViewportDimension: Math.min( minViewportDimension: Math.min(
cvWidth - globals.leftSidebarWidth || width, cvWidth - globals.leftSidebarWidth || width,
cvHeight || height cvHeight || height
) ),
}); });
regl._gl.flush(); regl._gl.flush();
} }
@@ -387,7 +381,7 @@ class Scatterplot extends React.PureComponent {
padding: "0px 20px 20px 0px", padding: "0px 20px 20px 0px",
backgroundColor: "white", backgroundColor: "white",
/* x y blur spread color */ /* x y blur spread color */
boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)" boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)",
}} }}
id="scatterplot_wrapper" id="scatterplot_wrapper"
> >
@@ -395,7 +389,7 @@ class Scatterplot extends React.PureComponent {
style={{ style={{
position: "absolute", position: "absolute",
right: 5, right: 5,
top: 5 top: 5,
}} }}
> >
<Button <Button
@@ -413,7 +407,7 @@ class Scatterplot extends React.PureComponent {
data-testid="clear-scatterplot" data-testid="clear-scatterplot"
onClick={() => onClick={() =>
dispatch({ dispatch({
type: "clear scatterplot" type: "clear scatterplot",
}) })
} }
> >
@@ -425,7 +419,7 @@ class Scatterplot extends React.PureComponent {
id="scatterplot" id="scatterplot"
style={{ style={{
width: `${width + margin.left + margin.right}px`, width: `${width + margin.left + margin.right}px`,
height: `${height + margin.top + margin.bottom}px` height: `${height + margin.top + margin.bottom}px`,
}} }}
> >
<canvas <canvas
@@ -434,9 +428,9 @@ class Scatterplot extends React.PureComponent {
data-testid="scatterplot" data-testid="scatterplot"
style={{ style={{
marginLeft: margin.left, marginLeft: margin.left,
marginTop: margin.top marginTop: margin.top,
}} }}
ref={canvas => { ref={(canvas) => {
this.reglCanvas = canvas; this.reglCanvas = canvas;
}} }}
/> />
@@ -19,7 +19,7 @@ const setupScatterplot = (width, height, margin) => {
.attr("transform", `translate(${margin.left},${margin.top})`); .attr("transform", `translate(${margin.left},${margin.top})`);
return { return {
svg svg,
}; };
}; };
+3 -3
View File
@@ -1,5 +1,5 @@
# Third-party fonts used in cellxgene # Third-party fonts used in cellxgene
| Font Family | License at time of download | URL | | Font Family | License at time of download | URL |
|-------------|-----------------------------|-----| | --------------- | --------------------------- | -------------------------------------------------- |
| Roboto Conensed | Apache v2.0 | https://fonts.google.com/specimen/Roboto+Condensed | | Roboto Conensed | Apache v2.0 | https://fonts.google.com/specimen/Roboto+Condensed |
+12 -12
View File
@@ -9,22 +9,22 @@ namespace mangling on these definitions. See webpack config for specifics.
@import "~@blueprintjs/select/lib/css/blueprint-select.css"; @import "~@blueprintjs/select/lib/css/blueprint-select.css";
@font-face { @font-face {
font-family: 'Roboto Condensed'; font-family: "Roboto Condensed";
font-style: italic; font-style: italic;
src: url('./fonts/RobotoCondensed-Italic.ttf') format('truetype'); src: url("./fonts/RobotoCondensed-Italic.ttf") format("truetype");
font-weight: 400; font-weight: 400;
} }
@font-face { @font-face {
font-family: 'Roboto Condensed'; font-family: "Roboto Condensed";
font-style: normal; font-style: normal;
src: url('./fonts/RobotoCondensed-Regular.ttf') format('truetype'); src: url("./fonts/RobotoCondensed-Regular.ttf") format("truetype");
font-weight: 400; font-weight: 400;
} }
@font-face { @font-face {
font-family: 'Roboto Condensed'; font-family: "Roboto Condensed";
font-style: normal; font-style: normal;
src: url('./fonts/RobotoCondensed-Bold.ttf') format('truetype'); src: url("./fonts/RobotoCondensed-Bold.ttf") format("truetype");
font-weight: 700; font-weight: 700;
} }
+10 -10
View File
@@ -25,7 +25,7 @@ const Annotations = (
isEditingLabelName: false, isEditingLabelName: false,
categoryBeingEdited: null, categoryBeingEdited: null,
categoryAddingNewLabel: null, categoryAddingNewLabel: null,
labelEditable: { category: null, label: null } labelEditable: { category: null, label: null },
}, },
action action
) => { ) => {
@@ -40,7 +40,7 @@ const Annotations = (
return { return {
...state, ...state,
dataCollectionNameIsReadOnly, dataCollectionNameIsReadOnly,
dataCollectionName dataCollectionName,
}; };
} }
@@ -50,7 +50,7 @@ const Annotations = (
} }
return { return {
...state, ...state,
dataCollectionName: action.data dataCollectionName: action.data,
}; };
} }
@@ -59,7 +59,7 @@ const Annotations = (
return { return {
...state, ...state,
isAddingNewLabel: true, isAddingNewLabel: true,
categoryAddingNewLabel: action.data categoryAddingNewLabel: action.data,
}; };
} }
@@ -67,7 +67,7 @@ const Annotations = (
return { return {
...state, ...state,
isAddingNewLabel: false, isAddingNewLabel: false,
categoryAddingNewLabel: null categoryAddingNewLabel: null,
}; };
} }
@@ -75,7 +75,7 @@ const Annotations = (
return { return {
...state, ...state,
isEditingCategoryName: true, isEditingCategoryName: true,
categoryBeingEdited: action.data categoryBeingEdited: action.data,
}; };
} }
@@ -83,7 +83,7 @@ const Annotations = (
return { return {
...state, ...state,
isEditingCategoryName: false, isEditingCategoryName: false,
categoryBeingEdited: null categoryBeingEdited: null,
}; };
} }
@@ -94,8 +94,8 @@ const Annotations = (
isEditingLabelName: true, isEditingLabelName: true,
labelEditable: { labelEditable: {
category: action.metadataField, category: action.metadataField,
label: action.categoryIndex label: action.categoryIndex,
} },
}; };
} }
@@ -103,7 +103,7 @@ const Annotations = (
return { return {
...state, ...state,
isEditingLabelName: false, isEditingLabelName: false,
labelEditable: { category: null, label: null } labelEditable: { category: null, label: null },
}; };
} }
+46 -46
View File
@@ -1,55 +1,55 @@
const Autosave = ( const Autosave = (
state = { state = {
saveInProgress: false, saveInProgress: false,
error: false, error: false,
lastSavedObsAnnotations: null, lastSavedObsAnnotations: null,
initialDataLoadComplete: false initialDataLoadComplete: false,
}, },
action, action,
nextSharedState nextSharedState
) => { ) => {
switch (action.type) { switch (action.type) {
case "initial data load complete (universe exists)": { case "initial data load complete (universe exists)": {
/* don't save on init */ /* don't save on init */
const { universe } = nextSharedState; const { universe } = nextSharedState;
return { return {
...state, ...state,
error: false, error: false,
saveInProgress: false, saveInProgress: false,
lastSavedObsAnnotations: universe.obsAnnotations, lastSavedObsAnnotations: universe.obsAnnotations,
initialDataLoadComplete: true, initialDataLoadComplete: true,
}; };
} }
case "writable obs annotations - save started": { case "writable obs annotations - save started": {
return { return {
...state, ...state,
saveInProgress: true saveInProgress: true,
}; };
} }
case "writable obs annotations - save error": { case "writable obs annotations - save error": {
const { message } = action; const { message } = action;
return { return {
...state, ...state,
error: message, error: message,
saveInProgress: false saveInProgress: false,
}; };
} }
case "writable obs annotations - save complete": { case "writable obs annotations - save complete": {
const lastSavedObsAnnotations = action.obsAnnotations; const lastSavedObsAnnotations = action.obsAnnotations;
return { return {
...state, ...state,
saveInProgress: false, saveInProgress: false,
error: false, error: false,
lastSavedObsAnnotations lastSavedObsAnnotations,
}; };
} }
default: default:
return { ...state }; return { ...state };
} }
}; };
export default Autosave; export default Autosave;
+11 -11
View File
@@ -36,7 +36,7 @@ const CategoricalSelection = (
if (names.length === 0) return state; if (names.length === 0) return state;
return { return {
...state, ...state,
...CH.createCategoricalSelection(world, names) ...CH.createCategoricalSelection(world, names),
}; };
} }
@@ -52,8 +52,8 @@ const CategoricalSelection = (
...state, ...state,
[action.metadataField]: { [action.metadataField]: {
...state[action.metadataField], ...state[action.metadataField],
categoryValueSelected: newCategoryValueSelected categoryValueSelected: newCategoryValueSelected,
} },
}; };
return newCategoricalSelection; return newCategoricalSelection;
} }
@@ -70,8 +70,8 @@ const CategoricalSelection = (
...state, ...state,
[action.metadataField]: { [action.metadataField]: {
...state[action.metadataField], ...state[action.metadataField],
categoryValueSelected: newCategoryValueSelected categoryValueSelected: newCategoryValueSelected,
} },
}; };
return newCategoricalSelection; return newCategoricalSelection;
} }
@@ -87,8 +87,8 @@ const CategoricalSelection = (
categorySelected: false, categorySelected: false,
categoryValueSelected: Array.from( categoryValueSelected: Array.from(
state[action.metadataField].categoryValueSelected state[action.metadataField].categoryValueSelected
).fill(false) ).fill(false),
} },
}; };
return newCategoricalSelection; return newCategoricalSelection;
} }
@@ -104,8 +104,8 @@ const CategoricalSelection = (
categorySelected: true, categorySelected: true,
categoryValueSelected: Array.from( categoryValueSelected: Array.from(
state[action.metadataField].categoryValueSelected state[action.metadataField].categoryValueSelected
).fill(true) ).fill(true),
} },
}; };
return newCategoricalSelection; return newCategoricalSelection;
} }
@@ -115,7 +115,7 @@ const CategoricalSelection = (
const name = action.data; const name = action.data;
return { return {
...state, ...state,
...CH.createCategoricalSelection(world, [name]) ...CH.createCategoricalSelection(world, [name]),
}; };
} }
@@ -143,7 +143,7 @@ const CategoricalSelection = (
const { [name]: _, ...partialState } = state; const { [name]: _, ...partialState } = state;
return { return {
...partialState, ...partialState,
...CH.createCategoricalSelection(world, [name]) ...CH.createCategoricalSelection(world, [name]),
}; };
} }
+5 -5
View File
@@ -2,7 +2,7 @@ import calcCentroid from "../util/centroid";
const initialState = { const initialState = {
labels: [], labels: [],
showLabels: false showLabels: false,
}; };
const centroidLabels = (state = initialState, action, sharedNextState) => { const centroidLabels = (state = initialState, action, sharedNextState) => {
@@ -10,7 +10,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
world, world,
layoutChoice, layoutChoice,
categoricalSelection, categoricalSelection,
colors: { colorAccessor } colors: { colorAccessor },
} = sharedNextState; } = sharedNextState;
const showLabels = action.showLabels ?? state.showLabels; const showLabels = action.showLabels ?? state.showLabels;
@@ -34,7 +34,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
categoricalSelection, categoricalSelection,
world.schema.annotations.obsByName world.schema.annotations.obsByName
) )
: [] : [],
}; };
case "color by categorical metadata": case "color by categorical metadata":
@@ -45,7 +45,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
return { return {
...state, ...state,
labels: [], labels: [],
showLabels showLabels,
}; };
} }
@@ -59,7 +59,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
categoricalSelection, categoricalSelection,
world.schema.annotations.obsByName world.schema.annotations.obsByName
), ),
showLabels showLabels,
}; };
case "color by continuous metadata": case "color by continuous metadata":
+20 -11
View File
@@ -5,7 +5,7 @@ const ColorsReducer = (
colorMode: null, colorMode: null,
colorAccessor: null, colorAccessor: null,
rgb: null, rgb: null,
scale: null scale: null,
}, },
action, action,
nextSharedState, nextSharedState,
@@ -23,7 +23,7 @@ const ColorsReducer = (
colorAccessor, colorAccessor,
colorMode, colorMode,
rgb, rgb,
scale scale,
}; };
} }
@@ -31,7 +31,7 @@ const ColorsReducer = (
const { userColors } = action; const { userColors } = action;
return { return {
...state, ...state,
userColors userColors,
}; };
} }
@@ -47,7 +47,7 @@ const ColorsReducer = (
return { return {
...state, ...state,
rgb, rgb,
scale scale,
}; };
} }
@@ -73,7 +73,7 @@ const ColorsReducer = (
return { return {
...state, ...state,
rgb, rgb,
scale scale,
}; };
} }
@@ -85,14 +85,14 @@ const ColorsReducer = (
/* else reset */ /* else reset */
return { return {
...state, ...state,
...ColorHelpers.resetColors(prevSharedState.world) ...ColorHelpers.resetColors(prevSharedState.world),
}; };
} }
case "reset colorscale": { case "reset colorscale": {
return { return {
...state, ...state,
...ColorHelpers.resetColors(prevSharedState.world) ...ColorHelpers.resetColors(prevSharedState.world),
}; };
} }
@@ -107,13 +107,18 @@ const ColorsReducer = (
const colorMode = !resetCurrent ? action.type : null; const colorMode = !resetCurrent ? action.type : null;
const colorAccessor = !resetCurrent ? action.colorAccessor : null; const colorAccessor = !resetCurrent ? action.colorAccessor : null;
const { rgb, scale } = ColorHelpers.createColors(world, colorMode, colorAccessor, colors.userColors); const { rgb, scale } = ColorHelpers.createColors(
world,
colorMode,
colorAccessor,
colors.userColors
);
return { return {
...state, ...state,
colorMode, colorMode,
colorAccessor, colorAccessor,
rgb, rgb,
scale scale,
}; };
} }
@@ -136,7 +141,7 @@ const ColorsReducer = (
colorMode, colorMode,
colorAccessor, colorAccessor,
rgb, rgb,
scale scale,
}; };
} }
@@ -153,7 +158,11 @@ const ColorsReducer = (
return state; return state;
/* else, we need to rebuild colors as labels have changed! */ /* else, we need to rebuild colors as labels have changed! */
const { rgb, scale } = ColorHelpers.createColors(world, colorMode, colorAccessor); const { rgb, scale } = ColorHelpers.createColors(
world,
colorMode,
colorAccessor
);
return { ...state, rgb, scale }; return { ...state, rgb, scale };
} }
+4 -4
View File
@@ -3,7 +3,7 @@ const Config = (
state = { state = {
displayNames: null, displayNames: null,
features: null, features: null,
parameters: null parameters: null,
}, },
action action
) => { ) => {
@@ -12,19 +12,19 @@ const Config = (
return { return {
...state, ...state,
loading: true, loading: true,
error: null error: null,
}; };
case "configuration load complete": case "configuration load complete":
return { return {
...state, ...state,
loading: false, loading: false,
error: null, error: null,
...action.config ...action.config,
}; };
case "initial data load error": case "initial data load error":
return { return {
...state, ...state,
error: action.error error: action.error,
}; };
default: default:
return state; return state;
+8 -8
View File
@@ -6,7 +6,7 @@ const Differential = (
loading: null, loading: null,
error: null, error: null,
celllist1: null, celllist1: null,
celllist2: null celllist2: null,
}, },
action action
) => { ) => {
@@ -15,37 +15,37 @@ const Differential = (
return { return {
...state, ...state,
loading: true, loading: true,
error: null error: null,
}; };
case "request differential expression success": case "request differential expression success":
return { return {
...state, ...state,
error: null, error: null,
loading: false, loading: false,
diffExp: action.data diffExp: action.data,
}; };
case "request differential expression error": case "request differential expression error":
return { return {
...state, ...state,
loading: false, loading: false,
error: action.data error: action.data,
}; };
case "store current cell selection as differential set 1": case "store current cell selection as differential set 1":
return { return {
...state, ...state,
celllist1: action.data celllist1: action.data,
}; };
case "store current cell selection as differential set 2": case "store current cell selection as differential set 2":
return { return {
...state, ...state,
celllist2: action.data celllist2: action.data,
}; };
case "clear differential expression": case "clear differential expression":
return { return {
...state, ...state,
diffExp: null, diffExp: null,
celllist1: null, celllist1: null,
celllist2: null celllist2: null,
}; };
case "reset World to eq Universe": case "reset World to eq Universe":
case "set World to current selection": case "set World to current selection":
@@ -53,7 +53,7 @@ const Differential = (
...state, ...state,
diffExp: null, diffExp: null,
celllist1: null, celllist1: null,
celllist2: null celllist2: null,
}; };
default: default:
return state; return state;
+9 -9
View File
@@ -1,7 +1,7 @@
const GraphSelection = ( const GraphSelection = (
state = { state = {
tool: "lasso", // what selection tool mode (lasso, brush, ...) tool: "lasso", // what selection tool mode (lasso, brush, ...)
selection: { mode: "all" } // current selection, which is tool specific selection: { mode: "all" }, // current selection, which is tool specific
}, },
action action
) => { ) => {
@@ -12,8 +12,8 @@ const GraphSelection = (
return { return {
...state, ...state,
selection: { selection: {
mode: "all" mode: "all",
} },
}; };
} }
@@ -24,8 +24,8 @@ const GraphSelection = (
...state, ...state,
selection: { selection: {
mode: "within-rect", mode: "within-rect",
brushCoords brushCoords,
} },
}; };
} }
@@ -35,8 +35,8 @@ const GraphSelection = (
...state, ...state,
selection: { selection: {
mode: "within-polygon", mode: "within-polygon",
polygon polygon,
} },
}; };
} }
@@ -47,8 +47,8 @@ const GraphSelection = (
return { return {
...state, ...state,
selection: { selection: {
mode: "all" mode: "all",
} },
}; };
} }
+7 -7
View File
@@ -9,14 +9,14 @@ about commonly used names. Preferentially, pick in the following order:
*/ */
function bestDefaultLayout(layouts) { function bestDefaultLayout(layouts) {
const preferredNames = ["umap", "tsne", "pca"]; const preferredNames = ["umap", "tsne", "pca"];
const idx = preferredNames.findIndex(name => layouts.indexOf(name) !== -1); const idx = preferredNames.findIndex((name) => layouts.indexOf(name) !== -1);
if (idx !== -1) return preferredNames[idx]; if (idx !== -1) return preferredNames[idx];
return layouts[0]; return layouts[0];
} }
function setToDefaultLayout(world) { function setToDefaultLayout(world) {
const { schema } = world; const { schema } = world;
const available = schema.layout.obs.map(v => v.name).sort(); const available = schema.layout.obs.map((v) => v.name).sort();
const current = bestDefaultLayout(available); const current = bestDefaultLayout(available);
const currentDimNames = schema.layout.obsByName[current].dims; const currentDimNames = schema.layout.obsByName[current].dims;
return { available, current, currentDimNames }; return { available, current, currentDimNames };
@@ -26,7 +26,7 @@ const LayoutChoice = (
state = { state = {
available: [], // all available choices available: [], // all available choices
current: undefined, // name of the current layout, eg, 'umap' current: undefined, // name of the current layout, eg, 'umap'
currentDimNames: [] // dimension name currentDimNames: [], // dimension name
}, },
action, action,
nextSharedState nextSharedState
@@ -37,7 +37,7 @@ const LayoutChoice = (
const { universe } = nextSharedState; const { universe } = nextSharedState;
return { return {
...state, ...state,
...setToDefaultLayout(universe) ...setToDefaultLayout(universe),
}; };
} }
@@ -53,7 +53,7 @@ const LayoutChoice = (
const available = Array.from(new Set(state.available).add(name)); const available = Array.from(new Set(state.available).add(name));
return { return {
...state, ...state,
available available,
}; };
} }
@@ -64,12 +64,12 @@ const LayoutChoice = (
if (dflt.available.includes(current)) { if (dflt.available.includes(current)) {
return { return {
...state, ...state,
available: dflt.available available: dflt.available,
}; };
} }
return { return {
...state, ...state,
...dflt ...dflt,
}; };
} }
+2 -2
View File
@@ -3,7 +3,7 @@ const Ontology = (
enabled: false, // are ontology terms enabled? enabled: false, // are ontology terms enabled?
terms: null, // an array of term names, eg, ['cell', 'lung cell', ...] terms: null, // an array of term names, eg, ['cell', 'lung cell', ...]
termSet: null, // a Set object containing all terms, for fast lookup termSet: null, // a Set object containing all terms, for fast lookup
loading: true loading: true,
}, },
action action
) => { ) => {
@@ -20,7 +20,7 @@ const Ontology = (
loading: false, loading: false,
enabled, enabled,
terms, terms,
termSet termSet,
}; };
} }
default: { default: {
+2 -2
View File
@@ -1,6 +1,6 @@
const initialState = { const initialState = {
metadataField: "", metadataField: "",
categoryField: "" categoryField: "",
}; };
const pointDialation = (state = initialState, action, sharedNextState) => { const pointDialation = (state = initialState, action, sharedNextState) => {
@@ -15,7 +15,7 @@ const pointDialation = (state = initialState, action, sharedNextState) => {
return { return {
...state, ...state,
metadataField, metadataField,
categoryField categoryField,
}; };
case "category value mouse hover end": case "category value mouse hover end":
+7 -7
View File
@@ -3,7 +3,7 @@ controller state is not part of the undo/redo history
*/ */
export const reembedController = ( export const reembedController = (
state = { state = {
pendingFetch: null pendingFetch: null,
}, },
action action
) => { ) => {
@@ -11,7 +11,7 @@ export const reembedController = (
case "reembed: request start": { case "reembed: request start": {
return { return {
...state, ...state,
pendingFetch: action.abortableFetch pendingFetch: action.abortableFetch,
}; };
} }
case "reembed: request aborted": case "reembed: request aborted":
@@ -19,7 +19,7 @@ export const reembedController = (
case "reembed: request completed": { case "reembed: request completed": {
return { return {
...state, ...state,
pendingFetch: null pendingFetch: null,
}; };
} }
default: { default: {
@@ -33,7 +33,7 @@ actual reembedding data is part of the undo/redo history
*/ */
export const reembedding = ( export const reembedding = (
state = { state = {
reembeddings: new Map() reembeddings: new Map(),
}, },
action action
) => { ) => {
@@ -47,14 +47,14 @@ export const reembedding = (
reembeddings: new Map(reembeddings).set(name, { reembeddings: new Map(reembeddings).set(name, {
name, name,
schema, schema,
embedding embedding,
}) }),
}; };
} }
case "reembed: clear all reembeddings": { case "reembed: clear all reembeddings": {
return { return {
...state, ...state,
reembeddings: new Map() reembeddings: new Map(),
}; };
} }
default: { default: {
+2 -2
View File
@@ -8,7 +8,7 @@ which improves Reset UI performance.
*/ */
const ResetCacheReducer = ( const ResetCacheReducer = (
state = { state = {
crossfilter: null crossfilter: null,
}, },
action, action,
nextSharedState nextSharedState
@@ -19,7 +19,7 @@ const ResetCacheReducer = (
const { crossfilter } = nextSharedState; const { crossfilter } = nextSharedState;
return { return {
...state, ...state,
crossfilter crossfilter,
}; };
} }
default: { default: {
+2 -2
View File
@@ -2,7 +2,7 @@
const Responsive = ( const Responsive = (
state = { state = {
width: null, width: null,
height: null height: null,
}, },
action action
) => { ) => {
@@ -11,7 +11,7 @@ const Responsive = (
return { return {
...state, ...state,
width: action.data.width, width: action.data.width,
height: action.data.height height: action.data.height,
}; };
default: default:
return state; return state;
+14 -14
View File
@@ -80,7 +80,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
const past = currentState[pastKey]; const past = currentState[pastKey];
const future = currentState[futureKey]; const future = currentState[futureKey];
if (past.length === 0) return currentState; if (past.length === 0) return currentState;
const currentUndoableState = Object.entries(currentState).filter(kv => const currentUndoableState = Object.entries(currentState).filter((kv) =>
undoableKeysSet.has(kv[0]) undoableKeysSet.has(kv[0])
); );
const newPast = [...past]; const newPast = [...past];
@@ -91,7 +91,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
...fromEntries(newState), ...fromEntries(newState),
[pastKey]: newPast, [pastKey]: newPast,
[futureKey]: newFuture, [futureKey]: newFuture,
[pendingKey]: null [pendingKey]: null,
}; };
return nextState; return nextState;
} }
@@ -103,7 +103,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
const past = currentState[pastKey] || []; const past = currentState[pastKey] || [];
const future = currentState[futureKey] || []; const future = currentState[futureKey] || [];
if (future.length === 0) return currentState; if (future.length === 0) return currentState;
const currentUndoableState = Object.entries(currentState).filter(kv => const currentUndoableState = Object.entries(currentState).filter((kv) =>
undoableKeysSet.has(kv[0]) undoableKeysSet.has(kv[0])
); );
const newFuture = [...future]; const newFuture = [...future];
@@ -114,7 +114,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
...fromEntries(newState), ...fromEntries(newState),
[pastKey]: newPast, [pastKey]: newPast,
[futureKey]: newFuture, [futureKey]: newFuture,
[pendingKey]: null [pendingKey]: null,
}; };
return nextState; return nextState;
} }
@@ -128,7 +128,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
[pastKey]: [], [pastKey]: [],
[futureKey]: [], [futureKey]: [],
[filterStateKey]: {}, [filterStateKey]: {},
[pendingKey]: null [pendingKey]: null,
}; };
} }
@@ -145,7 +145,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
[pastKey]: past, [pastKey]: past,
[futureKey]: future, [futureKey]: future,
[filterStateKey]: filterState, [filterStateKey]: filterState,
[pendingKey]: pending [pendingKey]: pending,
}; };
} }
@@ -154,7 +154,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
*/ */
function save(currentState, action, filterState) { function save(currentState, action, filterState) {
const past = currentState[pastKey] || []; const past = currentState[pastKey] || [];
const currentUndoableState = Object.entries(currentState).filter(kv => const currentUndoableState = Object.entries(currentState).filter((kv) =>
undoableKeysSet.has(kv[0]) undoableKeysSet.has(kv[0])
); );
const res = reducer(currentState, action); const res = reducer(currentState, action);
@@ -164,7 +164,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
[pastKey]: newPast, [pastKey]: newPast,
[futureKey]: [], [futureKey]: [],
[filterStateKey]: filterState, [filterStateKey]: filterState,
[pendingKey]: null [pendingKey]: null,
}; };
return nextState; return nextState;
} }
@@ -173,12 +173,12 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
Save current state as pending history change. No other side effects. Save current state as pending history change. No other side effects.
*/ */
function stashPending(currentState) { function stashPending(currentState) {
const currentUndoableState = Object.entries(currentState).filter(kv => const currentUndoableState = Object.entries(currentState).filter((kv) =>
undoableKeysSet.has(kv[0]) undoableKeysSet.has(kv[0])
); );
return { return {
...currentState, ...currentState,
[pendingKey]: currentUndoableState [pendingKey]: currentUndoableState,
}; };
} }
@@ -188,7 +188,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
function cancelPending(currentState) { function cancelPending(currentState) {
return { return {
...currentState, ...currentState,
[pendingKey]: null [pendingKey]: null,
}; };
} }
@@ -203,7 +203,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
...currentState, ...currentState,
[pastKey]: newPast, [pastKey]: newPast,
[futureKey]: [], [futureKey]: [],
[pendingKey]: null [pendingKey]: null,
}; };
return nextState; return nextState;
} }
@@ -213,7 +213,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
[pastKey]: [], [pastKey]: [],
[futureKey]: [], [futureKey]: [],
[filterStateKey]: {}, [filterStateKey]: {},
[pendingKey]: null [pendingKey]: null,
}, },
action action
) => { ) => {
@@ -241,7 +241,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
); );
const { const {
[filterActionKey]: filterAction, [filterActionKey]: filterAction,
[filterStateKey]: filterStateUpdate [filterStateKey]: filterStateUpdate,
} = actionFilterResp; } = actionFilterResp;
const nextFilterState = { ...currentFilterState, ...filterStateUpdate }; const nextFilterState = { ...currentFilterState, ...filterStateUpdate };
+16 -16
View File
@@ -49,7 +49,7 @@ const skipOnActions = new Set([
"annotation: disable category edit mode", "annotation: disable category edit mode",
"annotation: activate edit label mode", "annotation: activate edit label mode",
"annotation: cancel edit label mode", "annotation: cancel edit label mode",
"set annotations collection name" "set annotations collection name",
]); ]);
/* /*
@@ -101,7 +101,7 @@ const saveOnActions = new Set([
"annotation: label edited", "annotation: label edited",
"annotation: label current cell selection", "annotation: label current cell selection",
"annotation: delete label", "annotation: delete label",
"annotation: category edited" "annotation: category edited",
]); ]);
/** /**
@@ -115,26 +115,26 @@ See graph definition for the transitions that use each.
Signature: (fsm, transition, reducerState, reducerAction) => undoableAction Signature: (fsm, transition, reducerState, reducerAction) => undoableAction
*/ */
const stashPending = fsm => ({ const stashPending = (fsm) => ({
[actionKey]: "stashPending", [actionKey]: "stashPending",
[stateKey]: { fsm } [stateKey]: { fsm },
}); });
const cancelPending = () => ({ const cancelPending = () => ({
[actionKey]: "cancelPending", [actionKey]: "cancelPending",
[stateKey]: { fsm: null } [stateKey]: { fsm: null },
}); });
const applyPending = () => ({ const applyPending = () => ({
[actionKey]: "applyPending", [actionKey]: "applyPending",
[stateKey]: { fsm: null } [stateKey]: { fsm: null },
}); });
const skip = (fsm, transition) => ({ const skip = (fsm, transition) => ({
[actionKey]: "skip", [actionKey]: "skip",
[stateKey]: { fsm: transition.to !== "done" ? fsm : null } [stateKey]: { fsm: transition.to !== "done" ? fsm : null },
}); });
const clear = () => ({ [actionKey]: "clear", [stateKey]: { fsm: null } }); const clear = () => ({ [actionKey]: "clear", [stateKey]: { fsm: null } });
const save = (fsm, transition) => ({ const save = (fsm, transition) => ({
[actionKey]: "save", [actionKey]: "save",
[stateKey]: { fsm: transition.to !== "done" ? fsm : null } [stateKey]: { fsm: transition.to !== "done" ? fsm : null },
}); });
/* /*
@@ -171,11 +171,11 @@ Basic approach:
* only implement complex state machines where absolutely required (eg, * only implement complex state machines where absolutely required (eg,
multi-event seleciton and the like) multi-event seleciton and the like)
*/ */
const actionFilter = debug => (state, action, prevFilterState) => { const actionFilter = (debug) => (state, action, prevFilterState) => {
const actionType = action.type; const actionType = action.type;
const filterState = { const filterState = {
...prevFilterState, ...prevFilterState,
prevAction: action prevAction: action,
}; };
if (skipOnActions.has(actionType)) { if (skipOnActions.has(actionType)) {
return { [actionKey]: "skip", [stateKey]: filterState }; return { [actionKey]: "skip", [stateKey]: filterState };
@@ -246,7 +246,7 @@ const debug = false;
const undoableConfig = { const undoableConfig = {
debug, debug,
historyLimit: 50, // maximum history size historyLimit: 50, // maximum history size
actionFilter: actionFilter(debug) actionFilter: actionFilter(debug),
}; };
/* /*
@@ -258,9 +258,9 @@ if (debug) {
Confirm no intersection between the various trivial rejection action filters Confirm no intersection between the various trivial rejection action filters
*/ */
if ( if (
new Set([...skipOnActions].filter(x => clearOnActions.has(x))).size > 0 || new Set([...skipOnActions].filter((x) => clearOnActions.has(x))).size > 0 ||
new Set([...skipOnActions].filter(x => saveOnActions.has(x))).size > 0 || new Set([...skipOnActions].filter((x) => saveOnActions.has(x))).size > 0 ||
new Set([...clearOnActions].filter(x => saveOnActions.has(x))).size > 0 new Set([...clearOnActions].filter((x) => saveOnActions.has(x))).size > 0
) { ) {
console.error( console.error(
"Undoable misconfiguration - action filters have redundant events" "Undoable misconfiguration - action filters have redundant events"
@@ -275,10 +275,10 @@ if (debug) {
const trivialFilters = new Set([ const trivialFilters = new Set([
...skipOnActions, ...skipOnActions,
...clearOnActions, ...clearOnActions,
...saveOnActions ...saveOnActions,
]); ]);
const trivialOverlapWithFsm = new Set( const trivialOverlapWithFsm = new Set(
[...trivialFilters].filter(x => seedFsm.events.has(x)) [...trivialFilters].filter((x) => seedFsm.events.has(x))
); );
if (trivialOverlapWithFsm.size > 0) { if (trivialOverlapWithFsm.size > 0) {
console.error( console.error(
+26 -26
View File
@@ -32,13 +32,13 @@ const createFsmTransitions = (
event: "graph brush start", event: "graph brush start",
from: "init", from: "init",
to: "graph brush in progress", to: "graph brush in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "graph brush cancel", event: "graph brush cancel",
from: "graph brush in progress", from: "graph brush in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
{ {
event: "graph brush deselect", event: "graph brush deselect",
@@ -48,13 +48,13 @@ const createFsmTransitions = (
action: (fsm, transition, data) => action: (fsm, transition, data) =>
data.state.graphSelection.selection.mode === "all" data.state.graphSelection.selection.mode === "all"
? cancelPending() ? cancelPending()
: applyPending() : applyPending(),
}, },
{ {
event: "graph brush end", event: "graph brush end",
from: "graph brush in progress", from: "graph brush in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* graph selection lasso */ /* graph selection lasso */
@@ -62,13 +62,13 @@ const createFsmTransitions = (
event: "graph lasso start", event: "graph lasso start",
from: "init", from: "init",
to: "graph lasso in progress", to: "graph lasso in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "graph lasso cancel", event: "graph lasso cancel",
from: "graph lasso in progress", from: "graph lasso in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
{ {
event: "graph lasso deselect", event: "graph lasso deselect",
@@ -78,13 +78,13 @@ const createFsmTransitions = (
action: (fsm, transition, data) => action: (fsm, transition, data) =>
data.state.graphSelection.selection.mode === "all" data.state.graphSelection.selection.mode === "all"
? cancelPending() ? cancelPending()
: applyPending() : applyPending(),
}, },
{ {
event: "graph lasso end", event: "graph lasso end",
from: "graph lasso in progress", from: "graph lasso in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* Continuous metadata histogram brush selection */ /* Continuous metadata histogram brush selection */
@@ -92,19 +92,19 @@ const createFsmTransitions = (
event: "continuous metadata histogram start", event: "continuous metadata histogram start",
from: "init", from: "init",
to: "continuous histo select in progress", to: "continuous histo select in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "continuous metadata histogram cancel", event: "continuous metadata histogram cancel",
from: "continuous histo select in progress", from: "continuous histo select in progress",
to: "done", to: "done",
action: cancelPending action: cancelPending,
}, },
{ {
event: "continuous metadata histogram end", event: "continuous metadata histogram end",
from: "continuous histo select in progress", from: "continuous histo select in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* Single gene request by user */ /* Single gene request by user */
@@ -112,25 +112,25 @@ const createFsmTransitions = (
event: "single user defined gene start", event: "single user defined gene start",
from: "init", from: "init",
to: "single user gene request in progress", to: "single user gene request in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "request user defined gene error", event: "request user defined gene error",
from: "single user gene request in progress", from: "single user gene request in progress",
to: "single user gene error in progress", to: "single user gene error in progress",
action: skip action: skip,
}, },
{ {
event: "single user defined gene error", event: "single user defined gene error",
from: "single user gene error in progress", from: "single user gene error in progress",
to: "done", to: "done",
action: cancelPending action: cancelPending,
}, },
{ {
event: "single user defined gene complete", event: "single user defined gene complete",
from: "single user gene request in progress", from: "single user gene request in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* Bulk gene request by user */ /* Bulk gene request by user */
@@ -138,25 +138,25 @@ const createFsmTransitions = (
event: "bulk user defined gene start", event: "bulk user defined gene start",
from: "init", from: "init",
to: "bulk user gene request in progress", to: "bulk user gene request in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "request user defined gene error", event: "request user defined gene error",
from: "bulk user gene request in progress", from: "bulk user gene request in progress",
to: "bulk user gene request error in progress", to: "bulk user gene request error in progress",
action: skip action: skip,
}, },
{ {
event: "bulk user defined gene error", event: "bulk user defined gene error",
from: "bulk user gene request error in progress", from: "bulk user gene request error in progress",
to: "done", to: "done",
action: cancelPending action: cancelPending,
}, },
{ {
event: "bulk user defined gene complete", event: "bulk user defined gene complete",
from: "bulk user gene request in progress", from: "bulk user gene request in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* Compute Differential Expression button user action */ /* Compute Differential Expression button user action */
@@ -164,19 +164,19 @@ const createFsmTransitions = (
event: "request differential expression started", event: "request differential expression started",
from: "init", from: "init",
to: "diffexp in progress", to: "diffexp in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "request user defined gene error", event: "request user defined gene error",
from: "diffexp in progress", from: "diffexp in progress",
to: "done", to: "done",
action: cancelPending action: cancelPending,
}, },
{ {
event: "request differential expression success", event: "request differential expression success",
from: "diffexp in progress", from: "diffexp in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* Clear Differential Expression button user action */ /* Clear Differential Expression button user action */
@@ -184,13 +184,13 @@ const createFsmTransitions = (
event: "clear differential expression", event: "clear differential expression",
from: "init", from: "init",
to: "CDE Button in progress", to: "CDE Button in progress",
action: stashPending action: stashPending,
}, },
{ {
event: "clear scatterplot", event: "clear scatterplot",
from: "CDE Button in progress", from: "CDE Button in progress",
to: "done", to: "done",
action: applyPending action: applyPending,
}, },
/* clear scatter plot button (eg, on scatterplot view) */ /* clear scatter plot button (eg, on scatterplot view) */
@@ -198,8 +198,8 @@ const createFsmTransitions = (
event: "clear scatterplot", event: "clear scatterplot",
from: "init", from: "init",
to: "done", to: "done",
action: save action: save,
} },
]; ];
}; };
+12 -10
View File
@@ -2,12 +2,12 @@ import { unassignedCategoryLabel } from "../globals";
import { import {
addObsAnnotations, addObsAnnotations,
addVarAnnotations, addVarAnnotations,
addObsLayout addObsLayout,
} from "../util/stateManager/universe"; } from "../util/stateManager/universe";
import { import {
World, World,
ControlsHelpers, ControlsHelpers,
AnnotationsHelpers AnnotationsHelpers,
} from "../util/stateManager"; } from "../util/stateManager";
const Universe = (state = null, action, nextSharedState, prevSharedState) => { const Universe = (state = null, action, nextSharedState, prevSharedState) => {
@@ -23,19 +23,19 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
case "obsAnnotations": { case "obsAnnotations": {
return { return {
...state, ...state,
...addObsAnnotations(state, dataframe) ...addObsAnnotations(state, dataframe),
}; };
} }
case "varAnnotations": { case "varAnnotations": {
return { return {
...state, ...state,
...addVarAnnotations(state, dataframe) ...addVarAnnotations(state, dataframe),
}; };
} }
case "obsLayout": { case "obsLayout": {
return { return {
...state, ...state,
...addObsLayout(state, dataframe) ...addObsLayout(state, dataframe),
}; };
} }
default: { default: {
@@ -62,8 +62,10 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
const { userDefinedGenes, diffexpGenes } = prevSharedState; const { userDefinedGenes, diffexpGenes } = prevSharedState;
const allTheGenesWeNeed = [ const allTheGenesWeNeed = [
...new Set( ...new Set(
[userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)].filter(ele => ele).flat() [userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)]
) .filter((ele) => ele)
.flat()
),
]; ];
varData = ControlsHelpers.pruneVarDataCache(varData, allTheGenesWeNeed); varData = ControlsHelpers.pruneVarDataCache(varData, allTheGenesWeNeed);
return { ...state, varData }; return { ...state, varData };
@@ -98,7 +100,7 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
categoryToDuplicate, categoryToDuplicate,
name, name,
{ {
writable: true writable: true,
} }
); );
/* if we are duplicating a non-writable annotation, it may not have an unassigned category */ /* if we are duplicating a non-writable annotation, it may not have an unassigned category */
@@ -114,7 +116,7 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
name, name,
categories, categories,
type: "categorical", type: "categorical",
writable: true writable: true,
}); });
data = new Array(state.nObs).fill(unassignedCategoryLabel); data = new Array(state.nObs).fill(unassignedCategoryLabel);
} }
@@ -134,7 +136,7 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
const colSchema = { const colSchema = {
...state.schema.annotations.obsByName[name], ...state.schema.annotations.obsByName[name],
name: newName name: newName,
}; };
const schema = AnnotationsHelpers.addObsAnnoSchema( const schema = AnnotationsHelpers.addObsAnnoSchema(
AnnotationsHelpers.removeObsAnnoSchema(state.schema, name), AnnotationsHelpers.removeObsAnnoSchema(state.schema, name),
+23 -23
View File
@@ -2,11 +2,11 @@ import { unassignedCategoryLabel } from "../globals";
import { import {
World, World,
ControlsHelpers, ControlsHelpers,
AnnotationsHelpers AnnotationsHelpers,
} from "../util/stateManager"; } from "../util/stateManager";
import { import {
addObsLayout, addObsLayout,
removeObsLayout removeObsLayout,
} from "../util/stateManager/schemaHelpers"; } from "../util/stateManager/schemaHelpers";
import clip from "../util/clip"; import clip from "../util/clip";
import quantile from "../util/quantile"; import quantile from "../util/quantile";
@@ -45,14 +45,14 @@ const WorldReducer = (
if (dim == "varData" || dim == "obsAnnotations") { if (dim == "varData" || dim == "obsAnnotations") {
unclipped = { unclipped = {
...unclipped, ...unclipped,
[dim]: universe[dim].clone() [dim]: universe[dim].clone(),
}; };
} }
return { return {
...state, ...state,
schema: universe.schema, schema: universe.schema,
[dim]: universe[dim].clone(), [dim]: universe[dim].clone(),
unclipped unclipped,
}; };
} }
@@ -113,9 +113,9 @@ const WorldReducer = (
const allTheGenesWeNeed = [ const allTheGenesWeNeed = [
...new Set( ...new Set(
[userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)] [userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)]
.filter(ele => ele) .filter((ele) => ele)
.flat() .flat()
) ),
]; ];
unclippedVarData = ControlsHelpers.pruneVarDataCache( unclippedVarData = ControlsHelpers.pruneVarDataCache(
unclippedVarData, unclippedVarData,
@@ -130,14 +130,14 @@ const WorldReducer = (
let clippedVarData = state.varData; let clippedVarData = state.varData;
const keysToDrop = clippedVarData.colIndex const keysToDrop = clippedVarData.colIndex
.keys() .keys()
.filter(k => !unclippedVarData.hasCol(k)); .filter((k) => !unclippedVarData.hasCol(k));
const keysToAdd = unclippedVarData.colIndex const keysToAdd = unclippedVarData.colIndex
.keys() .keys()
.filter(k => !clippedVarData.hasCol(k)); .filter((k) => !clippedVarData.hasCol(k));
keysToDrop.forEach(k => { keysToDrop.forEach((k) => {
clippedVarData = clippedVarData.dropCol(k); clippedVarData = clippedVarData.dropCol(k);
}); });
keysToAdd.forEach(k => { keysToAdd.forEach((k) => {
const data = unclippedVarData.col(k).asArray(); const data = unclippedVarData.col(k).asArray();
const q = [state.clipQuantiles.min, state.clipQuantiles.max]; const q = [state.clipQuantiles.min, state.clipQuantiles.max];
const [qMinVal, qMaxVal] = quantile(q, data); const [qMinVal, qMaxVal] = quantile(q, data);
@@ -154,8 +154,8 @@ const WorldReducer = (
varData: clippedVarData, varData: clippedVarData,
unclipped: { unclipped: {
...state.unclipped, ...state.unclipped,
varData: unclippedVarData varData: unclippedVarData,
} },
}; };
} }
@@ -188,7 +188,7 @@ const WorldReducer = (
name, name,
newAnnotation, newAnnotation,
state.unclipped.obsAnnotations.rowIndex state.unclipped.obsAnnotations.rowIndex
) ),
}; };
return { ...state, schema, obsAnnotations, unclipped }; return { ...state, schema, obsAnnotations, unclipped };
} }
@@ -201,7 +201,7 @@ const WorldReducer = (
const obsAnnotations = state.obsAnnotations.renameCol(name, newName); const obsAnnotations = state.obsAnnotations.renameCol(name, newName);
const unclipped = { const unclipped = {
...state.unclipped, ...state.unclipped,
obsAnnotations: state.unclipped.obsAnnotations.renameCol(name, newName) obsAnnotations: state.unclipped.obsAnnotations.renameCol(name, newName),
}; };
return { ...state, schema, obsAnnotations, unclipped }; return { ...state, schema, obsAnnotations, unclipped };
} }
@@ -213,7 +213,7 @@ const WorldReducer = (
const obsAnnotations = state.obsAnnotations.dropCol(name); const obsAnnotations = state.obsAnnotations.dropCol(name);
const unclipped = { const unclipped = {
...state.unclipped, ...state.unclipped,
obsAnnotations: state.unclipped.obsAnnotations.dropCol(name) obsAnnotations: state.unclipped.obsAnnotations.dropCol(name),
}; };
return { ...state, schema, obsAnnotations, unclipped }; return { ...state, schema, obsAnnotations, unclipped };
} }
@@ -233,7 +233,7 @@ const WorldReducer = (
crossfilter, crossfilter,
metadataField, metadataField,
newLabelText newLabelText
) ),
}; };
} }
return { ...state, schema }; return { ...state, schema };
@@ -253,7 +253,7 @@ const WorldReducer = (
metadataField, metadataField,
oldLabelName, oldLabelName,
newLabelName newLabelName
) ),
}; };
const obsAnnotations = state.obsAnnotations.replaceColData( const obsAnnotations = state.obsAnnotations.replaceColData(
metadataField, metadataField,
@@ -274,7 +274,7 @@ const WorldReducer = (
metadataField, metadataField,
label, label,
unassignedCategoryLabel unassignedCategoryLabel
) ),
}; };
const obsAnnotations = state.obsAnnotations.replaceColData( const obsAnnotations = state.obsAnnotations.replaceColData(
metadataField, metadataField,
@@ -288,7 +288,7 @@ const WorldReducer = (
const { crossfilter } = prevSharedState; const { crossfilter } = prevSharedState;
return { return {
...state, ...state,
...setLabelOnCurrentSelection(state, crossfilter, metadataField, label) ...setLabelOnCurrentSelection(state, crossfilter, metadataField, label),
}; };
} }
@@ -306,14 +306,14 @@ const WorldReducer = (
const embedingLabels = embedding.colIndex.keys(); const embedingLabels = embedding.colIndex.keys();
const labels = { const labels = {
[embedingLabels[0]]: dims[0], [embedingLabels[0]]: dims[0],
[embedingLabels[1]]: dims[1] [embedingLabels[1]]: dims[1],
}; };
obsLayout = obsLayout.withColsFrom(embedding, labels); obsLayout = obsLayout.withColsFrom(embedding, labels);
schema = addObsLayout(schema, embeddingSchema); schema = addObsLayout(schema, embeddingSchema);
return { return {
...state, ...state,
obsLayout, obsLayout,
schema schema,
}; };
} }
@@ -334,7 +334,7 @@ const WorldReducer = (
return { return {
...state, ...state,
obsLayout, obsLayout,
schema schema,
}; };
} }
@@ -357,7 +357,7 @@ function setLabelOnCurrentSelection(world, crossfilter, metadataField, label) {
metadataField, metadataField,
mask, mask,
label label
) ),
}; };
const obsAnnotations = world.obsAnnotations.replaceColData( const obsAnnotations = world.obsAnnotations.replaceColData(
metadataField, metadataField,
+3 -3
View File
@@ -20,7 +20,7 @@ class Camera {
this.prevEvent = { this.prevEvent = {
clientX: 0, clientX: 0,
clientY: 0, clientY: 0,
type: 0 type: 0,
}; };
this.canvas = canvas; this.canvas = canvas;
this.viewMatrix = mat3.create(); this.viewMatrix = mat3.create();
@@ -43,11 +43,11 @@ class Camera {
const m = this.viewMatrix; const m = this.viewMatrix;
const dyRange = [ const dyRange = [
-panBound - (m[7] + 1) / m[4], -panBound - (m[7] + 1) / m[4],
panBound - (m[7] - 1) / m[4] panBound - (m[7] - 1) / m[4],
]; ];
const dxRange = [ const dxRange = [
-panBound - (m[6] + 1) / m[0], -panBound - (m[6] + 1) / m[0],
panBound - (m[6] - 1) / m[0] panBound - (m[6] - 1) / m[0],
]; ];
const dxClamped = clamp(dx, dxRange); const dxClamped = clamp(dx, dxRange);
+1 -1
View File
@@ -24,7 +24,7 @@ const catLabelSort = (isUserAnno, values) => {
const ints = []; const ints = [];
const unassignedOrNaN = []; const unassignedOrNaN = [];
values.forEach(v => { values.forEach((v) => {
if (isUserAnno && v === globals.unassignedCategoryLabel) { if (isUserAnno && v === globals.unassignedCategoryLabel) {
unassignedOrNaN.push(v); unassignedOrNaN.push(v);
} else if (String(v).toLowerCase() === "nan") { } else if (String(v).toLowerCase() === "nan") {
+1 -1
View File
@@ -70,7 +70,7 @@ const getCoordinatesByLabel = (
hasFinite: false, hasFinite: false,
xCoordinates: new Float32Array(numInLabel), xCoordinates: new Float32Array(numInLabel),
yCoordinates: new Float32Array(numInLabel), yCoordinates: new Float32Array(numInLabel),
length: 0 length: 0,
}; };
coordsByCategoryLabel.set(label, coords); coordsByCategoryLabel.set(label, coords);
} }
+11 -11
View File
@@ -11,15 +11,15 @@ If `setTo` is not undefined, values outside the [lower, upper] range will be set
*/ */
export default function clip(arr, lower, upper, setTo) { export default function clip(arr, lower, upper, setTo) {
const lowerSet = setTo === undefined ? lower : setTo; const lowerSet = setTo === undefined ? lower : setTo;
const upperSet = setTo === undefined ? upper : setTo; const upperSet = setTo === undefined ? upper : setTo;
for (let i = 0, l = arr.length; i < l; i += 1) { for (let i = 0, l = arr.length; i < l; i += 1) {
const v = arr[i]; const v = arr[i];
if (v < lower) { if (v < lower) {
arr[i] = lowerSet; arr[i] = lowerSet;
} else if (v > upper) { } else if (v > upper) {
arr[i] = upperSet; arr[i] = upperSet;
} }
} }
return arr; return arr;
} }
+12 -12
View File
@@ -5,17 +5,17 @@ import {
isTypedArray, isTypedArray,
isArrayOrTypedArray, isArrayOrTypedArray,
callOnceLazy, callOnceLazy,
memoize memoize,
} from "./util"; } from "./util";
import { import {
summarizeContinuous, summarizeContinuous,
summarizeCategorical as _summarizeCategorical summarizeCategorical as _summarizeCategorical,
} from "./summarize"; } from "./summarize";
import { import {
histogramCategorical as _histogramCategorical, histogramCategorical as _histogramCategorical,
hashCategorical, hashCategorical,
histogramContinuous, histogramContinuous,
hashContinuous hashContinuous,
} from "./histogram"; } from "./histogram";
/* /*
@@ -140,7 +140,7 @@ class Dataframe {
if (!Array.isArray(columnarData)) { if (!Array.isArray(columnarData)) {
throw new TypeError("Dataframe constructor requires array of columns"); throw new TypeError("Dataframe constructor requires array of columns");
} }
if (!columnarData.every(c => isArrayOrTypedArray(c))) { if (!columnarData.every((c) => isArrayOrTypedArray(c))) {
throw new TypeError("Dataframe columns must all be Array or TypedArray"); throw new TypeError("Dataframe columns must all be Array or TypedArray");
} }
if (!isLabelIndex(rowIndex)) { if (!isLabelIndex(rowIndex)) {
@@ -153,7 +153,7 @@ class Dataframe {
/* check for expected dimensionality / size */ /* check for expected dimensionality / size */
if ( if (
nCols !== columnarData.length || nCols !== columnarData.length ||
!columnarData.every(c => c.length === nRows) !columnarData.every((c) => c.length === nRows)
) { ) {
throw new RangeError( throw new RangeError(
"Dataframe dimension does not match provided data shape" "Dataframe dimension does not match provided data shape"
@@ -261,7 +261,7 @@ class Dataframe {
Create histogram bins for this column. Memoized. Create histogram bins for this column. Memoized.
*/ */
const _memoHistoCat = memoize(_histogramCategorical, hashCategorical); const _memoHistoCat = memoize(_histogramCategorical, hashCategorical);
const histogramCategorical = by => _memoHistoCat(get, by); const histogramCategorical = (by) => _memoHistoCat(get, by);
let histogram = null; let histogram = null;
if (isTypedArray(column)) { if (isTypedArray(column)) {
const mFn = memoize(histogramContinuous, hashContinuous); const mFn = memoize(histogramContinuous, hashContinuous);
@@ -293,7 +293,7 @@ class Dataframe {
*/ */
const { const {
getOffset: getRowByOffset, getOffset: getRowByOffset,
getLabel: getRowByLabel getLabel: getRowByLabel,
} = this.rowIndex; } = this.rowIndex;
this.__columnsAccessor = this.__columns.map((column, idx) => { this.__columnsAccessor = this.__columns.map((column, idx) => {
if (accessors[idx]) { if (accessors[idx]) {
@@ -423,7 +423,7 @@ class Dataframe {
// otherwise, bulid a new dataframe combining columns from both // otherwise, bulid a new dataframe combining columns from both
const srcOffsets = srcLabels.map(l => dataframe.colIndex.getOffset(l)); const srcOffsets = srcLabels.map((l) => dataframe.colIndex.getOffset(l));
// check for label collisions // check for label collisions
if (dstLabels.some(this.hasCol, this)) { if (dstLabels.some(this.hasCol, this)) {
@@ -435,12 +435,12 @@ class Dataframe {
const { rowIndex } = this; const { rowIndex } = this;
const columns = [ const columns = [
...this.__columns, ...this.__columns,
...srcOffsets.map(i => dataframe.__columns[i]) ...srcOffsets.map((i) => dataframe.__columns[i]),
]; ];
const colIndex = this.colIndex.withLabels(dstLabels); const colIndex = this.colIndex.withLabels(dstLabels);
const columnsAccessor = [ const columnsAccessor = [
...this.__columnsAccessor, ...this.__columnsAccessor,
...srcOffsets.map(i => dataframe.__columnsAccessor[i]) ...srcOffsets.map((i) => dataframe.__columnsAccessor[i]),
]; ];
return new this.constructor( return new this.constructor(
@@ -604,7 +604,7 @@ class Dataframe {
/* subset rows */ /* subset rows */
if (rowOffsets) { if (rowOffsets) {
columns = columns.map(col => { columns = columns.map((col) => {
const newCol = new col.constructor(rowOffsets.length); const newCol = new col.constructor(rowOffsets.length);
for (let i = 0, l = rowOffsets.length; i < l; i += 1) { for (let i = 0, l = rowOffsets.length; i < l; i += 1) {
newCol[i] = col[rowOffsets[i]]; newCol[i] = col[rowOffsets[i]];
@@ -630,7 +630,7 @@ class Dataframe {
if (!labels) { if (!labels) {
return null; return null;
} }
return labels.map(label => { return labels.map((label) => {
const off = index.getOffset(label); const off = index.getOffset(label);
if (off === undefined) { if (off === undefined) {
throw new RangeError(`unknown label: ${label}`); throw new RangeError(`unknown label: ${label}`);
+2 -2
View File
@@ -59,7 +59,7 @@ export function summarizeContinuous(col) {
nan, nan,
pinf, pinf,
ninf, ninf,
percentiles percentiles,
}; };
} }
@@ -77,6 +77,6 @@ export function summarizeCategorical(col) {
categorical: true, categorical: true,
categories: [...categoryCounts.keys()], categories: [...categoryCounts.keys()],
categoryCounts, categoryCounts,
numCategories: categoryCounts.size numCategories: categoryCounts.size,
}; };
} }

Some files were not shown because too many files have changed in this diff Show More