mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-23 04:08:11 +08:00
* refactor categorical controls state * lint * fix race condition in tests * fix typo * add missing update on subset * remove obsolete code * update jest and puppeteer major version; update all minors * update when label changes * remove lint from tests; increase timeouts in e2e tests * initial refactoring to new async annomatrix * refine error handling * fix bad merge * add continuous legend * lint * fix memoization in color table creators * partial implementation of user defined annotations * add new annotations action creator file * first pass at user annotations * additional user annotation bug fixes * user annotation auto-save * unit test cleanup * lint * refactor into multiple files * cleanup * add column GC * fix several bugs in user annotations * remove debug code * no anonymous functions * undo redo cleanup * file cleanup * scatterplot * performance * cleanup * remove old code * render in parallel with load * fix race condition * simply graph rendering * render throttle DRY * fix category label order * fix typo in e2e test setup * re-fix the e2e test setup * be more tolerant of races * anno matrix unit tests * temp disable reembedding * pilot port continuous histo to react-async * name change * lint * fix repaint bug * typo fix * update snap to match new ids * world/universe name cleanup * move annoMatrix to src dir * use private underscore naming convention * fix corner case in all selected * name cleanup * add layout control * init edge case * lint * port scatterplot * fix label indexing bug and improve tests * port category to react-async * fix user annotation labelling while subset * select all of prev layout on layout switch * fix race with crossfilter update * prettier lint * fix misleading comment * fix url composition in loader * first pass at crossfilter tests * lint * lint * fix typo * improved error handling for network errors * fix memoization bug * add memo * refactor for performnce * add missing single-value handling in select exact parser * small bugs discovered by tests * lint * additional crossfilter unit tests * remove extraneous comment * add support for automatic category determination * lint * fix render bug in category * take advantage of schema categories guarantee * lint * do not clear history when resetting * enhanced annomatrix gc * lint * finish renaming to follow conventions; fix clone race bug * lint * add priority based loading to improve initial data load UX * crossfilter cache perf * perf tuning * remove timers * documentation * PR review changes * PR review changes * more PR review edits * improve clarity of comment * more PR review fixes * port centroidLabels to use react-async * remove dead code * pr review updates * oops, remove logging
212 lines
5.8 KiB
JavaScript
212 lines
5.8 KiB
JavaScript
import { schema } from "./schema";
|
|
import { Dataframe, KeyIndex } from "../../../../src/util/dataframe";
|
|
import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix";
|
|
|
|
const indexedSchema = {
|
|
obsByName: Object.fromEntries(
|
|
schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? []
|
|
),
|
|
varByName: Object.fromEntries(
|
|
schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? []
|
|
),
|
|
embByName: Object.fromEntries(
|
|
schema.schema.layout.obs.map((v) => [v.name, v]) ?? []
|
|
),
|
|
};
|
|
|
|
function makeMockColumn(s, length) {
|
|
const { type } = s;
|
|
switch (type) {
|
|
case "int32":
|
|
return new Int32Array(length).fill(Math.floor(99 * Math.random()));
|
|
|
|
case "string":
|
|
return new Array(length).fill("test");
|
|
|
|
case "float32":
|
|
return new Float32Array(length).fill(99 * Math.random());
|
|
|
|
case "boolean":
|
|
return new Array(length).fill(false);
|
|
|
|
case "categorical":
|
|
return new Array(length).fill(s.categories[0]);
|
|
|
|
default:
|
|
throw new Error("unkonwn type");
|
|
}
|
|
}
|
|
|
|
function getEncodedDataframe(colNames, length, colSchemas) {
|
|
const colIndex = new KeyIndex(colNames);
|
|
const columns = colSchemas.map((s) => makeMockColumn(s, length));
|
|
const df = new Dataframe([length, colNames.length], columns, null, colIndex);
|
|
const body = encodeMatrixFBS(df);
|
|
return body;
|
|
}
|
|
|
|
export function dataframeResponse(colNames, columns) {
|
|
const colIndex = new KeyIndex(colNames);
|
|
const df = new Dataframe(
|
|
[columns[0].length, colNames.length],
|
|
columns,
|
|
null,
|
|
colIndex
|
|
);
|
|
const body = encodeMatrixFBS(df);
|
|
const headers = new Headers({
|
|
"Content-Type": "application/octet-stream",
|
|
});
|
|
return () => Promise.resolve({ body, init: { status: 200, headers } });
|
|
}
|
|
|
|
function annotationObsResponse(request) {
|
|
const url = new URL(request.url);
|
|
const params = Array.from(url.searchParams.entries());
|
|
const names = params
|
|
.filter(([k]) => k === "annotation-name")
|
|
.map(([, v]) => v);
|
|
if (!names.every((n) => indexedSchema.obsByName[n])) {
|
|
return Promise.reject(new Error("bad obs annotation name in URL"));
|
|
}
|
|
const colSchemas = names.map((n) => indexedSchema.obsByName[n]);
|
|
const body = getEncodedDataframe(
|
|
names,
|
|
schema.schema.dataframe.nObs,
|
|
colSchemas
|
|
);
|
|
|
|
const headers = new Headers({
|
|
"Content-Type": "application/octet-stream",
|
|
});
|
|
return Promise.resolve({
|
|
body,
|
|
init: { status: 200, headers },
|
|
});
|
|
}
|
|
|
|
function annotationVarResponse(request) {
|
|
const url = new URL(request.url);
|
|
const params = Array.from(url.searchParams.entries());
|
|
const names = params
|
|
.filter(([k]) => k === "annotation-name")
|
|
.map(([, v]) => v);
|
|
if (!names.every((n) => indexedSchema.varByName[n])) {
|
|
return Promise.reject(new Error("bad var annotation name in URL"));
|
|
}
|
|
const colSchemas = names.map((n) => indexedSchema.varByName[n]);
|
|
const body = getEncodedDataframe(
|
|
names,
|
|
schema.schema.dataframe.nVar,
|
|
colSchemas
|
|
);
|
|
|
|
const headers = new Headers({
|
|
"Content-Type": "application/octet-stream",
|
|
});
|
|
return Promise.resolve({
|
|
body,
|
|
init: { status: 200, headers },
|
|
});
|
|
}
|
|
|
|
function layoutObsResponse(request) {
|
|
const url = new URL(request.url);
|
|
const params = Array.from(url.searchParams.entries());
|
|
const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v);
|
|
if (!names.every((n) => indexedSchema.embByName[n])) {
|
|
return Promise.reject(new Error("bad layout name in URL"));
|
|
}
|
|
const dims = names.map((n) => indexedSchema.embByName[n].dims).flat();
|
|
const colSchemas = names
|
|
.map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]])
|
|
.flat();
|
|
const body = getEncodedDataframe(
|
|
dims,
|
|
schema.schema.dataframe.nObs,
|
|
colSchemas
|
|
);
|
|
|
|
const headers = new Headers({
|
|
"Content-Type": "application/octet-stream",
|
|
});
|
|
return Promise.resolve({
|
|
body,
|
|
init: { status: 200, headers },
|
|
});
|
|
}
|
|
|
|
function dataVarResponse(request) {
|
|
const url = new URL(request.url);
|
|
const params = Array.from(url.searchParams.entries());
|
|
|
|
const colNames = params.map((v) => `${v[0]}/${v[1]}`);
|
|
const colSchemas = colNames.map(() => schema.schema.dataframe);
|
|
const body = getEncodedDataframe(
|
|
colNames,
|
|
schema.schema.dataframe.nObs,
|
|
colSchemas
|
|
);
|
|
|
|
const headers = new Headers({
|
|
"Content-Type": "application/octet-stream",
|
|
});
|
|
return Promise.resolve({
|
|
body,
|
|
init: { status: 200, headers },
|
|
});
|
|
}
|
|
|
|
export function responder(request) {
|
|
const url = new URL(request.url);
|
|
const { pathname } = url;
|
|
if (pathname.endsWith("/annotations/obs")) {
|
|
return annotationObsResponse(request);
|
|
}
|
|
if (pathname.endsWith("/annotations/var")) {
|
|
return annotationVarResponse(request);
|
|
}
|
|
if (pathname.endsWith("/layout/obs")) {
|
|
return layoutObsResponse(request);
|
|
}
|
|
if (pathname.endsWith("/data/var")) {
|
|
return dataVarResponse(request);
|
|
}
|
|
return Promise.reject(new Error("bad URL"));
|
|
}
|
|
|
|
export function withExpected(expectedURL, expectedParams) {
|
|
/*
|
|
Do some additional error checking
|
|
*/
|
|
return (request) => {
|
|
// if URL is bogus, reject the promise
|
|
const url = new URL(request.url);
|
|
if (!url.pathname.endsWith(expectedURL)) {
|
|
return Promise.reject(new Error("Unexpected URL!"));
|
|
}
|
|
const params = Array.from(url.searchParams.entries()).sort(
|
|
(a, b) => a[0] < b[0]
|
|
);
|
|
expectedParams = expectedParams.slice().sort((a, b) => a[0] < b[0]);
|
|
|
|
if (
|
|
params.length !== expectedParams.length ||
|
|
!params.every(
|
|
(p, i) => p[0] === expectedParams[i][0] && p[1] === expectedParams[i][1]
|
|
)
|
|
) {
|
|
return Promise.reject(new Error("unexpected name requested in URL"));
|
|
}
|
|
|
|
return responder(request);
|
|
};
|
|
}
|
|
|
|
export function annotationsObs(names) {
|
|
return withExpected(
|
|
"/annotations/obs",
|
|
names.map((name) => ["annotation-name", name])
|
|
);
|
|
}
|