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