mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-02 12:18:11 +08:00
run prettier(2.0.5) (#1438)
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { strict as assert } from "assert";
|
import { strict as assert } from "assert";
|
||||||
|
|
||||||
export const cellxgeneActions = (page, utils) => ({
|
export const cellxgeneActions = (page, utils) => ({
|
||||||
|
|
||||||
async drag(testId, start, end, lasso = false) {
|
async drag(testId, start, end, lasso = false) {
|
||||||
const layout = await utils.waitByID(testId);
|
const layout = await utils.waitByID(testId);
|
||||||
const elBox = await layout.boxModel();
|
const elBox = await layout.boxModel();
|
||||||
@@ -31,22 +30,29 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async getAllHistograms(testclass, testIds) {
|
async getAllHistograms(testclass, testIds) {
|
||||||
const histTestIds = testIds.map(tid => `histogram-${tid}`);
|
const histTestIds = testIds.map((tid) => `histogram-${tid}`);
|
||||||
// these load asynchronously, so we need to wait for each histogram individually
|
// these load asynchronously, so we need to wait for each histogram individually
|
||||||
await utils.waitForAllByIds(histTestIds);
|
await utils.waitForAllByIds(histTestIds);
|
||||||
const allHistograms = await utils.getAllByClass(testclass);
|
const allHistograms = await utils.getAllByClass(testclass);
|
||||||
return allHistograms.map(hist => hist.replace(/^histogram-/, ""));
|
return allHistograms.map((hist) => hist.replace(/^histogram-/, ""));
|
||||||
},
|
},
|
||||||
|
|
||||||
async getAllCategoriesAndCounts(category) {
|
async getAllCategoriesAndCounts(category) {
|
||||||
await utils.waitByClass("categorical-row");
|
await utils.waitByClass("categorical-row");
|
||||||
return page.$$eval(
|
return page.$$eval(
|
||||||
`[data-testid="category-${category}"] [data-testclass='categorical-row']`,
|
`[data-testid="category-${category}"] [data-testclass='categorical-row']`,
|
||||||
rows => Object.fromEntries(rows.map(row => {
|
(rows) =>
|
||||||
const cat = row.querySelector("[data-testclass='categorical-value']").innerText;
|
Object.fromEntries(
|
||||||
const count = row.querySelector("[data-testclass='categorical-value-count']").innerText;
|
rows.map((row) => {
|
||||||
return [cat, count];
|
const cat = row.querySelector(
|
||||||
}))
|
"[data-testclass='categorical-value']"
|
||||||
|
).innerText;
|
||||||
|
const count = row.querySelector(
|
||||||
|
"[data-testclass='categorical-value-count']"
|
||||||
|
).innerText;
|
||||||
|
return [cat, count];
|
||||||
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -60,12 +66,14 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
await utils.waitByID(checkboxId);
|
await utils.waitByID(checkboxId);
|
||||||
const checkedPseudoclass = await page.$eval(
|
const checkedPseudoclass = await page.$eval(
|
||||||
`[data-testid='${checkboxId}']`,
|
`[data-testid='${checkboxId}']`,
|
||||||
el => el.matches(":checked")
|
(el) => el.matches(":checked")
|
||||||
);
|
);
|
||||||
if (!checkedPseudoclass) await utils.clickOn(checkboxId);
|
if (!checkedPseudoclass) await utils.clickOn(checkboxId);
|
||||||
try {
|
try {
|
||||||
const categoryRow = await utils.waitByID(`${category}:category-expand`);
|
const categoryRow = await utils.waitByID(`${category}:category-expand`);
|
||||||
const isExpanded = await categoryRow.$("[data-testclass='category-expand-is-expanded']");
|
const isExpanded = await categoryRow.$(
|
||||||
|
"[data-testclass='category-expand-is-expanded']"
|
||||||
|
);
|
||||||
if (isExpanded) await utils.clickOn(`${category}:category-expand`);
|
if (isExpanded) await utils.clickOn(`${category}:category-expand`);
|
||||||
} catch {}
|
} catch {}
|
||||||
},
|
},
|
||||||
@@ -75,14 +83,22 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
const size = await el.boxModel();
|
const size = await el.boxModel();
|
||||||
return {
|
return {
|
||||||
x: Math.floor(size.width * xAsPercent),
|
x: Math.floor(size.width * xAsPercent),
|
||||||
y: Math.floor(size.height * yAsPercent)
|
y: Math.floor(size.height * yAsPercent),
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
async calcDragCoordinates(testId, coordinateAsPercent) {
|
async calcDragCoordinates(testId, coordinateAsPercent) {
|
||||||
return {
|
return {
|
||||||
start: await this.calcCoordinate(testId, coordinateAsPercent.x1, coordinateAsPercent.y1),
|
start: await this.calcCoordinate(
|
||||||
end: await this.calcCoordinate(testId, coordinateAsPercent.x2, coordinateAsPercent.y2)
|
testId,
|
||||||
|
coordinateAsPercent.x1,
|
||||||
|
coordinateAsPercent.y1
|
||||||
|
),
|
||||||
|
end: await this.calcCoordinate(
|
||||||
|
testId,
|
||||||
|
coordinateAsPercent.x2,
|
||||||
|
coordinateAsPercent.y2
|
||||||
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -97,7 +113,9 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
|
|
||||||
async expandCategory(category) {
|
async expandCategory(category) {
|
||||||
const expand = await utils.waitByID(`${category}:category-expand`);
|
const expand = await utils.waitByID(`${category}:category-expand`);
|
||||||
const notExpanded = await expand.$("[data-testclass='category-expand-is-not-expanded']");
|
const notExpanded = await expand.$(
|
||||||
|
"[data-testclass='category-expand-is-not-expanded']"
|
||||||
|
);
|
||||||
if (notExpanded) await utils.clickOn(`${category}:category-expand`);
|
if (notExpanded) await utils.clickOn(`${category}:category-expand`);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -117,7 +135,10 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
async renameCategory(oldCatgoryName, newCategoryName) {
|
async renameCategory(oldCatgoryName, newCategoryName) {
|
||||||
await utils.clickOn(`${oldCatgoryName}:see-actions`);
|
await utils.clickOn(`${oldCatgoryName}:see-actions`);
|
||||||
await utils.clickOn(`${oldCatgoryName}:edit-category-mode`);
|
await utils.clickOn(`${oldCatgoryName}:edit-category-mode`);
|
||||||
await utils.clearInputAndTypeInto(`${oldCatgoryName}:edit-category-name-text`, newCategoryName);
|
await utils.clearInputAndTypeInto(
|
||||||
|
`${oldCatgoryName}:edit-category-name-text`,
|
||||||
|
newCategoryName
|
||||||
|
);
|
||||||
await utils.clickOn(`${oldCatgoryName}:submit-category-edit`);
|
await utils.clickOn(`${oldCatgoryName}:submit-category-edit`);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -136,7 +157,7 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
async deleteLabel(categoryName, labelName) {
|
async deleteLabel(categoryName, labelName) {
|
||||||
await this.expandCategory(categoryName);
|
await this.expandCategory(categoryName);
|
||||||
await utils.clickOn(`${categoryName}:${labelName}:see-actions`);
|
await utils.clickOn(`${categoryName}:${labelName}:see-actions`);
|
||||||
await utils.clickOn( `${categoryName}:${labelName}:delete-label`);
|
await utils.clickOn(`${categoryName}:${labelName}:delete-label`);
|
||||||
},
|
},
|
||||||
|
|
||||||
async renameLabel(categoryName, oldLabelName, newLabelName) {
|
async renameLabel(categoryName, oldLabelName, newLabelName) {
|
||||||
@@ -153,17 +174,23 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
async addGeneToSearch(geneName) {
|
async addGeneToSearch(geneName) {
|
||||||
await utils.typeInto("gene-search", geneName);
|
await utils.typeInto("gene-search", geneName);
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press("Enter");
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(`[data-testid='histogram-${geneName}']`);
|
||||||
`[data-testid='histogram-${geneName}']`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async subset(coordinatesAsPercent) {
|
async subset(coordinatesAsPercent) {
|
||||||
// In order to deselect the selection after the subset, make sure we have some clear part
|
// In order to deselect the selection after the subset, make sure we have some clear part
|
||||||
// of the scatterplot we can click on
|
// of the scatterplot we can click on
|
||||||
assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
|
assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
|
||||||
const lassoSelection = await this.calcDragCoordinates( "layout-graph", coordinatesAsPercent);
|
const lassoSelection = await this.calcDragCoordinates(
|
||||||
await this.drag("layout-graph", lassoSelection.start, lassoSelection.end, true );
|
"layout-graph",
|
||||||
|
coordinatesAsPercent
|
||||||
|
);
|
||||||
|
await this.drag(
|
||||||
|
"layout-graph",
|
||||||
|
lassoSelection.start,
|
||||||
|
lassoSelection.end,
|
||||||
|
true
|
||||||
|
);
|
||||||
await utils.clickOn("subset-button");
|
await utils.clickOn("subset-button");
|
||||||
const clearCoordinate = await this.calcCoordinate(
|
const clearCoordinate = await this.calcCoordinate(
|
||||||
"layout-graph",
|
"layout-graph",
|
||||||
@@ -174,7 +201,9 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async setSellSet(cellSet, cellSetNum) {
|
async setSellSet(cellSet, cellSetNum) {
|
||||||
for (const selection of cellSet.filter(sel => sel.kind === "categorical")) {
|
for (const selection of cellSet.filter(
|
||||||
|
(sel) => sel.kind === "categorical"
|
||||||
|
)) {
|
||||||
await this.selectCategory(selection.metadata, selection.values, true);
|
await this.selectCategory(selection.metadata, selection.values, true);
|
||||||
}
|
}
|
||||||
await this.cellSet(cellSetNum);
|
await this.cellSet(cellSetNum);
|
||||||
@@ -190,7 +219,5 @@ export const cellxgeneActions = (page, utils) => ({
|
|||||||
await utils.clickOn("section-bulk-add");
|
await utils.clickOn("section-bulk-add");
|
||||||
await utils.typeInto("input-bulk-add", geneNames.join(","));
|
await utils.typeInto("input-bulk-add", geneNames.join(","));
|
||||||
await page.keyboard.press("Enter");
|
await page.keyboard.press("Enter");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export const jest_env = process.env.JEST_ENV;
|
export const jest_env = process.env.JEST_ENV;
|
||||||
export const appPort = process.env.CXG_SERVER_PORT;
|
export const appPort = process.env.CXG_SERVER_PORT;
|
||||||
export const appUrlBase = process.env.CXG_URL_BASE || `http://localhost:${appPort}`;
|
export const appUrlBase =
|
||||||
|
process.env.CXG_URL_BASE || `http://localhost:${appPort}`;
|
||||||
export const DEV = jest_env === "dev";
|
export const DEV = jest_env === "dev";
|
||||||
export const DEBUG = jest_env === "debug";
|
export const DEBUG = jest_env === "debug";
|
||||||
export const DATASET = "pbmc3k";
|
export const DATASET = "pbmc3k";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const datasets = {
|
|||||||
dataframe: {
|
dataframe: {
|
||||||
nObs: "2638",
|
nObs: "2638",
|
||||||
nVar: "1838",
|
nVar: "1838",
|
||||||
type: "float32"
|
type: "float32",
|
||||||
},
|
},
|
||||||
categorical: {
|
categorical: {
|
||||||
louvain: {
|
louvain: {
|
||||||
@@ -15,47 +15,47 @@ export const datasets = {
|
|||||||
"Dendritic cells": "37",
|
"Dendritic cells": "37",
|
||||||
"FCGR3A+ Monocytes": "150",
|
"FCGR3A+ Monocytes": "150",
|
||||||
Megakaryocytes: "15",
|
Megakaryocytes: "15",
|
||||||
"NK cells": "154"
|
"NK cells": "154",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
continuous: {
|
continuous: {
|
||||||
n_genes: "int32",
|
n_genes: "int32",
|
||||||
percent_mito: "float32",
|
percent_mito: "float32",
|
||||||
n_counts: "float32"
|
n_counts: "float32",
|
||||||
},
|
},
|
||||||
cellsets: {
|
cellsets: {
|
||||||
lasso: [
|
lasso: [
|
||||||
{
|
{
|
||||||
"coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 },
|
"coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 },
|
||||||
count: "1181"
|
count: "1181",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
categorical: [
|
categorical: [
|
||||||
{
|
{
|
||||||
metadata: "louvain",
|
metadata: "louvain",
|
||||||
values: ["B cells", "Megakaryocytes"],
|
values: ["B cells", "Megakaryocytes"],
|
||||||
count: "357"
|
count: "357",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
continuous: [
|
continuous: [
|
||||||
{
|
{
|
||||||
metadata: "n_genes",
|
metadata: "n_genes",
|
||||||
"coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 },
|
"coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 },
|
||||||
count: "1537"
|
count: "1537",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
diffexp: {
|
diffexp: {
|
||||||
cellset1: [
|
cellset1: [
|
||||||
{ kind: "categorical", metadata: "louvain", values: ["B cells"] }
|
{ kind: "categorical", metadata: "louvain", values: ["B cells"] },
|
||||||
],
|
],
|
||||||
cellset2: [
|
cellset2: [
|
||||||
{
|
{
|
||||||
kind: "categorical",
|
kind: "categorical",
|
||||||
metadata: "louvain",
|
metadata: "louvain",
|
||||||
values: ["CD4 T cells", "NK cells"]
|
values: ["CD4 T cells", "NK cells"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"gene-results": [
|
"gene-results": [
|
||||||
"HLA-DRB1",
|
"HLA-DRB1",
|
||||||
@@ -67,21 +67,21 @@ export const datasets = {
|
|||||||
"HLA-DQB1",
|
"HLA-DQB1",
|
||||||
"MS4A1",
|
"MS4A1",
|
||||||
"IL32",
|
"IL32",
|
||||||
"CD37"
|
"CD37",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
genes: {
|
genes: {
|
||||||
bulkadd: ["S100A8", "FCGR3A", "LGALS2", "GSTP1"],
|
bulkadd: ["S100A8", "FCGR3A", "LGALS2", "GSTP1"],
|
||||||
search: "ACD"
|
search: "ACD",
|
||||||
},
|
},
|
||||||
subset: {
|
subset: {
|
||||||
cellset1: [
|
cellset1: [
|
||||||
{
|
{
|
||||||
kind: "categorical",
|
kind: "categorical",
|
||||||
metadata: "louvain",
|
metadata: "louvain",
|
||||||
values: ["B cells", "Megakaryocytes"]
|
values: ["B cells", "Megakaryocytes"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
count: "357",
|
count: "357",
|
||||||
categorical: {
|
categorical: {
|
||||||
@@ -93,27 +93,27 @@ export const datasets = {
|
|||||||
"Dendritic cells": "0",
|
"Dendritic cells": "0",
|
||||||
"FCGR3A+ Monocytes": "0",
|
"FCGR3A+ Monocytes": "0",
|
||||||
Megakaryocytes: "15",
|
Megakaryocytes: "15",
|
||||||
"NK cells": "0"
|
"NK cells": "0",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
lasso: {
|
lasso: {
|
||||||
"coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 },
|
"coordinates-as-percent": { x1: 0.25, y1: 0.05, x2: 0.75, y2: 0.55 },
|
||||||
count: "329"
|
count: "329",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
scatter: {
|
scatter: {
|
||||||
genes: { x: "S100A8", y: "FCGR3A" }
|
genes: { x: "S100A8", y: "FCGR3A" },
|
||||||
},
|
},
|
||||||
pan: {
|
pan: {
|
||||||
"coordinates-as-percent": { x1: 0.75, y1: 0.75, x2: 0.35, y2: 0.35 }
|
"coordinates-as-percent": { x1: 0.75, y1: 0.75, x2: 0.35, y2: 0.35 },
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
panzoom: {
|
panzoom: {
|
||||||
lasso: {
|
lasso: {
|
||||||
"coordinates-as-percent": { x1: 0.3, y1: 0.3, x2: 0.5, y2: 0.5 },
|
"coordinates-as-percent": { x1: 0.3, y1: 0.3, x2: 0.5, y2: 0.5 },
|
||||||
count: "24"
|
count: "24",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
categoryLabel: {
|
categoryLabel: {
|
||||||
lasso: {
|
lasso: {
|
||||||
@@ -122,17 +122,17 @@ export const datasets = {
|
|||||||
newCount: {
|
newCount: {
|
||||||
bySubsetConfig: {
|
bySubsetConfig: {
|
||||||
false: "600",
|
false: "600",
|
||||||
true: "591"
|
true: "591",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
annotationsFromFile: {
|
annotationsFromFile: {
|
||||||
count: {
|
count: {
|
||||||
bySubsetConfig: {
|
bySubsetConfig: {
|
||||||
false: "1161",
|
false: "1161",
|
||||||
true: "856"
|
true: "856",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
clip: {
|
clip: {
|
||||||
min: "30",
|
min: "30",
|
||||||
@@ -141,7 +141,7 @@ export const datasets = {
|
|||||||
gene: "S100A8",
|
gene: "S100A8",
|
||||||
"coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 },
|
"coordinates-as-percent": { x1: 0.25, y1: 0.5, x2: 0.55, y2: 0.5 },
|
||||||
count: "386",
|
count: "386",
|
||||||
"gene-cell-count": "416"
|
"gene-cell-count": "416",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ afterAll(() => {
|
|||||||
|
|
||||||
describe("did launch", () => {
|
describe("did launch", () => {
|
||||||
test("page launched", async () => {
|
test("page launched", async () => {
|
||||||
const element = await utils.getOneElementInnerHTML("[data-testid='header']");
|
const element = await utils.getOneElementInnerHTML(
|
||||||
|
"[data-testid='header']"
|
||||||
|
);
|
||||||
expect(element).toBe(data.title);
|
expect(element).toBe(data.title);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -32,7 +34,9 @@ describe("did launch", () => {
|
|||||||
describe("metadata loads", () => {
|
describe("metadata loads", () => {
|
||||||
test("categories and values from dataset appear", async () => {
|
test("categories and values from dataset appear", async () => {
|
||||||
for (const label in data.categorical) {
|
for (const label in data.categorical) {
|
||||||
const categoryName = await utils.getOneElementInnerText(`[data-testid="category-${label}"]`);
|
const categoryName = await utils.getOneElementInnerText(
|
||||||
|
`[data-testid="category-${label}"]`
|
||||||
|
);
|
||||||
expect(categoryName).toMatch(label);
|
expect(categoryName).toMatch(label);
|
||||||
await utils.clickOn(`${label}:category-expand`);
|
await utils.clickOn(`${label}:category-expand`);
|
||||||
const categories = await cxgActions.getAllCategoriesAndCounts(label);
|
const categories = await cxgActions.getAllCategoriesAndCounts(label);
|
||||||
@@ -50,7 +54,6 @@ describe("metadata loads", () => {
|
|||||||
await utils.waitByID(`histogram-${label}`);
|
await utils.waitByID(`histogram-${label}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("cell selection", () => {
|
describe("cell selection", () => {
|
||||||
@@ -81,7 +84,9 @@ describe("cell selection", () => {
|
|||||||
await utils.clickOn(`${cellset.metadata}:category-expand`);
|
await utils.clickOn(`${cellset.metadata}:category-expand`);
|
||||||
await utils.clickOn(`${cellset.metadata}:category-select`);
|
await utils.clickOn(`${cellset.metadata}:category-select`);
|
||||||
for (const val of cellset.values) {
|
for (const val of cellset.values) {
|
||||||
await utils.clickOn(`categorical-value-select-${cellset.metadata}-${val}`);
|
await utils.clickOn(
|
||||||
|
`categorical-value-select-${cellset.metadata}-${val}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const cellCount = await cxgActions.cellSet(1);
|
const cellCount = await cxgActions.cellSet(1);
|
||||||
expect(cellCount).toBe(cellset.count);
|
expect(cellCount).toBe(cellset.count);
|
||||||
@@ -103,12 +108,16 @@ describe("cell selection", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("gene entry", () => {
|
describe("gene entry", () => {
|
||||||
test("search for single gene", async () => cxgActions.addGeneToSearch(data.genes.search));
|
test("search for single gene", async () =>
|
||||||
|
cxgActions.addGeneToSearch(data.genes.search));
|
||||||
|
|
||||||
test("bulk add genes", async () => {
|
test("bulk add genes", async () => {
|
||||||
const testGenes = data.genes.bulkadd;
|
const testGenes = data.genes.bulkadd;
|
||||||
await cxgActions.bulkAddGenes(testGenes);
|
await cxgActions.bulkAddGenes(testGenes);
|
||||||
const allHistograms = await cxgActions.getAllHistograms("histogram-user-gene", testGenes);
|
const allHistograms = await cxgActions.getAllHistograms(
|
||||||
|
"histogram-user-gene",
|
||||||
|
testGenes
|
||||||
|
);
|
||||||
expect(allHistograms).toEqual(expect.arrayContaining(testGenes));
|
expect(allHistograms).toEqual(expect.arrayContaining(testGenes));
|
||||||
expect(allHistograms.length).toEqual(testGenes.length);
|
expect(allHistograms.length).toEqual(testGenes.length);
|
||||||
});
|
});
|
||||||
@@ -172,11 +181,19 @@ describe("subset", () => {
|
|||||||
const userDefinedGenes = data.genes.bulkadd;
|
const userDefinedGenes = data.genes.bulkadd;
|
||||||
const diffExpGenes = data.diffexp["gene-results"];
|
const diffExpGenes = data.diffexp["gene-results"];
|
||||||
await cxgActions.bulkAddGenes(userDefinedGenes);
|
await cxgActions.bulkAddGenes(userDefinedGenes);
|
||||||
const userDefinedHistograms = await cxgActions.getAllHistograms("histogram-user-gene", userDefinedGenes);
|
const userDefinedHistograms = await cxgActions.getAllHistograms(
|
||||||
expect(userDefinedHistograms).toEqual(expect.arrayContaining(userDefinedGenes));
|
"histogram-user-gene",
|
||||||
await cxgActions.subset({x1: 0.15, y1: 0.10, x2: 0.98, y2: 0.98});
|
userDefinedGenes
|
||||||
|
);
|
||||||
|
expect(userDefinedHistograms).toEqual(
|
||||||
|
expect.arrayContaining(userDefinedGenes)
|
||||||
|
);
|
||||||
|
await cxgActions.subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 });
|
||||||
await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
||||||
const diffExpHistograms = await cxgActions.getAllHistograms("histogram-diffexp", diffExpGenes);
|
const diffExpHistograms = await cxgActions.getAllHistograms(
|
||||||
|
"histogram-diffexp",
|
||||||
|
diffExpGenes
|
||||||
|
);
|
||||||
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
|
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
|
||||||
await utils.clickOn("reset-subset-button");
|
await utils.clickOn("reset-subset-button");
|
||||||
const expected = [].concat(userDefinedGenes, diffExpGenes);
|
const expected = [].concat(userDefinedGenes, diffExpGenes);
|
||||||
@@ -184,26 +201,38 @@ describe("subset", () => {
|
|||||||
"histogram-user-gene",
|
"histogram-user-gene",
|
||||||
expected
|
expected
|
||||||
);
|
);
|
||||||
expect(userDefinedHistogramsAfterSubset).toEqual(expect.arrayContaining(expected));
|
expect(userDefinedHistogramsAfterSubset).toEqual(
|
||||||
|
expect.arrayContaining(expected)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("subset selection appends the top diff exp genes to user defined genes", async () => {
|
test("subset selection appends the top diff exp genes to user defined genes", async () => {
|
||||||
const userDefinedGenes = data.genes.bulkadd;
|
const userDefinedGenes = data.genes.bulkadd;
|
||||||
const diffExpGenes = data.diffexp["gene-results"];
|
const diffExpGenes = data.diffexp["gene-results"];
|
||||||
await cxgActions.bulkAddGenes(userDefinedGenes);
|
await cxgActions.bulkAddGenes(userDefinedGenes);
|
||||||
const userDefinedHistograms = await cxgActions.getAllHistograms("histogram-user-gene", userDefinedGenes);
|
const userDefinedHistograms = await cxgActions.getAllHistograms(
|
||||||
expect(userDefinedHistograms).toEqual(expect.arrayContaining(userDefinedGenes));
|
"histogram-user-gene",
|
||||||
await cxgActions.subset({x1: 0.15, y1: 0.10, x2: 0.98, y2: 0.98});
|
userDefinedGenes
|
||||||
|
);
|
||||||
|
expect(userDefinedHistograms).toEqual(
|
||||||
|
expect.arrayContaining(userDefinedGenes)
|
||||||
|
);
|
||||||
|
await cxgActions.subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 });
|
||||||
await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2);
|
||||||
const diffExpHistograms = await cxgActions.getAllHistograms("histogram-diffexp", diffExpGenes);
|
const diffExpHistograms = await cxgActions.getAllHistograms(
|
||||||
|
"histogram-diffexp",
|
||||||
|
diffExpGenes
|
||||||
|
);
|
||||||
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
|
expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes));
|
||||||
await cxgActions.subset({x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97});
|
await cxgActions.subset({ x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97 });
|
||||||
const expected = [].concat(userDefinedGenes, diffExpGenes);
|
const expected = [].concat(userDefinedGenes, diffExpGenes);
|
||||||
const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms(
|
const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms(
|
||||||
"histogram-user-gene",
|
"histogram-user-gene",
|
||||||
expected
|
expected
|
||||||
);
|
);
|
||||||
expect(userDefinedHistogramsAfterSubset).toEqual(expect.arrayContaining(expected));
|
expect(userDefinedHistogramsAfterSubset).toEqual(
|
||||||
|
expect.arrayContaining(expected)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ beforeAll(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
if (browser !== undefined) browser.close()
|
if (browser !== undefined) browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
{withSubset: true, tag: "subset"},
|
{ withSubset: true, tag: "subset" },
|
||||||
{withSubset: false, tag: "whole"}
|
{ withSubset: false, tag: "whole" },
|
||||||
])("annotations", (config) => {
|
])("annotations", (config) => {
|
||||||
|
|
||||||
const perTestCategoryName = "per-test-category";
|
const perTestCategoryName = "per-test-category";
|
||||||
const perTestLabelName = "per-test-label";
|
const perTestLabelName = "per-test-label";
|
||||||
|
|
||||||
@@ -31,7 +30,8 @@ describe.each([
|
|||||||
// setup the test fixtures
|
// setup the test fixtures
|
||||||
await actions.createCategory(perTestCategoryName);
|
await actions.createCategory(perTestCategoryName);
|
||||||
await actions.createLabel(perTestCategoryName, perTestLabelName);
|
await actions.createLabel(perTestCategoryName, perTestLabelName);
|
||||||
if (config.withSubset) await actions.subset({x1: 0.10, y1: 0.10, x2: 0.80, y2: 0.80});
|
if (config.withSubset)
|
||||||
|
await actions.subset({ x1: 0.1, y1: 0.1, x2: 0.8, y2: 0.8 });
|
||||||
await utils.waitByClass("autosave-complete");
|
await utils.waitByClass("autosave-complete");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,7 +74,11 @@ describe.each([
|
|||||||
test("rename a label", async () => {
|
test("rename a label", async () => {
|
||||||
const newLabelName = "my-cool-new-label";
|
const newLabelName = "my-cool-new-label";
|
||||||
await assertLabelDoesNotExist(perTestCategoryName, newLabelName);
|
await assertLabelDoesNotExist(perTestCategoryName, newLabelName);
|
||||||
await actions.renameLabel(perTestCategoryName, perTestLabelName, newLabelName);
|
await actions.renameLabel(
|
||||||
|
perTestCategoryName,
|
||||||
|
perTestLabelName,
|
||||||
|
newLabelName
|
||||||
|
);
|
||||||
await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName);
|
await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName);
|
||||||
await assertLabelExists(perTestCategoryName, newLabelName);
|
await assertLabelExists(perTestCategoryName, newLabelName);
|
||||||
});
|
});
|
||||||
@@ -83,8 +87,10 @@ describe.each([
|
|||||||
const categoryName = "cluster-test";
|
const categoryName = "cluster-test";
|
||||||
const labelName = "four";
|
const labelName = "four";
|
||||||
await actions.expandCategory(categoryName);
|
await actions.expandCategory(categoryName);
|
||||||
const result = await utils.waitByID(`categorical-value-count-${categoryName}-${labelName}`);
|
const result = await utils.waitByID(
|
||||||
expect(await result.evaluate(node => node.innerText)).toBe(
|
`categorical-value-count-${categoryName}-${labelName}`
|
||||||
|
);
|
||||||
|
expect(await result.evaluate((node) => node.innerText)).toBe(
|
||||||
data.annotationsFromFile.count.bySubsetConfig[config.withSubset]
|
data.annotationsFromFile.count.bySubsetConfig[config.withSubset]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -101,11 +107,17 @@ describe.each([
|
|||||||
lassoSelection.end,
|
lassoSelection.end,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
await utils.waitByID("lasso-element", {visible: true});
|
await utils.waitByID("lasso-element", { visible: true });
|
||||||
await utils.clickOn(`${perTestCategoryName}:${perTestLabelName}:see-actions`);
|
await utils.clickOn(
|
||||||
await utils.clickOn(`${perTestCategoryName}:${perTestLabelName}:add-current-selection-to-this-label`);
|
`${perTestCategoryName}:${perTestLabelName}:see-actions`
|
||||||
const result = await utils.waitByID(`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`);
|
);
|
||||||
expect(await result.evaluate(node => node.innerText)).toBe(
|
await utils.clickOn(
|
||||||
|
`${perTestCategoryName}:${perTestLabelName}:add-current-selection-to-this-label`
|
||||||
|
);
|
||||||
|
const result = await utils.waitByID(
|
||||||
|
`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`
|
||||||
|
);
|
||||||
|
expect(await result.evaluate((node) => node.innerText)).toBe(
|
||||||
data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
|
data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -170,7 +182,11 @@ describe.each([
|
|||||||
test("undo/redo label rename", async () => {
|
test("undo/redo label rename", async () => {
|
||||||
const newLabelName = `label-renamed-undo-${config.tag}`;
|
const newLabelName = `label-renamed-undo-${config.tag}`;
|
||||||
await assertLabelDoesNotExist(perTestCategoryName, newLabelName);
|
await assertLabelDoesNotExist(perTestCategoryName, newLabelName);
|
||||||
await actions.renameLabel(perTestCategoryName, perTestLabelName, newLabelName);
|
await actions.renameLabel(
|
||||||
|
perTestCategoryName,
|
||||||
|
perTestLabelName,
|
||||||
|
newLabelName
|
||||||
|
);
|
||||||
await assertLabelExists(perTestCategoryName, newLabelName);
|
await assertLabelExists(perTestCategoryName, newLabelName);
|
||||||
await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName);
|
await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName);
|
||||||
await utils.clickOn("undo");
|
await utils.clickOn("undo");
|
||||||
@@ -183,14 +199,16 @@ describe.each([
|
|||||||
|
|
||||||
async function assertCategoryExists(categoryName) {
|
async function assertCategoryExists(categoryName) {
|
||||||
const handle = await utils.waitByID(`${categoryName}:category-expand`);
|
const handle = await utils.waitByID(`${categoryName}:category-expand`);
|
||||||
const result = await handle.evaluate(node => node.innerText);
|
const result = await handle.evaluate((node) => node.innerText);
|
||||||
// slice beginning and end of category name result to account for truncation of long names
|
// slice beginning and end of category name result to account for truncation of long names
|
||||||
expect(result.slice(0, 10)).toBe(categoryName.slice(0, 10));
|
expect(result.slice(0, 10)).toBe(categoryName.slice(0, 10));
|
||||||
expect(result.slice(-10)).toBe(categoryName.slice(-10));
|
expect(result.slice(-10)).toBe(categoryName.slice(-10));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertCategoryDoesNotExist(categoryName) {
|
async function assertCategoryDoesNotExist(categoryName) {
|
||||||
const result = await page.$(`[data-testid='${categoryName}:category-expand']`);
|
const result = await page.$(
|
||||||
|
`[data-testid='${categoryName}:category-expand']`
|
||||||
|
);
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,13 +216,17 @@ describe.each([
|
|||||||
const category = await utils.waitByID(`${categoryName}:category-expand`);
|
const category = await utils.waitByID(`${categoryName}:category-expand`);
|
||||||
expect(category).not.toBeNull();
|
expect(category).not.toBeNull();
|
||||||
await actions.expandCategory(categoryName);
|
await actions.expandCategory(categoryName);
|
||||||
const previous = await utils.waitByID(`categorical-value-${categoryName}-${labelName}`);
|
const previous = await utils.waitByID(
|
||||||
expect(await previous.evaluate(node => node.innerText)).toBe(labelName);
|
`categorical-value-${categoryName}-${labelName}`
|
||||||
|
);
|
||||||
|
expect(await previous.evaluate((node) => node.innerText)).toBe(labelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertLabelDoesNotExist(categoryName, labelName) {
|
async function assertLabelDoesNotExist(categoryName, labelName) {
|
||||||
await actions.expandCategory(categoryName);
|
await actions.expandCategory(categoryName);
|
||||||
const result = await page.$(`[data-testid='categorical-value-${categoryName}-${labelName}']`);
|
const result = await page.$(
|
||||||
|
`[data-testid='categorical-value-${categoryName}-${labelName}']`
|
||||||
|
);
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,10 +234,10 @@ describe.each([
|
|||||||
try {
|
try {
|
||||||
const category = await page.waitForSelector(
|
const category = await page.waitForSelector(
|
||||||
`[data-testid='${categoryName}:category-expand']`,
|
`[data-testid='${categoryName}:category-expand']`,
|
||||||
{timeout: 200}
|
{ timeout: 200 }
|
||||||
);
|
);
|
||||||
if (category !== null) return await actions.deleteCategory(categoryName);
|
if (category !== null) return await actions.deleteCategory(categoryName);
|
||||||
} catch {}
|
} catch {}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"preset": "jest-puppeteer",
|
"preset": "jest-puppeteer",
|
||||||
"testMatch": [
|
"testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"],
|
||||||
"**/__tests__/**/?(*.)(spec|test).js?(x)"
|
"setupFiles": ["../setupMissingGlobals.js"]
|
||||||
],
|
|
||||||
"setupFiles": [
|
|
||||||
"../setupMissingGlobals.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ beforeAll(async () => {
|
|||||||
page = await browser.newPage();
|
page = await browser.newPage();
|
||||||
await page.setViewport(browserViewport);
|
await page.setViewport(browserViewport);
|
||||||
if (DEV || DEBUG) {
|
if (DEV || DEBUG) {
|
||||||
page.on("console", msg => console.log(`PAGE LOG: ${msg.text()}`));
|
page.on("console", (msg) => console.log(`PAGE LOG: ${msg.text()}`));
|
||||||
}
|
}
|
||||||
page.on("pageerror", err => {
|
page.on("pageerror", (err) => {
|
||||||
throw new Error(`Console error: ${err}`);
|
throw new Error(`Console error: ${err}`);
|
||||||
});
|
});
|
||||||
utils = puppeteerUtils(page);
|
utils = puppeteerUtils(page);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const puppeteerUtils = page => ({
|
export const puppeteerUtils = (page) => ({
|
||||||
async waitByID(testId, props = {}) {
|
async waitByID(testId, props = {}) {
|
||||||
return page.waitForSelector(`[data-testid='${testId}']`, props);
|
return page.waitForSelector(`[data-testid='${testId}']`, props);
|
||||||
},
|
},
|
||||||
@@ -9,13 +9,13 @@ export const puppeteerUtils = page => ({
|
|||||||
|
|
||||||
async waitForAllByIds(testIds) {
|
async waitForAllByIds(testIds) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
testIds.map(testId => page.waitForSelector(`[data-testid='${testId}']`))
|
testIds.map((testId) => page.waitForSelector(`[data-testid='${testId}']`))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
async getAllByClass(testClass) {
|
async getAllByClass(testClass) {
|
||||||
return page.$$eval(`[data-testclass=${testClass}]`, eles =>
|
return page.$$eval(`[data-testclass=${testClass}]`, (eles) =>
|
||||||
eles.map(ele => ele.dataset.testid)
|
eles.map((ele) => ele.dataset.testid)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -52,18 +52,18 @@ export const puppeteerUtils = page => ({
|
|||||||
|
|
||||||
async getOneElementInnerHTML(selector) {
|
async getOneElementInnerHTML(selector) {
|
||||||
await page.waitForSelector(selector);
|
await page.waitForSelector(selector);
|
||||||
return page.$eval(selector, el => el.innerHTML);
|
return page.$eval(selector, (el) => el.innerHTML);
|
||||||
},
|
},
|
||||||
|
|
||||||
async getOneElementInnerText(selector) {
|
async getOneElementInnerText(selector) {
|
||||||
await page.waitForSelector(selector);
|
await page.waitForSelector(selector);
|
||||||
return page.$eval(selector, el => el.innerText);
|
return page.$eval(selector, (el) => el.innerText);
|
||||||
},
|
},
|
||||||
|
|
||||||
async getElementCoordinates(testid) {
|
async getElementCoordinates(testid) {
|
||||||
return page.$eval(`[data-testid='${testid}']`, elem => {
|
return page.$eval(`[data-testid='${testid}']`, (elem) => {
|
||||||
const { left, top } = elem.getBoundingClientRect();
|
const { left, top } = elem.getBoundingClientRect();
|
||||||
return [left, top];
|
return [left, top];
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,41 +7,48 @@ export async function setupTestBrowser() {
|
|||||||
const browserViewport = { width: 1280, height: 960 };
|
const browserViewport = { width: 1280, height: 960 };
|
||||||
const browserParams = DEV
|
const browserParams = DEV
|
||||||
? {
|
? {
|
||||||
headless: false,
|
headless: false,
|
||||||
slowMo: 5,
|
slowMo: 5,
|
||||||
args: [ `--window-size=${browserViewport.width},${browserViewport.height}`]
|
args: [
|
||||||
}
|
`--window-size=${browserViewport.width},${browserViewport.height}`,
|
||||||
|
],
|
||||||
|
}
|
||||||
: DEBUG
|
: DEBUG
|
||||||
? {
|
? {
|
||||||
headless: false,
|
headless: false,
|
||||||
slowMo: 100,
|
slowMo: 100,
|
||||||
devtools: true,
|
devtools: true,
|
||||||
args: [ `--window-size=${browserViewport.width + 560},${browserViewport.height}`]
|
args: [
|
||||||
|
`--window-size=${browserViewport.width + 560},${
|
||||||
|
browserViewport.height
|
||||||
|
}`,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
args: [ `--window-size=${browserViewport.width},${browserViewport.height}`]
|
args: [
|
||||||
|
`--window-size=${browserViewport.width},${browserViewport.height}`,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
const browser = await puppeteer.launch(browserParams);
|
const browser = await puppeteer.launch(browserParams);
|
||||||
const page = await browser.pages().then(pages => pages[0]);
|
const page = await browser.pages().then((pages) => pages[0]);
|
||||||
await page.setViewport(browserViewport);
|
await page.setViewport(browserViewport);
|
||||||
if (DEV || DEBUG) {
|
if (DEV || DEBUG) {
|
||||||
page.on("console", async msg => {
|
page.on("console", async (msg) => {
|
||||||
// If there is a console.error but an error is not thrown, this will ensure the test fails
|
// If there is a console.error but an error is not thrown, this will ensure the test fails
|
||||||
if (msg.type() === "error") {
|
if (msg.type() === "error") {
|
||||||
const errorMsgText = await Promise.all(
|
const errorMsgText = await Promise.all(
|
||||||
// TODO can we do this without internal properties?
|
// TODO can we do this without internal properties?
|
||||||
msg.args().map(arg => arg._remoteObject.description)
|
msg.args().map((arg) => arg._remoteObject.description)
|
||||||
);
|
);
|
||||||
throw new Error(`Console error: ${errorMsgText}`);
|
throw new Error(`Console error: ${errorMsgText}`);
|
||||||
}
|
}
|
||||||
console.log(`PAGE LOG: ${msg.text()}`);
|
console.log(`PAGE LOG: ${msg.text()}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
page.on("pageerror", err => {
|
page.on("pageerror", (err) => {
|
||||||
throw new Error(`Console error: ${err}`);
|
throw new Error(`Console error: ${err}`);
|
||||||
});
|
});
|
||||||
const utils = puppeteerUtils(page);
|
const utils = puppeteerUtils(page);
|
||||||
const cxgActions = cellxgeneActions(page, utils);
|
const cxgActions = cellxgeneActions(page, utils);
|
||||||
return [browser, page, utils, cxgActions];
|
return [browser, page, utils, cxgActions];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe("cascade", () => {
|
|||||||
expect(nextSharedState).toStrictEqual({});
|
expect(nextSharedState).toStrictEqual({});
|
||||||
expect(prevSharedState).toBe(topLevelState);
|
expect(prevSharedState).toBe(topLevelState);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"bar",
|
"bar",
|
||||||
@@ -36,8 +36,8 @@ describe("cascade", () => {
|
|||||||
expect(nextSharedState).toStrictEqual({ foo: 0 });
|
expect(nextSharedState).toStrictEqual({ foo: 0 });
|
||||||
expect(prevSharedState).toBe(topLevelState);
|
expect(prevSharedState).toBe(topLevelState);
|
||||||
return 99;
|
return 99;
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const nextState = reducer(topLevelState, topLevelAction);
|
const nextState = reducer(topLevelState, topLevelAction);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe("create", () => {
|
|||||||
describe("undo", () => {
|
describe("undo", () => {
|
||||||
test("expected state modifications", () => {
|
test("expected state modifications", () => {
|
||||||
const initialState = { a: 0, b: 1000 };
|
const initialState = { a: 0, b: 1000 };
|
||||||
const reducer = state => {
|
const reducer = (state) => {
|
||||||
return { a: state.a + 1, b: state.b + 1 };
|
return { a: state.a + 1, b: state.b + 1 };
|
||||||
};
|
};
|
||||||
const undoableReducer = undoable(reducer, ["a"]);
|
const undoableReducer = undoable(reducer, ["a"]);
|
||||||
@@ -43,7 +43,7 @@ describe("undo", () => {
|
|||||||
|
|
||||||
describe("redo", () => {
|
describe("redo", () => {
|
||||||
const initialState = { a: 0, b: 1000 };
|
const initialState = { a: 0, b: 1000 };
|
||||||
const reducer = state => {
|
const reducer = (state) => {
|
||||||
return { a: state.a + 1, b: state.b + 1 };
|
return { a: state.a + 1, b: state.b + 1 };
|
||||||
};
|
};
|
||||||
let UR;
|
let UR;
|
||||||
@@ -58,7 +58,7 @@ describe("redo", () => {
|
|||||||
|
|
||||||
// verify undo->redo reverts state.
|
// verify undo->redo reverts state.
|
||||||
const s2 = UR(UR(s1, { type: "@@undoable/undo" }), {
|
const s2 = UR(UR(s1, { type: "@@undoable/undo" }), {
|
||||||
type: "@@undoable/redo"
|
type: "@@undoable/redo",
|
||||||
});
|
});
|
||||||
expect(s2).toMatchObject({ a: 1, b: 1001 });
|
expect(s2).toMatchObject({ a: 1, b: 1001 });
|
||||||
|
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ describe("rangeEncodeIndices", () => {
|
|||||||
expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([
|
expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([
|
||||||
1,
|
1,
|
||||||
9,
|
9,
|
||||||
432
|
432,
|
||||||
]);
|
]);
|
||||||
expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([
|
expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([
|
||||||
1,
|
1,
|
||||||
9,
|
9,
|
||||||
432
|
432,
|
||||||
]);
|
]);
|
||||||
expect(rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)).toMatchObject(
|
expect(
|
||||||
[[0, 3], [9, 10], 432]
|
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)
|
||||||
);
|
).toMatchObject([[0, 3], [9, 10], 432]);
|
||||||
expect(
|
expect(
|
||||||
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false)
|
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false)
|
||||||
).toMatchObject([[0, 3], [9, 10], 432]);
|
).toMatchObject([[0, 3], [9, 10], 432]);
|
||||||
@@ -34,7 +34,10 @@ describe("rangeEncodeIndices", () => {
|
|||||||
).toMatchObject([[3, 7], [10, 12], 99]);
|
).toMatchObject([[3, 7], [10, 12], 99]);
|
||||||
expect(
|
expect(
|
||||||
rangeEncodeIndices([3, 4, 5, 6, 7, 10, 11, 12], 3, false)
|
rangeEncodeIndices([3, 4, 5, 6, 7, 10, 11, 12], 3, false)
|
||||||
).toMatchObject([[3, 7], [10, 12]]);
|
).toMatchObject([
|
||||||
|
[3, 7],
|
||||||
|
[10, 12],
|
||||||
|
]);
|
||||||
expect(
|
expect(
|
||||||
rangeEncodeIndices([0, 3, 4, 5, 6, 7, 10, 11, 12], 3, false)
|
rangeEncodeIndices([0, 3, 4, 5, 6, 7, 10, 11, 12], 3, false)
|
||||||
).toMatchObject([0, [3, 7], [10, 12]]);
|
).toMatchObject([0, [3, 7], [10, 12]]);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe("centroid", () => {
|
|||||||
...Universe.addObsLayout(
|
...Universe.addObsLayout(
|
||||||
universe,
|
universe,
|
||||||
Universe.matrixFBSToDataframe(REST.layoutObs)
|
Universe.matrixFBSToDataframe(REST.layoutObs)
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
world = World.createWorldFromEntireUniverse(universe);
|
world = World.createWorldFromEntireUniverse(universe);
|
||||||
|
|
||||||
@@ -61,10 +61,10 @@ describe("centroid", () => {
|
|||||||
// This expected result assumes that all cells belong in all categorical values inside of sample response
|
// This expected result assumes that all cells belong in all categorical values inside of sample response
|
||||||
const expectedResult = [
|
const expectedResult = [
|
||||||
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
|
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
|
||||||
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0]
|
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0],
|
||||||
];
|
];
|
||||||
|
|
||||||
centroidResult.forEach(coordinate => {
|
centroidResult.forEach((coordinate) => {
|
||||||
expect(coordinate).toEqual(expectedResult);
|
expect(coordinate).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -86,10 +86,10 @@ describe("centroid", () => {
|
|||||||
// This expected result assumes that all cells belong in all categorical values inside of sample response
|
// This expected result assumes that all cells belong in all categorical values inside of sample response
|
||||||
const expectedResult = [
|
const expectedResult = [
|
||||||
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
|
quantile([0.5], world.obsLayout.col("umap_0").asArray())[0],
|
||||||
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0]
|
quantile([0.5], world.obsLayout.col("umap_1").asArray())[0],
|
||||||
];
|
];
|
||||||
|
|
||||||
centroidResult.forEach(coordinate => {
|
centroidResult.forEach((coordinate) => {
|
||||||
expect(coordinate).toEqual(expectedResult);
|
expect(coordinate).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe("simple data access", () => {
|
|||||||
[4, 2],
|
[4, 2],
|
||||||
[
|
[
|
||||||
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
|
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
|
||||||
["red", "blue", "green", "nan"]
|
["red", "blue", "green", "nan"],
|
||||||
],
|
],
|
||||||
new Dataframe.DenseInt32Index([3, 2, 1, 0]),
|
new Dataframe.DenseInt32Index([3, 2, 1, 0]),
|
||||||
new Dataframe.KeyIndex(["numbers", "colors"])
|
new Dataframe.KeyIndex(["numbers", "colors"])
|
||||||
@@ -136,7 +136,7 @@ describe("dataframe subsetting", () => {
|
|||||||
new Int32Array([0, 1, 2]),
|
new Int32Array([0, 1, 2]),
|
||||||
["A", "B", "C"],
|
["A", "B", "C"],
|
||||||
new Float32Array([4.4, 5.5, 6.6]),
|
new Float32Array([4.4, 5.5, 6.6]),
|
||||||
["red", "green", "blue"]
|
["red", "green", "blue"],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
||||||
@@ -258,7 +258,7 @@ describe("dataframe subsetting", () => {
|
|||||||
new Int32Array([0, 1, 2]),
|
new Int32Array([0, 1, 2]),
|
||||||
["A", "B", "C"],
|
["A", "B", "C"],
|
||||||
new Float32Array([4.4, 5.5, 6.6]),
|
new Float32Array([4.4, 5.5, 6.6]),
|
||||||
["red", "green", "blue"]
|
["red", "green", "blue"],
|
||||||
],
|
],
|
||||||
new Dataframe.DenseInt32Index([2, 4, 6]),
|
new Dataframe.DenseInt32Index([2, 4, 6]),
|
||||||
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
||||||
@@ -283,7 +283,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
new Array(3).fill(0),
|
new Array(3).fill(0),
|
||||||
new Int16Array(3).fill(99),
|
new Int16Array(3).fill(99),
|
||||||
new Float64Array(3).fill(1.1)
|
new Float64Array(3).fill(1.1),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false]
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["colors", "bools"])
|
new Dataframe.KeyIndex(["colors", "bools"])
|
||||||
@@ -346,7 +346,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false]
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.DenseInt32Index([74, 75])
|
new Dataframe.DenseInt32Index([74, 75])
|
||||||
@@ -371,7 +371,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false]
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.DenseInt32Index([74, 75])
|
new Dataframe.DenseInt32Index([74, 75])
|
||||||
@@ -396,7 +396,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false]
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
@@ -421,7 +421,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false]
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
@@ -479,7 +479,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
@@ -553,7 +553,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
@@ -595,7 +595,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
@@ -618,7 +618,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
@@ -641,7 +641,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
@@ -665,7 +665,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
@@ -689,7 +689,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
["red", "blue"],
|
["red", "blue"],
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.DenseInt32Index([102, 101, 100])
|
new Dataframe.DenseInt32Index([102, 101, 100])
|
||||||
@@ -715,7 +715,7 @@ describe("dataframe factories", () => {
|
|||||||
[
|
[
|
||||||
new Array(3).fill(0),
|
new Array(3).fill(0),
|
||||||
new Int16Array(3).fill(99),
|
new Int16Array(3).fill(99),
|
||||||
new Float64Array(3).fill(1.1)
|
new Float64Array(3).fill(1.1),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
const dfB = dfA.mapColumns((col, idx) => {
|
const dfB = dfA.mapColumns((col, idx) => {
|
||||||
@@ -760,7 +760,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
@@ -781,7 +781,7 @@ describe("dataframe col", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[
|
[
|
||||||
[true, false],
|
[true, false],
|
||||||
[1, 0]
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe("Dataframe column histogram", () => {
|
|||||||
new Map([
|
new Map([
|
||||||
["n1", new Map([["c1", 1]])],
|
["n1", new Map([["c1", 1]])],
|
||||||
["n2", new Map([["c2", 1]])],
|
["n2", new Map([["c2", 1]])],
|
||||||
["n3", new Map([["c3", 1]])]
|
["n3", new Map([["c3", 1]])],
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
// memoized?
|
// memoized?
|
||||||
@@ -31,7 +31,11 @@ describe("Dataframe column histogram", () => {
|
|||||||
|
|
||||||
const h1 = df.col("value").histogram(3, [0, 2], df.col("name"));
|
const h1 = df.col("value").histogram(3, [0, 2], df.col("name"));
|
||||||
expect(h1).toMatchObject(
|
expect(h1).toMatchObject(
|
||||||
new Map([["n1", [1, 0, 0]], ["n2", [0, 1, 0]], ["n3", [0, 0, 1]]])
|
new Map([
|
||||||
|
["n1", [1, 0, 0]],
|
||||||
|
["n2", [0, 1, 0]],
|
||||||
|
["n3", [0, 0, 1]],
|
||||||
|
])
|
||||||
);
|
);
|
||||||
// memoized?
|
// memoized?
|
||||||
expect(df.col("value").histogram(3, [0, 2], df.col("name"))).toMatchObject(
|
expect(df.col("value").histogram(3, [0, 2], df.col("name"))).toMatchObject(
|
||||||
@@ -48,7 +52,13 @@ describe("Dataframe column histogram", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const h1 = df.col("cat").histogram();
|
const h1 = df.col("cat").histogram();
|
||||||
expect(h1).toMatchObject(new Map([["c1", 1], ["c2", 1], ["c3", 1]]));
|
expect(h1).toMatchObject(
|
||||||
|
new Map([
|
||||||
|
["c1", 1],
|
||||||
|
["c2", 1],
|
||||||
|
["c3", 1],
|
||||||
|
])
|
||||||
|
);
|
||||||
// memoized?
|
// memoized?
|
||||||
expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1);
|
expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1);
|
||||||
});
|
});
|
||||||
@@ -87,7 +97,7 @@ describe("Dataframe column histogram", () => {
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
2
|
2,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe("Dataframe column summary", () => {
|
|||||||
categorical: true,
|
categorical: true,
|
||||||
categories: [],
|
categories: [],
|
||||||
categoryCounts: new Map(),
|
categoryCounts: new Map(),
|
||||||
numCategories: 0
|
numCategories: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -27,7 +27,7 @@ describe("Dataframe column summary", () => {
|
|||||||
[true],
|
[true],
|
||||||
new Float32Array([39.3]),
|
new Float32Array([39.3]),
|
||||||
new Int32Array([99]),
|
new Int32Array([99]),
|
||||||
[1]
|
[1],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex([
|
new Dataframe.KeyIndex([
|
||||||
@@ -36,7 +36,7 @@ describe("Dataframe column summary", () => {
|
|||||||
"nameBoolean",
|
"nameBoolean",
|
||||||
"nameFloat32",
|
"nameFloat32",
|
||||||
"nameInt32",
|
"nameInt32",
|
||||||
"nameCategorical"
|
"nameCategorical",
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ describe("Dataframe column summary", () => {
|
|||||||
categorical: true,
|
categorical: true,
|
||||||
categories: ["n1"],
|
categories: ["n1"],
|
||||||
categoryCounts: new Map([["n1", 1]]),
|
categoryCounts: new Map([["n1", 1]]),
|
||||||
numCategories: 1
|
numCategories: 1,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(1).summarize()).toEqual(
|
expect(df.icol(1).summarize()).toEqual(
|
||||||
@@ -53,7 +53,7 @@ describe("Dataframe column summary", () => {
|
|||||||
categorical: true,
|
categorical: true,
|
||||||
categories: ["hi"],
|
categories: ["hi"],
|
||||||
categoryCounts: new Map([["hi", 1]]),
|
categoryCounts: new Map([["hi", 1]]),
|
||||||
numCategories: 1
|
numCategories: 1,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(2).summarize()).toEqual(
|
expect(df.icol(2).summarize()).toEqual(
|
||||||
@@ -61,7 +61,7 @@ describe("Dataframe column summary", () => {
|
|||||||
categorical: true,
|
categorical: true,
|
||||||
categories: [true],
|
categories: [true],
|
||||||
categoryCounts: new Map([[true, 1]]),
|
categoryCounts: new Map([[true, 1]]),
|
||||||
numCategories: 1
|
numCategories: 1,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(3).summarize()).toEqual(
|
expect(df.icol(3).summarize()).toEqual(
|
||||||
@@ -71,7 +71,7 @@ describe("Dataframe column summary", () => {
|
|||||||
max: float32Conversion(39.3),
|
max: float32Conversion(39.3),
|
||||||
nan: 0,
|
nan: 0,
|
||||||
ninf: 0,
|
ninf: 0,
|
||||||
pinf: 0
|
pinf: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(4).summarize()).toEqual(
|
expect(df.icol(4).summarize()).toEqual(
|
||||||
@@ -81,7 +81,7 @@ describe("Dataframe column summary", () => {
|
|||||||
max: 99,
|
max: 99,
|
||||||
nan: 0,
|
nan: 0,
|
||||||
ninf: 0,
|
ninf: 0,
|
||||||
pinf: 0
|
pinf: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(5).summarize()).toEqual(
|
expect(df.icol(5).summarize()).toEqual(
|
||||||
@@ -89,7 +89,7 @@ describe("Dataframe column summary", () => {
|
|||||||
categorical: true,
|
categorical: true,
|
||||||
categories: [1],
|
categories: [1],
|
||||||
categoryCounts: new Map([[1, 1]]),
|
categoryCounts: new Map([[1, 1]]),
|
||||||
numCategories: 1
|
numCategories: 1,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -103,7 +103,7 @@ describe("Dataframe column summary", () => {
|
|||||||
[false, true, true],
|
[false, true, true],
|
||||||
new Float32Array([39.3, 39.3, 0]),
|
new Float32Array([39.3, 39.3, 0]),
|
||||||
new Int32Array([99, 99, 99]),
|
new Int32Array([99, 99, 99]),
|
||||||
[1, false, "0"]
|
[1, false, "0"],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex([
|
new Dataframe.KeyIndex([
|
||||||
@@ -112,7 +112,7 @@ describe("Dataframe column summary", () => {
|
|||||||
"nameBoolean",
|
"nameBoolean",
|
||||||
"nameFloat32",
|
"nameFloat32",
|
||||||
"nameInt32",
|
"nameInt32",
|
||||||
"nameCategorical"
|
"nameCategorical",
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -120,24 +120,34 @@ describe("Dataframe column summary", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining(["n0", "n1", "n2"]),
|
categories: expect.arrayContaining(["n0", "n1", "n2"]),
|
||||||
categoryCounts: new Map([["n0", 1], ["n1", 1], ["n2", 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 3
|
["n0", 1],
|
||||||
|
["n1", 1],
|
||||||
|
["n2", 1],
|
||||||
|
]),
|
||||||
|
numCategories: 3,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(1).summarize()).toEqual(
|
expect(df.icol(1).summarize()).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining(["hi", "bye"]),
|
categories: expect.arrayContaining(["hi", "bye"]),
|
||||||
categoryCounts: new Map([["hi", 2], ["bye", 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 2
|
["hi", 2],
|
||||||
|
["bye", 1],
|
||||||
|
]),
|
||||||
|
numCategories: 2,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(2).summarize()).toEqual(
|
expect(df.icol(2).summarize()).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining([true, false]),
|
categories: expect.arrayContaining([true, false]),
|
||||||
categoryCounts: new Map([[true, 2], [false, 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 2
|
[true, 2],
|
||||||
|
[false, 1],
|
||||||
|
]),
|
||||||
|
numCategories: 2,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(3).summarize()).toEqual(
|
expect(df.icol(3).summarize()).toEqual(
|
||||||
@@ -147,7 +157,7 @@ describe("Dataframe column summary", () => {
|
|||||||
max: float32Conversion(39.3),
|
max: float32Conversion(39.3),
|
||||||
nan: 0,
|
nan: 0,
|
||||||
ninf: 0,
|
ninf: 0,
|
||||||
pinf: 0
|
pinf: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(4).summarize()).toEqual(
|
expect(df.icol(4).summarize()).toEqual(
|
||||||
@@ -157,15 +167,19 @@ describe("Dataframe column summary", () => {
|
|||||||
max: 99,
|
max: 99,
|
||||||
nan: 0,
|
nan: 0,
|
||||||
ninf: 0,
|
ninf: 0,
|
||||||
pinf: 0
|
pinf: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(5).summarize()).toEqual(
|
expect(df.icol(5).summarize()).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining([1, false, "0"]),
|
categories: expect.arrayContaining([1, false, "0"]),
|
||||||
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 3
|
[1, 1],
|
||||||
|
[false, 1],
|
||||||
|
["0", 1],
|
||||||
|
]),
|
||||||
|
numCategories: 3,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -181,10 +195,10 @@ describe("Dataframe column summary", () => {
|
|||||||
39.3,
|
39.3,
|
||||||
Number.NEGATIVE_INFINITY,
|
Number.NEGATIVE_INFINITY,
|
||||||
Number.NaN,
|
Number.NaN,
|
||||||
Number.POSITIVE_INFINITY
|
Number.POSITIVE_INFINITY,
|
||||||
]),
|
]),
|
||||||
new Int32Array([99, 99, 99, 99]),
|
new Int32Array([99, 99, 99, 99]),
|
||||||
[1, false, "0", "0"]
|
[1, false, "0", "0"],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
new Dataframe.KeyIndex([
|
new Dataframe.KeyIndex([
|
||||||
@@ -193,7 +207,7 @@ describe("Dataframe column summary", () => {
|
|||||||
"nameBoolean",
|
"nameBoolean",
|
||||||
"nameFloat32",
|
"nameFloat32",
|
||||||
"nameInt32",
|
"nameInt32",
|
||||||
"nameCategorical"
|
"nameCategorical",
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -201,24 +215,34 @@ describe("Dataframe column summary", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining(["n0", "n1", "n2"]),
|
categories: expect.arrayContaining(["n0", "n1", "n2"]),
|
||||||
categoryCounts: new Map([["n0", 1], ["n1", 1], ["n2", 2]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 3
|
["n0", 1],
|
||||||
|
["n1", 1],
|
||||||
|
["n2", 2],
|
||||||
|
]),
|
||||||
|
numCategories: 3,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(1).summarize()).toEqual(
|
expect(df.icol(1).summarize()).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining(["hi", "bye"]),
|
categories: expect.arrayContaining(["hi", "bye"]),
|
||||||
categoryCounts: new Map([["hi", 2], ["bye", 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 2
|
["hi", 2],
|
||||||
|
["bye", 1],
|
||||||
|
]),
|
||||||
|
numCategories: 2,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(2).summarize()).toEqual(
|
expect(df.icol(2).summarize()).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining([true, false]),
|
categories: expect.arrayContaining([true, false]),
|
||||||
categoryCounts: new Map([[true, 2], [false, 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 2
|
[true, 2],
|
||||||
|
[false, 1],
|
||||||
|
]),
|
||||||
|
numCategories: 2,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(3).summarize()).toEqual(
|
expect(df.icol(3).summarize()).toEqual(
|
||||||
@@ -228,7 +252,7 @@ describe("Dataframe column summary", () => {
|
|||||||
max: float32Conversion(39.3),
|
max: float32Conversion(39.3),
|
||||||
nan: 1,
|
nan: 1,
|
||||||
ninf: 1,
|
ninf: 1,
|
||||||
pinf: 1
|
pinf: 1,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(4).summarize()).toEqual(
|
expect(df.icol(4).summarize()).toEqual(
|
||||||
@@ -238,15 +262,19 @@ describe("Dataframe column summary", () => {
|
|||||||
max: 99,
|
max: 99,
|
||||||
nan: 0,
|
nan: 0,
|
||||||
ninf: 0,
|
ninf: 0,
|
||||||
pinf: 0
|
pinf: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(df.icol(5).summarize()).toEqual(
|
expect(df.icol(5).summarize()).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining([1, false, "0"]),
|
categories: expect.arrayContaining([1, false, "0"]),
|
||||||
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]),
|
categoryCounts: new Map([
|
||||||
numCategories: 3
|
[1, 1],
|
||||||
|
[false, 1],
|
||||||
|
["0", 1],
|
||||||
|
]),
|
||||||
|
numCategories: 3,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
obsAnnoDimensionName,
|
obsAnnoDimensionName,
|
||||||
diffexpDimensionName,
|
diffexpDimensionName,
|
||||||
userDefinedDimensionName,
|
userDefinedDimensionName,
|
||||||
makeContinuousDimensionName
|
makeContinuousDimensionName,
|
||||||
} from "../../src/util/nameCreators";
|
} from "../../src/util/nameCreators";
|
||||||
|
|
||||||
describe("nameCreators", () => {
|
describe("nameCreators", () => {
|
||||||
@@ -14,25 +14,25 @@ describe("nameCreators", () => {
|
|||||||
layoutDimensionName,
|
layoutDimensionName,
|
||||||
obsAnnoDimensionName,
|
obsAnnoDimensionName,
|
||||||
diffexpDimensionName,
|
diffexpDimensionName,
|
||||||
userDefinedDimensionName
|
userDefinedDimensionName,
|
||||||
];
|
];
|
||||||
|
|
||||||
test("check for namespace isolation", () => {
|
test("check for namespace isolation", () => {
|
||||||
const foo = "foo";
|
const foo = "foo";
|
||||||
nameCreators.forEach(fn => expect(fn(foo)).not.toBe(foo));
|
nameCreators.forEach((fn) => expect(fn(foo)).not.toBe(foo));
|
||||||
|
|
||||||
const bar = "bar";
|
const bar = "bar";
|
||||||
nameCreators.forEach(fn => expect(fn(foo)).not.toBe(fn(bar)));
|
nameCreators.forEach((fn) => expect(fn(foo)).not.toBe(fn(bar)));
|
||||||
|
|
||||||
nameCreators.forEach(fn => {
|
nameCreators.forEach((fn) => {
|
||||||
const allOtherFn = nameCreators.filter(elmnt => elmnt !== fn);
|
const allOtherFn = nameCreators.filter((elmnt) => elmnt !== fn);
|
||||||
allOtherFn.forEach(otherFn => expect(fn(foo)).not.toBe(otherFn(foo)));
|
allOtherFn.forEach((otherFn) => expect(fn(foo)).not.toBe(otherFn(foo)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("check for legal keys", () => {
|
test("check for legal keys", () => {
|
||||||
/* need namespace creators to return strings only */
|
/* need namespace creators to return strings only */
|
||||||
nameCreators.forEach(fn => expect(fn("X")).toMatch(/X/));
|
nameCreators.forEach((fn) => expect(fn("X")).toMatch(/X/));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +1,73 @@
|
|||||||
import { PromiseLimit } from "../../src/util/promiseLimit";
|
import { PromiseLimit } from "../../src/util/promiseLimit";
|
||||||
import { range } from "../../src/util/range";
|
import { range } from "../../src/util/range";
|
||||||
|
|
||||||
const delay = t => new Promise((resolve, reject) => setTimeout(resolve, t));
|
const delay = (t) => new Promise((resolve, reject) => setTimeout(resolve, t));
|
||||||
|
|
||||||
describe("PromiseLimit", () => {
|
describe("PromiseLimit", () => {
|
||||||
test("simple evaluation, concurrency 1", async () => {
|
test("simple evaluation, concurrency 1", async () => {
|
||||||
const plimit = new PromiseLimit(1);
|
const plimit = new PromiseLimit(1);
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
plimit.add(() => Promise.resolve(1)),
|
plimit.add(() => Promise.resolve(1)),
|
||||||
plimit.add(() => Promise.resolve(2)),
|
plimit.add(() => Promise.resolve(2)),
|
||||||
plimit.add(() => Promise.resolve(3)),
|
plimit.add(() => Promise.resolve(3)),
|
||||||
plimit.add(() => Promise.resolve(4))
|
plimit.add(() => Promise.resolve(4)),
|
||||||
]);
|
]);
|
||||||
expect(result).toEqual([1, 2, 3, 4]);
|
expect(result).toEqual([1, 2, 3, 4]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("simple evaluation, concurrency > 1", async () => {
|
test("simple evaluation, concurrency > 1", async () => {
|
||||||
const plimit = new PromiseLimit(100);
|
const plimit = new PromiseLimit(100);
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
plimit.add(() => Promise.resolve(1)),
|
plimit.add(() => Promise.resolve(1)),
|
||||||
plimit.add(() => Promise.resolve(2)),
|
plimit.add(() => Promise.resolve(2)),
|
||||||
plimit.add(() => Promise.resolve(3)),
|
plimit.add(() => Promise.resolve(3)),
|
||||||
plimit.add(() => Promise.resolve(4))
|
plimit.add(() => Promise.resolve(4)),
|
||||||
]);
|
]);
|
||||||
expect(result).toEqual([1, 2, 3, 4]);
|
expect(result).toEqual([1, 2, 3, 4]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("eval in order of insertion", async () => {
|
test("eval in order of insertion", async () => {
|
||||||
const plimit = new PromiseLimit(100);
|
const plimit = new PromiseLimit(100);
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
plimit.add(() => Promise.resolve((counter += 1))),
|
plimit.add(() => Promise.resolve((counter += 1))),
|
||||||
plimit.add(() => Promise.resolve((counter += 1))),
|
plimit.add(() => Promise.resolve((counter += 1))),
|
||||||
plimit.add(() => Promise.resolve((counter += 1))),
|
plimit.add(() => Promise.resolve((counter += 1))),
|
||||||
plimit.add(() => Promise.resolve((counter += 1)))
|
plimit.add(() => Promise.resolve((counter += 1))),
|
||||||
]);
|
]);
|
||||||
expect(result).toEqual([1, 2, 3, 4]);
|
expect(result).toEqual([1, 2, 3, 4]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("obeys concurrency limit", async () => {
|
test("obeys concurrency limit", async () => {
|
||||||
const plimit = new PromiseLimit(2);
|
const plimit = new PromiseLimit(2);
|
||||||
let running = 0;
|
let running = 0;
|
||||||
let maxRunning = 0;
|
let maxRunning = 0;
|
||||||
|
|
||||||
const cbfn = async i => {
|
const cbfn = async (i) => {
|
||||||
running = running + 1;
|
running = running + 1;
|
||||||
maxRunning = running > maxRunning ? running : maxRunning;
|
maxRunning = running > maxRunning ? running : maxRunning;
|
||||||
await delay(100);
|
await delay(100);
|
||||||
running = running - 1;
|
running = running - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
range(10).map(i => plimit.add(() => cbfn(i)))
|
range(10).map((i) => plimit.add(() => cbfn(i)))
|
||||||
);
|
);
|
||||||
expect(maxRunning).toEqual(2);
|
expect(maxRunning).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rejection", async () => {
|
test("rejection", async () => {
|
||||||
const plimit = new PromiseLimit(2);
|
const plimit = new PromiseLimit(2);
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
plimit.add(() => Promise.resolve("OK")),
|
plimit.add(() => Promise.resolve("OK")),
|
||||||
plimit.add(() => Promise.reject("not OK")).catch(e => e),
|
plimit.add(() => Promise.reject("not OK")).catch((e) => e),
|
||||||
plimit.add(() => Promise.resolve("OK")),
|
plimit.add(() => Promise.resolve("OK")),
|
||||||
plimit
|
plimit
|
||||||
.add(() => {
|
.add(() => {
|
||||||
throw new Error("not OK");
|
throw new Error("not OK");
|
||||||
})
|
})
|
||||||
.catch(e => e.message)
|
.catch((e) => e.message),
|
||||||
]);
|
]);
|
||||||
expect(result).toEqual(["OK", "not OK", "OK", "not OK"]);
|
expect(result).toEqual(["OK", "not OK", "OK", "not OK"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import quantile from "../../src/util/quantile";
|
import quantile from "../../src/util/quantile";
|
||||||
|
|
||||||
describe("quantile", () => {
|
describe("quantile", () => {
|
||||||
test("single q", () => {
|
test("single q", () => {
|
||||||
const arr = new Float32Array([9, 3, 5, 6, 0]);
|
const arr = new Float32Array([9, 3, 5, 6, 0]);
|
||||||
expect(quantile([1.0], arr)).toMatchObject([9]);
|
expect(quantile([1.0], arr)).toMatchObject([9]);
|
||||||
expect(quantile([0.9], arr)).toMatchObject([9]);
|
expect(quantile([0.9], arr)).toMatchObject([9]);
|
||||||
expect(quantile([0.8], arr)).toMatchObject([9]);
|
expect(quantile([0.8], arr)).toMatchObject([9]);
|
||||||
expect(quantile([0.7], arr)).toMatchObject([6]);
|
expect(quantile([0.7], arr)).toMatchObject([6]);
|
||||||
expect(quantile([0.6], arr)).toMatchObject([6]);
|
expect(quantile([0.6], arr)).toMatchObject([6]);
|
||||||
expect(quantile([0.5], arr)).toMatchObject([5]);
|
expect(quantile([0.5], arr)).toMatchObject([5]);
|
||||||
expect(quantile([0.4], arr)).toMatchObject([5]);
|
expect(quantile([0.4], arr)).toMatchObject([5]);
|
||||||
expect(quantile([0.3], arr)).toMatchObject([3]);
|
expect(quantile([0.3], arr)).toMatchObject([3]);
|
||||||
expect(quantile([0.2], arr)).toMatchObject([3]);
|
expect(quantile([0.2], arr)).toMatchObject([3]);
|
||||||
expect(quantile([0.1], arr)).toMatchObject([0]);
|
expect(quantile([0.1], arr)).toMatchObject([0]);
|
||||||
expect(quantile([0], arr)).toMatchObject([0]);
|
expect(quantile([0], arr)).toMatchObject([0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("multi q", () => {
|
test("multi q", () => {
|
||||||
const arr = new Float32Array([9, 3, 5, 6, 0]);
|
const arr = new Float32Array([9, 3, 5, 6, 0]);
|
||||||
expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([
|
expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([
|
||||||
0,
|
0,
|
||||||
3,
|
3,
|
||||||
5,
|
5,
|
||||||
6,
|
6,
|
||||||
9
|
9,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,50 +1,48 @@
|
|||||||
import { range, rangeFill, linspace } from "../../src/util/range";
|
import { range, rangeFill, linspace } from "../../src/util/range";
|
||||||
|
|
||||||
describe("range", () => {
|
describe("range", () => {
|
||||||
test("no defaults", () => {
|
test("no defaults", () => {
|
||||||
expect(range(0, 3, 1)).toMatchObject([0, 1, 2]);
|
expect(range(0, 3, 1)).toMatchObject([0, 1, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("range(stop)", () => {
|
test("range(stop)", () => {
|
||||||
expect(range(3)).toMatchObject([0, 1, 2]);
|
expect(range(3)).toMatchObject([0, 1, 2]);
|
||||||
expect(range(0)).toMatchObject([]);
|
expect(range(0)).toMatchObject([]);
|
||||||
expect(range(1)).toMatchObject([0]);
|
expect(range(1)).toMatchObject([0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("range(start,stop)", () => {
|
test("range(start,stop)", () => {
|
||||||
expect(range(0, 0)).toMatchObject([]);
|
expect(range(0, 0)).toMatchObject([]);
|
||||||
expect(range(0, 2)).toMatchObject([0, 1]);
|
expect(range(0, 2)).toMatchObject([0, 1]);
|
||||||
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
|
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("range(start, stop, step", () => {
|
test("range(start, stop, step", () => {
|
||||||
expect(range(4, 0, -1)).toMatchObject([4, 3, 2, 1]);
|
expect(range(4, 0, -1)).toMatchObject([4, 3, 2, 1]);
|
||||||
expect(range(0, 4, 2)).toMatchObject([0, 2]);
|
expect(range(0, 4, 2)).toMatchObject([0, 2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("rangefill", () => {
|
describe("rangefill", () => {
|
||||||
test("rangeFill(arr)", () => {
|
test("rangeFill(arr)", () => {
|
||||||
expect(rangeFill(new Int32Array(3))).toMatchObject(
|
expect(rangeFill(new Int32Array(3))).toMatchObject(
|
||||||
new Int32Array([0, 1, 2])
|
new Int32Array([0, 1, 2])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("rangeFill(arr, start)", () => {
|
test("rangeFill(arr, start)", () => {
|
||||||
expect(rangeFill(new Int32Array(2), 1)).toMatchObject(
|
expect(rangeFill(new Int32Array(2), 1)).toMatchObject(
|
||||||
new Int32Array([1, 2])
|
new Int32Array([1, 2])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("rangeFill(arr, start, step)", () => {
|
test("rangeFill(arr, start, step)", () => {
|
||||||
expect(rangeFill(new Int32Array(3), 2, -1)).toMatchObject(
|
expect(rangeFill(new Int32Array(3), 2, -1)).toMatchObject(
|
||||||
new Int32Array([2, 1, 0])
|
new Int32Array([2, 1, 0])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("linspace", () => {
|
describe("linspace", () => {
|
||||||
test("linspace(arr, start, step)", () => {
|
test("linspace(arr, start, step)", () => {
|
||||||
expect(linspace(0.0, 2.0, 5)).toMatchObject(
|
expect(linspace(0.0, 2.0, 5)).toMatchObject([0.0, 0.5, 1.0, 1.5, 2.0]);
|
||||||
[0.0, 0.5, 1.0, 1.5, 2.0]
|
});
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,20 +5,23 @@ import { subsetAndResetGeneLists } from "../../../src/util/stateManager/controls
|
|||||||
import * as globals from "../../../src/globals";
|
import * as globals from "../../../src/globals";
|
||||||
|
|
||||||
describe("controls helpers", () => {
|
describe("controls helpers", () => {
|
||||||
|
|
||||||
test("subsetAndResetGeneLists", () => {
|
test("subsetAndResetGeneLists", () => {
|
||||||
const geneList = [...Array(150).keys()].map(() =>
|
const geneList = [...Array(150).keys()].map(
|
||||||
Math.random().toString(36).substring(2, 6) // random string of 4 characters
|
() => Math.random().toString(36).substring(2, 6) // random string of 4 characters
|
||||||
);
|
);
|
||||||
const state = {
|
const state = {
|
||||||
userDefinedGenes: geneList.slice(0, 20),
|
userDefinedGenes: geneList.slice(0, 20),
|
||||||
diffexpGenes: geneList.slice(20),
|
diffexpGenes: geneList.slice(20),
|
||||||
};
|
};
|
||||||
const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists(state);
|
const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists(
|
||||||
|
state
|
||||||
|
);
|
||||||
expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes);
|
expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes);
|
||||||
expect(geneList.length).toBeGreaterThan(globals.maxGenes);
|
expect(geneList.length).toBeGreaterThan(globals.maxGenes);
|
||||||
expect(newUserDefinedGenes).toHaveLength(globals.maxGenes);
|
expect(newUserDefinedGenes).toHaveLength(globals.maxGenes);
|
||||||
expect(newUserDefinedGenes).toStrictEqual(geneList.slice(0, globals.maxGenes));
|
expect(newUserDefinedGenes).toStrictEqual(
|
||||||
|
geneList.slice(0, globals.maxGenes)
|
||||||
|
);
|
||||||
expect(newDiffExpGenes).toStrictEqual([]);
|
expect(newDiffExpGenes).toStrictEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,32 +3,32 @@ test FBS encode/decode API
|
|||||||
*/
|
*/
|
||||||
import { Dataframe, KeyIndex } from "../../../src/util/dataframe";
|
import { Dataframe, KeyIndex } from "../../../src/util/dataframe";
|
||||||
import {
|
import {
|
||||||
decodeMatrixFBS,
|
decodeMatrixFBS,
|
||||||
encodeMatrixFBS
|
encodeMatrixFBS,
|
||||||
} from "../../../src/util/stateManager/matrix";
|
} from "../../../src/util/stateManager/matrix";
|
||||||
|
|
||||||
describe("encode/decode", () => {
|
describe("encode/decode", () => {
|
||||||
test("round trip", () => {
|
test("round trip", () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
["red", "green", "blue"],
|
["red", "green", "blue"],
|
||||||
new Int32Array(3).fill(0),
|
new Int32Array(3).fill(0),
|
||||||
new Uint32Array(3).fill(1),
|
new Uint32Array(3).fill(1),
|
||||||
new Float32Array(3).fill(2)
|
new Float32Array(3).fill(2),
|
||||||
];
|
];
|
||||||
|
|
||||||
const dfNoColIdx = new Dataframe([3, 4], columns);
|
const dfNoColIdx = new Dataframe([3, 4], columns);
|
||||||
const dfA = decodeMatrixFBS(encodeMatrixFBS(dfNoColIdx));
|
const dfA = decodeMatrixFBS(encodeMatrixFBS(dfNoColIdx));
|
||||||
expect([dfA.nRows, dfA.nCols]).toEqual(dfNoColIdx.dims);
|
expect([dfA.nRows, dfA.nCols]).toEqual(dfNoColIdx.dims);
|
||||||
expect(dfA.colIdx).toBeNull();
|
expect(dfA.colIdx).toBeNull();
|
||||||
expect(dfA.rowIdx).toBeNull();
|
expect(dfA.rowIdx).toBeNull();
|
||||||
expect(dfA.columns).toEqual(columns);
|
expect(dfA.columns).toEqual(columns);
|
||||||
|
|
||||||
const colIndex = new KeyIndex(["a", "b", "c", "d"]);
|
const colIndex = new KeyIndex(["a", "b", "c", "d"]);
|
||||||
const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex);
|
const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex);
|
||||||
const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
|
const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
|
||||||
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);
|
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);
|
||||||
expect(dfB.colIdx).toEqual(colIndex.keys());
|
expect(dfB.colIdx).toEqual(colIndex.keys());
|
||||||
expect(dfB.rowIdx).toBeNull();
|
expect(dfB.rowIdx).toBeNull();
|
||||||
expect(dfB.columns).toEqual(columns);
|
expect(dfB.columns).toEqual(columns);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ const aConfigResponse = {
|
|||||||
{ method: "POST", path: "/cluster/", available: false },
|
{ method: "POST", path: "/cluster/", available: false },
|
||||||
{ method: "POST", path: "/layout/", available: false },
|
{ method: "POST", path: "/layout/", available: false },
|
||||||
{ method: "POST", path: "/diffexp/", available: false },
|
{ method: "POST", path: "/diffexp/", available: false },
|
||||||
{ method: "POST", path: "/saveLocal/", available: false }
|
{ method: "POST", path: "/saveLocal/", available: false },
|
||||||
],
|
],
|
||||||
displayNames: {
|
displayNames: {
|
||||||
engine: "the little engine that could",
|
engine: "the little engine that could",
|
||||||
dataset: "all your zeros are mine"
|
dataset: "all your zeros are mine",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const aSchemaResponse = {
|
const aSchemaResponse = {
|
||||||
@@ -31,7 +31,7 @@ const aSchemaResponse = {
|
|||||||
dataframe: {
|
dataframe: {
|
||||||
nObs,
|
nObs,
|
||||||
nVar,
|
nVar,
|
||||||
type: "float32"
|
type: "float32",
|
||||||
},
|
},
|
||||||
annotations: {
|
annotations: {
|
||||||
obs: {
|
obs: {
|
||||||
@@ -44,9 +44,9 @@ const aSchemaResponse = {
|
|||||||
{
|
{
|
||||||
name: "field4",
|
name: "field4",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
categories: field4Categories
|
categories: field4Categories,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
var: {
|
var: {
|
||||||
index: "name",
|
index: "name",
|
||||||
@@ -58,46 +58,46 @@ const aSchemaResponse = {
|
|||||||
{
|
{
|
||||||
name: "fieldD",
|
name: "fieldD",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
categories: fieldDCategories
|
categories: fieldDCategories,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
obs: [{ name: "umap", type: "float32", dims: ["umap_0", "umap_1"] }],
|
obs: [{ name: "umap", type: "float32", dims: ["umap_0", "umap_1"] }],
|
||||||
var: []
|
var: [],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const anAnnotationsObsJSONResponse = {
|
const anAnnotationsObsJSONResponse = {
|
||||||
names: ["name", "field1", "field2", "field3", "field4"],
|
names: ["name", "field1", "field2", "field3", "field4"],
|
||||||
data: _()
|
data: _()
|
||||||
.range(nObs)
|
.range(nObs)
|
||||||
.map(idx => [
|
.map((idx) => [
|
||||||
idx,
|
idx,
|
||||||
`obs${idx}`,
|
`obs${idx}`,
|
||||||
2 * idx,
|
2 * idx,
|
||||||
idx + 0.0133,
|
idx + 0.0133,
|
||||||
!!(idx & 1),
|
!!(idx & 1),
|
||||||
field4Categories[idx % field4Categories.length]
|
field4Categories[idx % field4Categories.length],
|
||||||
])
|
])
|
||||||
.value()
|
.value(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const anAnnotationsVarJSONResponse = {
|
const anAnnotationsVarJSONResponse = {
|
||||||
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
|
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
|
||||||
data: _()
|
data: _()
|
||||||
.range(nVar)
|
.range(nVar)
|
||||||
.map(idx => [
|
.map((idx) => [
|
||||||
idx,
|
idx,
|
||||||
10 * idx,
|
10 * idx,
|
||||||
idx + 2.90143,
|
idx + 2.90143,
|
||||||
!!(idx & 1),
|
!!(idx & 1),
|
||||||
fieldDCategories[idx % fieldDCategories.length],
|
fieldDCategories[idx % fieldDCategories.length],
|
||||||
`var${idx}`
|
`var${idx}`,
|
||||||
])
|
])
|
||||||
.value()
|
.value(),
|
||||||
};
|
};
|
||||||
|
|
||||||
function encodeTypedArray(builder, uType, uData) {
|
function encodeTypedArray(builder, uType, uData) {
|
||||||
@@ -120,7 +120,7 @@ function encodeMatrix(columns, colIndex = undefined) {
|
|||||||
*/
|
*/
|
||||||
const utf8Encoder = new TextEncoder("utf-8");
|
const utf8Encoder = new TextEncoder("utf-8");
|
||||||
const builder = new flatbuffers.Builder(1024);
|
const builder = new flatbuffers.Builder(1024);
|
||||||
const cols = _.map(columns, carr => {
|
const cols = _.map(columns, (carr) => {
|
||||||
let uType;
|
let uType;
|
||||||
let tarr;
|
let tarr;
|
||||||
if (_.every(carr, _.isNumber)) {
|
if (_.every(carr, _.isNumber)) {
|
||||||
@@ -178,7 +178,7 @@ const anAnnotationsVarFBSResponse = (() => {
|
|||||||
const aLayoutFBSResponse = (() => {
|
const aLayoutFBSResponse = (() => {
|
||||||
const coords = [
|
const coords = [
|
||||||
new Float32Array(nObs).fill(Math.random()),
|
new Float32Array(nObs).fill(Math.random()),
|
||||||
new Float32Array(nObs).fill(Math.random())
|
new Float32Array(nObs).fill(Math.random()),
|
||||||
];
|
];
|
||||||
return encodeMatrix(coords, ["umap_0", "umap_1"]);
|
return encodeMatrix(coords, ["umap_0", "umap_1"]);
|
||||||
})();
|
})();
|
||||||
@@ -187,8 +187,8 @@ const aDataObsResponse = {
|
|||||||
var: [2, 4, 29],
|
var: [2, 4, 29],
|
||||||
obs: _()
|
obs: _()
|
||||||
.range(nObs)
|
.range(nObs)
|
||||||
.map(idx => [idx, Math.random(), Math.random(), Math.random()])
|
.map((idx) => [idx, Math.random(), Math.random(), Math.random()])
|
||||||
.value()
|
.value(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -197,5 +197,5 @@ export {
|
|||||||
anAnnotationsVarFBSResponse as annotationsVar,
|
anAnnotationsVarFBSResponse as annotationsVar,
|
||||||
anAnnotationsObsFBSResponse as annotationsObs,
|
anAnnotationsObsFBSResponse as annotationsObs,
|
||||||
aSchemaResponse as schema,
|
aSchemaResponse as schema,
|
||||||
aConfigResponse as config
|
aConfigResponse as config,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe("createUniverseFromResponse", () => {
|
|||||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||||
varAnnotations: expect.any(Dataframe.Dataframe),
|
varAnnotations: expect.any(Dataframe.Dataframe),
|
||||||
obsLayout: expect.any(Dataframe.Dataframe),
|
obsLayout: expect.any(Dataframe.Dataframe),
|
||||||
varData: expect.any(Dataframe.Dataframe)
|
varData: expect.any(Dataframe.Dataframe),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ describe("createUniverseFromResponse", () => {
|
|||||||
...Universe.addObsLayout(
|
...Universe.addObsLayout(
|
||||||
universe,
|
universe,
|
||||||
Universe.matrixFBSToDataframe(REST.layoutObs)
|
Universe.matrixFBSToDataframe(REST.layoutObs)
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(universe).toMatchObject(
|
expect(universe).toMatchObject(
|
||||||
@@ -71,13 +71,13 @@ describe("createUniverseFromResponse", () => {
|
|||||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||||
varAnnotations: expect.any(Dataframe.Dataframe),
|
varAnnotations: expect.any(Dataframe.Dataframe),
|
||||||
obsLayout: expect.any(Dataframe.Dataframe),
|
obsLayout: expect.any(Dataframe.Dataframe),
|
||||||
varData: expect.any(Dataframe.Dataframe)
|
varData: expect.any(Dataframe.Dataframe),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(universe.obsAnnotations.dims).toEqual([
|
expect(universe.obsAnnotations.dims).toEqual([
|
||||||
nObs,
|
nObs,
|
||||||
REST.schema.schema.annotations.obs.columns.length
|
REST.schema.schema.annotations.obs.columns.length,
|
||||||
]);
|
]);
|
||||||
expect(universe.obsLayout.dims).toEqual([nObs, 2]);
|
expect(universe.obsLayout.dims).toEqual([nObs, 2]);
|
||||||
expect(universe.obsLayout.colIndex.keys()).toEqual(
|
expect(universe.obsLayout.colIndex.keys()).toEqual(
|
||||||
@@ -85,7 +85,7 @@ describe("createUniverseFromResponse", () => {
|
|||||||
);
|
);
|
||||||
expect(universe.varAnnotations.dims).toEqual([
|
expect(universe.varAnnotations.dims).toEqual([
|
||||||
nVar,
|
nVar,
|
||||||
REST.schema.schema.annotations.var.columns.length
|
REST.schema.schema.annotations.var.columns.length,
|
||||||
]);
|
]);
|
||||||
expect(universe.varData.isEmpty()).toBeTruthy();
|
expect(universe.varData.isEmpty()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { DimTypes } from "../../../src/util/typedCrossfilter/crossfilter";
|
|||||||
import * as REST from "./sampleResponses";
|
import * as REST from "./sampleResponses";
|
||||||
import {
|
import {
|
||||||
obsAnnoDimensionName,
|
obsAnnoDimensionName,
|
||||||
layoutDimensionName
|
layoutDimensionName,
|
||||||
} from "../../../src/util/nameCreators";
|
} from "../../../src/util/nameCreators";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -35,7 +35,7 @@ const defaultBigBang = () => {
|
|||||||
...Universe.addObsLayout(
|
...Universe.addObsLayout(
|
||||||
universe,
|
universe,
|
||||||
Universe.matrixFBSToDataframe(REST.layoutObs)
|
Universe.matrixFBSToDataframe(REST.layoutObs)
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
/* create world */
|
/* create world */
|
||||||
@@ -50,7 +50,7 @@ const defaultBigBang = () => {
|
|||||||
return {
|
return {
|
||||||
universe,
|
universe,
|
||||||
world,
|
world,
|
||||||
crossfilter
|
crossfilter,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ describe("createWorldFromEntireUniverse", () => {
|
|||||||
clipQuantiles: { min: 0, max: 1 },
|
clipQuantiles: { min: 0, max: 1 },
|
||||||
unclipped: {
|
unclipped: {
|
||||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||||
varData: expect.any(Dataframe.Dataframe)
|
varData: expect.any(Dataframe.Dataframe),
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -92,7 +92,7 @@ describe("createWorldFromCurrentSelection", () => {
|
|||||||
const {
|
const {
|
||||||
universe,
|
universe,
|
||||||
world: originalWorld,
|
world: originalWorld,
|
||||||
crossfilter: originalCrossfilter
|
crossfilter: originalCrossfilter,
|
||||||
} = defaultBigBang();
|
} = defaultBigBang();
|
||||||
|
|
||||||
/* mock a selection */
|
/* mock a selection */
|
||||||
@@ -100,7 +100,7 @@ describe("createWorldFromCurrentSelection", () => {
|
|||||||
.select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 })
|
.select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 })
|
||||||
.select(obsAnnoDimensionName("field3"), {
|
.select(obsAnnoDimensionName("field3"), {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: [false]
|
values: [false],
|
||||||
});
|
});
|
||||||
|
|
||||||
/* create the world from the selection */
|
/* create the world from the selection */
|
||||||
@@ -124,7 +124,7 @@ describe("createWorldFromCurrentSelection", () => {
|
|||||||
};
|
};
|
||||||
const matchingIndices = _()
|
const matchingIndices = _()
|
||||||
.range(universe.nObs)
|
.range(universe.nObs)
|
||||||
.filter(idx => matchFilter(universe.obsAnnotations, idx))
|
.filter((idx) => matchFilter(universe.obsAnnotations, idx))
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
expect(world).toMatchObject(
|
expect(world).toMatchObject(
|
||||||
@@ -139,8 +139,8 @@ describe("createWorldFromCurrentSelection", () => {
|
|||||||
varData: expect.any(Dataframe.Dataframe),
|
varData: expect.any(Dataframe.Dataframe),
|
||||||
unclipped: {
|
unclipped: {
|
||||||
obsAnnotations: expect.any(Dataframe.Dataframe),
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
||||||
varData: expect.any(Dataframe.Dataframe)
|
varData: expect.any(Dataframe.Dataframe),
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ describe("createObsDimensionMap", () => {
|
|||||||
const { crossfilter } = defaultBigBang();
|
const { crossfilter } = defaultBigBang();
|
||||||
const annotationNames = _.map(
|
const annotationNames = _.map(
|
||||||
REST.schema.schema.annotations.obs.columns,
|
REST.schema.schema.annotations.obs.columns,
|
||||||
c => c.name
|
(c) => c.name
|
||||||
);
|
);
|
||||||
const obsIndexColName = REST.schema.schema.annotations.obs.index;
|
const obsIndexColName = REST.schema.schema.annotations.obs.index;
|
||||||
const schemaByObsName = _.keyBy(
|
const schemaByObsName = _.keyBy(
|
||||||
@@ -178,7 +178,7 @@ describe("createObsDimensionMap", () => {
|
|||||||
"name"
|
"name"
|
||||||
);
|
);
|
||||||
expect(crossfilter).toBeDefined();
|
expect(crossfilter).toBeDefined();
|
||||||
annotationNames.forEach(name => {
|
annotationNames.forEach((name) => {
|
||||||
const dim = crossfilter.dimensions[obsAnnoDimensionName(name)];
|
const dim = crossfilter.dimensions[obsAnnoDimensionName(name)];
|
||||||
if (name === obsIndexColName) {
|
if (name === obsIndexColName) {
|
||||||
expect(dim).toBeUndefined();
|
expect(dim).toBeUndefined();
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ describe("fillBySelection", () => {
|
|||||||
describe("wide bitarray", () => {
|
describe("wide bitarray", () => {
|
||||||
test.each([9, 30, 31, 32, 33, 54, 63, 64, 65, 127, 128, 129])(
|
test.each([9, 30, 31, 32, 33, 54, 63, 64, 65, 127, 128, 129])(
|
||||||
"more than %d dimensions",
|
"more than %d dimensions",
|
||||||
ndim => {
|
(ndim) => {
|
||||||
/* ensure we move across the uint boundary correctly */
|
/* ensure we move across the uint boundary correctly */
|
||||||
|
|
||||||
const ba = new BitArray(defaultTestLength);
|
const ba = new BitArray(defaultTestLength);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["001"],
|
productIDs: ["001"],
|
||||||
coords: [0, 0],
|
coords: [0, 0],
|
||||||
nonFinite: 0.0
|
nonFinite: 0.0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:20:19Z",
|
date: "2011-11-14T16:20:19Z",
|
||||||
@@ -21,7 +21,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["001", "005"],
|
productIDs: ["001", "005"],
|
||||||
coords: [0.4, 0.4],
|
coords: [0.4, 0.4],
|
||||||
nonFinite: Number.NaN
|
nonFinite: Number.NaN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:28:54Z",
|
date: "2011-11-14T16:28:54Z",
|
||||||
@@ -31,7 +31,7 @@ const someData = [
|
|||||||
type: "visa",
|
type: "visa",
|
||||||
productIDs: ["004", "005"],
|
productIDs: ["004", "005"],
|
||||||
coords: [0.3, 0.1],
|
coords: [0.3, 0.1],
|
||||||
nonFinite: Number.POSITIVE_INFINITY
|
nonFinite: Number.POSITIVE_INFINITY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:30:43Z",
|
date: "2011-11-14T16:30:43Z",
|
||||||
@@ -41,7 +41,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["001", "002"],
|
productIDs: ["001", "002"],
|
||||||
coords: [0.392, 0.1],
|
coords: [0.392, 0.1],
|
||||||
nonFinite: Number.NEGATIVE_INFINITY
|
nonFinite: Number.NEGATIVE_INFINITY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:48:46Z",
|
date: "2011-11-14T16:48:46Z",
|
||||||
@@ -51,7 +51,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["005"],
|
productIDs: ["005"],
|
||||||
coords: [0.7, 0.0482],
|
coords: [0.7, 0.0482],
|
||||||
nonFinite: 1.0
|
nonFinite: 1.0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:53:41Z",
|
date: "2011-11-14T16:53:41Z",
|
||||||
@@ -61,7 +61,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["001", "004", "005"],
|
productIDs: ["001", "004", "005"],
|
||||||
coords: [0.9999, 1.0],
|
coords: [0.9999, 1.0],
|
||||||
nonFinite: Number.NaN
|
nonFinite: Number.NaN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:54:06Z",
|
date: "2011-11-14T16:54:06Z",
|
||||||
@@ -71,7 +71,7 @@ const someData = [
|
|||||||
type: "cash",
|
type: "cash",
|
||||||
productIDs: ["001", "002", "003", "004", "005"],
|
productIDs: ["001", "002", "003", "004", "005"],
|
||||||
coords: [0.384, 0.6938],
|
coords: [0.384, 0.6938],
|
||||||
nonFinite: 99.0
|
nonFinite: 99.0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T16:58:03Z",
|
date: "2011-11-14T16:58:03Z",
|
||||||
@@ -81,7 +81,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["001"],
|
productIDs: ["001"],
|
||||||
coords: [0.4822, 0.482],
|
coords: [0.4822, 0.482],
|
||||||
nonFinite: Number.NaN
|
nonFinite: Number.NaN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T17:07:21Z",
|
date: "2011-11-14T17:07:21Z",
|
||||||
@@ -91,7 +91,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["004", "005"],
|
productIDs: ["004", "005"],
|
||||||
coords: [0.2234, 0],
|
coords: [0.2234, 0],
|
||||||
nonFinite: Number.NaN
|
nonFinite: Number.NaN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T17:22:59Z",
|
date: "2011-11-14T17:22:59Z",
|
||||||
@@ -101,7 +101,7 @@ const someData = [
|
|||||||
type: "tab",
|
type: "tab",
|
||||||
productIDs: ["001", "002", "004", "005"],
|
productIDs: ["001", "002", "004", "005"],
|
||||||
coords: [0.382, 0.38485],
|
coords: [0.382, 0.38485],
|
||||||
nonFinite: -1
|
nonFinite: -1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T17:25:45Z",
|
date: "2011-11-14T17:25:45Z",
|
||||||
@@ -111,7 +111,7 @@ const someData = [
|
|||||||
type: "cash",
|
type: "cash",
|
||||||
productIDs: ["002"],
|
productIDs: ["002"],
|
||||||
coords: [0.998, 0.8472],
|
coords: [0.998, 0.8472],
|
||||||
nonFinite: 0.0
|
nonFinite: 0.0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: "2011-11-14T17:29:52Z",
|
date: "2011-11-14T17:29:52Z",
|
||||||
@@ -121,8 +121,8 @@ const someData = [
|
|||||||
type: "visa",
|
type: "visa",
|
||||||
productIDs: ["004"],
|
productIDs: ["004"],
|
||||||
coords: [0.8273, 0.3384],
|
coords: [0.8273, 0.3384],
|
||||||
nonFinite: 0.0
|
nonFinite: 0.0,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let payments = null;
|
let payments = null;
|
||||||
@@ -248,16 +248,21 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
test("none", () => {
|
test("none", () => {
|
||||||
expect(p.select("quantity", { mode: "none" }).countSelected()).toEqual(0);
|
expect(p.select("quantity", { mode: "none" }).countSelected()).toEqual(0);
|
||||||
});
|
});
|
||||||
test.each([[[]], [[2]], [[2, 1]], [[9, 82]], [[0, 1]]])("exact: %p", v =>
|
test.each([[[]], [[2]], [[2, 1]], [[9, 82]], [[0, 1]]])("exact: %p", (v) =>
|
||||||
expect(
|
expect(
|
||||||
p.select("quantity", { mode: "exact", values: v }).countSelected()
|
p.select("quantity", { mode: "exact", values: v }).countSelected()
|
||||||
).toEqual(_.filter(someData, d => v.includes(d.quantity)).length)
|
).toEqual(_.filter(someData, (d) => v.includes(d.quantity)).length)
|
||||||
);
|
);
|
||||||
test.each([[0, 1], [1, 2], [0, 99], [99, 100000]])("range %p", (lo, hi) =>
|
test.each([
|
||||||
|
[0, 1],
|
||||||
|
[1, 2],
|
||||||
|
[0, 99],
|
||||||
|
[99, 100000],
|
||||||
|
])("range %p", (lo, hi) =>
|
||||||
expect(
|
expect(
|
||||||
p.select("quantity", { mode: "range", lo, hi }).countSelected()
|
p.select("quantity", { mode: "range", lo, hi }).countSelected()
|
||||||
).toEqual(
|
).toEqual(
|
||||||
_.filter(someData, d => d.quantity >= lo && d.quantity < hi).length
|
_.filter(someData, (d) => d.quantity >= lo && d.quantity < hi).length
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
test("bad mode", () => {
|
test("bad mode", () => {
|
||||||
@@ -284,11 +289,11 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
[["tab"]],
|
[["tab"]],
|
||||||
[["visa"]],
|
[["visa"]],
|
||||||
[["visa", "tab"]],
|
[["visa", "tab"]],
|
||||||
[["cash", "tab", "visa"]]
|
[["cash", "tab", "visa"]],
|
||||||
])("exact: %p", v =>
|
])("exact: %p", (v) =>
|
||||||
expect(
|
expect(
|
||||||
p.select("type", { mode: "exact", values: v }).countSelected()
|
p.select("type", { mode: "exact", values: v }).countSelected()
|
||||||
).toEqual(_.filter(someData, d => v.includes(d.type)).length)
|
).toEqual(_.filter(someData, (d) => v.includes(d.type)).length)
|
||||||
);
|
);
|
||||||
test("range", () => {
|
test("range", () => {
|
||||||
expect(() => p.select("type", { mode: "range", lo: 0, hi: 9 })).toThrow(
|
expect(() => p.select("type", { mode: "range", lo: 0, hi: 9 })).toThrow(
|
||||||
@@ -303,8 +308,8 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
describe("spatial dimension", () => {
|
describe("spatial dimension", () => {
|
||||||
let p;
|
let p;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const X = someData.map(r => r.coords[0]);
|
const X = someData.map((r) => r.coords[0]);
|
||||||
const Y = someData.map(r => r.coords[1]);
|
const Y = someData.map((r) => r.coords[1]);
|
||||||
p = payments.addDimension("coords", "spatial", X, Y);
|
p = payments.addDimension("coords", "spatial", X, Y);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -316,34 +321,76 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
test("none", () => {
|
test("none", () => {
|
||||||
expect(p.select("coords", { mode: "none" }).countSelected()).toEqual(0);
|
expect(p.select("coords", { mode: "none" }).countSelected()).toEqual(0);
|
||||||
});
|
});
|
||||||
test.each([[0, 0, 1, 1], [0, 0, 0.5, 0.5], [0.5, 0.5, 1, 1]])(
|
test.each([
|
||||||
"within-rect %d %d %d %d",
|
[0, 0, 1, 1],
|
||||||
(minX, minY, maxX, maxY) => {
|
[0, 0, 0.5, 0.5],
|
||||||
expect(
|
[0.5, 0.5, 1, 1],
|
||||||
p
|
])("within-rect %d %d %d %d", (minX, minY, maxX, maxY) => {
|
||||||
.select("coords", { mode: "within-rect", minX, minY, maxX, maxY })
|
expect(
|
||||||
.allSelected()
|
p
|
||||||
).toEqual(
|
.select("coords", { mode: "within-rect", minX, minY, maxX, maxY })
|
||||||
_.filter(someData, d => {
|
.allSelected()
|
||||||
const [x, y] = d.coords;
|
).toEqual(
|
||||||
return minX <= x && x < maxX && minY <= y && y < maxY;
|
_.filter(someData, (d) => {
|
||||||
})
|
const [x, y] = d.coords;
|
||||||
);
|
return minX <= x && x < maxX && minY <= y && y < maxY;
|
||||||
}
|
})
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
[
|
[
|
||||||
[[0, 0], [0, 1], [1, 1], [1, 0]],
|
[
|
||||||
[true, true, true, true, true, false, true, true, true, true, true, true]
|
[0, 0],
|
||||||
|
[0, 1],
|
||||||
|
[1, 1],
|
||||||
|
[1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[[0, 0], [0, 0.5], [0.5, 0.5], [0.5, 0]],
|
[
|
||||||
[true, true, true, true, false, false, false, true, true, true, false, false]
|
[0, 0],
|
||||||
]
|
[0, 0.5],
|
||||||
|
[0.5, 0.5],
|
||||||
|
[0.5, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
],
|
||||||
])("within-polygon %p", (polygon, expected) => {
|
])("within-polygon %p", (polygon, expected) => {
|
||||||
expect(p.select("coords", { mode: "within-polygon", polygon }).allSelected())
|
expect(
|
||||||
.toEqual(_.zip(someData, expected).filter(x => x[1]).map(x => x[0]));
|
p.select("coords", { mode: "within-polygon", polygon }).allSelected()
|
||||||
|
).toEqual(
|
||||||
|
_.zip(someData, expected)
|
||||||
|
.filter((x) => x[1])
|
||||||
|
.map((x) => x[0])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -381,7 +428,7 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
p
|
p
|
||||||
.select("nonFinite", {
|
.select("nonFinite", {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: [Number.POSITIVE_INFINITY]
|
values: [Number.POSITIVE_INFINITY],
|
||||||
})
|
})
|
||||||
.countSelected()
|
.countSelected()
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
@@ -389,7 +436,7 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
p
|
p
|
||||||
.select("nonFinite", {
|
.select("nonFinite", {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: [Number.NEGATIVE_INFINITY]
|
values: [Number.NEGATIVE_INFINITY],
|
||||||
})
|
})
|
||||||
.countSelected()
|
.countSelected()
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
@@ -402,7 +449,7 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
p
|
p
|
||||||
.select("nonFinite", {
|
.select("nonFinite", {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: [Number.POSITIVE_INFINITY, 0, 1, 99]
|
values: [Number.POSITIVE_INFINITY, 0, 1, 99],
|
||||||
})
|
})
|
||||||
.countSelected()
|
.countSelected()
|
||||||
).toEqual(6);
|
).toEqual(6);
|
||||||
@@ -414,7 +461,7 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
.select("nonFinite", {
|
.select("nonFinite", {
|
||||||
mode: "range",
|
mode: "range",
|
||||||
lo: 0,
|
lo: 0,
|
||||||
hi: Number.POSITIVE_INFINITY
|
hi: Number.POSITIVE_INFINITY,
|
||||||
})
|
})
|
||||||
.countSelected()
|
.countSelected()
|
||||||
).toEqual(5);
|
).toEqual(5);
|
||||||
@@ -423,7 +470,7 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
.select("nonFinite", {
|
.select("nonFinite", {
|
||||||
mode: "range",
|
mode: "range",
|
||||||
lo: 0,
|
lo: 0,
|
||||||
hi: Number.NaN
|
hi: Number.NaN,
|
||||||
})
|
})
|
||||||
.countSelected()
|
.countSelected()
|
||||||
).toEqual(6);
|
).toEqual(6);
|
||||||
@@ -432,7 +479,7 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
.select("nonFinite", {
|
.select("nonFinite", {
|
||||||
mode: "range",
|
mode: "range",
|
||||||
lo: Number.NEGATIVE_INFINITY,
|
lo: Number.NEGATIVE_INFINITY,
|
||||||
hi: Number.POSITIVE_INFINITY
|
hi: Number.POSITIVE_INFINITY,
|
||||||
})
|
})
|
||||||
.countSelected()
|
.countSelected()
|
||||||
).toEqual(7);
|
).toEqual(7);
|
||||||
|
|||||||
@@ -10,15 +10,30 @@ describe("canonicalize", () => {
|
|||||||
|
|
||||||
test("simple, already correct", () => {
|
test("simple, already correct", () => {
|
||||||
expect(PositiveIntervals.canonicalize([[0, 1]])).toEqual([[0, 1]]);
|
expect(PositiveIntervals.canonicalize([[0, 1]])).toEqual([[0, 1]]);
|
||||||
expect(PositiveIntervals.canonicalize([[0, 1], [2, 3]])).toEqual([
|
expect(
|
||||||
|
PositiveIntervals.canonicalize([
|
||||||
|
[0, 1],
|
||||||
|
[2, 3],
|
||||||
|
])
|
||||||
|
).toEqual([
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[2, 3]
|
[2, 3],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("non-canonical, need to be canonicalized", () => {
|
test("non-canonical, need to be canonicalized", () => {
|
||||||
expect(PositiveIntervals.canonicalize([[0, 1], [1, 2]])).toEqual([[0, 2]]);
|
expect(
|
||||||
expect(PositiveIntervals.canonicalize([[1, 2], [2, 3]])).toEqual([[1, 3]]);
|
PositiveIntervals.canonicalize([
|
||||||
|
[0, 1],
|
||||||
|
[1, 2],
|
||||||
|
])
|
||||||
|
).toEqual([[0, 2]]);
|
||||||
|
expect(
|
||||||
|
PositiveIntervals.canonicalize([
|
||||||
|
[1, 2],
|
||||||
|
[2, 3],
|
||||||
|
])
|
||||||
|
).toEqual([[1, 3]]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,14 +41,30 @@ describe("union", () => {
|
|||||||
test("empty range", () => {
|
test("empty range", () => {
|
||||||
expect(PositiveIntervals.union([], [])).toEqual([]);
|
expect(PositiveIntervals.union([], [])).toEqual([]);
|
||||||
expect(PositiveIntervals.union([], [[1, 2]])).toEqual([[1, 2]]);
|
expect(PositiveIntervals.union([], [[1, 2]])).toEqual([[1, 2]]);
|
||||||
expect(PositiveIntervals.union([], [[1, 2], [3, 4]])).toEqual([
|
expect(
|
||||||
|
PositiveIntervals.union(
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
[1, 2],
|
||||||
|
[3, 4],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
[1, 2],
|
[1, 2],
|
||||||
[3, 4]
|
[3, 4],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.union([[3, 4]], [])).toEqual([[3, 4]]);
|
expect(PositiveIntervals.union([[3, 4]], [])).toEqual([[3, 4]]);
|
||||||
expect(PositiveIntervals.union([[1, 2], [3, 4]], [])).toEqual([
|
expect(
|
||||||
|
PositiveIntervals.union(
|
||||||
|
[
|
||||||
|
[1, 2],
|
||||||
|
[3, 4],
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
[1, 2],
|
[1, 2],
|
||||||
[3, 4]
|
[3, 4],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.union([[3, 3]], [])).toEqual([[3, 3]]);
|
expect(PositiveIntervals.union([[3, 3]], [])).toEqual([[3, 3]]);
|
||||||
expect(PositiveIntervals.union([], [[3, 3]])).toEqual([[3, 3]]);
|
expect(PositiveIntervals.union([], [[3, 3]])).toEqual([[3, 3]]);
|
||||||
@@ -44,17 +75,37 @@ describe("union", () => {
|
|||||||
expect(PositiveIntervals.union([[2, 3]], [[1, 2]])).toEqual([[1, 3]]);
|
expect(PositiveIntervals.union([[2, 3]], [[1, 2]])).toEqual([[1, 3]]);
|
||||||
expect(PositiveIntervals.union([[1, 2]], [[3, 4]])).toEqual([
|
expect(PositiveIntervals.union([[1, 2]], [[3, 4]])).toEqual([
|
||||||
[1, 2],
|
[1, 2],
|
||||||
[3, 4]
|
[3, 4],
|
||||||
]);
|
]);
|
||||||
expect(
|
expect(
|
||||||
PositiveIntervals.union([[1, 2], [3, 4]], [[6, 7], [19, 40]])
|
PositiveIntervals.union(
|
||||||
).toEqual([[1, 2], [3, 4], [6, 7], [19, 40]]);
|
[
|
||||||
expect(PositiveIntervals.union([[1, 4]], [[1, 1], [3, 4]])).toEqual([
|
[1, 2],
|
||||||
[1, 4]
|
[3, 4],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[6, 7],
|
||||||
|
[19, 40],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
[1, 2],
|
||||||
|
[3, 4],
|
||||||
|
[6, 7],
|
||||||
|
[19, 40],
|
||||||
]);
|
]);
|
||||||
|
expect(
|
||||||
|
PositiveIntervals.union(
|
||||||
|
[[1, 4]],
|
||||||
|
[
|
||||||
|
[1, 1],
|
||||||
|
[3, 4],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).toEqual([[1, 4]]);
|
||||||
expect(PositiveIntervals.union([[3, 3]], [[4, 4]])).toEqual([
|
expect(PositiveIntervals.union([[3, 3]], [[4, 4]])).toEqual([
|
||||||
[3, 3],
|
[3, 3],
|
||||||
[4, 4]
|
[4, 4],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -70,34 +121,43 @@ describe("intersection", () => {
|
|||||||
expect(PositiveIntervals.intersection([[1, 2]], [[2, 3]])).toEqual([]);
|
expect(PositiveIntervals.intersection([[1, 2]], [[2, 3]])).toEqual([]);
|
||||||
expect(PositiveIntervals.intersection([[2, 3]], [[1, 2]])).toEqual([]);
|
expect(PositiveIntervals.intersection([[2, 3]], [[1, 2]])).toEqual([]);
|
||||||
expect(PositiveIntervals.intersection([[1, 10]], [[1, 10]])).toEqual([
|
expect(PositiveIntervals.intersection([[1, 10]], [[1, 10]])).toEqual([
|
||||||
[1, 10]
|
[1, 10],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[1, 10]], [[2, 8]])).toEqual([
|
expect(PositiveIntervals.intersection([[1, 10]], [[2, 8]])).toEqual([
|
||||||
[2, 8]
|
[2, 8],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[2, 8]], [[1, 10]])).toEqual([
|
expect(PositiveIntervals.intersection([[2, 8]], [[1, 10]])).toEqual([
|
||||||
[2, 8]
|
[2, 8],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[1, 10]], [[2, 12]])).toEqual([
|
expect(PositiveIntervals.intersection([[1, 10]], [[2, 12]])).toEqual([
|
||||||
[2, 10]
|
[2, 10],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[2, 12]], [[1, 10]])).toEqual([
|
expect(PositiveIntervals.intersection([[2, 12]], [[1, 10]])).toEqual([
|
||||||
[2, 10]
|
[2, 10],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[1, 10]], [[1, 8]])).toEqual([
|
expect(PositiveIntervals.intersection([[1, 10]], [[1, 8]])).toEqual([
|
||||||
[1, 8]
|
[1, 8],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[1, 8]], [[1, 10]])).toEqual([
|
expect(PositiveIntervals.intersection([[1, 8]], [[1, 10]])).toEqual([
|
||||||
[1, 8]
|
[1, 8],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.intersection([[1, 10]], [[1, 2], [6, 9]])).toEqual(
|
expect(
|
||||||
[[1, 2], [6, 9]]
|
PositiveIntervals.intersection(
|
||||||
);
|
[[1, 10]],
|
||||||
expect(PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])).toEqual(
|
[
|
||||||
[[1363, 2638]]
|
[1, 2],
|
||||||
);
|
[6, 9],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
[1, 2],
|
||||||
|
[6, 9],
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])
|
||||||
|
).toEqual([[1363, 2638]]);
|
||||||
expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([
|
expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([
|
||||||
[1, 2]
|
[1, 2],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -110,32 +170,66 @@ describe("difference", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("simple", () => {
|
test("simple", () => {
|
||||||
expect(PositiveIntervals.difference([[1, 2], [3, 4]], [])).toEqual([
|
expect(
|
||||||
|
PositiveIntervals.difference(
|
||||||
|
[
|
||||||
|
[1, 2],
|
||||||
|
[3, 4],
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
[1, 2],
|
[1, 2],
|
||||||
[3, 4]
|
[3, 4],
|
||||||
]);
|
|
||||||
expect(PositiveIntervals.difference([[1, 2], [3, 10]], [[5, 10]])).toEqual([
|
|
||||||
[1, 2],
|
|
||||||
[3, 5]
|
|
||||||
]);
|
|
||||||
expect(PositiveIntervals.difference([[1, 2], [3, 10]], [[0, 5]])).toEqual([
|
|
||||||
[5, 10]
|
|
||||||
]);
|
]);
|
||||||
expect(
|
expect(
|
||||||
PositiveIntervals.difference([[0, 2638]], [[0, 1363], [2055, 2638]])
|
PositiveIntervals.difference(
|
||||||
|
[
|
||||||
|
[1, 2],
|
||||||
|
[3, 10],
|
||||||
|
],
|
||||||
|
[[5, 10]]
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
[1, 2],
|
||||||
|
[3, 5],
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
PositiveIntervals.difference(
|
||||||
|
[
|
||||||
|
[1, 2],
|
||||||
|
[3, 10],
|
||||||
|
],
|
||||||
|
[[0, 5]]
|
||||||
|
)
|
||||||
|
).toEqual([[5, 10]]);
|
||||||
|
expect(
|
||||||
|
PositiveIntervals.difference(
|
||||||
|
[[0, 2638]],
|
||||||
|
[
|
||||||
|
[0, 1363],
|
||||||
|
[2055, 2638],
|
||||||
|
]
|
||||||
|
)
|
||||||
).toEqual([[1363, 2055]]);
|
).toEqual([[1363, 2055]]);
|
||||||
expect(
|
expect(
|
||||||
PositiveIntervals.difference([[0, 1363], [2055, 2638]], [[0, 2638]])
|
PositiveIntervals.difference(
|
||||||
|
[
|
||||||
|
[0, 1363],
|
||||||
|
[2055, 2638],
|
||||||
|
],
|
||||||
|
[[0, 2638]]
|
||||||
|
)
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
expect(PositiveIntervals.difference([[0, 10]], [[0, 1]])).toEqual([
|
expect(PositiveIntervals.difference([[0, 10]], [[0, 1]])).toEqual([
|
||||||
[1, 10]
|
[1, 10],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.difference([[0, 10]], [[1, 2]])).toEqual([
|
expect(PositiveIntervals.difference([[0, 10]], [[1, 2]])).toEqual([
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[2, 10]
|
[2, 10],
|
||||||
]);
|
]);
|
||||||
expect(PositiveIntervals.difference([[0, 10]], [[9, 10]])).toEqual([
|
expect(PositiveIntervals.difference([[0, 10]], [[9, 10]])).toEqual([
|
||||||
[0, 9]
|
[0, 9],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
lowerBound,
|
lowerBound,
|
||||||
upperBound,
|
upperBound,
|
||||||
lowerBoundIndirect,
|
lowerBoundIndirect,
|
||||||
upperBoundIndirect
|
upperBoundIndirect,
|
||||||
} from "../../../src/util/typedCrossfilter/sort";
|
} from "../../../src/util/typedCrossfilter/sort";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -40,7 +40,7 @@ describe("sortArray", () => {
|
|||||||
["a", "b", "0", "1"],
|
["a", "b", "0", "1"],
|
||||||
[0, "a", true, null, undefined, 3.1415],
|
[0, "a", true, null, undefined, 3.1415],
|
||||||
fillRand(new Array(1000)),
|
fillRand(new Array(1000)),
|
||||||
["a", NaN, null, pInf]
|
["a", NaN, null, pInf],
|
||||||
].map((val, idx) =>
|
].map((val, idx) =>
|
||||||
test(`JS vals ${idx}`, () => {
|
test(`JS vals ${idx}`, () => {
|
||||||
expect(sortArray(val)).toMatchObject(val.sort());
|
expect(sortArray(val)).toMatchObject(val.sort());
|
||||||
@@ -49,7 +49,7 @@ describe("sortArray", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("finite numbers", () => {
|
describe("finite numbers", () => {
|
||||||
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map(Type =>
|
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
|
||||||
test(Type.name, () => {
|
test(Type.name, () => {
|
||||||
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject(
|
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject(
|
||||||
Type.from([0, 1, 2, 3, 4, 5, 6])
|
Type.from([0, 1, 2, 3, 4, 5, 6])
|
||||||
@@ -131,7 +131,7 @@ describe("sortArray", () => {
|
|||||||
|
|
||||||
describe("sortIndex", () => {
|
describe("sortIndex", () => {
|
||||||
describe("finite numbers", () => {
|
describe("finite numbers", () => {
|
||||||
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map(Type =>
|
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
|
||||||
test(Type.name, () => {
|
test(Type.name, () => {
|
||||||
const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]);
|
const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]);
|
||||||
const index1 = fillRange(new Uint32Array(source1.length));
|
const index1 = fillRange(new Uint32Array(source1.length));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
sliceByIndex,
|
sliceByIndex,
|
||||||
makeSortIndex
|
makeSortIndex,
|
||||||
} from "../../../src/util/typedCrossfilter/util";
|
} from "../../../src/util/typedCrossfilter/util";
|
||||||
import { rangeFill as fillRange } from "../../../src/util/range";
|
import { rangeFill as fillRange } from "../../../src/util/range";
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module.exports = {
|
|||||||
cacheDirectory: true,
|
cacheDirectory: true,
|
||||||
presets: [
|
presets: [
|
||||||
["modern-browsers", { loose: true, modules: false }],
|
["modern-browsers", { loose: true, modules: false }],
|
||||||
"@babel/preset-react"
|
"@babel/preset-react",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@babel/plugin-proposal-function-bind",
|
"@babel/plugin-proposal-function-bind",
|
||||||
@@ -11,6 +11,6 @@ module.exports = {
|
|||||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||||
"@babel/plugin-proposal-export-namespace-from",
|
"@babel/plugin-proposal-export-namespace-from",
|
||||||
"@babel/plugin-proposal-optional-chaining",
|
"@babel/plugin-proposal-optional-chaining",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator"
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module.exports = {
|
|||||||
babelrc: false,
|
babelrc: false,
|
||||||
presets: [
|
presets: [
|
||||||
["modern-browsers", { loose: true, modules: false }],
|
["modern-browsers", { loose: true, modules: false }],
|
||||||
"@babel/preset-react"
|
"@babel/preset-react",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@babel/plugin-proposal-function-bind",
|
"@babel/plugin-proposal-function-bind",
|
||||||
@@ -12,6 +12,6 @@ module.exports = {
|
|||||||
"@babel/plugin-transform-react-constant-elements",
|
"@babel/plugin-transform-react-constant-elements",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
"@babel/plugin-proposal-optional-chaining",
|
"@babel/plugin-proposal-optional-chaining",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator"
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ module.exports = {
|
|||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true,
|
jsx: true,
|
||||||
generators: true
|
generators: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
@@ -26,7 +26,7 @@ module.exports = {
|
|||||||
"operator-linebreak": [
|
"operator-linebreak": [
|
||||||
"error",
|
"error",
|
||||||
"after",
|
"after",
|
||||||
{ overrides: { "?": "before", ":": "before" } }
|
{ overrides: { "?": "before", ":": "before" } },
|
||||||
],
|
],
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"spaced-comment": ["error", "always", { exceptions: ["*"] }],
|
"spaced-comment": ["error", "always", { exceptions: ["*"] }],
|
||||||
@@ -35,13 +35,13 @@ module.exports = {
|
|||||||
"react/prop-types": [0],
|
"react/prop-types": [0],
|
||||||
"space-before-function-paren": "off",
|
"space-before-function-paren": "off",
|
||||||
"function-paren-newline": "off",
|
"function-paren-newline": "off",
|
||||||
"prefer-destructuring": ["error", { object: true, array: false }]
|
"prefer-destructuring": ["error", { object: true, array: false }],
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["**/*.test.js"],
|
files: ["**/*.test.js"],
|
||||||
env: {
|
env: {
|
||||||
jest: true // now **/*.test.js files' env has both es6 *and* jest
|
jest: true, // now **/*.test.js files' env has both es6 *and* jest
|
||||||
},
|
},
|
||||||
// Can't extend in overrides: https://github.com/eslint/eslint/issues/8813
|
// Can't extend in overrides: https://github.com/eslint/eslint/issues/8813
|
||||||
// "extends": ["plugin:jest/recommended"]
|
// "extends": ["plugin:jest/recommended"]
|
||||||
@@ -51,8 +51,8 @@ module.exports = {
|
|||||||
"jest/no-focused-tests": "error",
|
"jest/no-focused-tests": "error",
|
||||||
"jest/no-identical-title": "error",
|
"jest/no-identical-title": "error",
|
||||||
"jest/prefer-to-have-length": "warn",
|
"jest/prefer-to-have-length": "warn",
|
||||||
"jest/valid-expect": "error"
|
"jest/valid-expect": "error",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module.exports = {
|
|||||||
path: path.resolve("build"),
|
path: path.resolve("build"),
|
||||||
pathinfo: true,
|
pathinfo: true,
|
||||||
filename: "static/js/bundle.js",
|
filename: "static/js/bundle.js",
|
||||||
publicPath: "/"
|
publicPath: "/",
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -26,7 +26,7 @@ module.exports = {
|
|||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: src,
|
include: src,
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: babelOptions
|
options: babelOptions,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
@@ -34,29 +34,29 @@ module.exports = {
|
|||||||
exclude: [path.resolve(src, "index.css")],
|
exclude: [path.resolve(src, "index.css")],
|
||||||
loader: [
|
loader: [
|
||||||
{
|
{
|
||||||
loader: "style-loader"
|
loader: "style-loader",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: "css-loader",
|
loader: "css-loader",
|
||||||
options: {
|
options: {
|
||||||
modules: {
|
modules: {
|
||||||
localIdentName: "[name]__[local]___[hash:base64:5]"
|
localIdentName: "[name]__[local]___[hash:base64:5]",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /index\.css$/,
|
test: /index\.css$/,
|
||||||
include: [path.resolve(src, "index.css")],
|
include: [path.resolve(src, "index.css")],
|
||||||
loader: [
|
loader: [
|
||||||
{
|
{
|
||||||
loader: "style-loader"
|
loader: "style-loader",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: "css-loader"
|
loader: "css-loader",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{ test: /\.json$/, include: [src, nodeModules], loader: "json-loader" },
|
{ test: /\.json$/, include: [src, nodeModules], loader: "json-loader" },
|
||||||
@@ -64,14 +64,14 @@ module.exports = {
|
|||||||
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
|
||||||
loader: "file-loader",
|
loader: "file-loader",
|
||||||
include: [nodeModules, fonts],
|
include: [nodeModules, fonts],
|
||||||
query: { name: "static/assets/[name].[ext]" }
|
query: { name: "static/assets/[name].[ext]" },
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: true,
|
inject: true,
|
||||||
template: path.resolve("index.html")
|
template: path.resolve("index.html"),
|
||||||
}),
|
}),
|
||||||
new FaviconsWebpackPlugin({
|
new FaviconsWebpackPlugin({
|
||||||
logo: "./favicon.png",
|
logo: "./favicon.png",
|
||||||
@@ -84,16 +84,18 @@ module.exports = {
|
|||||||
coast: false,
|
coast: false,
|
||||||
firefox: false,
|
firefox: false,
|
||||||
windows: false,
|
windows: false,
|
||||||
yandex: false
|
yandex: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: "({ isDisabled: true })"
|
__REACT_DEVTOOLS_GLOBAL_HOOK__: "({ isDisabled: true })",
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.CXG_SERVER_PORT": JSON.stringify(process.env.CXG_SERVER_PORT)
|
"process.env.CXG_SERVER_PORT": JSON.stringify(
|
||||||
})
|
process.env.CXG_SERVER_PORT
|
||||||
]
|
),
|
||||||
|
}),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
+24
-6
@@ -1,12 +1,27 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>cell×gene</title>
|
<title>cell×gene</title>
|
||||||
<style>
|
<style>
|
||||||
html, body, p, h1, h2, h3, h4, h5, h6, span, button, input, label, text, div {
|
html,
|
||||||
font-family: 'Roboto Condensed','Helvetica Neue','Helvetica','Arial',sans-serif;
|
body,
|
||||||
|
p,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
span,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
label,
|
||||||
|
text,
|
||||||
|
div {
|
||||||
|
font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", "Arial",
|
||||||
|
sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@@ -20,9 +35,12 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript>
|
<noscript
|
||||||
|
>If you're seeing this message, that means
|
||||||
|
<strong>JavaScript has been disabled on your browser</strong>, please
|
||||||
|
<strong>enable JS</strong> to make this app work.</noscript
|
||||||
|
>
|
||||||
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+55
-54
@@ -1,60 +1,61 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>cell×gene</title>
|
<title>cell×gene</title>
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
p,
|
p,
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6,
|
h6,
|
||||||
span,
|
span,
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
label,
|
label,
|
||||||
text,
|
text,
|
||||||
div {
|
div {
|
||||||
font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica",
|
font-family: "Roboto Condensed", "Helvetica Neue", "Helvetica", "Arial",
|
||||||
"Arial", sans-serif;
|
sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.CELLXGENE = {};
|
window.CELLXGENE = {};
|
||||||
window.CELLXGENE.API = {
|
window.CELLXGENE.API = {
|
||||||
prefix: window.location.href + "api/",
|
prefix: window.location.href + "api/",
|
||||||
version: "v0.2/"
|
version: "v0.2/",
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<noscript
|
<noscript
|
||||||
>If you're seeing this message, that means
|
>If you're seeing this message, that means
|
||||||
<strong>JavaScript has been disabled on your browser</strong>,
|
<strong>JavaScript has been disabled on your browser</strong>, please
|
||||||
please <strong>enable JS</strong> to make this app work.
|
<strong>enable JS</strong> to make this app work.
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
{% for script in SCRIPTS %}
|
{% for script in SCRIPTS %}
|
||||||
<script type="text/javascript" src="{{script | safe}}"></script>
|
<script type="text/javascript" src="{{script | safe}}"></script>
|
||||||
{% endfor %}
|
{% endfor %} {% for ils in INLINE_SCRIPTS %}
|
||||||
{% for ils in INLINE_SCRIPTS %}
|
<script type="text/javascript">
|
||||||
<script type="text/javascript">{% include ils %}</script>
|
{% include ils %}
|
||||||
{% endfor %}
|
</script>
|
||||||
</body>
|
{% endfor %}
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ compiler.plugin("invalid", () => {
|
|||||||
console.log("Compiling...");
|
console.log("Compiling...");
|
||||||
});
|
});
|
||||||
|
|
||||||
compiler.plugin("done", stats => {
|
compiler.plugin("done", (stats) => {
|
||||||
utils.formatStats(stats, CLIENT_PORT);
|
utils.formatStats(stats, CLIENT_PORT);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ app.use(historyApiFallback({ verbose: false }));
|
|||||||
app.use(
|
app.use(
|
||||||
require("webpack-dev-middleware")(compiler, {
|
require("webpack-dev-middleware")(compiler, {
|
||||||
logLevel: "warn",
|
logLevel: "warn",
|
||||||
publicPath: config.output.publicPath
|
publicPath: config.output.publicPath,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ app.get("*", (req, res) => {
|
|||||||
res.sendFile(path.resolve("index.html"));
|
res.sendFile(path.resolve("index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(CLIENT_PORT, err => {
|
app.listen(CLIENT_PORT, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ var formatStats = (stats, port) => {
|
|||||||
|
|
||||||
var json = stats.toJson();
|
var json = stats.toJson();
|
||||||
var formattedErrors = json.errors.map(
|
var formattedErrors = json.errors.map(
|
||||||
message => "Error in " + formatMessage(message)
|
(message) => "Error in " + formatMessage(message)
|
||||||
);
|
);
|
||||||
var formattedWarnings = json.warnings.map(
|
var formattedWarnings = json.warnings.map(
|
||||||
message => "Warning in " + formatMessage(message)
|
(message) => "Warning in " + formatMessage(message)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
@@ -48,7 +48,7 @@ var formatStats = (stats, port) => {
|
|||||||
if (formattedErrors.some(isLikelyASyntaxError)) {
|
if (formattedErrors.some(isLikelyASyntaxError)) {
|
||||||
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
|
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
|
||||||
}
|
}
|
||||||
formattedErrors.forEach(message => {
|
formattedErrors.forEach((message) => {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
console.log();
|
console.log();
|
||||||
});
|
});
|
||||||
@@ -58,7 +58,7 @@ var formatStats = (stats, port) => {
|
|||||||
if (hasWarnings) {
|
if (hasWarnings) {
|
||||||
console.log(chalk.yellow("Compiled with warnings."));
|
console.log(chalk.yellow("Compiled with warnings."));
|
||||||
console.log();
|
console.log();
|
||||||
formattedWarnings.forEach(message => {
|
formattedWarnings.forEach((message) => {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
console.log();
|
console.log();
|
||||||
});
|
});
|
||||||
|
|||||||
+37
-27
@@ -24,15 +24,17 @@ async function obsAnnotationFetchAndLoad(dispatch, schema) {
|
|||||||
|
|
||||||
const plimit = new PromiseLimit(5);
|
const plimit = new PromiseLimit(5);
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
columns.map(col =>
|
columns.map((col) =>
|
||||||
plimit.add(() =>
|
plimit.add(() =>
|
||||||
fetchBinary(`annotations/obs?annotation-name=${encodeURIComponent(col.name)}`)
|
fetchBinary(
|
||||||
.then(buffer => Universe.matrixFBSToDataframe(buffer))
|
`annotations/obs?annotation-name=${encodeURIComponent(col.name)}`
|
||||||
.then(df =>
|
)
|
||||||
|
.then((buffer) => Universe.matrixFBSToDataframe(buffer))
|
||||||
|
.then((df) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "universe: column load success",
|
type: "universe: column load success",
|
||||||
dim: "obsAnnotations",
|
dim: "obsAnnotations",
|
||||||
dataframe: df
|
dataframe: df,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -48,14 +50,14 @@ async function varAnnotationFetchAndLoad(dispatch, schema) {
|
|||||||
const index = varAnnotations.index ?? false;
|
const index = varAnnotations.index ?? false;
|
||||||
const names = index ? [index] : [];
|
const names = index ? [index] : [];
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
names.map(name =>
|
names.map((name) =>
|
||||||
fetchBinary(`annotations/var?annotation-name=${encodeURIComponent(name)}`)
|
fetchBinary(`annotations/var?annotation-name=${encodeURIComponent(name)}`)
|
||||||
.then(buffer => Universe.matrixFBSToDataframe(buffer))
|
.then((buffer) => Universe.matrixFBSToDataframe(buffer))
|
||||||
.then(df =>
|
.then((df) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "universe: column load success",
|
type: "universe: column load success",
|
||||||
dim: "varAnnotations",
|
dim: "varAnnotations",
|
||||||
dataframe: df
|
dataframe: df,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -71,17 +73,18 @@ function layoutFetchAndLoad(dispatch, schema) {
|
|||||||
|
|
||||||
const plimit = new PromiseLimit(5);
|
const plimit = new PromiseLimit(5);
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
embNames.map(e =>
|
embNames.map((e) =>
|
||||||
plimit.add(() =>
|
plimit.add(() =>
|
||||||
fetchBinary(`layout/obs?layout-name=${encodeURIComponent(e)}`)
|
fetchBinary(
|
||||||
.then(buffer => Universe.matrixFBSToDataframe(buffer))
|
`layout/obs?layout-name=${encodeURIComponent(e)}`
|
||||||
|
).then((buffer) => Universe.matrixFBSToDataframe(buffer))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).then(dfs =>
|
).then((dfs) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "universe: column load success",
|
type: "universe: column load success",
|
||||||
dim: "obsLayout",
|
dim: "obsLayout",
|
||||||
dataframe: Dataframe.Dataframe.empty().withColsFromAll(dfs)
|
dataframe: Dataframe.Dataframe.empty().withColsFromAll(dfs),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -90,13 +93,12 @@ function layoutFetchAndLoad(dispatch, schema) {
|
|||||||
return promise fetching user-configured colors
|
return promise fetching user-configured colors
|
||||||
*/
|
*/
|
||||||
async function userColorsFetchAndLoad(dispatch) {
|
async function userColorsFetchAndLoad(dispatch) {
|
||||||
return fetchJson("colors")
|
return fetchJson("colors").then((response) =>
|
||||||
.then(response =>
|
dispatch({
|
||||||
dispatch({
|
type: "universe: user color load success",
|
||||||
type: "universe: user color load success",
|
userColors: loadUserColorConfig(response),
|
||||||
userColors: loadUserColorConfig(response)
|
})
|
||||||
})
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -185,11 +187,15 @@ async function _doRequestExpressionData(dispatch, getState, genes) {
|
|||||||
/* helper for this function only */
|
/* helper for this function only */
|
||||||
const fetchData = async (geneNames) => {
|
const fetchData = async (geneNames) => {
|
||||||
const query = geneNames
|
const query = geneNames
|
||||||
.map(g => `var:${dubEncURIComponent(varIndexName)}=${dubEncURIComponent(g)}`)
|
.map(
|
||||||
|
(g) =>
|
||||||
|
`var:${dubEncURIComponent(varIndexName)}=${dubEncURIComponent(g)}`
|
||||||
|
)
|
||||||
.join("&");
|
.join("&");
|
||||||
// TODO: why convert to an Object and not a Dataframe?
|
// TODO: why convert to an Object and not a Dataframe?
|
||||||
return fetchBinary(`data/var?${query}`)
|
return fetchBinary(`data/var?${query}`).then((buffer) =>
|
||||||
.then(buffer => Universe.convertDataFBStoObject(universe, buffer));
|
Universe.convertDataFBStoObject(universe, buffer)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* preload data already in cache */
|
/* preload data already in cache */
|
||||||
@@ -351,7 +357,7 @@ const requestDifferentialExpression = (set1, set2, num_genes = 10) => async (
|
|||||||
*/
|
*/
|
||||||
const plimit = new PromiseLimit(5);
|
const plimit = new PromiseLimit(5);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
topNGenes.map(gene =>
|
topNGenes.map((gene) =>
|
||||||
plimit.add(() => _doRequestExpressionData(dispatch, getState, [gene]))
|
plimit.add(() => _doRequestExpressionData(dispatch, getState, [gene]))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -432,11 +438,15 @@ const saveObsAnnotations = () => async (dispatch, getState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function fetchJson(pathAndQuery) {
|
function fetchJson(pathAndQuery) {
|
||||||
return doJsonRequest(`${globals.API.prefix}${globals.API.version}${pathAndQuery}`);
|
return doJsonRequest(
|
||||||
|
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchBinary(pathAndQuery) {
|
function fetchBinary(pathAndQuery) {
|
||||||
return doBinaryRequest(`${globals.API.prefix}${globals.API.version}${pathAndQuery}`);
|
return doBinaryRequest(
|
||||||
|
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Universe } from "../util/stateManager";
|
|||||||
import {
|
import {
|
||||||
postNetworkErrorToast,
|
postNetworkErrorToast,
|
||||||
postAsyncSuccessToast,
|
postAsyncSuccessToast,
|
||||||
postAsyncFailureToast
|
postAsyncFailureToast,
|
||||||
} from "../components/framework/toasters";
|
} from "../components/framework/toasters";
|
||||||
|
|
||||||
function abortableFetch(request, opts, timeout = 0) {
|
function abortableFetch(request, opts, timeout = 0) {
|
||||||
@@ -18,7 +18,7 @@ function abortableFetch(request, opts, timeout = 0) {
|
|||||||
setTimeout(() => controller.abort(), timeout);
|
setTimeout(() => controller.abort(), timeout);
|
||||||
}
|
}
|
||||||
return fetch(request, { ...opts, signal });
|
return fetch(request, { ...opts, signal });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,19 +38,19 @@ async function doReembedFetch(dispatch, getState) {
|
|||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
Accept: "application/octet-stream",
|
Accept: "application/octet-stream",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
method: "umap",
|
method: "umap",
|
||||||
filter: { obs: { index: cells } }
|
filter: { obs: { index: cells } },
|
||||||
}),
|
}),
|
||||||
credentials: "include"
|
credentials: "include",
|
||||||
},
|
},
|
||||||
60000 // 1 minute timeout
|
60000 // 1 minute timeout
|
||||||
);
|
);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "reembed: request start",
|
type: "reembed: request start",
|
||||||
abortableFetch: af
|
abortableFetch: af,
|
||||||
});
|
});
|
||||||
const res = await af.ready();
|
const res = await af.ready();
|
||||||
|
|
||||||
@@ -82,17 +82,17 @@ export function requestReembed() {
|
|||||||
const buffer = await res.arrayBuffer();
|
const buffer = await res.arrayBuffer();
|
||||||
const df = Universe.matrixFBSToDataframe(buffer);
|
const df = Universe.matrixFBSToDataframe(buffer);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "reembed: request completed"
|
type: "reembed: request completed",
|
||||||
});
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "reembed: add reembedding",
|
type: "reembed: add reembedding",
|
||||||
embedding: df,
|
embedding: df,
|
||||||
schema
|
schema,
|
||||||
});
|
});
|
||||||
postAsyncSuccessToast("Re-embedding has completed.");
|
postAsyncSuccessToast("Re-embedding has completed.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "reembed: request aborted"
|
type: "reembed: request aborted",
|
||||||
});
|
});
|
||||||
if (error.name === "AbortError") {
|
if (error.name === "AbortError") {
|
||||||
postAsyncFailureToast("Re-embedding calculation was aborted.");
|
postAsyncFailureToast("Re-embedding calculation was aborted.");
|
||||||
@@ -108,6 +108,6 @@ export function reembedResetWorldToUniverse(dispatch, getState) {
|
|||||||
const { reembedController } = getState();
|
const { reembedController } = getState();
|
||||||
if (reembedController.pendingFetch) reembedController.pendingFetch.abort();
|
if (reembedController.pendingFetch) reembedController.pendingFetch.abort();
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "reembed: clear all reembeddings"
|
type: "reembed: clear all reembeddings",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import TermsOfServicePrompt from "./termsPrompt";
|
|||||||
|
|
||||||
import actions from "../actions";
|
import actions from "../actions";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
loading: state.controls.loading,
|
loading: state.controls.loading,
|
||||||
error: state.controls.error,
|
error: state.controls.error,
|
||||||
graphRenderCounter: state.controls.graphRenderCounter
|
graphRenderCounter: state.controls.graphRenderCounter,
|
||||||
}))
|
}))
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -40,16 +40,16 @@ class App extends React.Component {
|
|||||||
type: "window resize",
|
type: "window resize",
|
||||||
data: {
|
data: {
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
width: window.innerWidth
|
width: window.innerWidth,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "window resize",
|
type: "window resize",
|
||||||
data: {
|
data: {
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
width: window.innerWidth
|
width: window.innerWidth,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class App extends React.Component {
|
|||||||
position: "fixed",
|
position: "fixed",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
top: window.innerHeight / 2,
|
top: window.innerHeight / 2,
|
||||||
left: window.innerWidth / 2 - 50
|
left: window.innerWidth / 2 - 50,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
loading cellxgene
|
loading cellxgene
|
||||||
@@ -82,7 +82,7 @@ class App extends React.Component {
|
|||||||
position: "fixed",
|
position: "fixed",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
top: window.innerHeight / 2,
|
top: window.innerHeight / 2,
|
||||||
left: window.innerWidth / 2 - 50
|
left: window.innerWidth / 2 - 50,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
error loading
|
error loading
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import {
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
Dialog,
|
Dialog,
|
||||||
Classes,
|
Classes,
|
||||||
Colors
|
Colors,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
universe: state.universe,
|
universe: state.universe,
|
||||||
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
|
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
|
||||||
annotations: state.annotations,
|
annotations: state.annotations,
|
||||||
@@ -18,13 +18,13 @@ import {
|
|||||||
saveInProgress: state.autosave?.saveInProgress ?? false,
|
saveInProgress: state.autosave?.saveInProgress ?? false,
|
||||||
lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations,
|
lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations,
|
||||||
error: state.autosave?.error,
|
error: state.autosave?.error,
|
||||||
writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false
|
writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false,
|
||||||
}))
|
}))
|
||||||
class FilenameDialog extends React.Component {
|
class FilenameDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
filenameText: ""
|
filenameText: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class FilenameDialog extends React.Component {
|
|||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "set annotations collection name",
|
type: "set annotations collection name",
|
||||||
data: filenameText
|
data: filenameText,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ class FilenameDialog extends React.Component {
|
|||||||
fontStyle: "italic",
|
fontStyle: "italic",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
color: Colors.ORANGE3
|
color: Colors.ORANGE3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Name cannot be blank
|
Name cannot be blank
|
||||||
@@ -84,7 +84,7 @@ class FilenameDialog extends React.Component {
|
|||||||
fontStyle: "italic",
|
fontStyle: "italic",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
color: Colors.ORANGE3
|
color: Colors.ORANGE3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Only alphanumeric and underscore allowed
|
Only alphanumeric and underscore allowed
|
||||||
@@ -108,7 +108,7 @@ class FilenameDialog extends React.Component {
|
|||||||
onClose={this.dismissFilenameDialog}
|
onClose={this.dismissFilenameDialog}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={e => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.handleCreateFilename();
|
this.handleCreateFilename();
|
||||||
}}
|
}}
|
||||||
@@ -120,7 +120,9 @@ class FilenameDialog extends React.Component {
|
|||||||
autoFocus
|
autoFocus
|
||||||
value={filenameText}
|
value={filenameText}
|
||||||
intent={this.filenameError(filenameText) ? "warning" : "none"}
|
intent={this.filenameError(filenameText) ? "warning" : "none"}
|
||||||
onChange={e => this.setState({ filenameText: e.target.value })}
|
onChange={(e) =>
|
||||||
|
this.setState({ filenameText: e.target.value })
|
||||||
|
}
|
||||||
leftIcon="tag"
|
leftIcon="tag"
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
@@ -129,7 +131,7 @@ class FilenameDialog extends React.Component {
|
|||||||
visibility: this.filenameError(filenameText)
|
visibility: this.filenameError(filenameText)
|
||||||
? "visible"
|
? "visible"
|
||||||
: "hidden",
|
: "hidden",
|
||||||
color: Colors.ORANGE3
|
color: Colors.ORANGE3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.filenameErrorMessage(filenameText)}
|
{this.filenameErrorMessage(filenameText)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as globals from "../../globals";
|
|||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import FilenameDialog from "./filenameDialog";
|
import FilenameDialog from "./filenameDialog";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
universe: state.universe,
|
universe: state.universe,
|
||||||
annotations: state.annotations,
|
annotations: state.annotations,
|
||||||
obsAnnotations: state.universe.obsAnnotations,
|
obsAnnotations: state.universe.obsAnnotations,
|
||||||
@@ -12,13 +12,13 @@ import FilenameDialog from "./filenameDialog";
|
|||||||
lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations,
|
lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations,
|
||||||
error: state.autosave?.error,
|
error: state.autosave?.error,
|
||||||
writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false,
|
writableCategoriesEnabled: state.config?.parameters?.["annotations"] ?? false,
|
||||||
initialDataLoadComplete: state.autosave?.initialDataLoadComplete
|
initialDataLoadComplete: state.autosave?.initialDataLoadComplete,
|
||||||
}))
|
}))
|
||||||
class Autosave extends React.Component {
|
class Autosave extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
timer: null
|
timer: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,22 +64,26 @@ class Autosave extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { writableCategoriesEnabled, saveInProgress, initialDataLoadComplete } = this.props;
|
const {
|
||||||
|
writableCategoriesEnabled,
|
||||||
|
saveInProgress,
|
||||||
|
initialDataLoadComplete,
|
||||||
|
} = this.props;
|
||||||
return writableCategoriesEnabled ? (
|
return writableCategoriesEnabled ? (
|
||||||
<div
|
<div
|
||||||
id="autosave"
|
id="autosave"
|
||||||
data-testclass={
|
data-testclass={
|
||||||
!initialDataLoadComplete
|
!initialDataLoadComplete
|
||||||
? "autosave-init"
|
? "autosave-init"
|
||||||
: (this.needToSave() || saveInProgress)
|
: this.needToSave() || saveInProgress
|
||||||
? "autosave-incomplete"
|
? "autosave-incomplete"
|
||||||
: "autosave-complete"
|
: "autosave-complete"
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
display: "inherit",
|
display: "inherit",
|
||||||
right: globals.leftSidebarWidth + 5,
|
right: globals.leftSidebarWidth + 5,
|
||||||
bottom: 5
|
bottom: 5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.statusMessage()}
|
{this.statusMessage()}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
|
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
categoricalSelection: state.categoricalSelection,
|
categoricalSelection: state.categoricalSelection,
|
||||||
annotations: state.annotations,
|
annotations: state.annotations,
|
||||||
universe: state.universe
|
universe: state.universe,
|
||||||
}))
|
}))
|
||||||
class AnnoDialog extends React.PureComponent {
|
class AnnoDialog extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -30,13 +30,13 @@ class AnnoDialog extends React.PureComponent {
|
|||||||
primaryButtonText,
|
primaryButtonText,
|
||||||
secondaryButtonText,
|
secondaryButtonText,
|
||||||
handleSecondaryButtonSubmit,
|
handleSecondaryButtonSubmit,
|
||||||
primaryButtonProps
|
primaryButtonProps,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
|
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
|
||||||
<form
|
<form
|
||||||
onSubmit={e => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -48,7 +48,7 @@ class AnnoDialog extends React.PureComponent {
|
|||||||
style={{
|
style={{
|
||||||
marginTop: 7,
|
marginTop: 7,
|
||||||
visibility: validationError ? "visible" : "hidden",
|
visibility: validationError ? "visible" : "hidden",
|
||||||
color: Colors.ORANGE3
|
color: Colors.ORANGE3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { connect } from "react-redux";
|
|||||||
import { Button, MenuItem } from "@blueprintjs/core";
|
import { Button, MenuItem } from "@blueprintjs/core";
|
||||||
import { Select } from "@blueprintjs/select";
|
import { Select } from "@blueprintjs/select";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
categoricalSelection: state.categoricalSelection,
|
categoricalSelection: state.categoricalSelection,
|
||||||
annotations: state.annotations,
|
annotations: state.annotations,
|
||||||
universe: state.universe
|
universe: state.universe,
|
||||||
}))
|
}))
|
||||||
class DuplicateCategorySelect extends React.PureComponent {
|
class DuplicateCategorySelect extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -19,7 +19,7 @@ class DuplicateCategorySelect extends React.PureComponent {
|
|||||||
const {
|
const {
|
||||||
allCategoryNames,
|
allCategoryNames,
|
||||||
categoryToDuplicate,
|
categoryToDuplicate,
|
||||||
handleModalDuplicateCategorySelection
|
handleModalDuplicateCategorySelection,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -37,7 +37,7 @@ class DuplicateCategorySelect extends React.PureComponent {
|
|||||||
return <MenuItem onClick={handleClick} key={d} text={d} />;
|
return <MenuItem onClick={handleClick} key={d} text={d} />;
|
||||||
}}
|
}}
|
||||||
noResults={<MenuItem disabled text="No results." />}
|
noResults={<MenuItem disabled text="No results." />}
|
||||||
onItemSelect={d => {
|
onItemSelect={(d) => {
|
||||||
handleModalDuplicateCategorySelection(d);
|
handleModalDuplicateCategorySelection(d);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Popover,
|
Popover,
|
||||||
Position,
|
Position,
|
||||||
PopoverInteractionKind
|
PopoverInteractionKind,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
annotations: state.annotations
|
annotations: state.annotations,
|
||||||
}))
|
}))
|
||||||
class AnnoMenuCategory extends React.PureComponent {
|
class AnnoMenuCategory extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -22,7 +22,7 @@ class AnnoMenuCategory extends React.PureComponent {
|
|||||||
const { dispatch, metadataField } = this.props;
|
const { dispatch, metadataField } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "annotation: activate add new label mode",
|
type: "annotation: activate add new label mode",
|
||||||
data: metadataField
|
data: metadataField,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class AnnoMenuCategory extends React.PureComponent {
|
|||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "annotation: activate category edit mode",
|
type: "annotation: activate category edit mode",
|
||||||
data: metadataField
|
data: metadataField,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class AnnoMenuCategory extends React.PureComponent {
|
|||||||
const { dispatch, metadataField } = this.props;
|
const { dispatch, metadataField } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "annotation: delete category",
|
type: "annotation: delete category",
|
||||||
metadataField
|
metadataField,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ class AnnoMenuCategory extends React.PureComponent {
|
|||||||
isUserAnno,
|
isUserAnno,
|
||||||
createText,
|
createText,
|
||||||
editText,
|
editText,
|
||||||
deleteText
|
deleteText,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class LabelInput extends React.PureComponent {
|
|||||||
const queryResults = this.filterLabels(query);
|
const queryResults = this.filterLabels(query);
|
||||||
this.state = {
|
this.state = {
|
||||||
query,
|
query,
|
||||||
queryResults
|
queryResults,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export default class LabelInput extends React.PureComponent {
|
|||||||
const queryResults = this.filterLabels(query);
|
const queryResults = this.filterLabels(query);
|
||||||
this.setState({
|
this.setState({
|
||||||
query,
|
query,
|
||||||
queryResults
|
queryResults,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { onChange } = this.props;
|
const { onChange } = this.props;
|
||||||
@@ -57,7 +57,7 @@ export default class LabelInput extends React.PureComponent {
|
|||||||
if (target !== query && onSelect) onSelect(target, event);
|
if (target !== query && onSelect) onSelect(target, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleKeyDown = e => {
|
handleKeyDown = (e) => {
|
||||||
/*
|
/*
|
||||||
prevent these events from propagating to containing form/dialog
|
prevent these events from propagating to containing form/dialog
|
||||||
and causing further side effects (eg, closing dialog, submitting
|
and causing further side effects (eg, closing dialog, submitting
|
||||||
@@ -72,7 +72,7 @@ export default class LabelInput extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChange = e => {
|
handleChange = (e) => {
|
||||||
const { onChange } = this.props;
|
const { onChange } = this.props;
|
||||||
if (onChange) onChange(e.target.value);
|
if (onChange) onChange(e.target.value);
|
||||||
};
|
};
|
||||||
@@ -112,16 +112,18 @@ export default class LabelInput extends React.PureComponent {
|
|||||||
|
|
||||||
/* empty query is wildcard */
|
/* empty query is wildcard */
|
||||||
if (query === "") {
|
if (query === "") {
|
||||||
return labelSuggestions.slice(0, LabelInput.QueryResultLimit).map(l => ({
|
return labelSuggestions
|
||||||
target: l,
|
.slice(0, LabelInput.QueryResultLimit)
|
||||||
score: -10000
|
.map((l) => ({
|
||||||
}));
|
target: l,
|
||||||
|
score: -10000,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* else, do a fuzzy query */
|
/* else, do a fuzzy query */
|
||||||
const options = {
|
const options = {
|
||||||
limit: LabelInput.QueryResultLimit,
|
limit: LabelInput.QueryResultLimit,
|
||||||
threshold: -10000 // don't return bad results
|
threshold: -10000, // don't return bad results
|
||||||
};
|
};
|
||||||
let queryResults = fuzzysort.go(query, labelSuggestions, options);
|
let queryResults = fuzzysort.go(query, labelSuggestions, options);
|
||||||
/* exact match will always be first in list */
|
/* exact match will always be first in list */
|
||||||
@@ -149,18 +151,18 @@ export default class LabelInput extends React.PureComponent {
|
|||||||
|
|
||||||
const popoverProps = {
|
const popoverProps = {
|
||||||
minimal: true,
|
minimal: true,
|
||||||
...props.popoverProps
|
...props.popoverProps,
|
||||||
};
|
};
|
||||||
const inputProps = {
|
const inputProps = {
|
||||||
...props.inputProps,
|
...props.inputProps,
|
||||||
autoFocus: false
|
autoFocus: false,
|
||||||
};
|
};
|
||||||
const { queryResults } = this.state;
|
const { queryResults } = this.state;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suggest
|
<Suggest
|
||||||
fill
|
fill
|
||||||
inputValueRenderer={i => i.target}
|
inputValueRenderer={(i) => i.target}
|
||||||
items={queryResults}
|
items={queryResults}
|
||||||
itemRenderer={this.renderLabelSuggestion}
|
itemRenderer={this.renderLabelSuggestion}
|
||||||
onItemSelect={this.handleItemSelect}
|
onItemSelect={this.handleItemSelect}
|
||||||
|
|||||||
@@ -4,60 +4,60 @@ import { Colors } from "@blueprintjs/core";
|
|||||||
import { AnnotationsHelpers } from "../../util/stateManager";
|
import { AnnotationsHelpers } from "../../util/stateManager";
|
||||||
|
|
||||||
export function isLabelErroneous(label, metadataField, ontology, schema) {
|
export function isLabelErroneous(label, metadataField, ontology, schema) {
|
||||||
/*
|
/*
|
||||||
return false if this is a LEGAL/acceptable category name or NULL/empty string,
|
return false if this is a LEGAL/acceptable category name or NULL/empty string,
|
||||||
or return an error type.
|
or return an error type.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* allow empty string */
|
/* allow empty string */
|
||||||
if (label === "") return false;
|
if (label === "") return false;
|
||||||
|
|
||||||
/* check for label syntax errors, but allow terms in ontology */
|
/* check for label syntax errors, but allow terms in ontology */
|
||||||
const termInOntology = ontology?.termSet.has(label) ?? false;
|
const termInOntology = ontology?.termSet.has(label) ?? false;
|
||||||
const error = AnnotationsHelpers.annotationNameIsErroneous(label);
|
const error = AnnotationsHelpers.annotationNameIsErroneous(label);
|
||||||
if (error && !termInOntology) return error;
|
if (error && !termInOntology) return error;
|
||||||
|
|
||||||
/* disallow duplicates */
|
/* disallow duplicates */
|
||||||
const { obsByName } = schema.annotations;
|
const { obsByName } = schema.annotations;
|
||||||
if (obsByName[metadataField].categories.indexOf(label) !== -1)
|
if (obsByName[metadataField].categories.indexOf(label) !== -1)
|
||||||
return "duplicate";
|
return "duplicate";
|
||||||
|
|
||||||
/* otherwise, no error */
|
/* otherwise, no error */
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* all other errors - map code to human error message */
|
/* all other errors - map code to human error message */
|
||||||
const errorMessageMap = {
|
const errorMessageMap = {
|
||||||
"empty-string": "Blank names not allowed",
|
"empty-string": "Blank names not allowed",
|
||||||
duplicate: "Name must be unique",
|
duplicate: "Name must be unique",
|
||||||
"trim-spaces": "Leading and trailing spaces not allowed",
|
"trim-spaces": "Leading and trailing spaces not allowed",
|
||||||
"illegal-characters":
|
"illegal-characters":
|
||||||
"Only alphanumeric and special characters (-_.) allowed",
|
"Only alphanumeric and special characters (-_.) allowed",
|
||||||
"multi-space-run": "Multiple consecutive spaces not allowed"
|
"multi-space-run": "Multiple consecutive spaces not allowed",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function labelPrompt(err, prolog, epilog) {
|
export function labelPrompt(err, prolog, epilog) {
|
||||||
let errPrompt = null;
|
let errPrompt = null;
|
||||||
if (err) {
|
if (err) {
|
||||||
let errMsg = errorMessageMap[err] ?? "error";
|
let errMsg = errorMessageMap[err] ?? "error";
|
||||||
errMsg = errMsg[0].toLowerCase() + errMsg.slice(1);
|
errMsg = errMsg[0].toLowerCase() + errMsg.slice(1);
|
||||||
errPrompt = (
|
errPrompt = (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
marginTop: 7,
|
marginTop: 7,
|
||||||
color: Colors.ORANGE3
|
color: Colors.ORANGE3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{errMsg}
|
{errMsg}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{prolog}
|
{prolog}
|
||||||
{err ? " - " : null}
|
{err ? " - " : null}
|
||||||
{errPrompt}
|
{errPrompt}
|
||||||
{epilog}
|
{epilog}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const setupParallelCoordinates = (width, height, margin) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
svg,
|
svg,
|
||||||
ctx
|
ctx,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const innerHeight = height - 2;
|
|||||||
|
|
||||||
export const devicePixelRatio = window.devicePixelRatio || 1;
|
export const devicePixelRatio = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
export const createDimensions = data => {
|
export const createDimensions = (data) => {
|
||||||
const newArr = [];
|
const newArr = [];
|
||||||
_.each(data, (value, key) => {
|
_.each(data, (value, key) => {
|
||||||
if (value.range) {
|
if (value.range) {
|
||||||
@@ -21,12 +21,12 @@ export const createDimensions = data => {
|
|||||||
key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */,
|
key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */,
|
||||||
type: {
|
type: {
|
||||||
within: (d, extent, dim) =>
|
within: (d, extent, dim) =>
|
||||||
extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1]
|
extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1],
|
||||||
},
|
},
|
||||||
scale: d3
|
scale: d3
|
||||||
.scaleSqrt()
|
.scaleSqrt()
|
||||||
.range([innerHeight, 0])
|
.range([innerHeight, 0])
|
||||||
.domain([0, value.range.max])
|
.domain([0, value.range.max]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
.range([1, legendheight - margin.top - margin.bottom])
|
.range([1, legendheight - margin.top - margin.bottom])
|
||||||
.domain([
|
.domain([
|
||||||
colorscale.domain()[1],
|
colorscale.domain()[1],
|
||||||
colorscale.domain()[0]
|
colorscale.domain()[0],
|
||||||
]); /* we flip this to make viridis colors dark if high in the color scale */
|
]); /* we flip this to make viridis colors dark if high in the color scale */
|
||||||
|
|
||||||
// image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5
|
// image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5
|
||||||
const image = ctx.createImageData(1, legendheight);
|
const image = ctx.createImageData(1, legendheight);
|
||||||
d3.range(legendheight).forEach(i => {
|
d3.range(legendheight).forEach((i) => {
|
||||||
const c = d3.rgb(colorscale(legendscale.invert(i)));
|
const c = d3.rgb(colorscale(legendscale.invert(i)));
|
||||||
image.data[4 * i] = c.r;
|
image.data[4 * i] = c.r;
|
||||||
image.data[4 * i + 1] = c.g;
|
image.data[4 * i + 1] = c.g;
|
||||||
@@ -62,11 +62,7 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const legendaxis = d3
|
const legendaxis = d3.axisRight().scale(legendscale).tickSize(6).ticks(8);
|
||||||
.axisRight()
|
|
||||||
.scale(legendscale)
|
|
||||||
.tickSize(6)
|
|
||||||
.ticks(8);
|
|
||||||
|
|
||||||
const svg = d3
|
const svg = d3
|
||||||
.select(selectorId)
|
.select(selectorId)
|
||||||
@@ -98,10 +94,10 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
.text(colorAccessor);
|
.text(colorAccessor);
|
||||||
};
|
};
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
colorScale: state.colors.scale,
|
colorScale: state.colors.scale,
|
||||||
responsive: state.responsive
|
responsive: state.responsive,
|
||||||
}))
|
}))
|
||||||
class ContinuousLegend extends React.Component {
|
class ContinuousLegend extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -118,9 +114,7 @@ class ContinuousLegend extends React.Component {
|
|||||||
prevProps.responsive.width !== responsive.width
|
prevProps.responsive.width !== responsive.width
|
||||||
) {
|
) {
|
||||||
/* always remove it, if it's not continuous we don't put it back. */
|
/* always remove it, if it's not continuous we don't put it back. */
|
||||||
d3.select("#continuous_legend")
|
d3.select("#continuous_legend").selectAll("*").remove();
|
||||||
.selectAll("*")
|
|
||||||
.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (colorAccessor && colorScale && colorScale.range) {
|
if (colorAccessor && colorScale && colorScale.range) {
|
||||||
@@ -144,7 +138,7 @@ class ContinuousLegend extends React.Component {
|
|||||||
position: "fixed",
|
position: "fixed",
|
||||||
display: colorAccessor ? "inherit" : "none",
|
display: colorAccessor ? "inherit" : "none",
|
||||||
right: globals.leftSidebarWidth,
|
right: globals.leftSidebarWidth,
|
||||||
top: responsive.height / 2
|
top: responsive.height / 2,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from "react";
|
|||||||
|
|
||||||
import styles from "./container.css";
|
import styles from "./container.css";
|
||||||
|
|
||||||
const Container = props => {
|
const Container = (props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
return <div className={styles.container}>{children}</div>;
|
return <div className={styles.container}>{children}</div>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
const Logo = props => {
|
const Logo = (props) => {
|
||||||
const { size } = props;
|
const { size } = props;
|
||||||
return (
|
return (
|
||||||
<svg width={size} height={size} viewBox="0 0 48 48" fill="none">
|
<svg width={size} height={size} viewBox="0 0 48 48" fill="none">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
ControlGroup
|
ControlGroup,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { Suggest } from "@blueprintjs/select";
|
import { Suggest } from "@blueprintjs/select";
|
||||||
import HistogramBrush from "../brushableHistogram";
|
import HistogramBrush from "../brushableHistogram";
|
||||||
@@ -19,7 +19,7 @@ import * as globals from "../../globals";
|
|||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import {
|
import {
|
||||||
postUserErrorToast,
|
postUserErrorToast,
|
||||||
keepAroundErrorToast
|
keepAroundErrorToast,
|
||||||
} from "../framework/toasters";
|
} from "../framework/toasters";
|
||||||
|
|
||||||
import { memoize } from "../../util/dataframe/util";
|
import { memoize } from "../../util/dataframe/util";
|
||||||
@@ -40,7 +40,7 @@ const renderGene = (fuzzySortResult, { handleClick, modifiers }) => {
|
|||||||
// See https://github.com/chanzuckerberg/cellxgene/issues/483
|
// See https://github.com/chanzuckerberg/cellxgene/issues/483
|
||||||
// label={gene.n_counts}
|
// label={gene.n_counts}
|
||||||
key={geneName}
|
key={geneName}
|
||||||
onClick={g =>
|
onClick={(g) =>
|
||||||
/* this fires when user clicks a menu item */
|
/* this fires when user clicks a menu item */
|
||||||
handleClick(g)
|
handleClick(g)
|
||||||
}
|
}
|
||||||
@@ -53,17 +53,17 @@ const filterGenes = (query, genes) =>
|
|||||||
/* fires on load, once, and then for each character typed into the input */
|
/* fires on load, once, and then for each character typed into the input */
|
||||||
fuzzysort.go(query, genes, {
|
fuzzysort.go(query, genes, {
|
||||||
limit: 5,
|
limit: 5,
|
||||||
threshold: -10000 // don't return bad results
|
threshold: -10000, // don't return bad results
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => {
|
@connect((state) => {
|
||||||
return {
|
return {
|
||||||
obsAnnotations: state.world?.obsAnnotations,
|
obsAnnotations: state.world?.obsAnnotations,
|
||||||
userDefinedGenes: state.controls.userDefinedGenes,
|
userDefinedGenes: state.controls.userDefinedGenes,
|
||||||
userDefinedGenesLoading: state.controls.userDefinedGenesLoading,
|
userDefinedGenesLoading: state.controls.userDefinedGenesLoading,
|
||||||
world: state.world,
|
world: state.world,
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
differential: state.differential
|
differential: state.differential,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
class GeneExpression extends React.Component {
|
class GeneExpression extends React.Component {
|
||||||
@@ -72,11 +72,11 @@ class GeneExpression extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
bulkAdd: "",
|
bulkAdd: "",
|
||||||
tab: "autosuggest",
|
tab: "autosuggest",
|
||||||
activeItem: null
|
activeItem: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_genesToUpper = listGenes => {
|
_genesToUpper = (listGenes) => {
|
||||||
// Has to be a Map to preserve index
|
// Has to be a Map to preserve index
|
||||||
const upperGenes = new Map();
|
const upperGenes = new Map();
|
||||||
for (let i = 0, { length } = listGenes; i < length; i += 1) {
|
for (let i = 0, { length } = listGenes; i < length; i += 1) {
|
||||||
@@ -87,7 +87,7 @@ class GeneExpression extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
_memoGenesToUpper = memoize(this._genesToUpper, arr => arr);
|
_memoGenesToUpper = memoize(this._genesToUpper, (arr) => arr);
|
||||||
|
|
||||||
handleBulkAddClick = () => {
|
handleBulkAddClick = () => {
|
||||||
const { world, dispatch, userDefinedGenes } = this.props;
|
const { world, dispatch, userDefinedGenes } = this.props;
|
||||||
@@ -115,7 +115,7 @@ class GeneExpression extends React.Component {
|
|||||||
dispatch({ type: "bulk user defined gene start" });
|
dispatch({ type: "bulk user defined gene start" });
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
[...upperGenes.keys()].map(upperGene => {
|
[...upperGenes.keys()].map((upperGene) => {
|
||||||
if (upperUserDefinedGenes.get(upperGene) !== undefined) {
|
if (upperUserDefinedGenes.get(upperGene) !== undefined) {
|
||||||
return keepAroundErrorToast("That gene already exists");
|
return keepAroundErrorToast("That gene already exists");
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ class GeneExpression extends React.Component {
|
|||||||
world,
|
world,
|
||||||
userDefinedGenes,
|
userDefinedGenes,
|
||||||
userDefinedGenesLoading,
|
userDefinedGenesLoading,
|
||||||
differential
|
differential,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const varIndexName = world?.schema?.annotations?.var?.index;
|
const varIndexName = world?.schema?.annotations?.var?.index;
|
||||||
const varIndex = world?.varAnnotations?.col(varIndexName)?.asArray();
|
const varIndex = world?.varAnnotations?.col(varIndexName)?.asArray();
|
||||||
@@ -218,7 +218,7 @@ class GeneExpression extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: globals.leftSidebarSectionPadding
|
padding: globals.leftSidebarSectionPadding,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -250,7 +250,7 @@ class GeneExpression extends React.Component {
|
|||||||
<ControlGroup
|
<ControlGroup
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: globals.leftSidebarSectionPadding,
|
paddingLeft: globals.leftSidebarSectionPadding,
|
||||||
paddingBottom: globals.leftSidebarSectionPadding
|
paddingBottom: globals.leftSidebarSectionPadding,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Suggest
|
<Suggest
|
||||||
@@ -261,7 +261,7 @@ class GeneExpression extends React.Component {
|
|||||||
userDefinedGenesLoading ? () => true : () => false
|
userDefinedGenesLoading ? () => true : () => false
|
||||||
}
|
}
|
||||||
noResults={<MenuItem disabled text="No matching genes." />}
|
noResults={<MenuItem disabled text="No matching genes." />}
|
||||||
onItemSelect={g => {
|
onItemSelect={(g) => {
|
||||||
/* this happens on 'enter' */
|
/* this happens on 'enter' */
|
||||||
this.handleClick(g);
|
this.handleClick(g);
|
||||||
}}
|
}}
|
||||||
@@ -271,7 +271,9 @@ class GeneExpression extends React.Component {
|
|||||||
return "";
|
return "";
|
||||||
}}
|
}}
|
||||||
itemListPredicate={filterGenes}
|
itemListPredicate={filterGenes}
|
||||||
onActiveItemChange={item => this.setState({ activeItem: item })}
|
onActiveItemChange={(item) =>
|
||||||
|
this.setState({ activeItem: item })
|
||||||
|
}
|
||||||
itemRenderer={renderGene.bind(this)}
|
itemRenderer={renderGene.bind(this)}
|
||||||
items={varIndex || ["No genes"]}
|
items={varIndex || ["No genes"]}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
@@ -289,7 +291,7 @@ class GeneExpression extends React.Component {
|
|||||||
{tab === "bulkadd" ? (
|
{tab === "bulkadd" ? (
|
||||||
<div style={{ paddingLeft: globals.leftSidebarSectionPadding }}>
|
<div style={{ paddingLeft: globals.leftSidebarSectionPadding }}>
|
||||||
<form
|
<form
|
||||||
onSubmit={e => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.handleBulkAddClick();
|
this.handleBulkAddClick();
|
||||||
}}
|
}}
|
||||||
@@ -300,7 +302,7 @@ class GeneExpression extends React.Component {
|
|||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
onChange={e => {
|
onChange={(e) => {
|
||||||
this.setState({ bulkAdd: e.target.value });
|
this.setState({ bulkAdd: e.target.value });
|
||||||
}}
|
}}
|
||||||
id="text-input-bulk-add"
|
id="text-input-bulk-add"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { glPointFlags, glPointSize } from "../../util/glHelpers";
|
import { glPointFlags, glPointSize } from "../../util/glHelpers";
|
||||||
|
|
||||||
export default function(regl) {
|
export default function (regl) {
|
||||||
return regl({
|
return regl({
|
||||||
vert: `
|
vert: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
@@ -54,18 +54,18 @@ export default function(regl) {
|
|||||||
attributes: {
|
attributes: {
|
||||||
position: regl.prop("position"),
|
position: regl.prop("position"),
|
||||||
color: regl.prop("color"),
|
color: regl.prop("color"),
|
||||||
flag: regl.prop("flag")
|
flag: regl.prop("flag"),
|
||||||
},
|
},
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
distance: regl.prop("distance"),
|
distance: regl.prop("distance"),
|
||||||
projView: regl.prop("projView"),
|
projView: regl.prop("projView"),
|
||||||
nPoints: regl.prop("nPoints"),
|
nPoints: regl.prop("nPoints"),
|
||||||
minViewportDimension: regl.prop("minViewportDimension")
|
minViewportDimension: regl.prop("minViewportDimension"),
|
||||||
},
|
},
|
||||||
|
|
||||||
count: regl.prop("count"),
|
count: regl.prop("count"),
|
||||||
|
|
||||||
primitive: "points"
|
primitive: "points",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ function createProjectionTF(viewportWidth, viewportHeight) {
|
|||||||
const minDim = Math.min(viewportWidth, heightMinusGutter);
|
const minDim = Math.min(viewportWidth, heightMinusGutter);
|
||||||
const aspectScale = [
|
const aspectScale = [
|
||||||
(fractionToUse * minDim) / viewportWidth,
|
(fractionToUse * minDim) / viewportWidth,
|
||||||
(fractionToUse * minDim) / viewportHeight
|
(fractionToUse * minDim) / viewportHeight,
|
||||||
];
|
];
|
||||||
const m = mat3.create();
|
const m = mat3.create();
|
||||||
mat3.fromTranslation(m, [
|
mat3.fromTranslation(m, [
|
||||||
0,
|
0,
|
||||||
-topGutterSizePx / viewportHeight / aspectScale[1]
|
-topGutterSizePx / viewportHeight / aspectScale[1],
|
||||||
]);
|
]);
|
||||||
mat3.scale(m, m, aspectScale);
|
mat3.scale(m, m, aspectScale);
|
||||||
return m;
|
return m;
|
||||||
@@ -73,7 +73,7 @@ function renderThrottle(callback) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
universe: state.universe,
|
universe: state.universe,
|
||||||
world: state.world,
|
world: state.world,
|
||||||
crossfilter: state.crossfilter,
|
crossfilter: state.crossfilter,
|
||||||
@@ -85,7 +85,7 @@ function renderThrottle(callback) {
|
|||||||
centroidLabels: state.centroidLabels,
|
centroidLabels: state.centroidLabels,
|
||||||
graphInteractionMode: state.controls.graphInteractionMode,
|
graphInteractionMode: state.controls.graphInteractionMode,
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
pointDilation: state.pointDilation
|
pointDilation: state.pointDilation,
|
||||||
}))
|
}))
|
||||||
class Graph extends React.Component {
|
class Graph extends React.Component {
|
||||||
computePointPositions = memoize((X, Y, modelTF) => {
|
computePointPositions = memoize((X, Y, modelTF) => {
|
||||||
@@ -102,7 +102,7 @@ class Graph extends React.Component {
|
|||||||
return positions;
|
return positions;
|
||||||
});
|
});
|
||||||
|
|
||||||
computePointColors = memoize(rgb => {
|
computePointColors = memoize((rgb) => {
|
||||||
/*
|
/*
|
||||||
compute webgl colors for each point
|
compute webgl colors for each point
|
||||||
*/
|
*/
|
||||||
@@ -186,13 +186,13 @@ class Graph extends React.Component {
|
|||||||
positions: null,
|
positions: null,
|
||||||
colors: null,
|
colors: null,
|
||||||
sizes: null,
|
sizes: null,
|
||||||
flags: null
|
flags: null,
|
||||||
};
|
};
|
||||||
this.state = {
|
this.state = {
|
||||||
toolSVG: null,
|
toolSVG: null,
|
||||||
tool: null,
|
tool: null,
|
||||||
container: null,
|
container: null,
|
||||||
cameraRender: 0
|
cameraRender: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ class Graph extends React.Component {
|
|||||||
camera,
|
camera,
|
||||||
modelTF,
|
modelTF,
|
||||||
modelInvTF: mat3.invert([], modelTF),
|
modelInvTF: mat3.invert([], modelTF),
|
||||||
projectionTF
|
projectionTF,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ class Graph extends React.Component {
|
|||||||
layoutChoice,
|
layoutChoice,
|
||||||
graphInteractionMode,
|
graphInteractionMode,
|
||||||
pointDilation,
|
pointDilation,
|
||||||
colorAccessor
|
colorAccessor,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { regl, toolSVG, camera, modelTF } = this.state;
|
const { regl, toolSVG, camera, modelTF } = this.state;
|
||||||
let stateChanges = {};
|
let stateChanges = {};
|
||||||
@@ -274,7 +274,7 @@ class Graph extends React.Component {
|
|||||||
needsRepaint = true;
|
needsRepaint = true;
|
||||||
stateChanges = {
|
stateChanges = {
|
||||||
...stateChanges,
|
...stateChanges,
|
||||||
projectionTF
|
projectionTF,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class Graph extends React.Component {
|
|||||||
// If the window size has changed we want to recreate all SVGs
|
// If the window size has changed we want to recreate all SVGs
|
||||||
stateChanges = {
|
stateChanges = {
|
||||||
...stateChanges,
|
...stateChanges,
|
||||||
...this.createToolSVG()
|
...this.createToolSVG(),
|
||||||
};
|
};
|
||||||
} else if (
|
} else if (
|
||||||
(responsive.height && responsive.width && !toolSVG) ||
|
(responsive.height && responsive.width && !toolSVG) ||
|
||||||
@@ -345,7 +345,7 @@ class Graph extends React.Component {
|
|||||||
// If lasso/zoom is switched
|
// If lasso/zoom is switched
|
||||||
stateChanges = {
|
stateChanges = {
|
||||||
...stateChanges,
|
...stateChanges,
|
||||||
...this.createToolSVG()
|
...this.createToolSVG(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,12 +369,12 @@ class Graph extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCanvasEvent = e => {
|
handleCanvasEvent = (e) => {
|
||||||
const { camera, projectionTF } = this.state;
|
const { camera, projectionTF } = this.state;
|
||||||
if (e.type !== "wheel") e.preventDefault();
|
if (e.type !== "wheel") e.preventDefault();
|
||||||
if (camera.handleEvent(e, projectionTF)) {
|
if (camera.handleEvent(e, projectionTF)) {
|
||||||
this.renderCanvas();
|
this.renderCanvas();
|
||||||
this.setState(state => {
|
this.setState((state) => {
|
||||||
return { ...state, updateOverlay: !state.updateOverlay };
|
return { ...state, updateOverlay: !state.updateOverlay };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -389,9 +389,7 @@ class Graph extends React.Component {
|
|||||||
|
|
||||||
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */
|
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */
|
||||||
|
|
||||||
d3.select("#lasso-layer")
|
d3.select("#lasso-layer").selectAll(".lasso-group").remove();
|
||||||
.selectAll(".lasso-group")
|
|
||||||
.remove();
|
|
||||||
|
|
||||||
// Don't render or recreate toolSVG if currently in zoom mode
|
// Don't render or recreate toolSVG if currently in zoom mode
|
||||||
if (graphInteractionMode !== "select") {
|
if (graphInteractionMode !== "select") {
|
||||||
@@ -443,7 +441,7 @@ class Graph extends React.Component {
|
|||||||
*/
|
*/
|
||||||
const screenCoords = [
|
const screenCoords = [
|
||||||
this.mapPointToScreen(currentSelection.brushCoords.northwest),
|
this.mapPointToScreen(currentSelection.brushCoords.northwest),
|
||||||
this.mapPointToScreen(currentSelection.brushCoords.southeast)
|
this.mapPointToScreen(currentSelection.brushCoords.southeast),
|
||||||
];
|
];
|
||||||
if (!toolCurrentSelection) {
|
if (!toolCurrentSelection) {
|
||||||
/* tool is not selected, so just move the brush */
|
/* tool is not selected, so just move the brush */
|
||||||
@@ -480,7 +478,7 @@ class Graph extends React.Component {
|
|||||||
/*
|
/*
|
||||||
if there is a current selection, make sure the lasso tool matches
|
if there is a current selection, make sure the lasso tool matches
|
||||||
*/
|
*/
|
||||||
const polygon = currentSelection.polygon.map(p =>
|
const polygon = currentSelection.polygon.map((p) =>
|
||||||
this.mapPointToScreen(p)
|
this.mapPointToScreen(p)
|
||||||
);
|
);
|
||||||
tool.move(polygon);
|
tool.move(polygon);
|
||||||
@@ -551,7 +549,7 @@ class Graph extends React.Component {
|
|||||||
),
|
),
|
||||||
Math.round(
|
Math.round(
|
||||||
-((xy[1] + 1) / 2 - 1) * (responsive.height - this.graphPaddingTop)
|
-((xy[1] + 1) / 2 - 1) * (responsive.height - this.graphPaddingTop)
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
return pin;
|
return pin;
|
||||||
}
|
}
|
||||||
@@ -571,12 +569,12 @@ class Graph extends React.Component {
|
|||||||
const s = d3.event.selection;
|
const s = d3.event.selection;
|
||||||
const brushCoords = {
|
const brushCoords = {
|
||||||
northwest: this.mapScreenToPoint([s[0][0], s[0][1]]),
|
northwest: this.mapScreenToPoint([s[0][0], s[0][1]]),
|
||||||
southeast: this.mapScreenToPoint([s[1][0], s[1][1]])
|
southeast: this.mapScreenToPoint([s[1][0], s[1][1]]),
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "graph brush change",
|
type: "graph brush change",
|
||||||
brushCoords
|
brushCoords,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,15 +599,15 @@ class Graph extends React.Component {
|
|||||||
if (s) {
|
if (s) {
|
||||||
const brushCoords = {
|
const brushCoords = {
|
||||||
northwest: this.mapScreenToPoint(s[0]),
|
northwest: this.mapScreenToPoint(s[0]),
|
||||||
southeast: this.mapScreenToPoint(s[1])
|
southeast: this.mapScreenToPoint(s[1]),
|
||||||
};
|
};
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "graph brush end",
|
type: "graph brush end",
|
||||||
brushCoords
|
brushCoords,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "graph brush deselect"
|
type: "graph brush deselect",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,14 +615,14 @@ class Graph extends React.Component {
|
|||||||
handleBrushDeselectAction() {
|
handleBrushDeselectAction() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "graph brush deselect"
|
type: "graph brush deselect",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLassoStart() {
|
handleLassoStart() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "graph lasso start"
|
type: "graph lasso start",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,7 +640,7 @@ class Graph extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "graph lasso end",
|
type: "graph lasso end",
|
||||||
polygon: polygon.map(xy => this.mapScreenToPoint(xy)) // transform the polygon
|
polygon: polygon.map((xy) => this.mapScreenToPoint(xy)), // transform the polygon
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -667,7 +665,7 @@ class Graph extends React.Component {
|
|||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "change opacity deselected cells in 2d graph background",
|
type: "change opacity deselected cells in 2d graph background",
|
||||||
data: e.target.value
|
data: e.target.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,7 +686,7 @@ class Graph extends React.Component {
|
|||||||
regl.poll();
|
regl.poll();
|
||||||
regl.clear({
|
regl.clear({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
color: [1, 1, 1, 1]
|
color: [1, 1, 1, 1],
|
||||||
});
|
});
|
||||||
drawPoints({
|
drawPoints({
|
||||||
distance: camera.distance(),
|
distance: camera.distance(),
|
||||||
@@ -698,7 +696,7 @@ class Graph extends React.Component {
|
|||||||
count: this.count,
|
count: this.count,
|
||||||
projView,
|
projView,
|
||||||
nPoints: universe.nObs,
|
nPoints: universe.nObs,
|
||||||
minViewportDimension: Math.min(width || 800, height || 600)
|
minViewportDimension: Math.min(width || 800, height || 600),
|
||||||
});
|
});
|
||||||
regl._gl.flush();
|
regl._gl.flush();
|
||||||
}
|
}
|
||||||
@@ -711,7 +709,7 @@ class Graph extends React.Component {
|
|||||||
pointBuffer,
|
pointBuffer,
|
||||||
flagBuffer,
|
flagBuffer,
|
||||||
camera,
|
camera,
|
||||||
projectionTF
|
projectionTF,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
this.renderPoints(
|
this.renderPoints(
|
||||||
regl,
|
regl,
|
||||||
@@ -737,7 +735,7 @@ class Graph extends React.Component {
|
|||||||
zIndex: -9999,
|
zIndex: -9999,
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: this.graphPaddingTop,
|
top: this.graphPaddingTop,
|
||||||
right: globals.leftSidebarWidth
|
right: globals.leftSidebarWidth,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div id="graphAttachPoint">
|
<div id="graphAttachPoint">
|
||||||
@@ -774,7 +772,7 @@ class Graph extends React.Component {
|
|||||||
width={responsive.width - this.graphPaddingRightLeft}
|
width={responsive.width - this.graphPaddingRightLeft}
|
||||||
height={responsive.height - this.graphPaddingTop}
|
height={responsive.height - this.graphPaddingTop}
|
||||||
data-testid="layout-graph"
|
data-testid="layout-graph"
|
||||||
ref={canvas => {
|
ref={(canvas) => {
|
||||||
this.reglCanvas = canvas;
|
this.reglCanvas = canvas;
|
||||||
}}
|
}}
|
||||||
onMouseDown={this.handleCanvasEvent}
|
onMouseDown={this.handleCanvasEvent}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { connect } from "react-redux";
|
|||||||
import { categoryLabelDisplayStringLongLength } from "../../../globals";
|
import { categoryLabelDisplayStringLongLength } from "../../../globals";
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
colorAccessor: state.colors.colorAccessor,
|
colorAccessor: state.colors.colorAccessor,
|
||||||
dilatedValue: state.pointDilation.categoryField,
|
dilatedValue: state.pointDilation.categoryField,
|
||||||
labels: state.centroidLabels.labels
|
labels: state.centroidLabels.labels,
|
||||||
}))
|
}))
|
||||||
class CentroidLabels extends PureComponent {
|
class CentroidLabels extends PureComponent {
|
||||||
// Check to see if centroids have either just been displayed or removed from the overlay
|
// Check to see if centroids have either just been displayed or removed from the overlay
|
||||||
componentDidUpdate = prevProps => {
|
componentDidUpdate = (prevProps) => {
|
||||||
const { labels, overlayToggled } = this.props;
|
const { labels, overlayToggled } = this.props;
|
||||||
const prevSize = prevProps.labels.size;
|
const prevSize = prevProps.labels.size;
|
||||||
const { size } = labels;
|
const { size } = labels;
|
||||||
@@ -33,7 +33,7 @@ class CentroidLabels extends PureComponent {
|
|||||||
inverseTransform,
|
inverseTransform,
|
||||||
dilatedValue,
|
dilatedValue,
|
||||||
dispatch,
|
dispatch,
|
||||||
colorAccessor
|
colorAccessor,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const labelSVGS = [];
|
const labelSVGS = [];
|
||||||
@@ -74,20 +74,20 @@ class CentroidLabels extends PureComponent {
|
|||||||
fontSize,
|
fontSize,
|
||||||
fontWeight,
|
fontWeight,
|
||||||
fill: "black",
|
fill: "black",
|
||||||
userSelect: "none"
|
userSelect: "none",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={e =>
|
onMouseEnter={(e) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "category value mouse hover start",
|
type: "category value mouse hover start",
|
||||||
metadataField: colorAccessor,
|
metadataField: colorAccessor,
|
||||||
categoryField: e.target.getAttribute("data-label")
|
categoryField: e.target.getAttribute("data-label"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onMouseOut={e =>
|
onMouseOut={(e) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "category value mouse hover end",
|
type: "category value mouse hover end",
|
||||||
metadataField: colorAccessor,
|
metadataField: colorAccessor,
|
||||||
categoryField: e.target.getAttribute("data-label")
|
categoryField: e.target.getAttribute("data-label"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pointerEvents="visiblePainted"
|
pointerEvents="visiblePainted"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { connect } from "react-redux";
|
|||||||
import styles from "../graph.css";
|
import styles from "../graph.css";
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
responsive: state.responsive
|
responsive: state.responsive,
|
||||||
}))
|
}))
|
||||||
class GraphOverlayLayer extends PureComponent {
|
class GraphOverlayLayer extends PureComponent {
|
||||||
/*
|
/*
|
||||||
@@ -18,11 +18,11 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
display: {}
|
display: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixToTransformString = m => {
|
matrixToTransformString = (m) => {
|
||||||
/*
|
/*
|
||||||
Translates the gl-matrix mat3 to SVG matrix transform style
|
Translates the gl-matrix mat3 to SVG matrix transform style
|
||||||
|
|
||||||
@@ -34,13 +34,13 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
return `matrix(${m[0]} ${m[1]} ${m[3]} ${m[4]} ${m[6]} ${m[7]})`;
|
return `matrix(${m[0]} ${m[1]} ${m[3]} ${m[4]} ${m[6]} ${m[7]})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
reverseMatrixScaleTransformString = m => {
|
reverseMatrixScaleTransformString = (m) => {
|
||||||
return `matrix(${1 / m[0]} 0 0 ${1 / m[4]} 0 0)`;
|
return `matrix(${1 / m[0]} 0 0 ${1 / m[4]} 0 0)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is passed to all children, should be called when an overlay's display state is toggled along with the overlay name and its new display state in boolean form
|
// This is passed to all children, should be called when an overlay's display state is toggled along with the overlay name and its new display state in boolean form
|
||||||
overlayToggled = (overlay, displaying) => {
|
overlayToggled = (overlay, displaying) => {
|
||||||
this.setState(state => {
|
this.setState((state) => {
|
||||||
return { ...state, display: { ...state.display, [overlay]: displaying } };
|
return { ...state, display: { ...state.display, [overlay]: displaying } };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -54,13 +54,13 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
graphPaddingRightLeft,
|
graphPaddingRightLeft,
|
||||||
graphPaddingTop,
|
graphPaddingTop,
|
||||||
children,
|
children,
|
||||||
handleCanvasEvent
|
handleCanvasEvent,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!cameraTF) return null;
|
if (!cameraTF) return null;
|
||||||
|
|
||||||
const { display } = this.state;
|
const { display } = this.state;
|
||||||
const displaying = Object.values(display).some(value => value); // check to see if at least one overlay is currently displayed
|
const displaying = Object.values(display).some((value) => value); // check to see if at least one overlay is currently displayed
|
||||||
|
|
||||||
const inverseTransform = `${this.reverseMatrixScaleTransformString(
|
const inverseTransform = `${this.reverseMatrixScaleTransformString(
|
||||||
modelTF
|
modelTF
|
||||||
@@ -68,15 +68,15 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
cameraTF
|
cameraTF
|
||||||
)} ${this.reverseMatrixScaleTransformString(
|
)} ${this.reverseMatrixScaleTransformString(
|
||||||
projectionTF
|
projectionTF
|
||||||
)} scale(1 2) scale(1 ${1 /
|
)} scale(1 2) scale(1 ${
|
||||||
-(responsive.height - graphPaddingTop)}) scale(2 1) scale(${1 /
|
1 / -(responsive.height - graphPaddingTop)
|
||||||
(responsive.width - graphPaddingRightLeft)} 1)`;
|
}) scale(2 1) scale(${1 / (responsive.width - graphPaddingRightLeft)} 1)`;
|
||||||
|
|
||||||
// Copy the children passed with the overlay and add the inverse transform and onDisplayChange props
|
// Copy the children passed with the overlay and add the inverse transform and onDisplayChange props
|
||||||
const newChildren = React.Children.map(children, child =>
|
const newChildren = React.Children.map(children, (child) =>
|
||||||
cloneElement(child, {
|
cloneElement(child, {
|
||||||
inverseTransform,
|
inverseTransform,
|
||||||
overlayToggled: this.overlayToggled
|
overlayToggled: this.overlayToggled,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -88,15 +88,16 @@ class GraphOverlayLayer extends PureComponent {
|
|||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
style={{
|
style={{
|
||||||
zIndex: 99,
|
zIndex: 99,
|
||||||
backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : ""
|
backgroundColor: displaying ? "rgba(255, 255, 255, 0.55)" : "",
|
||||||
}}
|
}}
|
||||||
onMouseMove={handleCanvasEvent}
|
onMouseMove={handleCanvasEvent}
|
||||||
onWheel={handleCanvasEvent}
|
onWheel={handleCanvasEvent}
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
id="canvas-transformation-group-x"
|
id="canvas-transformation-group-x"
|
||||||
transform={`scale(${responsive.width -
|
transform={`scale(${
|
||||||
graphPaddingRightLeft} 1) scale(.5 1) translate(1 0)`}
|
responsive.width - graphPaddingRightLeft
|
||||||
|
} 1) scale(.5 1) translate(1 0)`}
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
id="canvas-transformation-group-y"
|
id="canvas-transformation-group-y"
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import * as d3 from "d3";
|
|||||||
const Lasso = () => {
|
const Lasso = () => {
|
||||||
const dispatch = d3.dispatch("start", "end", "cancel");
|
const dispatch = d3.dispatch("start", "end", "cancel");
|
||||||
|
|
||||||
const polygonToPath = polygon =>
|
const polygonToPath = (polygon) =>
|
||||||
`M${polygon.map(d => d.join(",")).join("L")}`;
|
`M${polygon.map((d) => d.join(",")).join("L")}`;
|
||||||
|
|
||||||
const distance = (pt1, pt2) =>
|
const distance = (pt1, pt2) =>
|
||||||
Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2);
|
Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2);
|
||||||
@@ -14,7 +14,7 @@ const Lasso = () => {
|
|||||||
// distance last point has to be to first point before it auto closes when mouse is released
|
// distance last point has to be to first point before it auto closes when mouse is released
|
||||||
const closeDistance = 75;
|
const closeDistance = 75;
|
||||||
|
|
||||||
const lasso = svg => {
|
const lasso = (svg) => {
|
||||||
let lassoPolygon;
|
let lassoPolygon;
|
||||||
let lassoPath;
|
let lassoPath;
|
||||||
let closePath;
|
let closePath;
|
||||||
@@ -55,10 +55,7 @@ const Lasso = () => {
|
|||||||
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
|
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
|
||||||
closeDistance
|
closeDistance
|
||||||
) {
|
) {
|
||||||
closePath
|
closePath.attr("x1", point[0]).attr("y1", point[1]).attr("opacity", 1);
|
||||||
.attr("x1", point[0])
|
|
||||||
.attr("y1", point[1])
|
|
||||||
.attr("opacity", 1);
|
|
||||||
} else {
|
} else {
|
||||||
closePath.attr("opacity", 0);
|
closePath.attr("opacity", 0);
|
||||||
}
|
}
|
||||||
@@ -116,7 +113,7 @@ const Lasso = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
lasso.move = polygon => {
|
lasso.move = (polygon) => {
|
||||||
if (polygon !== lassoPolygon || polygon.length !== lassoPolygon.length) {
|
if (polygon !== lassoPolygon || polygon.length !== lassoPolygon.length) {
|
||||||
lasso.reset();
|
lasso.reset();
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default (
|
|||||||
.brush()
|
.brush()
|
||||||
.extent([
|
.extent([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[responsive.width - graphPaddingRight, responsive.height]
|
[responsive.width - graphPaddingRight, responsive.height],
|
||||||
])
|
])
|
||||||
.on("start", handleStartAction)
|
.on("start", handleStartAction)
|
||||||
.on("brush", handleDragAction)
|
.on("brush", handleDragAction)
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { connect } from "react-redux";
|
|||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import Logo from "../framework/logo";
|
import Logo from "../framework/logo";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
responsive: state.responsive,
|
responsive: state.responsive,
|
||||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||||
aboutURL: state.config?.links?.["about-dataset"],
|
aboutURL: state.config?.links?.["about-dataset"],
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
}))
|
}))
|
||||||
class LeftSideBar extends React.Component {
|
class LeftSideBar extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
@@ -35,7 +35,7 @@ class LeftSideBar extends React.Component {
|
|||||||
width: globals.leftSidebarWidth - paddingToAvoidScrollBar,
|
width: globals.leftSidebarWidth - paddingToAvoidScrollBar,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
zIndex: 8888
|
zIndex: 8888,
|
||||||
/* x y blur spread color */
|
/* x y blur spread color */
|
||||||
// boxShadow: "-5px -1px 4px 2px rgba(225,225,225,0.4)"
|
// boxShadow: "-5px -1px 4px 2px rgba(225,225,225,0.4)"
|
||||||
}}
|
}}
|
||||||
@@ -49,7 +49,7 @@ class LeftSideBar extends React.Component {
|
|||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
color: globals.logoColor,
|
color: globals.logoColor,
|
||||||
userSelect: "none"
|
userSelect: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
cell
|
cell
|
||||||
@@ -58,7 +58,7 @@ class LeftSideBar extends React.Component {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
top: 1,
|
top: 1,
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
fontSize: 24
|
fontSize: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
@@ -76,11 +76,17 @@ class LeftSideBar extends React.Component {
|
|||||||
marginLeft: "7px",
|
marginLeft: "7px",
|
||||||
height: "1.2em",
|
height: "1.2em",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
wordBreak: "break-all"
|
wordBreak: "break-all",
|
||||||
}}
|
}}
|
||||||
title={datasetTitle}
|
title={datasetTitle}
|
||||||
>
|
>
|
||||||
{aboutURL ? <a href={aboutURL} target="_blank">{displayTitle}</a> : displayTitle}
|
{aboutURL ? (
|
||||||
|
<a href={aboutURL} target="_blank">
|
||||||
|
{displayTitle}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
displayTitle
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class CellSetButton extends React.PureComponent {
|
|||||||
differential,
|
differential,
|
||||||
crossfilter,
|
crossfilter,
|
||||||
dispatch,
|
dispatch,
|
||||||
eitherCellSetOneOrTwo
|
eitherCellSetOneOrTwo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// Reducer and components assume that value will be null if
|
// Reducer and components assume that value will be null if
|
||||||
@@ -25,7 +25,7 @@ class CellSetButton extends React.PureComponent {
|
|||||||
/* diffexp needs to be cleared before we store a new set */
|
/* diffexp needs to be cleared before we store a new set */
|
||||||
dispatch({
|
dispatch({
|
||||||
type: `store current cell selection as differential set ${eitherCellSetOneOrTwo}`,
|
type: `store current cell selection as differential set ${eitherCellSetOneOrTwo}`,
|
||||||
data: set
|
data: set,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
NumericInput,
|
NumericInput,
|
||||||
Icon,
|
Icon,
|
||||||
Tooltip
|
Tooltip,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { tooltipHoverOpenDelay } from "../../globals";
|
import { tooltipHoverOpenDelay } from "../../globals";
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ function Clip(props) {
|
|||||||
isClipDisabled,
|
isClipDisabled,
|
||||||
handleClipOnKeyPress,
|
handleClipOnKeyPress,
|
||||||
handleClipPercentileMaxValueChange,
|
handleClipPercentileMaxValueChange,
|
||||||
handleClipPercentileMinValueChange
|
handleClipPercentileMinValueChange,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const clipMin =
|
const clipMin =
|
||||||
@@ -37,7 +37,7 @@ function Clip(props) {
|
|||||||
<div
|
<div
|
||||||
className="bp3-button-group"
|
className="bp3-button-group"
|
||||||
style={{
|
style={{
|
||||||
marginRight: 10
|
marginRight: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Popover
|
<Popover
|
||||||
@@ -52,7 +52,7 @@ function Clip(props) {
|
|||||||
data-testid="visualization-settings"
|
data-testid="visualization-settings"
|
||||||
className={`bp3-button bp3-icon-timeline-bar-chart ${activeClipClass}`}
|
className={`bp3-button bp3-icon-timeline-bar-chart ${activeClipClass}`}
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -67,7 +67,7 @@ function Clip(props) {
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: 10
|
padding: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>Clip all continuous values to percentile range</div>
|
<div>Clip all continuous values to percentile range</div>
|
||||||
@@ -77,7 +77,7 @@ function Clip(props) {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingTop: 5,
|
paddingTop: 5,
|
||||||
paddingBottom: 5
|
paddingBottom: 5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NumericInput
|
<NumericInput
|
||||||
@@ -121,7 +121,7 @@ function Clip(props) {
|
|||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
marginLeft: 5
|
marginLeft: 5,
|
||||||
}}
|
}}
|
||||||
onClick={handleClipCommit}
|
onClick={handleClipCommit}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,26 +7,26 @@ import {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
AnchorButton,
|
AnchorButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Position
|
Position,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import CellSetButton from "./cellSetButtons";
|
import CellSetButton from "./cellSetButtons";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
config: state.config,
|
config: state.config,
|
||||||
crossfilter: state.crossfilter,
|
crossfilter: state.crossfilter,
|
||||||
differential: state.differential,
|
differential: state.differential,
|
||||||
celllist1: state.differential?.celllist1,
|
celllist1: state.differential?.celllist1,
|
||||||
celllist2: state.differential?.celllist2,
|
celllist2: state.differential?.celllist2,
|
||||||
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
||||||
diffexpCellcountMax: state.config?.limits?.diffexp_cellcount_max
|
diffexpCellcountMax: state.config?.limits?.diffexp_cellcount_max,
|
||||||
}))
|
}))
|
||||||
class DiffexpButtons extends React.Component {
|
class DiffexpButtons extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
userDismissedPopover: false
|
userDismissedPopover: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,16 +46,16 @@ class DiffexpButtons extends React.Component {
|
|||||||
const { dispatch, differential } = this.props;
|
const { dispatch, differential } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "clear differential expression",
|
type: "clear differential expression",
|
||||||
diffExp: differential.diffExp
|
diffExp: differential.diffExp,
|
||||||
});
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "clear scatterplot"
|
type: "clear scatterplot",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePopoverDismiss = () => {
|
handlePopoverDismiss = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
userDismissedPopover: true
|
userDismissedPopover: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ class DiffexpButtons extends React.Component {
|
|||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
maxWidth: 310
|
maxWidth: 310,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -7,26 +7,26 @@ import {
|
|||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Position
|
Position,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import { World } from "../../util/stateManager";
|
import { World } from "../../util/stateManager";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
universe: state.universe,
|
universe: state.universe,
|
||||||
world: state.world,
|
world: state.world,
|
||||||
layoutChoice: state.layoutChoice,
|
layoutChoice: state.layoutChoice,
|
||||||
reembedController: state.reembedController,
|
reembedController: state.reembedController,
|
||||||
enableReembedding: state.config?.parameters?.["enable-reembedding"] ?? false
|
enableReembedding: state.config?.parameters?.["enable-reembedding"] ?? false,
|
||||||
}))
|
}))
|
||||||
class Embedding extends React.PureComponent {
|
class Embedding extends React.PureComponent {
|
||||||
handleLayoutChoiceChange = e => {
|
handleLayoutChoiceChange = (e) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "set layout choice",
|
type: "set layout choice",
|
||||||
layoutChoice: e.currentTarget.value
|
layoutChoice: e.currentTarget.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class Embedding extends React.PureComponent {
|
|||||||
world,
|
world,
|
||||||
universe,
|
universe,
|
||||||
dispatch,
|
dispatch,
|
||||||
reembedController
|
reembedController,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!enableReembedding) return null;
|
if (!enableReembedding) return null;
|
||||||
@@ -70,7 +70,7 @@ class Embedding extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
style={{
|
style={{
|
||||||
marginRight: 10
|
marginRight: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Popover
|
<Popover
|
||||||
@@ -85,7 +85,7 @@ class Embedding extends React.PureComponent {
|
|||||||
data-testid="layout-choice"
|
data-testid="layout-choice"
|
||||||
icon="heatmap"
|
icon="heatmap"
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -98,7 +98,7 @@ class Embedding extends React.PureComponent {
|
|||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: 10
|
padding: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
@@ -106,7 +106,7 @@ class Embedding extends React.PureComponent {
|
|||||||
onChange={this.handleLayoutChoiceChange}
|
onChange={this.handleLayoutChoiceChange}
|
||||||
selectedValue={layoutChoice.current}
|
selectedValue={layoutChoice.current}
|
||||||
>
|
>
|
||||||
{layoutChoice.available.map(name => (
|
{layoutChoice.available.map((name) => (
|
||||||
<Radio label={name} value={name} key={name} />
|
<Radio label={name} value={name} key={name} />
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Subset from "./subset";
|
|||||||
import UndoRedoReset from "./undoRedo";
|
import UndoRedoReset from "./undoRedo";
|
||||||
import DiffexpButtons from "./diffexpButtons";
|
import DiffexpButtons from "./diffexpButtons";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
universe: state.universe,
|
universe: state.universe,
|
||||||
world: state.world,
|
world: state.world,
|
||||||
crossfilter: state.crossfilter,
|
crossfilter: state.crossfilter,
|
||||||
@@ -34,7 +34,7 @@ import DiffexpButtons from "./diffexpButtons";
|
|||||||
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
||||||
showCentroidLabels: state.centroidLabels.showLabels,
|
showCentroidLabels: state.centroidLabels.showLabels,
|
||||||
tosURL: state.config?.parameters?.about_legal_tos,
|
tosURL: state.config?.parameters?.about_legal_tos,
|
||||||
privacyURL: state.config?.parameters?.about_legal_privacy
|
privacyURL: state.config?.parameters?.about_legal_privacy,
|
||||||
}))
|
}))
|
||||||
class MenuBar extends React.Component {
|
class MenuBar extends React.Component {
|
||||||
static isValidDigitKeyEvent(e) {
|
static isValidDigitKeyEvent(e) {
|
||||||
@@ -64,7 +64,7 @@ class MenuBar extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
pendingClipPercentiles: null
|
pendingClipPercentiles: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class MenuBar extends React.Component {
|
|||||||
return isDisabled;
|
return isDisabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClipOnKeyPress = e => {
|
handleClipOnKeyPress = (e) => {
|
||||||
/*
|
/*
|
||||||
allow only numbers, plus other critical keys which
|
allow only numbers, plus other critical keys which
|
||||||
may be required to make a number
|
may be required to make a number
|
||||||
@@ -100,7 +100,7 @@ class MenuBar extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClipPercentileMinValueChange = v => {
|
handleClipPercentileMinValueChange = (v) => {
|
||||||
/*
|
/*
|
||||||
Ignore anything that isn't a legit number
|
Ignore anything that isn't a legit number
|
||||||
*/
|
*/
|
||||||
@@ -116,11 +116,11 @@ class MenuBar extends React.Component {
|
|||||||
if (v > 100) v = 100;
|
if (v > 100) v = 100;
|
||||||
const clipPercentileMin = Math.round(v); // paranoia
|
const clipPercentileMin = Math.round(v); // paranoia
|
||||||
this.setState({
|
this.setState({
|
||||||
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax }
|
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClipPercentileMaxValueChange = v => {
|
handleClipPercentileMaxValueChange = (v) => {
|
||||||
/*
|
/*
|
||||||
Ignore anything that isn't a legit number
|
Ignore anything that isn't a legit number
|
||||||
*/
|
*/
|
||||||
@@ -137,7 +137,7 @@ class MenuBar extends React.Component {
|
|||||||
const clipPercentileMax = Math.round(v); // paranoia
|
const clipPercentileMax = Math.round(v); // paranoia
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax }
|
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,14 +149,14 @@ class MenuBar extends React.Component {
|
|||||||
const max = clipPercentileMax / 100;
|
const max = clipPercentileMax / 100;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "set clip quantiles",
|
type: "set clip quantiles",
|
||||||
clipQuantiles: { min, max }
|
clipQuantiles: { min, max },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClipOpening = () => {
|
handleClipOpening = () => {
|
||||||
const { clipPercentileMin, clipPercentileMax } = this.props;
|
const { clipPercentileMin, clipPercentileMax } = this.props;
|
||||||
this.setState({
|
this.setState({
|
||||||
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax }
|
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ class MenuBar extends React.Component {
|
|||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "show centroid labels for category",
|
type: "show centroid labels for category",
|
||||||
showLabels: !showCentroidLabels
|
showLabels: !showCentroidLabels,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ class MenuBar extends React.Component {
|
|||||||
aboutLink,
|
aboutLink,
|
||||||
showCentroidLabels,
|
showCentroidLabels,
|
||||||
privacyURL,
|
privacyURL,
|
||||||
tosURL
|
tosURL,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { pendingClipPercentiles } = this.state;
|
const { pendingClipPercentiles } = this.state;
|
||||||
|
|
||||||
@@ -221,10 +221,10 @@ class MenuBar extends React.Component {
|
|||||||
position: "fixed",
|
position: "fixed",
|
||||||
right: globals.leftSidebarWidth + 8,
|
right: globals.leftSidebarWidth + 8,
|
||||||
top: 8,
|
top: 8,
|
||||||
display: "flex"
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{disableDiffexp ? null : <DiffexpButtons/>}
|
{disableDiffexp ? null : <DiffexpButtons />}
|
||||||
<Subset
|
<Subset
|
||||||
subsetPossible={this.subsetPossible()}
|
subsetPossible={this.subsetPossible()}
|
||||||
subsetResetPossible={this.subsetResetPossible()}
|
subsetResetPossible={this.subsetResetPossible()}
|
||||||
@@ -251,11 +251,11 @@ class MenuBar extends React.Component {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "change graph interaction mode",
|
type: "change graph interaction mode",
|
||||||
data: "select"
|
data: "select",
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -272,11 +272,11 @@ class MenuBar extends React.Component {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "change graph interaction mode",
|
type: "change graph interaction mode",
|
||||||
data: "zoom"
|
data: "zoom",
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -294,7 +294,7 @@ class MenuBar extends React.Component {
|
|||||||
active={showCentroidLabels}
|
active={showCentroidLabels}
|
||||||
intent={showCentroidLabels ? "primary" : "none"}
|
intent={showCentroidLabels ? "primary" : "none"}
|
||||||
style={{
|
style={{
|
||||||
marginRight: 10
|
marginRight: 10,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -47,16 +47,16 @@ function InformationMenu(props) {
|
|||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<MenuItem text="MIT License" />
|
<MenuItem text="MIT License" />
|
||||||
{tosURL ? <MenuItem
|
{tosURL ? (
|
||||||
href={tosURL}
|
<MenuItem href={tosURL} target="_blank" text="Terms of Service" />
|
||||||
target="_blank"
|
) : null}
|
||||||
text="Terms of Service"
|
{privacyURL ? (
|
||||||
/> : null }
|
<MenuItem
|
||||||
{privacyURL ? <MenuItem
|
href={privacyURL}
|
||||||
href={privacyURL}
|
target="_blank"
|
||||||
target="_blank"
|
text="Privacy Policy"
|
||||||
text="Privacy Policy"
|
/>
|
||||||
/> : null }
|
) : null}
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
position={Position.BOTTOM_RIGHT}
|
position={Position.BOTTOM_RIGHT}
|
||||||
@@ -65,7 +65,7 @@ function InformationMenu(props) {
|
|||||||
type="button"
|
type="button"
|
||||||
className="bp3-button bp3-icon-info-sign"
|
className="bp3-button bp3-icon-info-sign"
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function Subset(props) {
|
|||||||
subsetPossible,
|
subsetPossible,
|
||||||
subsetResetPossible,
|
subsetResetPossible,
|
||||||
handleSubset,
|
handleSubset,
|
||||||
handleSubsetReset
|
handleSubsetReset,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
|||||||
import { tooltipHoverOpenDelay } from "../../globals";
|
import { tooltipHoverOpenDelay } from "../../globals";
|
||||||
|
|
||||||
function InformationMenu(props) {
|
function InformationMenu(props) {
|
||||||
const {
|
const { undoDisabled, redoDisabled, dispatch } = props;
|
||||||
undoDisabled,
|
|
||||||
redoDisabled,
|
|
||||||
dispatch
|
|
||||||
} = props;
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginRight: 10 }} className="bp3-button-group">
|
<div style={{ marginRight: 10 }} className="bp3-button-group">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -24,7 +20,7 @@ function InformationMenu(props) {
|
|||||||
dispatch({ type: "@@undoable/undo" });
|
dispatch({ type: "@@undoable/undo" });
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
data-testid="undo"
|
data-testid="undo"
|
||||||
/>
|
/>
|
||||||
@@ -42,7 +38,7 @@ function InformationMenu(props) {
|
|||||||
dispatch({ type: "@@undoable/redo" });
|
dispatch({ type: "@@undoable/redo" });
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
data-testid="redo"
|
data-testid="redo"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import Continuous from "../continuous/continuous";
|
|||||||
import GeneExpression from "../geneExpression";
|
import GeneExpression from "../geneExpression";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
@connect(state => ({
|
@connect((state) => ({
|
||||||
responsive: state.responsive,
|
responsive: state.responsive,
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor
|
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||||
}))
|
}))
|
||||||
class RightSidebar extends React.Component {
|
class RightSidebar extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
@@ -21,7 +21,7 @@ class RightSidebar extends React.Component {
|
|||||||
right: 0,
|
right: 0,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
/* x y blur spread color */
|
/* x y blur spread color */
|
||||||
borderLeft: `1px solid ${globals.lightGrey}`
|
borderLeft: `1px solid ${globals.lightGrey}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -29,7 +29,7 @@ class RightSidebar extends React.Component {
|
|||||||
height: responsive.height,
|
height: responsive.height,
|
||||||
width: globals.leftSidebarWidth,
|
width: globals.leftSidebarWidth,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
overflowX: "hidden"
|
overflowX: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GeneExpression />
|
<GeneExpression />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { glPointFlags, glPointSize } from "../../util/glHelpers";
|
import { glPointFlags, glPointSize } from "../../util/glHelpers";
|
||||||
|
|
||||||
export default function(regl) {
|
export default function (regl) {
|
||||||
return regl({
|
return regl({
|
||||||
vert: `
|
vert: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
@@ -52,17 +52,17 @@ export default function(regl) {
|
|||||||
attributes: {
|
attributes: {
|
||||||
position: regl.prop("position"),
|
position: regl.prop("position"),
|
||||||
color: regl.prop("color"),
|
color: regl.prop("color"),
|
||||||
flag: regl.prop("flag")
|
flag: regl.prop("flag"),
|
||||||
},
|
},
|
||||||
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
projection: regl.prop("projection"),
|
projection: regl.prop("projection"),
|
||||||
nPoints: regl.prop("nPoints"),
|
nPoints: regl.prop("nPoints"),
|
||||||
minViewportDimension: regl.prop("minViewportDimension")
|
minViewportDimension: regl.prop("minViewportDimension"),
|
||||||
},
|
},
|
||||||
|
|
||||||
count: regl.prop("count"),
|
count: regl.prop("count"),
|
||||||
|
|
||||||
primitive: "points"
|
primitive: "points",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function createProjectionTF(viewportWidth, viewportHeight) {
|
|||||||
return mat3.projection(m, viewportWidth, viewportHeight);
|
return mat3.projection(m, viewportWidth, viewportHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@connect(state => {
|
@connect((state) => {
|
||||||
const { world, crossfilter, universe } = state;
|
const { world, crossfilter, universe } = state;
|
||||||
const { scatterplotXXaccessor, scatterplotYYaccessor } = state.controls;
|
const { scatterplotXXaccessor, scatterplotYYaccessor } = state.controls;
|
||||||
const expressionX = scatterplotXXaccessor
|
const expressionX = scatterplotXXaccessor
|
||||||
@@ -54,7 +54,7 @@ function createProjectionTF(viewportWidth, viewportHeight) {
|
|||||||
|
|
||||||
crossfilter,
|
crossfilter,
|
||||||
|
|
||||||
responsive: state.responsive
|
responsive: state.responsive,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
class Scatterplot extends React.PureComponent {
|
class Scatterplot extends React.PureComponent {
|
||||||
@@ -67,7 +67,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
return positions;
|
return positions;
|
||||||
});
|
});
|
||||||
|
|
||||||
computePointColors = memoize(rgb => {
|
computePointColors = memoize((rgb) => {
|
||||||
/*
|
/*
|
||||||
compute webgl colors for each point
|
compute webgl colors for each point
|
||||||
*/
|
*/
|
||||||
@@ -135,11 +135,11 @@ class Scatterplot extends React.PureComponent {
|
|||||||
colors: null,
|
colors: null,
|
||||||
flags: null,
|
flags: null,
|
||||||
xScale: null,
|
xScale: null,
|
||||||
yScale: null
|
yScale: null,
|
||||||
};
|
};
|
||||||
this.state = {
|
this.state = {
|
||||||
svg: null,
|
svg: null,
|
||||||
minimized: null
|
minimized: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
colorBuffer,
|
colorBuffer,
|
||||||
svg,
|
svg,
|
||||||
drawPoints,
|
drawPoints,
|
||||||
projectionTF
|
projectionTF,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
expressionY,
|
expressionY,
|
||||||
colorRGB,
|
colorRGB,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
pointDilation
|
pointDilation,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
regl,
|
regl,
|
||||||
@@ -207,7 +207,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
flagBuffer,
|
flagBuffer,
|
||||||
svg,
|
svg,
|
||||||
drawPoints,
|
drawPoints,
|
||||||
projectionTF
|
projectionTF,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -284,7 +284,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
xScale,
|
xScale,
|
||||||
yScale
|
yScale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,15 +294,9 @@ class Scatterplot extends React.PureComponent {
|
|||||||
|
|
||||||
// the axes are much cleaner and easier now. No need to rotate and orient
|
// the axes are much cleaner and easier now. No need to rotate and orient
|
||||||
// the axis, just call axisBottom, axisLeft etc.
|
// the axis, just call axisBottom, axisLeft etc.
|
||||||
const xAxis = d3
|
const xAxis = d3.axisBottom().ticks(7).scale(xScale);
|
||||||
.axisBottom()
|
|
||||||
.ticks(7)
|
|
||||||
.scale(xScale);
|
|
||||||
|
|
||||||
const yAxis = d3
|
const yAxis = d3.axisLeft().ticks(7).scale(yScale);
|
||||||
.axisLeft()
|
|
||||||
.ticks(7)
|
|
||||||
.scale(yScale);
|
|
||||||
|
|
||||||
// adding axes is also simpler now, just translate x-axis to (0,height)
|
// adding axes is also simpler now, just translate x-axis to (0,height)
|
||||||
// and it's alread defined to be a bottom axis.
|
// and it's alread defined to be a bottom axis.
|
||||||
@@ -356,7 +350,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
regl.poll();
|
regl.poll();
|
||||||
regl.clear({
|
regl.clear({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
color: [1, 1, 1, 1]
|
color: [1, 1, 1, 1],
|
||||||
});
|
});
|
||||||
drawPoints({
|
drawPoints({
|
||||||
flag: flagBuffer,
|
flag: flagBuffer,
|
||||||
@@ -368,7 +362,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
minViewportDimension: Math.min(
|
minViewportDimension: Math.min(
|
||||||
cvWidth - globals.leftSidebarWidth || width,
|
cvWidth - globals.leftSidebarWidth || width,
|
||||||
cvHeight || height
|
cvHeight || height
|
||||||
)
|
),
|
||||||
});
|
});
|
||||||
regl._gl.flush();
|
regl._gl.flush();
|
||||||
}
|
}
|
||||||
@@ -387,7 +381,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
padding: "0px 20px 20px 0px",
|
padding: "0px 20px 20px 0px",
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
/* x y blur spread color */
|
/* x y blur spread color */
|
||||||
boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)"
|
boxShadow: "0px 0px 6px 2px rgba(153,153,153,0.4)",
|
||||||
}}
|
}}
|
||||||
id="scatterplot_wrapper"
|
id="scatterplot_wrapper"
|
||||||
>
|
>
|
||||||
@@ -395,7 +389,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: 5,
|
right: 5,
|
||||||
top: 5
|
top: 5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -413,7 +407,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
data-testid="clear-scatterplot"
|
data-testid="clear-scatterplot"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "clear scatterplot"
|
type: "clear scatterplot",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -425,7 +419,7 @@ class Scatterplot extends React.PureComponent {
|
|||||||
id="scatterplot"
|
id="scatterplot"
|
||||||
style={{
|
style={{
|
||||||
width: `${width + margin.left + margin.right}px`,
|
width: `${width + margin.left + margin.right}px`,
|
||||||
height: `${height + margin.top + margin.bottom}px`
|
height: `${height + margin.top + margin.bottom}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
@@ -434,9 +428,9 @@ class Scatterplot extends React.PureComponent {
|
|||||||
data-testid="scatterplot"
|
data-testid="scatterplot"
|
||||||
style={{
|
style={{
|
||||||
marginLeft: margin.left,
|
marginLeft: margin.left,
|
||||||
marginTop: margin.top
|
marginTop: margin.top,
|
||||||
}}
|
}}
|
||||||
ref={canvas => {
|
ref={(canvas) => {
|
||||||
this.reglCanvas = canvas;
|
this.reglCanvas = canvas;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const setupScatterplot = (width, height, margin) => {
|
|||||||
.attr("transform", `translate(${margin.left},${margin.top})`);
|
.attr("transform", `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
svg
|
svg,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Third-party fonts used in cellxgene
|
# Third-party fonts used in cellxgene
|
||||||
|
|
||||||
| Font Family | License at time of download | URL |
|
| Font Family | License at time of download | URL |
|
||||||
|-------------|-----------------------------|-----|
|
| --------------- | --------------------------- | -------------------------------------------------- |
|
||||||
| Roboto Conensed | Apache v2.0 | https://fonts.google.com/specimen/Roboto+Condensed |
|
| Roboto Conensed | Apache v2.0 | https://fonts.google.com/specimen/Roboto+Condensed |
|
||||||
|
|||||||
+12
-12
@@ -9,22 +9,22 @@ namespace mangling on these definitions. See webpack config for specifics.
|
|||||||
@import "~@blueprintjs/select/lib/css/blueprint-select.css";
|
@import "~@blueprintjs/select/lib/css/blueprint-select.css";
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto Condensed';
|
font-family: "Roboto Condensed";
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
src: url('./fonts/RobotoCondensed-Italic.ttf') format('truetype');
|
src: url("./fonts/RobotoCondensed-Italic.ttf") format("truetype");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto Condensed';
|
font-family: "Roboto Condensed";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
src: url('./fonts/RobotoCondensed-Regular.ttf') format('truetype');
|
src: url("./fonts/RobotoCondensed-Regular.ttf") format("truetype");
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto Condensed';
|
font-family: "Roboto Condensed";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
src: url('./fonts/RobotoCondensed-Bold.ttf') format('truetype');
|
src: url("./fonts/RobotoCondensed-Bold.ttf") format("truetype");
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Annotations = (
|
|||||||
isEditingLabelName: false,
|
isEditingLabelName: false,
|
||||||
categoryBeingEdited: null,
|
categoryBeingEdited: null,
|
||||||
categoryAddingNewLabel: null,
|
categoryAddingNewLabel: null,
|
||||||
labelEditable: { category: null, label: null }
|
labelEditable: { category: null, label: null },
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -40,7 +40,7 @@ const Annotations = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
dataCollectionNameIsReadOnly,
|
dataCollectionNameIsReadOnly,
|
||||||
dataCollectionName
|
dataCollectionName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ const Annotations = (
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
dataCollectionName: action.data
|
dataCollectionName: action.data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ const Annotations = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isAddingNewLabel: true,
|
isAddingNewLabel: true,
|
||||||
categoryAddingNewLabel: action.data
|
categoryAddingNewLabel: action.data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const Annotations = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isAddingNewLabel: false,
|
isAddingNewLabel: false,
|
||||||
categoryAddingNewLabel: null
|
categoryAddingNewLabel: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ const Annotations = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isEditingCategoryName: true,
|
isEditingCategoryName: true,
|
||||||
categoryBeingEdited: action.data
|
categoryBeingEdited: action.data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ const Annotations = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isEditingCategoryName: false,
|
isEditingCategoryName: false,
|
||||||
categoryBeingEdited: null
|
categoryBeingEdited: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ const Annotations = (
|
|||||||
isEditingLabelName: true,
|
isEditingLabelName: true,
|
||||||
labelEditable: {
|
labelEditable: {
|
||||||
category: action.metadataField,
|
category: action.metadataField,
|
||||||
label: action.categoryIndex
|
label: action.categoryIndex,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ const Annotations = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isEditingLabelName: false,
|
isEditingLabelName: false,
|
||||||
labelEditable: { category: null, label: null }
|
labelEditable: { category: null, label: null },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,55 @@
|
|||||||
const Autosave = (
|
const Autosave = (
|
||||||
state = {
|
state = {
|
||||||
saveInProgress: false,
|
saveInProgress: false,
|
||||||
error: false,
|
error: false,
|
||||||
lastSavedObsAnnotations: null,
|
lastSavedObsAnnotations: null,
|
||||||
initialDataLoadComplete: false
|
initialDataLoadComplete: false,
|
||||||
},
|
},
|
||||||
action,
|
action,
|
||||||
nextSharedState
|
nextSharedState
|
||||||
) => {
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "initial data load complete (universe exists)": {
|
case "initial data load complete (universe exists)": {
|
||||||
/* don't save on init */
|
/* don't save on init */
|
||||||
const { universe } = nextSharedState;
|
const { universe } = nextSharedState;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: false,
|
error: false,
|
||||||
saveInProgress: false,
|
saveInProgress: false,
|
||||||
lastSavedObsAnnotations: universe.obsAnnotations,
|
lastSavedObsAnnotations: universe.obsAnnotations,
|
||||||
initialDataLoadComplete: true,
|
initialDataLoadComplete: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "writable obs annotations - save started": {
|
case "writable obs annotations - save started": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
saveInProgress: true
|
saveInProgress: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "writable obs annotations - save error": {
|
case "writable obs annotations - save error": {
|
||||||
const { message } = action;
|
const { message } = action;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: message,
|
error: message,
|
||||||
saveInProgress: false
|
saveInProgress: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "writable obs annotations - save complete": {
|
case "writable obs annotations - save complete": {
|
||||||
const lastSavedObsAnnotations = action.obsAnnotations;
|
const lastSavedObsAnnotations = action.obsAnnotations;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
saveInProgress: false,
|
saveInProgress: false,
|
||||||
error: false,
|
error: false,
|
||||||
lastSavedObsAnnotations
|
lastSavedObsAnnotations,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return { ...state };
|
return { ...state };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Autosave;
|
export default Autosave;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const CategoricalSelection = (
|
|||||||
if (names.length === 0) return state;
|
if (names.length === 0) return state;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...CH.createCategoricalSelection(world, names)
|
...CH.createCategoricalSelection(world, names),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +52,8 @@ const CategoricalSelection = (
|
|||||||
...state,
|
...state,
|
||||||
[action.metadataField]: {
|
[action.metadataField]: {
|
||||||
...state[action.metadataField],
|
...state[action.metadataField],
|
||||||
categoryValueSelected: newCategoryValueSelected
|
categoryValueSelected: newCategoryValueSelected,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return newCategoricalSelection;
|
return newCategoricalSelection;
|
||||||
}
|
}
|
||||||
@@ -70,8 +70,8 @@ const CategoricalSelection = (
|
|||||||
...state,
|
...state,
|
||||||
[action.metadataField]: {
|
[action.metadataField]: {
|
||||||
...state[action.metadataField],
|
...state[action.metadataField],
|
||||||
categoryValueSelected: newCategoryValueSelected
|
categoryValueSelected: newCategoryValueSelected,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return newCategoricalSelection;
|
return newCategoricalSelection;
|
||||||
}
|
}
|
||||||
@@ -87,8 +87,8 @@ const CategoricalSelection = (
|
|||||||
categorySelected: false,
|
categorySelected: false,
|
||||||
categoryValueSelected: Array.from(
|
categoryValueSelected: Array.from(
|
||||||
state[action.metadataField].categoryValueSelected
|
state[action.metadataField].categoryValueSelected
|
||||||
).fill(false)
|
).fill(false),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return newCategoricalSelection;
|
return newCategoricalSelection;
|
||||||
}
|
}
|
||||||
@@ -104,8 +104,8 @@ const CategoricalSelection = (
|
|||||||
categorySelected: true,
|
categorySelected: true,
|
||||||
categoryValueSelected: Array.from(
|
categoryValueSelected: Array.from(
|
||||||
state[action.metadataField].categoryValueSelected
|
state[action.metadataField].categoryValueSelected
|
||||||
).fill(true)
|
).fill(true),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return newCategoricalSelection;
|
return newCategoricalSelection;
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ const CategoricalSelection = (
|
|||||||
const name = action.data;
|
const name = action.data;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...CH.createCategoricalSelection(world, [name])
|
...CH.createCategoricalSelection(world, [name]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ const CategoricalSelection = (
|
|||||||
const { [name]: _, ...partialState } = state;
|
const { [name]: _, ...partialState } = state;
|
||||||
return {
|
return {
|
||||||
...partialState,
|
...partialState,
|
||||||
...CH.createCategoricalSelection(world, [name])
|
...CH.createCategoricalSelection(world, [name]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import calcCentroid from "../util/centroid";
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
labels: [],
|
labels: [],
|
||||||
showLabels: false
|
showLabels: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const centroidLabels = (state = initialState, action, sharedNextState) => {
|
const centroidLabels = (state = initialState, action, sharedNextState) => {
|
||||||
@@ -10,7 +10,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
|
|||||||
world,
|
world,
|
||||||
layoutChoice,
|
layoutChoice,
|
||||||
categoricalSelection,
|
categoricalSelection,
|
||||||
colors: { colorAccessor }
|
colors: { colorAccessor },
|
||||||
} = sharedNextState;
|
} = sharedNextState;
|
||||||
|
|
||||||
const showLabels = action.showLabels ?? state.showLabels;
|
const showLabels = action.showLabels ?? state.showLabels;
|
||||||
@@ -34,7 +34,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
|
|||||||
categoricalSelection,
|
categoricalSelection,
|
||||||
world.schema.annotations.obsByName
|
world.schema.annotations.obsByName
|
||||||
)
|
)
|
||||||
: []
|
: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
case "color by categorical metadata":
|
case "color by categorical metadata":
|
||||||
@@ -45,7 +45,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
labels: [],
|
labels: [],
|
||||||
showLabels
|
showLabels,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ const centroidLabels = (state = initialState, action, sharedNextState) => {
|
|||||||
categoricalSelection,
|
categoricalSelection,
|
||||||
world.schema.annotations.obsByName
|
world.schema.annotations.obsByName
|
||||||
),
|
),
|
||||||
showLabels
|
showLabels,
|
||||||
};
|
};
|
||||||
|
|
||||||
case "color by continuous metadata":
|
case "color by continuous metadata":
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const ColorsReducer = (
|
|||||||
colorMode: null,
|
colorMode: null,
|
||||||
colorAccessor: null,
|
colorAccessor: null,
|
||||||
rgb: null,
|
rgb: null,
|
||||||
scale: null
|
scale: null,
|
||||||
},
|
},
|
||||||
action,
|
action,
|
||||||
nextSharedState,
|
nextSharedState,
|
||||||
@@ -23,7 +23,7 @@ const ColorsReducer = (
|
|||||||
colorAccessor,
|
colorAccessor,
|
||||||
colorMode,
|
colorMode,
|
||||||
rgb,
|
rgb,
|
||||||
scale
|
scale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ const ColorsReducer = (
|
|||||||
const { userColors } = action;
|
const { userColors } = action;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
userColors
|
userColors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ const ColorsReducer = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
rgb,
|
rgb,
|
||||||
scale
|
scale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ const ColorsReducer = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
rgb,
|
rgb,
|
||||||
scale
|
scale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,14 +85,14 @@ const ColorsReducer = (
|
|||||||
/* else reset */
|
/* else reset */
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...ColorHelpers.resetColors(prevSharedState.world)
|
...ColorHelpers.resetColors(prevSharedState.world),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "reset colorscale": {
|
case "reset colorscale": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...ColorHelpers.resetColors(prevSharedState.world)
|
...ColorHelpers.resetColors(prevSharedState.world),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,13 +107,18 @@ const ColorsReducer = (
|
|||||||
const colorMode = !resetCurrent ? action.type : null;
|
const colorMode = !resetCurrent ? action.type : null;
|
||||||
const colorAccessor = !resetCurrent ? action.colorAccessor : null;
|
const colorAccessor = !resetCurrent ? action.colorAccessor : null;
|
||||||
|
|
||||||
const { rgb, scale } = ColorHelpers.createColors(world, colorMode, colorAccessor, colors.userColors);
|
const { rgb, scale } = ColorHelpers.createColors(
|
||||||
|
world,
|
||||||
|
colorMode,
|
||||||
|
colorAccessor,
|
||||||
|
colors.userColors
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
colorMode,
|
colorMode,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
rgb,
|
rgb,
|
||||||
scale
|
scale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +141,7 @@ const ColorsReducer = (
|
|||||||
colorMode,
|
colorMode,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
rgb,
|
rgb,
|
||||||
scale
|
scale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +158,11 @@ const ColorsReducer = (
|
|||||||
return state;
|
return state;
|
||||||
|
|
||||||
/* else, we need to rebuild colors as labels have changed! */
|
/* else, we need to rebuild colors as labels have changed! */
|
||||||
const { rgb, scale } = ColorHelpers.createColors(world, colorMode, colorAccessor);
|
const { rgb, scale } = ColorHelpers.createColors(
|
||||||
|
world,
|
||||||
|
colorMode,
|
||||||
|
colorAccessor
|
||||||
|
);
|
||||||
return { ...state, rgb, scale };
|
return { ...state, rgb, scale };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const Config = (
|
|||||||
state = {
|
state = {
|
||||||
displayNames: null,
|
displayNames: null,
|
||||||
features: null,
|
features: null,
|
||||||
parameters: null
|
parameters: null,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -12,19 +12,19 @@ const Config = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null
|
error: null,
|
||||||
};
|
};
|
||||||
case "configuration load complete":
|
case "configuration load complete":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
...action.config
|
...action.config,
|
||||||
};
|
};
|
||||||
case "initial data load error":
|
case "initial data load error":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: action.error
|
error: action.error,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const Differential = (
|
|||||||
loading: null,
|
loading: null,
|
||||||
error: null,
|
error: null,
|
||||||
celllist1: null,
|
celllist1: null,
|
||||||
celllist2: null
|
celllist2: null,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -15,37 +15,37 @@ const Differential = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null
|
error: null,
|
||||||
};
|
};
|
||||||
case "request differential expression success":
|
case "request differential expression success":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: null,
|
error: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
diffExp: action.data
|
diffExp: action.data,
|
||||||
};
|
};
|
||||||
case "request differential expression error":
|
case "request differential expression error":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: action.data
|
error: action.data,
|
||||||
};
|
};
|
||||||
case "store current cell selection as differential set 1":
|
case "store current cell selection as differential set 1":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
celllist1: action.data
|
celllist1: action.data,
|
||||||
};
|
};
|
||||||
case "store current cell selection as differential set 2":
|
case "store current cell selection as differential set 2":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
celllist2: action.data
|
celllist2: action.data,
|
||||||
};
|
};
|
||||||
case "clear differential expression":
|
case "clear differential expression":
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
diffExp: null,
|
diffExp: null,
|
||||||
celllist1: null,
|
celllist1: null,
|
||||||
celllist2: null
|
celllist2: null,
|
||||||
};
|
};
|
||||||
case "reset World to eq Universe":
|
case "reset World to eq Universe":
|
||||||
case "set World to current selection":
|
case "set World to current selection":
|
||||||
@@ -53,7 +53,7 @@ const Differential = (
|
|||||||
...state,
|
...state,
|
||||||
diffExp: null,
|
diffExp: null,
|
||||||
celllist1: null,
|
celllist1: null,
|
||||||
celllist2: null
|
celllist2: null,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const GraphSelection = (
|
const GraphSelection = (
|
||||||
state = {
|
state = {
|
||||||
tool: "lasso", // what selection tool mode (lasso, brush, ...)
|
tool: "lasso", // what selection tool mode (lasso, brush, ...)
|
||||||
selection: { mode: "all" } // current selection, which is tool specific
|
selection: { mode: "all" }, // current selection, which is tool specific
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -12,8 +12,8 @@ const GraphSelection = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selection: {
|
selection: {
|
||||||
mode: "all"
|
mode: "all",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ const GraphSelection = (
|
|||||||
...state,
|
...state,
|
||||||
selection: {
|
selection: {
|
||||||
mode: "within-rect",
|
mode: "within-rect",
|
||||||
brushCoords
|
brushCoords,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ const GraphSelection = (
|
|||||||
...state,
|
...state,
|
||||||
selection: {
|
selection: {
|
||||||
mode: "within-polygon",
|
mode: "within-polygon",
|
||||||
polygon
|
polygon,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ const GraphSelection = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selection: {
|
selection: {
|
||||||
mode: "all"
|
mode: "all",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ about commonly used names. Preferentially, pick in the following order:
|
|||||||
*/
|
*/
|
||||||
function bestDefaultLayout(layouts) {
|
function bestDefaultLayout(layouts) {
|
||||||
const preferredNames = ["umap", "tsne", "pca"];
|
const preferredNames = ["umap", "tsne", "pca"];
|
||||||
const idx = preferredNames.findIndex(name => layouts.indexOf(name) !== -1);
|
const idx = preferredNames.findIndex((name) => layouts.indexOf(name) !== -1);
|
||||||
if (idx !== -1) return preferredNames[idx];
|
if (idx !== -1) return preferredNames[idx];
|
||||||
return layouts[0];
|
return layouts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToDefaultLayout(world) {
|
function setToDefaultLayout(world) {
|
||||||
const { schema } = world;
|
const { schema } = world;
|
||||||
const available = schema.layout.obs.map(v => v.name).sort();
|
const available = schema.layout.obs.map((v) => v.name).sort();
|
||||||
const current = bestDefaultLayout(available);
|
const current = bestDefaultLayout(available);
|
||||||
const currentDimNames = schema.layout.obsByName[current].dims;
|
const currentDimNames = schema.layout.obsByName[current].dims;
|
||||||
return { available, current, currentDimNames };
|
return { available, current, currentDimNames };
|
||||||
@@ -26,7 +26,7 @@ const LayoutChoice = (
|
|||||||
state = {
|
state = {
|
||||||
available: [], // all available choices
|
available: [], // all available choices
|
||||||
current: undefined, // name of the current layout, eg, 'umap'
|
current: undefined, // name of the current layout, eg, 'umap'
|
||||||
currentDimNames: [] // dimension name
|
currentDimNames: [], // dimension name
|
||||||
},
|
},
|
||||||
action,
|
action,
|
||||||
nextSharedState
|
nextSharedState
|
||||||
@@ -37,7 +37,7 @@ const LayoutChoice = (
|
|||||||
const { universe } = nextSharedState;
|
const { universe } = nextSharedState;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...setToDefaultLayout(universe)
|
...setToDefaultLayout(universe),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ const LayoutChoice = (
|
|||||||
const available = Array.from(new Set(state.available).add(name));
|
const available = Array.from(new Set(state.available).add(name));
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
available
|
available,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,12 +64,12 @@ const LayoutChoice = (
|
|||||||
if (dflt.available.includes(current)) {
|
if (dflt.available.includes(current)) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
available: dflt.available
|
available: dflt.available,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...dflt
|
...dflt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const Ontology = (
|
|||||||
enabled: false, // are ontology terms enabled?
|
enabled: false, // are ontology terms enabled?
|
||||||
terms: null, // an array of term names, eg, ['cell', 'lung cell', ...]
|
terms: null, // an array of term names, eg, ['cell', 'lung cell', ...]
|
||||||
termSet: null, // a Set object containing all terms, for fast lookup
|
termSet: null, // a Set object containing all terms, for fast lookup
|
||||||
loading: true
|
loading: true,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -20,7 +20,7 @@ const Ontology = (
|
|||||||
loading: false,
|
loading: false,
|
||||||
enabled,
|
enabled,
|
||||||
terms,
|
terms,
|
||||||
termSet
|
termSet,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
metadataField: "",
|
metadataField: "",
|
||||||
categoryField: ""
|
categoryField: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const pointDialation = (state = initialState, action, sharedNextState) => {
|
const pointDialation = (state = initialState, action, sharedNextState) => {
|
||||||
@@ -15,7 +15,7 @@ const pointDialation = (state = initialState, action, sharedNextState) => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
metadataField,
|
metadataField,
|
||||||
categoryField
|
categoryField,
|
||||||
};
|
};
|
||||||
|
|
||||||
case "category value mouse hover end":
|
case "category value mouse hover end":
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ controller state is not part of the undo/redo history
|
|||||||
*/
|
*/
|
||||||
export const reembedController = (
|
export const reembedController = (
|
||||||
state = {
|
state = {
|
||||||
pendingFetch: null
|
pendingFetch: null,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -11,7 +11,7 @@ export const reembedController = (
|
|||||||
case "reembed: request start": {
|
case "reembed: request start": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
pendingFetch: action.abortableFetch
|
pendingFetch: action.abortableFetch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "reembed: request aborted":
|
case "reembed: request aborted":
|
||||||
@@ -19,7 +19,7 @@ export const reembedController = (
|
|||||||
case "reembed: request completed": {
|
case "reembed: request completed": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
pendingFetch: null
|
pendingFetch: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -33,7 +33,7 @@ actual reembedding data is part of the undo/redo history
|
|||||||
*/
|
*/
|
||||||
export const reembedding = (
|
export const reembedding = (
|
||||||
state = {
|
state = {
|
||||||
reembeddings: new Map()
|
reembeddings: new Map(),
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -47,14 +47,14 @@ export const reembedding = (
|
|||||||
reembeddings: new Map(reembeddings).set(name, {
|
reembeddings: new Map(reembeddings).set(name, {
|
||||||
name,
|
name,
|
||||||
schema,
|
schema,
|
||||||
embedding
|
embedding,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "reembed: clear all reembeddings": {
|
case "reembed: clear all reembeddings": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
reembeddings: new Map()
|
reembeddings: new Map(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ which improves Reset UI performance.
|
|||||||
*/
|
*/
|
||||||
const ResetCacheReducer = (
|
const ResetCacheReducer = (
|
||||||
state = {
|
state = {
|
||||||
crossfilter: null
|
crossfilter: null,
|
||||||
},
|
},
|
||||||
action,
|
action,
|
||||||
nextSharedState
|
nextSharedState
|
||||||
@@ -19,7 +19,7 @@ const ResetCacheReducer = (
|
|||||||
const { crossfilter } = nextSharedState;
|
const { crossfilter } = nextSharedState;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
crossfilter
|
crossfilter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
const Responsive = (
|
const Responsive = (
|
||||||
state = {
|
state = {
|
||||||
width: null,
|
width: null,
|
||||||
height: null
|
height: null,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -11,7 +11,7 @@ const Responsive = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
width: action.data.width,
|
width: action.data.width,
|
||||||
height: action.data.height
|
height: action.data.height,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
const past = currentState[pastKey];
|
const past = currentState[pastKey];
|
||||||
const future = currentState[futureKey];
|
const future = currentState[futureKey];
|
||||||
if (past.length === 0) return currentState;
|
if (past.length === 0) return currentState;
|
||||||
const currentUndoableState = Object.entries(currentState).filter(kv =>
|
const currentUndoableState = Object.entries(currentState).filter((kv) =>
|
||||||
undoableKeysSet.has(kv[0])
|
undoableKeysSet.has(kv[0])
|
||||||
);
|
);
|
||||||
const newPast = [...past];
|
const newPast = [...past];
|
||||||
@@ -91,7 +91,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
...fromEntries(newState),
|
...fromEntries(newState),
|
||||||
[pastKey]: newPast,
|
[pastKey]: newPast,
|
||||||
[futureKey]: newFuture,
|
[futureKey]: newFuture,
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
};
|
};
|
||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
const past = currentState[pastKey] || [];
|
const past = currentState[pastKey] || [];
|
||||||
const future = currentState[futureKey] || [];
|
const future = currentState[futureKey] || [];
|
||||||
if (future.length === 0) return currentState;
|
if (future.length === 0) return currentState;
|
||||||
const currentUndoableState = Object.entries(currentState).filter(kv =>
|
const currentUndoableState = Object.entries(currentState).filter((kv) =>
|
||||||
undoableKeysSet.has(kv[0])
|
undoableKeysSet.has(kv[0])
|
||||||
);
|
);
|
||||||
const newFuture = [...future];
|
const newFuture = [...future];
|
||||||
@@ -114,7 +114,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
...fromEntries(newState),
|
...fromEntries(newState),
|
||||||
[pastKey]: newPast,
|
[pastKey]: newPast,
|
||||||
[futureKey]: newFuture,
|
[futureKey]: newFuture,
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
};
|
};
|
||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
[pastKey]: [],
|
[pastKey]: [],
|
||||||
[futureKey]: [],
|
[futureKey]: [],
|
||||||
[filterStateKey]: {},
|
[filterStateKey]: {},
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
[pastKey]: past,
|
[pastKey]: past,
|
||||||
[futureKey]: future,
|
[futureKey]: future,
|
||||||
[filterStateKey]: filterState,
|
[filterStateKey]: filterState,
|
||||||
[pendingKey]: pending
|
[pendingKey]: pending,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
*/
|
*/
|
||||||
function save(currentState, action, filterState) {
|
function save(currentState, action, filterState) {
|
||||||
const past = currentState[pastKey] || [];
|
const past = currentState[pastKey] || [];
|
||||||
const currentUndoableState = Object.entries(currentState).filter(kv =>
|
const currentUndoableState = Object.entries(currentState).filter((kv) =>
|
||||||
undoableKeysSet.has(kv[0])
|
undoableKeysSet.has(kv[0])
|
||||||
);
|
);
|
||||||
const res = reducer(currentState, action);
|
const res = reducer(currentState, action);
|
||||||
@@ -164,7 +164,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
[pastKey]: newPast,
|
[pastKey]: newPast,
|
||||||
[futureKey]: [],
|
[futureKey]: [],
|
||||||
[filterStateKey]: filterState,
|
[filterStateKey]: filterState,
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
};
|
};
|
||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
@@ -173,12 +173,12 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
Save current state as pending history change. No other side effects.
|
Save current state as pending history change. No other side effects.
|
||||||
*/
|
*/
|
||||||
function stashPending(currentState) {
|
function stashPending(currentState) {
|
||||||
const currentUndoableState = Object.entries(currentState).filter(kv =>
|
const currentUndoableState = Object.entries(currentState).filter((kv) =>
|
||||||
undoableKeysSet.has(kv[0])
|
undoableKeysSet.has(kv[0])
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...currentState,
|
...currentState,
|
||||||
[pendingKey]: currentUndoableState
|
[pendingKey]: currentUndoableState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
function cancelPending(currentState) {
|
function cancelPending(currentState) {
|
||||||
return {
|
return {
|
||||||
...currentState,
|
...currentState,
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
...currentState,
|
...currentState,
|
||||||
[pastKey]: newPast,
|
[pastKey]: newPast,
|
||||||
[futureKey]: [],
|
[futureKey]: [],
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
};
|
};
|
||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
[pastKey]: [],
|
[pastKey]: [],
|
||||||
[futureKey]: [],
|
[futureKey]: [],
|
||||||
[filterStateKey]: {},
|
[filterStateKey]: {},
|
||||||
[pendingKey]: null
|
[pendingKey]: null,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -241,7 +241,7 @@ const Undoable = (reducer, undoableKeys, options = {}) => {
|
|||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
[filterActionKey]: filterAction,
|
[filterActionKey]: filterAction,
|
||||||
[filterStateKey]: filterStateUpdate
|
[filterStateKey]: filterStateUpdate,
|
||||||
} = actionFilterResp;
|
} = actionFilterResp;
|
||||||
const nextFilterState = { ...currentFilterState, ...filterStateUpdate };
|
const nextFilterState = { ...currentFilterState, ...filterStateUpdate };
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const skipOnActions = new Set([
|
|||||||
"annotation: disable category edit mode",
|
"annotation: disable category edit mode",
|
||||||
"annotation: activate edit label mode",
|
"annotation: activate edit label mode",
|
||||||
"annotation: cancel edit label mode",
|
"annotation: cancel edit label mode",
|
||||||
"set annotations collection name"
|
"set annotations collection name",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -101,7 +101,7 @@ const saveOnActions = new Set([
|
|||||||
"annotation: label edited",
|
"annotation: label edited",
|
||||||
"annotation: label current cell selection",
|
"annotation: label current cell selection",
|
||||||
"annotation: delete label",
|
"annotation: delete label",
|
||||||
"annotation: category edited"
|
"annotation: category edited",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,26 +115,26 @@ See graph definition for the transitions that use each.
|
|||||||
|
|
||||||
Signature: (fsm, transition, reducerState, reducerAction) => undoableAction
|
Signature: (fsm, transition, reducerState, reducerAction) => undoableAction
|
||||||
*/
|
*/
|
||||||
const stashPending = fsm => ({
|
const stashPending = (fsm) => ({
|
||||||
[actionKey]: "stashPending",
|
[actionKey]: "stashPending",
|
||||||
[stateKey]: { fsm }
|
[stateKey]: { fsm },
|
||||||
});
|
});
|
||||||
const cancelPending = () => ({
|
const cancelPending = () => ({
|
||||||
[actionKey]: "cancelPending",
|
[actionKey]: "cancelPending",
|
||||||
[stateKey]: { fsm: null }
|
[stateKey]: { fsm: null },
|
||||||
});
|
});
|
||||||
const applyPending = () => ({
|
const applyPending = () => ({
|
||||||
[actionKey]: "applyPending",
|
[actionKey]: "applyPending",
|
||||||
[stateKey]: { fsm: null }
|
[stateKey]: { fsm: null },
|
||||||
});
|
});
|
||||||
const skip = (fsm, transition) => ({
|
const skip = (fsm, transition) => ({
|
||||||
[actionKey]: "skip",
|
[actionKey]: "skip",
|
||||||
[stateKey]: { fsm: transition.to !== "done" ? fsm : null }
|
[stateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
||||||
});
|
});
|
||||||
const clear = () => ({ [actionKey]: "clear", [stateKey]: { fsm: null } });
|
const clear = () => ({ [actionKey]: "clear", [stateKey]: { fsm: null } });
|
||||||
const save = (fsm, transition) => ({
|
const save = (fsm, transition) => ({
|
||||||
[actionKey]: "save",
|
[actionKey]: "save",
|
||||||
[stateKey]: { fsm: transition.to !== "done" ? fsm : null }
|
[stateKey]: { fsm: transition.to !== "done" ? fsm : null },
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -171,11 +171,11 @@ Basic approach:
|
|||||||
* only implement complex state machines where absolutely required (eg,
|
* only implement complex state machines where absolutely required (eg,
|
||||||
multi-event seleciton and the like)
|
multi-event seleciton and the like)
|
||||||
*/
|
*/
|
||||||
const actionFilter = debug => (state, action, prevFilterState) => {
|
const actionFilter = (debug) => (state, action, prevFilterState) => {
|
||||||
const actionType = action.type;
|
const actionType = action.type;
|
||||||
const filterState = {
|
const filterState = {
|
||||||
...prevFilterState,
|
...prevFilterState,
|
||||||
prevAction: action
|
prevAction: action,
|
||||||
};
|
};
|
||||||
if (skipOnActions.has(actionType)) {
|
if (skipOnActions.has(actionType)) {
|
||||||
return { [actionKey]: "skip", [stateKey]: filterState };
|
return { [actionKey]: "skip", [stateKey]: filterState };
|
||||||
@@ -246,7 +246,7 @@ const debug = false;
|
|||||||
const undoableConfig = {
|
const undoableConfig = {
|
||||||
debug,
|
debug,
|
||||||
historyLimit: 50, // maximum history size
|
historyLimit: 50, // maximum history size
|
||||||
actionFilter: actionFilter(debug)
|
actionFilter: actionFilter(debug),
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -258,9 +258,9 @@ if (debug) {
|
|||||||
Confirm no intersection between the various trivial rejection action filters
|
Confirm no intersection between the various trivial rejection action filters
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
new Set([...skipOnActions].filter(x => clearOnActions.has(x))).size > 0 ||
|
new Set([...skipOnActions].filter((x) => clearOnActions.has(x))).size > 0 ||
|
||||||
new Set([...skipOnActions].filter(x => saveOnActions.has(x))).size > 0 ||
|
new Set([...skipOnActions].filter((x) => saveOnActions.has(x))).size > 0 ||
|
||||||
new Set([...clearOnActions].filter(x => saveOnActions.has(x))).size > 0
|
new Set([...clearOnActions].filter((x) => saveOnActions.has(x))).size > 0
|
||||||
) {
|
) {
|
||||||
console.error(
|
console.error(
|
||||||
"Undoable misconfiguration - action filters have redundant events"
|
"Undoable misconfiguration - action filters have redundant events"
|
||||||
@@ -275,10 +275,10 @@ if (debug) {
|
|||||||
const trivialFilters = new Set([
|
const trivialFilters = new Set([
|
||||||
...skipOnActions,
|
...skipOnActions,
|
||||||
...clearOnActions,
|
...clearOnActions,
|
||||||
...saveOnActions
|
...saveOnActions,
|
||||||
]);
|
]);
|
||||||
const trivialOverlapWithFsm = new Set(
|
const trivialOverlapWithFsm = new Set(
|
||||||
[...trivialFilters].filter(x => seedFsm.events.has(x))
|
[...trivialFilters].filter((x) => seedFsm.events.has(x))
|
||||||
);
|
);
|
||||||
if (trivialOverlapWithFsm.size > 0) {
|
if (trivialOverlapWithFsm.size > 0) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ const createFsmTransitions = (
|
|||||||
event: "graph brush start",
|
event: "graph brush start",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "graph brush in progress",
|
to: "graph brush in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "graph brush cancel",
|
event: "graph brush cancel",
|
||||||
from: "graph brush in progress",
|
from: "graph brush in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "graph brush deselect",
|
event: "graph brush deselect",
|
||||||
@@ -48,13 +48,13 @@ const createFsmTransitions = (
|
|||||||
action: (fsm, transition, data) =>
|
action: (fsm, transition, data) =>
|
||||||
data.state.graphSelection.selection.mode === "all"
|
data.state.graphSelection.selection.mode === "all"
|
||||||
? cancelPending()
|
? cancelPending()
|
||||||
: applyPending()
|
: applyPending(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "graph brush end",
|
event: "graph brush end",
|
||||||
from: "graph brush in progress",
|
from: "graph brush in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* graph selection lasso */
|
/* graph selection lasso */
|
||||||
@@ -62,13 +62,13 @@ const createFsmTransitions = (
|
|||||||
event: "graph lasso start",
|
event: "graph lasso start",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "graph lasso in progress",
|
to: "graph lasso in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "graph lasso cancel",
|
event: "graph lasso cancel",
|
||||||
from: "graph lasso in progress",
|
from: "graph lasso in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "graph lasso deselect",
|
event: "graph lasso deselect",
|
||||||
@@ -78,13 +78,13 @@ const createFsmTransitions = (
|
|||||||
action: (fsm, transition, data) =>
|
action: (fsm, transition, data) =>
|
||||||
data.state.graphSelection.selection.mode === "all"
|
data.state.graphSelection.selection.mode === "all"
|
||||||
? cancelPending()
|
? cancelPending()
|
||||||
: applyPending()
|
: applyPending(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "graph lasso end",
|
event: "graph lasso end",
|
||||||
from: "graph lasso in progress",
|
from: "graph lasso in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Continuous metadata histogram brush selection */
|
/* Continuous metadata histogram brush selection */
|
||||||
@@ -92,19 +92,19 @@ const createFsmTransitions = (
|
|||||||
event: "continuous metadata histogram start",
|
event: "continuous metadata histogram start",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "continuous histo select in progress",
|
to: "continuous histo select in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "continuous metadata histogram cancel",
|
event: "continuous metadata histogram cancel",
|
||||||
from: "continuous histo select in progress",
|
from: "continuous histo select in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: cancelPending
|
action: cancelPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "continuous metadata histogram end",
|
event: "continuous metadata histogram end",
|
||||||
from: "continuous histo select in progress",
|
from: "continuous histo select in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Single gene request by user */
|
/* Single gene request by user */
|
||||||
@@ -112,25 +112,25 @@ const createFsmTransitions = (
|
|||||||
event: "single user defined gene start",
|
event: "single user defined gene start",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "single user gene request in progress",
|
to: "single user gene request in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "request user defined gene error",
|
event: "request user defined gene error",
|
||||||
from: "single user gene request in progress",
|
from: "single user gene request in progress",
|
||||||
to: "single user gene error in progress",
|
to: "single user gene error in progress",
|
||||||
action: skip
|
action: skip,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "single user defined gene error",
|
event: "single user defined gene error",
|
||||||
from: "single user gene error in progress",
|
from: "single user gene error in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: cancelPending
|
action: cancelPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "single user defined gene complete",
|
event: "single user defined gene complete",
|
||||||
from: "single user gene request in progress",
|
from: "single user gene request in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Bulk gene request by user */
|
/* Bulk gene request by user */
|
||||||
@@ -138,25 +138,25 @@ const createFsmTransitions = (
|
|||||||
event: "bulk user defined gene start",
|
event: "bulk user defined gene start",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "bulk user gene request in progress",
|
to: "bulk user gene request in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "request user defined gene error",
|
event: "request user defined gene error",
|
||||||
from: "bulk user gene request in progress",
|
from: "bulk user gene request in progress",
|
||||||
to: "bulk user gene request error in progress",
|
to: "bulk user gene request error in progress",
|
||||||
action: skip
|
action: skip,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "bulk user defined gene error",
|
event: "bulk user defined gene error",
|
||||||
from: "bulk user gene request error in progress",
|
from: "bulk user gene request error in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: cancelPending
|
action: cancelPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "bulk user defined gene complete",
|
event: "bulk user defined gene complete",
|
||||||
from: "bulk user gene request in progress",
|
from: "bulk user gene request in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Compute Differential Expression button user action */
|
/* Compute Differential Expression button user action */
|
||||||
@@ -164,19 +164,19 @@ const createFsmTransitions = (
|
|||||||
event: "request differential expression started",
|
event: "request differential expression started",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "diffexp in progress",
|
to: "diffexp in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "request user defined gene error",
|
event: "request user defined gene error",
|
||||||
from: "diffexp in progress",
|
from: "diffexp in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: cancelPending
|
action: cancelPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "request differential expression success",
|
event: "request differential expression success",
|
||||||
from: "diffexp in progress",
|
from: "diffexp in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Clear Differential Expression button user action */
|
/* Clear Differential Expression button user action */
|
||||||
@@ -184,13 +184,13 @@ const createFsmTransitions = (
|
|||||||
event: "clear differential expression",
|
event: "clear differential expression",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "CDE Button in progress",
|
to: "CDE Button in progress",
|
||||||
action: stashPending
|
action: stashPending,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "clear scatterplot",
|
event: "clear scatterplot",
|
||||||
from: "CDE Button in progress",
|
from: "CDE Button in progress",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: applyPending
|
action: applyPending,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* clear scatter plot button (eg, on scatterplot view) */
|
/* clear scatter plot button (eg, on scatterplot view) */
|
||||||
@@ -198,8 +198,8 @@ const createFsmTransitions = (
|
|||||||
event: "clear scatterplot",
|
event: "clear scatterplot",
|
||||||
from: "init",
|
from: "init",
|
||||||
to: "done",
|
to: "done",
|
||||||
action: save
|
action: save,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { unassignedCategoryLabel } from "../globals";
|
|||||||
import {
|
import {
|
||||||
addObsAnnotations,
|
addObsAnnotations,
|
||||||
addVarAnnotations,
|
addVarAnnotations,
|
||||||
addObsLayout
|
addObsLayout,
|
||||||
} from "../util/stateManager/universe";
|
} from "../util/stateManager/universe";
|
||||||
import {
|
import {
|
||||||
World,
|
World,
|
||||||
ControlsHelpers,
|
ControlsHelpers,
|
||||||
AnnotationsHelpers
|
AnnotationsHelpers,
|
||||||
} from "../util/stateManager";
|
} from "../util/stateManager";
|
||||||
|
|
||||||
const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
||||||
@@ -23,19 +23,19 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
|||||||
case "obsAnnotations": {
|
case "obsAnnotations": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...addObsAnnotations(state, dataframe)
|
...addObsAnnotations(state, dataframe),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "varAnnotations": {
|
case "varAnnotations": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...addVarAnnotations(state, dataframe)
|
...addVarAnnotations(state, dataframe),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "obsLayout": {
|
case "obsLayout": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...addObsLayout(state, dataframe)
|
...addObsLayout(state, dataframe),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -62,8 +62,10 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
|||||||
const { userDefinedGenes, diffexpGenes } = prevSharedState;
|
const { userDefinedGenes, diffexpGenes } = prevSharedState;
|
||||||
const allTheGenesWeNeed = [
|
const allTheGenesWeNeed = [
|
||||||
...new Set(
|
...new Set(
|
||||||
[userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)].filter(ele => ele).flat()
|
[userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)]
|
||||||
)
|
.filter((ele) => ele)
|
||||||
|
.flat()
|
||||||
|
),
|
||||||
];
|
];
|
||||||
varData = ControlsHelpers.pruneVarDataCache(varData, allTheGenesWeNeed);
|
varData = ControlsHelpers.pruneVarDataCache(varData, allTheGenesWeNeed);
|
||||||
return { ...state, varData };
|
return { ...state, varData };
|
||||||
@@ -98,7 +100,7 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
|||||||
categoryToDuplicate,
|
categoryToDuplicate,
|
||||||
name,
|
name,
|
||||||
{
|
{
|
||||||
writable: true
|
writable: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
/* if we are duplicating a non-writable annotation, it may not have an unassigned category */
|
/* if we are duplicating a non-writable annotation, it may not have an unassigned category */
|
||||||
@@ -114,7 +116,7 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
|||||||
name,
|
name,
|
||||||
categories,
|
categories,
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
writable: true
|
writable: true,
|
||||||
});
|
});
|
||||||
data = new Array(state.nObs).fill(unassignedCategoryLabel);
|
data = new Array(state.nObs).fill(unassignedCategoryLabel);
|
||||||
}
|
}
|
||||||
@@ -134,7 +136,7 @@ const Universe = (state = null, action, nextSharedState, prevSharedState) => {
|
|||||||
|
|
||||||
const colSchema = {
|
const colSchema = {
|
||||||
...state.schema.annotations.obsByName[name],
|
...state.schema.annotations.obsByName[name],
|
||||||
name: newName
|
name: newName,
|
||||||
};
|
};
|
||||||
const schema = AnnotationsHelpers.addObsAnnoSchema(
|
const schema = AnnotationsHelpers.addObsAnnoSchema(
|
||||||
AnnotationsHelpers.removeObsAnnoSchema(state.schema, name),
|
AnnotationsHelpers.removeObsAnnoSchema(state.schema, name),
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { unassignedCategoryLabel } from "../globals";
|
|||||||
import {
|
import {
|
||||||
World,
|
World,
|
||||||
ControlsHelpers,
|
ControlsHelpers,
|
||||||
AnnotationsHelpers
|
AnnotationsHelpers,
|
||||||
} from "../util/stateManager";
|
} from "../util/stateManager";
|
||||||
import {
|
import {
|
||||||
addObsLayout,
|
addObsLayout,
|
||||||
removeObsLayout
|
removeObsLayout,
|
||||||
} from "../util/stateManager/schemaHelpers";
|
} from "../util/stateManager/schemaHelpers";
|
||||||
import clip from "../util/clip";
|
import clip from "../util/clip";
|
||||||
import quantile from "../util/quantile";
|
import quantile from "../util/quantile";
|
||||||
@@ -45,14 +45,14 @@ const WorldReducer = (
|
|||||||
if (dim == "varData" || dim == "obsAnnotations") {
|
if (dim == "varData" || dim == "obsAnnotations") {
|
||||||
unclipped = {
|
unclipped = {
|
||||||
...unclipped,
|
...unclipped,
|
||||||
[dim]: universe[dim].clone()
|
[dim]: universe[dim].clone(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
schema: universe.schema,
|
schema: universe.schema,
|
||||||
[dim]: universe[dim].clone(),
|
[dim]: universe[dim].clone(),
|
||||||
unclipped
|
unclipped,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +113,9 @@ const WorldReducer = (
|
|||||||
const allTheGenesWeNeed = [
|
const allTheGenesWeNeed = [
|
||||||
...new Set(
|
...new Set(
|
||||||
[userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)]
|
[userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)]
|
||||||
.filter(ele => ele)
|
.filter((ele) => ele)
|
||||||
.flat()
|
.flat()
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
unclippedVarData = ControlsHelpers.pruneVarDataCache(
|
unclippedVarData = ControlsHelpers.pruneVarDataCache(
|
||||||
unclippedVarData,
|
unclippedVarData,
|
||||||
@@ -130,14 +130,14 @@ const WorldReducer = (
|
|||||||
let clippedVarData = state.varData;
|
let clippedVarData = state.varData;
|
||||||
const keysToDrop = clippedVarData.colIndex
|
const keysToDrop = clippedVarData.colIndex
|
||||||
.keys()
|
.keys()
|
||||||
.filter(k => !unclippedVarData.hasCol(k));
|
.filter((k) => !unclippedVarData.hasCol(k));
|
||||||
const keysToAdd = unclippedVarData.colIndex
|
const keysToAdd = unclippedVarData.colIndex
|
||||||
.keys()
|
.keys()
|
||||||
.filter(k => !clippedVarData.hasCol(k));
|
.filter((k) => !clippedVarData.hasCol(k));
|
||||||
keysToDrop.forEach(k => {
|
keysToDrop.forEach((k) => {
|
||||||
clippedVarData = clippedVarData.dropCol(k);
|
clippedVarData = clippedVarData.dropCol(k);
|
||||||
});
|
});
|
||||||
keysToAdd.forEach(k => {
|
keysToAdd.forEach((k) => {
|
||||||
const data = unclippedVarData.col(k).asArray();
|
const data = unclippedVarData.col(k).asArray();
|
||||||
const q = [state.clipQuantiles.min, state.clipQuantiles.max];
|
const q = [state.clipQuantiles.min, state.clipQuantiles.max];
|
||||||
const [qMinVal, qMaxVal] = quantile(q, data);
|
const [qMinVal, qMaxVal] = quantile(q, data);
|
||||||
@@ -154,8 +154,8 @@ const WorldReducer = (
|
|||||||
varData: clippedVarData,
|
varData: clippedVarData,
|
||||||
unclipped: {
|
unclipped: {
|
||||||
...state.unclipped,
|
...state.unclipped,
|
||||||
varData: unclippedVarData
|
varData: unclippedVarData,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ const WorldReducer = (
|
|||||||
name,
|
name,
|
||||||
newAnnotation,
|
newAnnotation,
|
||||||
state.unclipped.obsAnnotations.rowIndex
|
state.unclipped.obsAnnotations.rowIndex
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
return { ...state, schema, obsAnnotations, unclipped };
|
return { ...state, schema, obsAnnotations, unclipped };
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ const WorldReducer = (
|
|||||||
const obsAnnotations = state.obsAnnotations.renameCol(name, newName);
|
const obsAnnotations = state.obsAnnotations.renameCol(name, newName);
|
||||||
const unclipped = {
|
const unclipped = {
|
||||||
...state.unclipped,
|
...state.unclipped,
|
||||||
obsAnnotations: state.unclipped.obsAnnotations.renameCol(name, newName)
|
obsAnnotations: state.unclipped.obsAnnotations.renameCol(name, newName),
|
||||||
};
|
};
|
||||||
return { ...state, schema, obsAnnotations, unclipped };
|
return { ...state, schema, obsAnnotations, unclipped };
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ const WorldReducer = (
|
|||||||
const obsAnnotations = state.obsAnnotations.dropCol(name);
|
const obsAnnotations = state.obsAnnotations.dropCol(name);
|
||||||
const unclipped = {
|
const unclipped = {
|
||||||
...state.unclipped,
|
...state.unclipped,
|
||||||
obsAnnotations: state.unclipped.obsAnnotations.dropCol(name)
|
obsAnnotations: state.unclipped.obsAnnotations.dropCol(name),
|
||||||
};
|
};
|
||||||
return { ...state, schema, obsAnnotations, unclipped };
|
return { ...state, schema, obsAnnotations, unclipped };
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ const WorldReducer = (
|
|||||||
crossfilter,
|
crossfilter,
|
||||||
metadataField,
|
metadataField,
|
||||||
newLabelText
|
newLabelText
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { ...state, schema };
|
return { ...state, schema };
|
||||||
@@ -253,7 +253,7 @@ const WorldReducer = (
|
|||||||
metadataField,
|
metadataField,
|
||||||
oldLabelName,
|
oldLabelName,
|
||||||
newLabelName
|
newLabelName
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
const obsAnnotations = state.obsAnnotations.replaceColData(
|
const obsAnnotations = state.obsAnnotations.replaceColData(
|
||||||
metadataField,
|
metadataField,
|
||||||
@@ -274,7 +274,7 @@ const WorldReducer = (
|
|||||||
metadataField,
|
metadataField,
|
||||||
label,
|
label,
|
||||||
unassignedCategoryLabel
|
unassignedCategoryLabel
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
const obsAnnotations = state.obsAnnotations.replaceColData(
|
const obsAnnotations = state.obsAnnotations.replaceColData(
|
||||||
metadataField,
|
metadataField,
|
||||||
@@ -288,7 +288,7 @@ const WorldReducer = (
|
|||||||
const { crossfilter } = prevSharedState;
|
const { crossfilter } = prevSharedState;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...setLabelOnCurrentSelection(state, crossfilter, metadataField, label)
|
...setLabelOnCurrentSelection(state, crossfilter, metadataField, label),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,14 +306,14 @@ const WorldReducer = (
|
|||||||
const embedingLabels = embedding.colIndex.keys();
|
const embedingLabels = embedding.colIndex.keys();
|
||||||
const labels = {
|
const labels = {
|
||||||
[embedingLabels[0]]: dims[0],
|
[embedingLabels[0]]: dims[0],
|
||||||
[embedingLabels[1]]: dims[1]
|
[embedingLabels[1]]: dims[1],
|
||||||
};
|
};
|
||||||
obsLayout = obsLayout.withColsFrom(embedding, labels);
|
obsLayout = obsLayout.withColsFrom(embedding, labels);
|
||||||
schema = addObsLayout(schema, embeddingSchema);
|
schema = addObsLayout(schema, embeddingSchema);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
obsLayout,
|
obsLayout,
|
||||||
schema
|
schema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ const WorldReducer = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
obsLayout,
|
obsLayout,
|
||||||
schema
|
schema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ function setLabelOnCurrentSelection(world, crossfilter, metadataField, label) {
|
|||||||
metadataField,
|
metadataField,
|
||||||
mask,
|
mask,
|
||||||
label
|
label
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
const obsAnnotations = world.obsAnnotations.replaceColData(
|
const obsAnnotations = world.obsAnnotations.replaceColData(
|
||||||
metadataField,
|
metadataField,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Camera {
|
|||||||
this.prevEvent = {
|
this.prevEvent = {
|
||||||
clientX: 0,
|
clientX: 0,
|
||||||
clientY: 0,
|
clientY: 0,
|
||||||
type: 0
|
type: 0,
|
||||||
};
|
};
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.viewMatrix = mat3.create();
|
this.viewMatrix = mat3.create();
|
||||||
@@ -43,11 +43,11 @@ class Camera {
|
|||||||
const m = this.viewMatrix;
|
const m = this.viewMatrix;
|
||||||
const dyRange = [
|
const dyRange = [
|
||||||
-panBound - (m[7] + 1) / m[4],
|
-panBound - (m[7] + 1) / m[4],
|
||||||
panBound - (m[7] - 1) / m[4]
|
panBound - (m[7] - 1) / m[4],
|
||||||
];
|
];
|
||||||
const dxRange = [
|
const dxRange = [
|
||||||
-panBound - (m[6] + 1) / m[0],
|
-panBound - (m[6] + 1) / m[0],
|
||||||
panBound - (m[6] - 1) / m[0]
|
panBound - (m[6] - 1) / m[0],
|
||||||
];
|
];
|
||||||
|
|
||||||
const dxClamped = clamp(dx, dxRange);
|
const dxClamped = clamp(dx, dxRange);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const catLabelSort = (isUserAnno, values) => {
|
|||||||
const ints = [];
|
const ints = [];
|
||||||
const unassignedOrNaN = [];
|
const unassignedOrNaN = [];
|
||||||
|
|
||||||
values.forEach(v => {
|
values.forEach((v) => {
|
||||||
if (isUserAnno && v === globals.unassignedCategoryLabel) {
|
if (isUserAnno && v === globals.unassignedCategoryLabel) {
|
||||||
unassignedOrNaN.push(v);
|
unassignedOrNaN.push(v);
|
||||||
} else if (String(v).toLowerCase() === "nan") {
|
} else if (String(v).toLowerCase() === "nan") {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const getCoordinatesByLabel = (
|
|||||||
hasFinite: false,
|
hasFinite: false,
|
||||||
xCoordinates: new Float32Array(numInLabel),
|
xCoordinates: new Float32Array(numInLabel),
|
||||||
yCoordinates: new Float32Array(numInLabel),
|
yCoordinates: new Float32Array(numInLabel),
|
||||||
length: 0
|
length: 0,
|
||||||
};
|
};
|
||||||
coordsByCategoryLabel.set(label, coords);
|
coordsByCategoryLabel.set(label, coords);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -11,15 +11,15 @@ If `setTo` is not undefined, values outside the [lower, upper] range will be set
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
export default function clip(arr, lower, upper, setTo) {
|
export default function clip(arr, lower, upper, setTo) {
|
||||||
const lowerSet = setTo === undefined ? lower : setTo;
|
const lowerSet = setTo === undefined ? lower : setTo;
|
||||||
const upperSet = setTo === undefined ? upper : setTo;
|
const upperSet = setTo === undefined ? upper : setTo;
|
||||||
for (let i = 0, l = arr.length; i < l; i += 1) {
|
for (let i = 0, l = arr.length; i < l; i += 1) {
|
||||||
const v = arr[i];
|
const v = arr[i];
|
||||||
if (v < lower) {
|
if (v < lower) {
|
||||||
arr[i] = lowerSet;
|
arr[i] = lowerSet;
|
||||||
} else if (v > upper) {
|
} else if (v > upper) {
|
||||||
arr[i] = upperSet;
|
arr[i] = upperSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import {
|
|||||||
isTypedArray,
|
isTypedArray,
|
||||||
isArrayOrTypedArray,
|
isArrayOrTypedArray,
|
||||||
callOnceLazy,
|
callOnceLazy,
|
||||||
memoize
|
memoize,
|
||||||
} from "./util";
|
} from "./util";
|
||||||
import {
|
import {
|
||||||
summarizeContinuous,
|
summarizeContinuous,
|
||||||
summarizeCategorical as _summarizeCategorical
|
summarizeCategorical as _summarizeCategorical,
|
||||||
} from "./summarize";
|
} from "./summarize";
|
||||||
import {
|
import {
|
||||||
histogramCategorical as _histogramCategorical,
|
histogramCategorical as _histogramCategorical,
|
||||||
hashCategorical,
|
hashCategorical,
|
||||||
histogramContinuous,
|
histogramContinuous,
|
||||||
hashContinuous
|
hashContinuous,
|
||||||
} from "./histogram";
|
} from "./histogram";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -140,7 +140,7 @@ class Dataframe {
|
|||||||
if (!Array.isArray(columnarData)) {
|
if (!Array.isArray(columnarData)) {
|
||||||
throw new TypeError("Dataframe constructor requires array of columns");
|
throw new TypeError("Dataframe constructor requires array of columns");
|
||||||
}
|
}
|
||||||
if (!columnarData.every(c => isArrayOrTypedArray(c))) {
|
if (!columnarData.every((c) => isArrayOrTypedArray(c))) {
|
||||||
throw new TypeError("Dataframe columns must all be Array or TypedArray");
|
throw new TypeError("Dataframe columns must all be Array or TypedArray");
|
||||||
}
|
}
|
||||||
if (!isLabelIndex(rowIndex)) {
|
if (!isLabelIndex(rowIndex)) {
|
||||||
@@ -153,7 +153,7 @@ class Dataframe {
|
|||||||
/* check for expected dimensionality / size */
|
/* check for expected dimensionality / size */
|
||||||
if (
|
if (
|
||||||
nCols !== columnarData.length ||
|
nCols !== columnarData.length ||
|
||||||
!columnarData.every(c => c.length === nRows)
|
!columnarData.every((c) => c.length === nRows)
|
||||||
) {
|
) {
|
||||||
throw new RangeError(
|
throw new RangeError(
|
||||||
"Dataframe dimension does not match provided data shape"
|
"Dataframe dimension does not match provided data shape"
|
||||||
@@ -261,7 +261,7 @@ class Dataframe {
|
|||||||
Create histogram bins for this column. Memoized.
|
Create histogram bins for this column. Memoized.
|
||||||
*/
|
*/
|
||||||
const _memoHistoCat = memoize(_histogramCategorical, hashCategorical);
|
const _memoHistoCat = memoize(_histogramCategorical, hashCategorical);
|
||||||
const histogramCategorical = by => _memoHistoCat(get, by);
|
const histogramCategorical = (by) => _memoHistoCat(get, by);
|
||||||
let histogram = null;
|
let histogram = null;
|
||||||
if (isTypedArray(column)) {
|
if (isTypedArray(column)) {
|
||||||
const mFn = memoize(histogramContinuous, hashContinuous);
|
const mFn = memoize(histogramContinuous, hashContinuous);
|
||||||
@@ -293,7 +293,7 @@ class Dataframe {
|
|||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
getOffset: getRowByOffset,
|
getOffset: getRowByOffset,
|
||||||
getLabel: getRowByLabel
|
getLabel: getRowByLabel,
|
||||||
} = this.rowIndex;
|
} = this.rowIndex;
|
||||||
this.__columnsAccessor = this.__columns.map((column, idx) => {
|
this.__columnsAccessor = this.__columns.map((column, idx) => {
|
||||||
if (accessors[idx]) {
|
if (accessors[idx]) {
|
||||||
@@ -423,7 +423,7 @@ class Dataframe {
|
|||||||
|
|
||||||
// otherwise, bulid a new dataframe combining columns from both
|
// otherwise, bulid a new dataframe combining columns from both
|
||||||
|
|
||||||
const srcOffsets = srcLabels.map(l => dataframe.colIndex.getOffset(l));
|
const srcOffsets = srcLabels.map((l) => dataframe.colIndex.getOffset(l));
|
||||||
|
|
||||||
// check for label collisions
|
// check for label collisions
|
||||||
if (dstLabels.some(this.hasCol, this)) {
|
if (dstLabels.some(this.hasCol, this)) {
|
||||||
@@ -435,12 +435,12 @@ class Dataframe {
|
|||||||
const { rowIndex } = this;
|
const { rowIndex } = this;
|
||||||
const columns = [
|
const columns = [
|
||||||
...this.__columns,
|
...this.__columns,
|
||||||
...srcOffsets.map(i => dataframe.__columns[i])
|
...srcOffsets.map((i) => dataframe.__columns[i]),
|
||||||
];
|
];
|
||||||
const colIndex = this.colIndex.withLabels(dstLabels);
|
const colIndex = this.colIndex.withLabels(dstLabels);
|
||||||
const columnsAccessor = [
|
const columnsAccessor = [
|
||||||
...this.__columnsAccessor,
|
...this.__columnsAccessor,
|
||||||
...srcOffsets.map(i => dataframe.__columnsAccessor[i])
|
...srcOffsets.map((i) => dataframe.__columnsAccessor[i]),
|
||||||
];
|
];
|
||||||
|
|
||||||
return new this.constructor(
|
return new this.constructor(
|
||||||
@@ -604,7 +604,7 @@ class Dataframe {
|
|||||||
|
|
||||||
/* subset rows */
|
/* subset rows */
|
||||||
if (rowOffsets) {
|
if (rowOffsets) {
|
||||||
columns = columns.map(col => {
|
columns = columns.map((col) => {
|
||||||
const newCol = new col.constructor(rowOffsets.length);
|
const newCol = new col.constructor(rowOffsets.length);
|
||||||
for (let i = 0, l = rowOffsets.length; i < l; i += 1) {
|
for (let i = 0, l = rowOffsets.length; i < l; i += 1) {
|
||||||
newCol[i] = col[rowOffsets[i]];
|
newCol[i] = col[rowOffsets[i]];
|
||||||
@@ -630,7 +630,7 @@ class Dataframe {
|
|||||||
if (!labels) {
|
if (!labels) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return labels.map(label => {
|
return labels.map((label) => {
|
||||||
const off = index.getOffset(label);
|
const off = index.getOffset(label);
|
||||||
if (off === undefined) {
|
if (off === undefined) {
|
||||||
throw new RangeError(`unknown label: ${label}`);
|
throw new RangeError(`unknown label: ${label}`);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function summarizeContinuous(col) {
|
|||||||
nan,
|
nan,
|
||||||
pinf,
|
pinf,
|
||||||
ninf,
|
ninf,
|
||||||
percentiles
|
percentiles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +77,6 @@ export function summarizeCategorical(col) {
|
|||||||
categorical: true,
|
categorical: true,
|
||||||
categories: [...categoryCounts.keys()],
|
categories: [...categoryCounts.keys()],
|
||||||
categoryCounts,
|
categoryCounts,
|
||||||
numCategories: categoryCounts.size
|
numCategories: categoryCounts.size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user