mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-21 06:18:12 +08:00
* icons, partway * redux for values * onChange * cancel * annotations lifecycle for category names * copy categorical * edit category * add Dataframe.withColsFrom * render user annotations; default add/delete annotation category * add label name to actions * category name edit * error checking improvements * change schema field isUserAnnotation to writable * always have an unassigned label; implement delete label * implement add new label and edit label name * label current cell selection * fix select exact bug in crossfilter * clean up categorical reducer * fix tests * remove debugging printf * implement subset/reset for user annotations * undo redo support for user annotations * remove duplicate button from categories * add modal * remove obsolete duplicate annotation reducers * remove old debugging printf * connect modal to annotation create and dup * initial full-stack wiring * finish up end-to-end wiring * fix existing unit tests * fix pytests to match new schema API * remove debugging printfs * add label file rotation * remove obsolete comment * add fbs encode/decode tests * add tests for writable annotations * simplify code * fix hashing bug with FBS encoding * lint * fix smoke tests * improve error checking in Dataframe.withColsFrom * add unit test for Dataframe.withColsFrom * add unit test for Dataframe.columns and Dataframe.renameCol * fix bug in FBS encode, add better error checks, refactor * add FBS encode/decode test * add clarifying comment * clean up action type names; fix state inconsistency in crossfilter update * change autosave timer to 2.5sec * sort categorical metadata render order so it remains consistent * add temporary autogenerated label for add-new-label operation * fix hover-over label menu interference with cell highlighting * remove debugging code * add missing reducer cases & fix typo * make dataframe memoize more general purpose * add dev mode for annos * fix error on select duplicate * handle zero occupancy categories * correctly maintain unclipped AND clipped world * correctly handle zero length FBS matrix and label files * ensure all writable categorical schema contains an unassigned category * handle case where building occupancy stack for category with no members * dialog for creating label, disable button if duplicate or empty * visually separate writeable * edit category * fix edit category name * remove debugging code * fix edit annotation label * visually define unassigned, change options * Pull in requirements.txt from `master` * label currently selected cells * duplicate label * lint * fix pytest merge issues * rename --label-file to --experimental-label-file * remove debugging console log * spelling error fix; fix bug found in PR review. * lint
191 lines
5.7 KiB
JavaScript
191 lines
5.7 KiB
JavaScript
import _ from "lodash";
|
|
import * as Universe from "../../../src/util/stateManager/universe";
|
|
import * as World from "../../../src/util/stateManager/world";
|
|
import * as Dataframe from "../../../src/util/dataframe";
|
|
import Crossfilter from "../../../src/util/typedCrossfilter";
|
|
import { DimTypes } from "../../../src/util/typedCrossfilter/crossfilter";
|
|
import * as REST from "./sampleResponses";
|
|
import {
|
|
obsAnnoDimensionName,
|
|
layoutDimensionName
|
|
} from "../../../src/util/nameCreators";
|
|
|
|
/*
|
|
Helper - creates universe, world, corssfilter and dimensionMap from
|
|
the default REST test response.
|
|
*/
|
|
const defaultBigBang = () => {
|
|
/* create unverse, world, crossfilter and dimensionMap */
|
|
/* create universe */
|
|
const universe = Universe.createUniverseFromResponse(
|
|
_.cloneDeep(REST.config),
|
|
_.cloneDeep(REST.schema),
|
|
_.cloneDeep(REST.annotationsObs),
|
|
_.cloneDeep(REST.annotationsVar),
|
|
_.cloneDeep(REST.layoutObs)
|
|
);
|
|
/* create world */
|
|
const world = World.createWorldFromEntireUniverse(universe);
|
|
/* create crossfilter */
|
|
const crossfilter = World.createObsDimensions(
|
|
new Crossfilter(world.obsAnnotations),
|
|
world,
|
|
REST.schema.schema.layout.obs[0].dims
|
|
);
|
|
|
|
return {
|
|
universe,
|
|
world,
|
|
crossfilter
|
|
};
|
|
};
|
|
|
|
describe("createWorldFromEntireUniverse", () => {
|
|
test("create from REST sample", () => {
|
|
const universe = Universe.createUniverseFromResponse(
|
|
_.cloneDeep(REST.config),
|
|
_.cloneDeep(REST.schema),
|
|
_.cloneDeep(REST.annotationsObs),
|
|
_.cloneDeep(REST.annotationsVar),
|
|
_.cloneDeep(REST.layoutObs)
|
|
);
|
|
expect(universe).toBeDefined();
|
|
|
|
const world = World.createWorldFromEntireUniverse(universe);
|
|
expect(world).toBeDefined();
|
|
|
|
expect(world).toMatchObject(
|
|
expect.objectContaining({
|
|
nObs: universe.nObs,
|
|
nVar: universe.nVar,
|
|
schema: universe.schema,
|
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
|
varAnnotations: expect.any(Dataframe.Dataframe),
|
|
obsLayout: expect.any(Dataframe.Dataframe),
|
|
varData: expect.any(Dataframe.Dataframe),
|
|
clipQuantiles: { min: 0, max: 1 },
|
|
unclipped: {
|
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
|
varData: expect.any(Dataframe.Dataframe)
|
|
}
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("createWorldFromCurrentSelection", () => {
|
|
test("create from REST sample", () => {
|
|
const {
|
|
universe,
|
|
world: originalWorld,
|
|
crossfilter: originalCrossfilter
|
|
} = defaultBigBang();
|
|
|
|
/* mock a selection */
|
|
const crossfilter = originalCrossfilter
|
|
.select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 })
|
|
.select(obsAnnoDimensionName("field3"), {
|
|
mode: "exact",
|
|
values: [false]
|
|
});
|
|
|
|
/* create the world from the selection */
|
|
const world = World.createWorldBySelection(
|
|
universe,
|
|
originalWorld,
|
|
crossfilter
|
|
);
|
|
expect(world).toBeDefined();
|
|
expect(world.nObs).toEqual(crossfilter.countSelected());
|
|
|
|
/*
|
|
calculate expected values and match against result
|
|
*/
|
|
|
|
/* matchFilter must match the dimension filters above */
|
|
const matchFilter = (df, row) => {
|
|
const field1 = df.at(row, "field1");
|
|
const field3 = df.at(row, "field3");
|
|
return field1 >= 0 && field1 < 5 && !field3;
|
|
};
|
|
const matchingIndices = _()
|
|
.range(universe.nObs)
|
|
.filter(idx => matchFilter(universe.obsAnnotations, idx))
|
|
.value();
|
|
|
|
expect(world).toMatchObject(
|
|
expect.objectContaining({
|
|
nObs: matchingIndices.length,
|
|
nVar: universe.nVar,
|
|
schema: universe.schema,
|
|
clipQuantiles: { min: 0, max: 1 },
|
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
|
varAnnotations: expect.any(Dataframe.Dataframe),
|
|
obsLayout: expect.any(Dataframe.Dataframe),
|
|
varData: expect.any(Dataframe.Dataframe),
|
|
unclipped: {
|
|
obsAnnotations: expect.any(Dataframe.Dataframe),
|
|
varData: expect.any(Dataframe.Dataframe)
|
|
}
|
|
})
|
|
);
|
|
|
|
expect(world.obsAnnotations.rowIndex.keys()).toEqual(
|
|
new Int32Array(matchingIndices)
|
|
);
|
|
expect(world.obsAnnotations.colIndex.keys()).toEqual(
|
|
universe.obsAnnotations.colIndex.keys()
|
|
);
|
|
expect(world.obsLayout.rowIndex.keys()).toEqual(
|
|
new Int32Array(matchingIndices)
|
|
);
|
|
expect(world.obsLayout.colIndex.keys()).toEqual(
|
|
world.schema.layout.obs[0].dims
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("createObsDimensionMap", () => {
|
|
test("when universe eq world", () => {
|
|
/*
|
|
check for:
|
|
- creates a dimension for all obsAnnotations, PLUS X/Y layout
|
|
- check that dimension typing is sane
|
|
*/
|
|
|
|
const { crossfilter } = defaultBigBang();
|
|
const annotationNames = _.map(
|
|
REST.schema.schema.annotations.obs.columns,
|
|
c => c.name
|
|
);
|
|
const obsIndexColName = REST.schema.schema.annotations.obs.index;
|
|
const schemaByObsName = _.keyBy(
|
|
REST.schema.schema.annotations.obs.columns,
|
|
"name"
|
|
);
|
|
expect(crossfilter).toBeDefined();
|
|
annotationNames.forEach(name => {
|
|
const dim = crossfilter.dimensions[obsAnnoDimensionName(name)];
|
|
if (name === obsIndexColName) {
|
|
expect(dim).toBeUndefined();
|
|
} else {
|
|
const { type } = schemaByObsName[name];
|
|
if (type === "string" || type === "boolean" || type === "categorical") {
|
|
expect(dim.dim).toBeInstanceOf(DimTypes.enum);
|
|
} else {
|
|
expect(dim.dim).toBeInstanceOf(DimTypes.scalar);
|
|
}
|
|
}
|
|
});
|
|
expect(
|
|
crossfilter.dimensions[layoutDimensionName("XY")].dim
|
|
).toBeInstanceOf(DimTypes.spatial);
|
|
});
|
|
});
|
|
|
|
describe("worldEqUniverse", () => {
|
|
const { universe, world } = defaultBigBang();
|
|
const result = World.worldEqUniverse(world, universe);
|
|
expect(result).toBe(true);
|
|
});
|