mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-23 00:38:12 +08:00
TS Revert (1) (#2402)
* revert all commits to before Typescript migration * update compat workflow to match latest deps (#2335) * update compat workflow to match latest deps * attempt to debug * attempt to debug * remove debugging code * typo * update deps to match desktop (#2340) * fix: don't run lint with `--fix` on push tests (#2273) * fix: don't run lint with `--fix` on push tests * npx Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com> * rename X_approx_distribution to X_approximate_distribution (#2337) * Correctly handle non-finite numbers in heuristic determination of X distribution (#2342) * handle non-finites explicitly * improve and test edge case handling for distribution estimation * revert debugging changes * code readability * clean up type inferencing (#2332) * unit tests for 64 bit conversion * clean up type handling * type inference tests * more type inference fixes * use schema to determine user intent for data typing * stop using deprecated API * fbs type encoding test * add missing test * add more tests * correctly infer X type for CXG adaptor * lint * fix typo * ts migration * cleanup from PR review * lint * PR review changes * remove unused packages from client (#2359) * remove unused packages from client * add missing peer dep * fix: disable FE auth testing on compatibility tests (#2377) * update: release process (#2277) Co-authored-by: maniarathi <mani.arathi@gmail.com> * fix: remove spaces in param setup (#2380) * delete deploy workflow (#2396) * undo reformatting which now does not pass lint * fix snapshots which changed due to npm dep changes * add missing quoting to snapshot * another snapshot typo fix * TS Revert (2) - replay PR #2347 and #2354 (#2403) * replay edits from PR 2347 * TS Revert (3) - replay edits in PR #2327 (#2404) * replay edits in PR 2327 * TS Revert (4) - replay PR #2355 (#2405) * replay edits in PR 2355 * add additional babel config * reformat with new prettier config Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com> Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
This commit is contained in:
co-authored by
maniarathi
Madison Dunitz
Severiano Badajoz
parent
295590a7c6
commit
eaae6df5e3
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
Action creators for user annotation
|
||||
*/
|
||||
import difference from "lodash.difference";
|
||||
import pako from "pako";
|
||||
import * as globals from "../globals";
|
||||
import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
|
||||
|
||||
const { isUserAnnotation } = AnnotationsHelpers;
|
||||
|
||||
export const annotationCreateCategoryAction =
|
||||
(newCategoryName, categoryToDuplicate) => async (dispatch, getState) => {
|
||||
/*
|
||||
Add a new user-created category to the obs annotations.
|
||||
|
||||
Arguments:
|
||||
newCategoryName - string name for the category.
|
||||
categoryToDuplicate - obs category to use for initial values, or null.
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
const { schema } = prevAnnoMatrix;
|
||||
|
||||
/* name must be a string, non-zero length */
|
||||
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
|
||||
throw new Error("user annotations require string name");
|
||||
|
||||
/* ensure the name isn't already in use! */
|
||||
if (schema.annotations.obsByName[newCategoryName])
|
||||
throw new Error("name collision on annotation category create");
|
||||
|
||||
let initialValue;
|
||||
let newSchema;
|
||||
let ctor;
|
||||
if (categoryToDuplicate) {
|
||||
/* if we are duplicating a category, retrieve it */
|
||||
const catDupSchema = schema.annotations.obsByName[categoryToDuplicate];
|
||||
const catDupType = catDupSchema?.type;
|
||||
if (catDupType !== "string" && catDupType !== "categorical")
|
||||
throw new Error(
|
||||
"categoryToDuplicate does not exist or has invalid type"
|
||||
);
|
||||
|
||||
const catToDupDf = await prevAnnoMatrix
|
||||
.base()
|
||||
.fetch("obs", categoryToDuplicate);
|
||||
const col = catToDupDf.col(categoryToDuplicate);
|
||||
initialValue = col.asArray();
|
||||
const { categories } = col.summarizeCategorical();
|
||||
// all user-created annotations must have the unassigned category
|
||||
if (!categories.includes(globals.unassignedCategoryLabel)) {
|
||||
categories.push(globals.unassignedCategoryLabel);
|
||||
}
|
||||
ctor = initialValue.constructor;
|
||||
newSchema = {
|
||||
...catDupSchema,
|
||||
name: newCategoryName,
|
||||
categories,
|
||||
writable: true,
|
||||
};
|
||||
} else {
|
||||
/* else assign to the standard default value */
|
||||
initialValue = globals.unassignedCategoryLabel;
|
||||
ctor = Array;
|
||||
newSchema = {
|
||||
name: newCategoryName,
|
||||
type: "categorical",
|
||||
categories: [globals.unassignedCategoryLabel],
|
||||
writable: true,
|
||||
};
|
||||
}
|
||||
|
||||
const obsCrossfilter = prevObsCrossfilter.addObsColumn(
|
||||
newSchema,
|
||||
ctor,
|
||||
initialValue
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: create category",
|
||||
data: newCategoryName,
|
||||
categoryToDuplicate,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationRenameCategoryAction =
|
||||
(oldCategoryName, newCategoryName) => (dispatch, getState) => {
|
||||
/*
|
||||
Rename a user-created annotation category
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, oldCategoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
/* name must be a string, non-zero length */
|
||||
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
|
||||
throw new Error("user annotations require string name");
|
||||
|
||||
if (oldCategoryName === newCategoryName) return;
|
||||
|
||||
const obsCrossfilter = prevObsCrossfilter.renameObsColumn(
|
||||
oldCategoryName,
|
||||
newCategoryName
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: category edited",
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
metadataField: oldCategoryName,
|
||||
newCategoryText: newCategoryName,
|
||||
data: newCategoryName,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationDeleteCategoryAction =
|
||||
(categoryName) => (dispatch, getState) => {
|
||||
/*
|
||||
Delete a user-created category
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
const obsCrossfilter = prevObsCrossfilter.dropObsColumn(categoryName);
|
||||
dispatch({
|
||||
type: "annotation: delete category",
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
metadataField: categoryName,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationCreateLabelInCategory =
|
||||
(categoryName, labelName, assignSelected) => async (dispatch, getState) => {
|
||||
/*
|
||||
Add a new label to a user-defined category. If assignSelected is true, assign
|
||||
the label to all currently selected cells.
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
let obsCrossfilter = prevObsCrossfilter.addObsAnnoCategory(
|
||||
categoryName,
|
||||
labelName
|
||||
);
|
||||
if (assignSelected) {
|
||||
obsCrossfilter = await obsCrossfilter.setObsColumnValues(
|
||||
categoryName,
|
||||
prevObsCrossfilter.allSelectedLabels(),
|
||||
labelName
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "annotation: add new label to category",
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
metadataField: categoryName,
|
||||
newLabelText: labelName,
|
||||
assignSelectedCells: assignSelected,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationDeleteLabelFromCategory =
|
||||
(categoryName, labelName) => async (dispatch, getState) => {
|
||||
/*
|
||||
delete a label from a user-defined category
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.removeObsAnnoCategory(
|
||||
categoryName,
|
||||
labelName,
|
||||
globals.unassignedCategoryLabel
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: delete label",
|
||||
metadataField: categoryName,
|
||||
label: labelName,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationRenameLabelInCategory =
|
||||
(categoryName, oldLabelName, newLabelName) => async (dispatch, getState) => {
|
||||
/*
|
||||
label name change
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
let obsCrossfilter = await prevObsCrossfilter.resetObsColumnValues(
|
||||
categoryName,
|
||||
oldLabelName,
|
||||
newLabelName
|
||||
);
|
||||
obsCrossfilter = await obsCrossfilter.removeObsAnnoCategory(
|
||||
categoryName,
|
||||
oldLabelName,
|
||||
globals.unassignedCategoryLabel
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: label edited",
|
||||
editedLabel: newLabelName,
|
||||
metadataField: categoryName,
|
||||
label: oldLabelName,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationLabelCurrentSelection =
|
||||
(categoryName, labelName) => async (dispatch, getState) => {
|
||||
/*
|
||||
set the label on all currently selected
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.setObsColumnValues(
|
||||
categoryName,
|
||||
prevObsCrossfilter.allSelectedLabels(),
|
||||
labelName
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: label current cell selection",
|
||||
metadataField: categoryName,
|
||||
label: labelName,
|
||||
obsCrossfilter,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
});
|
||||
};
|
||||
|
||||
function writableAnnotations(annoMatrix) {
|
||||
return annoMatrix.schema.annotations.obs.columns
|
||||
.filter((s) => s.writable)
|
||||
.map((s) => s.name);
|
||||
}
|
||||
|
||||
export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => {
|
||||
/*
|
||||
Return true if there are LIKELY user-defined annotation modifications between the two
|
||||
annoMatrices. Technically not an action creator, but intimately intertwined
|
||||
with the save process.
|
||||
|
||||
Two conditions will trigger a need to save:
|
||||
* the collection of user-defined columns have changed
|
||||
* the contents of the user-defined columns have change
|
||||
*/
|
||||
|
||||
annoMatrix = annoMatrix.base();
|
||||
|
||||
// if the annoMatrix hasn't changed, we are guaranteed no changes to the matrix schema or contents.
|
||||
if (annoMatrix === lastSavedAnnoMatrix) return false;
|
||||
|
||||
// if the schema has changed, we need to save
|
||||
const currentWritable = writableAnnotations(annoMatrix);
|
||||
if (difference(currentWritable, writableAnnotations(lastSavedAnnoMatrix))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// no schema changes; check for change in contents
|
||||
return currentWritable.some(
|
||||
(col) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
|
||||
);
|
||||
};
|
||||
|
||||
export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
|
||||
/*
|
||||
Save the user-created obs annotations IF any have changed.
|
||||
*/
|
||||
const state = getState();
|
||||
const { annotations, autosave } = state;
|
||||
const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations;
|
||||
const { lastSavedAnnoMatrix, saveInProgress } = autosave;
|
||||
|
||||
const annoMatrix = state.annoMatrix.base();
|
||||
|
||||
if (saveInProgress || annoMatrix === lastSavedAnnoMatrix) return;
|
||||
if (!needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix)) {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save complete",
|
||||
lastSavedAnnoMatrix: annoMatrix,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Else, we really do need to save
|
||||
*/
|
||||
|
||||
dispatch({
|
||||
type: "writable obs annotations - save started",
|
||||
});
|
||||
|
||||
const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix));
|
||||
const matrix = MatrixFBS.encodeMatrixFBS(df);
|
||||
const compressedMatrix = pako.deflate(matrix);
|
||||
try {
|
||||
const queryString =
|
||||
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
||||
? `?annotation-collection-name=${encodeURIComponent(
|
||||
dataCollectionName
|
||||
)}`
|
||||
: "";
|
||||
const res = await fetch(
|
||||
`${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: compressedMatrix,
|
||||
headers: new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
}),
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
if (res.ok) {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save complete",
|
||||
lastSavedAnnoMatrix: annoMatrix,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save error",
|
||||
message: `HTTP error ${res.status} - ${res.statusText}`,
|
||||
res,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save error",
|
||||
message: error.toString(),
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const saveGenesetsAction = () => async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
// bail if gene sets not available, or in readonly mode.
|
||||
const { config } = state;
|
||||
const { lastTid, genesets } = state.genesets;
|
||||
|
||||
const genesetsAreAvailable =
|
||||
config?.parameters?.annotations_genesets ?? false;
|
||||
const genesetsReadonly =
|
||||
config?.parameters?.annotations_genesets_readonly ?? true;
|
||||
if (!genesetsAreAvailable || genesetsReadonly) {
|
||||
// our non-save was completed!
|
||||
return dispatch({
|
||||
type: "autosave: genesets complete",
|
||||
lastSavedGenesets: genesets,
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "autosave: genesets started",
|
||||
});
|
||||
|
||||
/* Create the JSON OTA data structure */
|
||||
const tid = (lastTid ?? 0) + 1;
|
||||
const genesetsOTA = [];
|
||||
for (const [name, gs] of genesets) {
|
||||
const genes = [];
|
||||
for (const g of gs.genes.values()) {
|
||||
genes.push({
|
||||
gene_symbol: g.geneSymbol,
|
||||
gene_description: g.geneDescription,
|
||||
});
|
||||
}
|
||||
genesetsOTA.push({
|
||||
geneset_name: name,
|
||||
geneset_description: gs.genesetDescription,
|
||||
genes,
|
||||
});
|
||||
}
|
||||
const ota = {
|
||||
tid,
|
||||
genesets: genesetsOTA,
|
||||
};
|
||||
|
||||
/* Save to server */
|
||||
try {
|
||||
const { dataCollectionNameIsReadOnly, dataCollectionName } =
|
||||
state.annotations;
|
||||
const queryString =
|
||||
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
||||
? `?annotation-collection-name=${encodeURIComponent(
|
||||
dataCollectionName
|
||||
)}`
|
||||
: "";
|
||||
|
||||
const res = await fetch(
|
||||
`${globals.API.prefix}${globals.API.version}genesets${queryString}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: new Headers({
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify(ota),
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
return dispatch({
|
||||
type: "autosave: genesets error",
|
||||
message: `HTTP error ${res.status} - ${res.statusText}`,
|
||||
res,
|
||||
});
|
||||
}
|
||||
return Promise.all([
|
||||
dispatch({
|
||||
type: "autosave: genesets complete",
|
||||
lastSavedGenesets: genesets,
|
||||
}),
|
||||
dispatch({
|
||||
type: "geneset: set tid",
|
||||
tid,
|
||||
}),
|
||||
]);
|
||||
} catch (error) {
|
||||
return dispatch({
|
||||
type: "autosave: genesets error",
|
||||
message: error.toString(),
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,530 +0,0 @@
|
||||
/*
|
||||
Action creators for user annotation
|
||||
*/
|
||||
import difference from "lodash.difference";
|
||||
import pako from "pako";
|
||||
import * as globals from "../globals";
|
||||
import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
|
||||
|
||||
const { isUserAnnotation } = AnnotationsHelpers;
|
||||
|
||||
export const annotationCreateCategoryAction = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
newCategoryName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryToDuplicate: any
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
/*
|
||||
Add a new user-created category to the obs annotations.
|
||||
|
||||
Arguments:
|
||||
newCategoryName - string name for the category.
|
||||
categoryToDuplicate - obs category to use for initial values, or null.
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
const { schema } = prevAnnoMatrix;
|
||||
|
||||
/* name must be a string, non-zero length */
|
||||
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
|
||||
throw new Error("user annotations require string name");
|
||||
|
||||
/* ensure the name isn't already in use! */
|
||||
if (schema.annotations.obsByName[newCategoryName])
|
||||
throw new Error("name collision on annotation category create");
|
||||
|
||||
let initialValue;
|
||||
let newSchema;
|
||||
let ctor;
|
||||
if (categoryToDuplicate) {
|
||||
/* if we are duplicating a category, retrieve it */
|
||||
const catDupSchema = schema.annotations.obsByName[categoryToDuplicate];
|
||||
const catDupType = catDupSchema?.type;
|
||||
if (catDupType !== "string" && catDupType !== "categorical")
|
||||
throw new Error("categoryToDuplicate does not exist or has invalid type");
|
||||
|
||||
const catToDupDf = await prevAnnoMatrix
|
||||
.base()
|
||||
.fetch("obs", categoryToDuplicate);
|
||||
const col = catToDupDf.col(categoryToDuplicate);
|
||||
initialValue = col.asArray();
|
||||
const { categories } = col.summarizeCategorical();
|
||||
// all user-created annotations must have the unassigned category
|
||||
if (!categories.includes(globals.unassignedCategoryLabel)) {
|
||||
categories.push(globals.unassignedCategoryLabel);
|
||||
}
|
||||
ctor = initialValue.constructor;
|
||||
newSchema = {
|
||||
...catDupSchema,
|
||||
name: newCategoryName,
|
||||
categories,
|
||||
writable: true,
|
||||
};
|
||||
} else {
|
||||
/* else assign to the standard default value */
|
||||
initialValue = globals.unassignedCategoryLabel;
|
||||
ctor = Array;
|
||||
newSchema = {
|
||||
name: newCategoryName,
|
||||
type: "categorical",
|
||||
categories: [globals.unassignedCategoryLabel],
|
||||
writable: true,
|
||||
};
|
||||
}
|
||||
|
||||
const obsCrossfilter = prevObsCrossfilter.addObsColumn(
|
||||
newSchema,
|
||||
ctor,
|
||||
initialValue
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: create category",
|
||||
data: newCategoryName,
|
||||
categoryToDuplicate,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationRenameCategoryAction = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
oldCategoryName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
newCategoryName: any
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => (dispatch: any, getState: any) => {
|
||||
/*
|
||||
Rename a user-created annotation category
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, oldCategoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
/* name must be a string, non-zero length */
|
||||
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
|
||||
throw new Error("user annotations require string name");
|
||||
|
||||
if (oldCategoryName === newCategoryName) return;
|
||||
|
||||
const obsCrossfilter = prevObsCrossfilter.renameObsColumn(
|
||||
oldCategoryName,
|
||||
newCategoryName
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: category edited",
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
metadataField: oldCategoryName,
|
||||
newCategoryText: newCategoryName,
|
||||
data: newCategoryName,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const annotationDeleteCategoryAction = (categoryName: any) => (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
/*
|
||||
Delete a user-created category
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
const obsCrossfilter = prevObsCrossfilter.dropObsColumn(categoryName);
|
||||
dispatch({
|
||||
type: "annotation: delete category",
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
metadataField: categoryName,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationCreateLabelInCategory = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labelName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
assignSelected: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
/*
|
||||
Add a new label to a user-defined category. If assignSelected is true, assign
|
||||
the label to all currently selected cells.
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
let obsCrossfilter = prevObsCrossfilter.addObsAnnoCategory(
|
||||
categoryName,
|
||||
labelName
|
||||
);
|
||||
if (assignSelected) {
|
||||
obsCrossfilter = await obsCrossfilter.setObsColumnValues(
|
||||
categoryName,
|
||||
prevObsCrossfilter.allSelectedLabels(),
|
||||
labelName
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "annotation: add new label to category",
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
metadataField: categoryName,
|
||||
newLabelText: labelName,
|
||||
assignSelectedCells: assignSelected,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationDeleteLabelFromCategory = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labelName: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
/*
|
||||
delete a label from a user-defined category
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.removeObsAnnoCategory(
|
||||
categoryName,
|
||||
labelName,
|
||||
globals.unassignedCategoryLabel
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: delete label",
|
||||
metadataField: categoryName,
|
||||
label: labelName,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationRenameLabelInCategory = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
oldLabelName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
newLabelName: any
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
/*
|
||||
label name change
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
let obsCrossfilter = await prevObsCrossfilter.resetObsColumnValues(
|
||||
categoryName,
|
||||
oldLabelName,
|
||||
newLabelName
|
||||
);
|
||||
obsCrossfilter = await obsCrossfilter.removeObsAnnoCategory(
|
||||
categoryName,
|
||||
oldLabelName,
|
||||
globals.unassignedCategoryLabel
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: label edited",
|
||||
editedLabel: newLabelName,
|
||||
metadataField: categoryName,
|
||||
label: oldLabelName,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const annotationLabelCurrentSelection = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labelName: any
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
/*
|
||||
set the label on all currently selected
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
|
||||
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
|
||||
throw new Error("not a user annotation");
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.setObsColumnValues(
|
||||
categoryName,
|
||||
prevObsCrossfilter.allSelectedLabels(),
|
||||
labelName
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "annotation: label current cell selection",
|
||||
metadataField: categoryName,
|
||||
label: labelName,
|
||||
obsCrossfilter,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function writableAnnotations(annoMatrix: any) {
|
||||
return (
|
||||
annoMatrix.schema.annotations.obs.columns
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.filter((s: any) => s.writable)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.map((s: any) => s.name)
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export const needToSaveObsAnnotations = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
lastSavedAnnoMatrix: any
|
||||
) => {
|
||||
/*
|
||||
Return true if there are LIKELY user-defined annotation modifications between the two
|
||||
annoMatrices. Technically not an action creator, but intimately intertwined
|
||||
with the save process.
|
||||
|
||||
Two conditions will trigger a need to save:
|
||||
* the collection of user-defined columns have changed
|
||||
* the contents of the user-defined columns have change
|
||||
*/
|
||||
|
||||
annoMatrix = annoMatrix.base();
|
||||
|
||||
// if the annoMatrix hasn't changed, we are guaranteed no changes to the matrix schema or contents.
|
||||
if (annoMatrix === lastSavedAnnoMatrix) return false;
|
||||
|
||||
// if the schema has changed, we need to save
|
||||
const currentWritable = writableAnnotations(annoMatrix);
|
||||
if (difference(currentWritable, writableAnnotations(lastSavedAnnoMatrix))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// no schema changes; check for change in contents
|
||||
return currentWritable.some(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(col: any) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export const saveObsAnnotationsAction = () => async (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
/*
|
||||
Save the user-created obs annotations IF any have changed.
|
||||
*/
|
||||
const state = getState();
|
||||
const { annotations, autosave } = state;
|
||||
const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations;
|
||||
const { lastSavedAnnoMatrix, saveInProgress } = autosave;
|
||||
|
||||
const annoMatrix = state.annoMatrix.base();
|
||||
|
||||
if (saveInProgress || annoMatrix === lastSavedAnnoMatrix) return;
|
||||
if (!needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix)) {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save complete",
|
||||
lastSavedAnnoMatrix: annoMatrix,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Else, we really do need to save
|
||||
*/
|
||||
|
||||
dispatch({
|
||||
type: "writable obs annotations - save started",
|
||||
});
|
||||
|
||||
const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix));
|
||||
const matrix = MatrixFBS.encodeMatrixFBS(df);
|
||||
const compressedMatrix = pako.deflate(matrix);
|
||||
try {
|
||||
const queryString =
|
||||
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
||||
? `?annotation-collection-name=${encodeURIComponent(
|
||||
dataCollectionName
|
||||
)}`
|
||||
: "";
|
||||
const res = await fetch(
|
||||
`${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: compressedMatrix,
|
||||
headers: new Headers({
|
||||
"Content-Type": "application/octet-stream",
|
||||
}),
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
if (res.ok) {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save complete",
|
||||
lastSavedAnnoMatrix: annoMatrix,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save error",
|
||||
message: `HTTP error ${res.status} - ${res.statusText}`,
|
||||
res,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: "writable obs annotations - save error",
|
||||
message: error.toString(),
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export const saveGenesetsAction = () => async (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
const state = getState();
|
||||
|
||||
// bail if gene sets not available, or in readonly mode.
|
||||
const { config } = state;
|
||||
const { lastTid, genesets } = state.genesets;
|
||||
|
||||
const genesetsAreAvailable =
|
||||
config?.parameters?.annotations_genesets ?? false;
|
||||
const genesetsReadonly =
|
||||
config?.parameters?.annotations_genesets_readonly ?? true;
|
||||
if (!genesetsAreAvailable || genesetsReadonly) {
|
||||
// our non-save was completed!
|
||||
return dispatch({
|
||||
type: "autosave: genesets complete",
|
||||
lastSavedGenesets: genesets,
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "autosave: genesets started",
|
||||
});
|
||||
|
||||
/* Create the JSON OTA data structure */
|
||||
const tid = (lastTid ?? 0) + 1;
|
||||
const genesetsOTA = [];
|
||||
for (const [name, gs] of genesets) {
|
||||
const genes = [];
|
||||
for (const g of gs.genes.values()) {
|
||||
genes.push({
|
||||
gene_symbol: g.geneSymbol,
|
||||
gene_description: g.geneDescription,
|
||||
});
|
||||
}
|
||||
genesetsOTA.push({
|
||||
geneset_name: name,
|
||||
geneset_description: gs.genesetDescription,
|
||||
genes,
|
||||
});
|
||||
}
|
||||
const ota = {
|
||||
tid,
|
||||
genesets: genesetsOTA,
|
||||
};
|
||||
|
||||
/* Save to server */
|
||||
try {
|
||||
const {
|
||||
dataCollectionNameIsReadOnly,
|
||||
dataCollectionName,
|
||||
} = state.annotations;
|
||||
const queryString =
|
||||
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
||||
? `?annotation-collection-name=${encodeURIComponent(
|
||||
dataCollectionName
|
||||
)}`
|
||||
: "";
|
||||
|
||||
const res = await fetch(
|
||||
`${globals.API.prefix}${globals.API.version}genesets${queryString}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: new Headers({
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify(ota),
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
return dispatch({
|
||||
type: "autosave: genesets error",
|
||||
message: `HTTP error ${res.status} - ${res.statusText}`,
|
||||
res,
|
||||
});
|
||||
}
|
||||
return await Promise.all([
|
||||
dispatch({
|
||||
type: "autosave: genesets complete",
|
||||
lastSavedGenesets: genesets,
|
||||
}),
|
||||
dispatch({
|
||||
type: "geneset: set tid",
|
||||
tid,
|
||||
}),
|
||||
]);
|
||||
} catch (error) {
|
||||
return dispatch({
|
||||
type: "autosave: genesets error",
|
||||
message: error.toString(),
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
action creators related to embeddings choice
|
||||
*/
|
||||
|
||||
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
|
||||
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
|
||||
|
||||
export async function _switchEmbedding(
|
||||
prevAnnoMatrix,
|
||||
prevCrossfilter,
|
||||
newEmbeddingName
|
||||
) {
|
||||
/*
|
||||
DRY helper used by embedding action creators
|
||||
*/
|
||||
const base = prevAnnoMatrix.base();
|
||||
const embeddingDf = await base.fetch("emb", newEmbeddingName);
|
||||
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
|
||||
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(
|
||||
annoMatrix,
|
||||
prevCrossfilter.obsCrossfilter
|
||||
).select("emb", newEmbeddingName, {
|
||||
mode: "all",
|
||||
});
|
||||
return [annoMatrix, obsCrossfilter];
|
||||
}
|
||||
|
||||
export const layoutChoiceAction =
|
||||
(newLayoutChoice) => async (dispatch, getState) => {
|
||||
/*
|
||||
On layout choice, make sure we have selected all on the previous layout, AND the new
|
||||
layout.
|
||||
*/
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevCrossfilter } =
|
||||
getState();
|
||||
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
||||
prevAnnoMatrix,
|
||||
prevCrossfilter,
|
||||
newLayoutChoice
|
||||
);
|
||||
dispatch({
|
||||
type: "set layout choice",
|
||||
layoutChoice: newLayoutChoice,
|
||||
obsCrossfilter,
|
||||
annoMatrix,
|
||||
});
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
action creators related to embeddings choice
|
||||
*/
|
||||
|
||||
import { Action, ActionCreator } from "redux";
|
||||
import { ThunkAction } from "redux-thunk";
|
||||
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
|
||||
import type { AppDispatch, RootState } from "../reducers";
|
||||
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
|
||||
import { Field } from "../common/types/schema";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export async function _switchEmbedding(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
prevAnnoMatrix: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
prevCrossfilter: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
newEmbeddingName: any
|
||||
) {
|
||||
/*
|
||||
DRY helper used by embedding action creators
|
||||
*/
|
||||
const base = prevAnnoMatrix.base();
|
||||
const embeddingDf = await base.fetch("emb", newEmbeddingName);
|
||||
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
|
||||
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(
|
||||
annoMatrix,
|
||||
prevCrossfilter.obsCrossfilter
|
||||
).select(Field.emb, newEmbeddingName, {
|
||||
mode: "all",
|
||||
});
|
||||
return [annoMatrix, obsCrossfilter];
|
||||
}
|
||||
|
||||
export const layoutChoiceAction: ActionCreator<
|
||||
ThunkAction<Promise<void>, RootState, never, Action<"set layout choice">>
|
||||
> =
|
||||
(newLayoutChoice: string) =>
|
||||
async (dispatch: AppDispatch, getState: () => RootState): Promise<void> => {
|
||||
/*
|
||||
On layout choice, make sure we have selected all on the previous layout, AND the new
|
||||
layout.
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevCrossfilter,
|
||||
} = getState();
|
||||
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
||||
prevAnnoMatrix,
|
||||
prevCrossfilter,
|
||||
newLayoutChoice
|
||||
);
|
||||
dispatch({
|
||||
type: "set layout choice",
|
||||
layoutChoice: newLayoutChoice,
|
||||
obsCrossfilter,
|
||||
annoMatrix,
|
||||
});
|
||||
};
|
||||
@@ -21,45 +21,34 @@ The behavior manifest in these action creators:
|
||||
Note that crossfilter indices are lazy created, as needed.
|
||||
*/
|
||||
|
||||
import { Dataframe } from "../util/dataframe";
|
||||
|
||||
export const genesetDelete =
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
(genesetName: any) => (dispatch: any, getState: any) => {
|
||||
const state = getState();
|
||||
const { genesets } = state;
|
||||
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
||||
const geneSymbols = Array.from(gs.genes.keys());
|
||||
const obsCrossfilter = dropGeneset(
|
||||
dispatch,
|
||||
state,
|
||||
genesetName,
|
||||
geneSymbols
|
||||
);
|
||||
if (genesetName === state.colors.colorAccessor) {
|
||||
dispatch({
|
||||
type: "reset colorscale",
|
||||
});
|
||||
}
|
||||
export const genesetDelete = (genesetName) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const { genesets } = state;
|
||||
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
||||
const geneSymbols = Array.from(gs.genes.keys());
|
||||
const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
|
||||
if (genesetName === state.colors.colorAccessor) {
|
||||
dispatch({
|
||||
type: "geneset: delete",
|
||||
genesetName,
|
||||
obsCrossfilter,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
type: "reset colorscale",
|
||||
});
|
||||
};
|
||||
}
|
||||
dispatch({
|
||||
type: "geneset: delete",
|
||||
genesetName,
|
||||
obsCrossfilter,
|
||||
annoMatrix: obsCrossfilter.annoMatrix,
|
||||
});
|
||||
};
|
||||
|
||||
export const genesetAddGenes =
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
(genesetName: any, genes: any) => async (dispatch: any, getState: any) => {
|
||||
(genesetName, genes) => async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
|
||||
const { schema } = annoMatrix;
|
||||
const varIndex = schema.annotations.var.index;
|
||||
const df: Dataframe = await annoMatrix.fetch("var", varIndex);
|
||||
const df = await annoMatrix.fetch("var", varIndex);
|
||||
const geneNames = df.col(varIndex).asArray();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genes = genes.reduce((acc: any, gene: any) => {
|
||||
genes = genes.reduce((acc, gene) => {
|
||||
if (geneNames.indexOf(gene.geneSymbol) === -1) {
|
||||
postUserErrorToast(
|
||||
`${gene.geneSymbol} doesn't appear to be a valid gene name.`
|
||||
@@ -88,8 +77,7 @@ export const genesetAddGenes =
|
||||
};
|
||||
|
||||
export const genesetDeleteGenes =
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
(genesetName: any, geneSymbols: any) => (dispatch: any, getState: any) => {
|
||||
(genesetName, geneSymbols) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const obsCrossfilter = dropGeneset(
|
||||
dispatch,
|
||||
@@ -110,14 +98,7 @@ export const genesetDeleteGenes =
|
||||
Private
|
||||
*/
|
||||
|
||||
function dropGenesetSummaryDimension(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
obsCrossfilter: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
state: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetName: any
|
||||
) {
|
||||
function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
|
||||
const { annoMatrix, genesets } = state;
|
||||
const varIndex = annoMatrix.schema.annotations?.var?.index;
|
||||
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
||||
@@ -133,8 +114,7 @@ function dropGenesetSummaryDimension(
|
||||
return obsCrossfilter.dropDimension("X", query);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
|
||||
function dropGeneDimension(obsCrossfilter, state, gene) {
|
||||
const { annoMatrix } = state;
|
||||
const varIndex = annoMatrix.schema.annotations?.var?.index;
|
||||
const query = {
|
||||
@@ -147,21 +127,10 @@ function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
|
||||
return obsCrossfilter.dropDimension("X", query);
|
||||
}
|
||||
|
||||
function dropGeneset(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
state: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
geneSymbols: any
|
||||
) {
|
||||
function dropGeneset(dispatch, state, genesetName, geneSymbols) {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = state;
|
||||
const obsCrossfilter = geneSymbols.reduce(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(crossfilter: any, gene: any) =>
|
||||
dropGeneDimension(crossfilter, state, gene),
|
||||
(crossfilter, gene) => dropGeneDimension(crossfilter, state, gene),
|
||||
dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName)
|
||||
);
|
||||
dispatch({
|
||||
@@ -169,8 +138,7 @@ function dropGeneset(
|
||||
continuousNamespace: { isGeneSetSummary: true },
|
||||
selection: genesetName,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
geneSymbols.forEach((g: any) =>
|
||||
geneSymbols.forEach((g) =>
|
||||
dispatch({
|
||||
type: "continuous metadata histogram cancel",
|
||||
continuousNamespace: { isUserDefined: true },
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Config } from "../globals";
|
||||
import * as globals from "../globals";
|
||||
import { AnnoMatrixLoader, AnnoMatrixObsCrossfilter } from "../annoMatrix";
|
||||
import {
|
||||
@@ -12,12 +11,8 @@ import * as annoActions from "./annotation";
|
||||
import * as viewActions from "./viewStack";
|
||||
import * as embActions from "./embedding";
|
||||
import * as genesetActions from "./geneset";
|
||||
import { AppDispatch, RootState } from "../reducers";
|
||||
import { EmbeddingSchema, Schema } from "../common/types/schema";
|
||||
import { UserInfoPayload } from "../reducers/userInfo";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function setGlobalConfig(config: any) {
|
||||
function setGlobalConfig(config) {
|
||||
/**
|
||||
* Set any global run-time config not _exclusively_ managed by the config reducer.
|
||||
* This should only set fields defined in globals.globalConfig.
|
||||
@@ -30,8 +25,7 @@ function setGlobalConfig(config: any) {
|
||||
/*
|
||||
return promise fetching user-configured colors
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
async function userColorsFetchAndLoad(dispatch: any) {
|
||||
async function userColorsFetchAndLoad(dispatch) {
|
||||
return fetchJson("colors").then((response) =>
|
||||
dispatch({
|
||||
type: "universe: user color load success",
|
||||
@@ -40,38 +34,36 @@ async function userColorsFetchAndLoad(dispatch: any) {
|
||||
);
|
||||
}
|
||||
|
||||
async function schemaFetch(): Promise<{ schema: Schema }> {
|
||||
return fetchJson<{ schema: Schema }>("schema");
|
||||
async function schemaFetch() {
|
||||
return fetchJson("schema");
|
||||
}
|
||||
|
||||
async function configFetch(dispatch: AppDispatch): Promise<Config> {
|
||||
const response = await fetchJson<{ config: globals.Config }>("config");
|
||||
const config = { ...globals.configDefaults, ...response.config };
|
||||
async function configFetch(dispatch) {
|
||||
return fetchJson("config").then((response) => {
|
||||
const config = { ...globals.configDefaults, ...response.config };
|
||||
|
||||
setGlobalConfig(config);
|
||||
setGlobalConfig(config);
|
||||
|
||||
dispatch({
|
||||
type: "configuration load complete",
|
||||
config,
|
||||
dispatch({
|
||||
type: "configuration load complete",
|
||||
config,
|
||||
});
|
||||
return config;
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
async function userInfoFetch(dispatch: AppDispatch): Promise<UserInfoPayload> {
|
||||
return fetchJson<{ userinfo: UserInfoPayload }>("userinfo").then(
|
||||
(response) => {
|
||||
const { userinfo: userInfo } = response || {};
|
||||
dispatch({
|
||||
type: "userInfo load complete",
|
||||
userInfo,
|
||||
});
|
||||
return userInfo;
|
||||
}
|
||||
);
|
||||
async function userInfoFetch(dispatch) {
|
||||
return fetchJson("userinfo").then((response) => {
|
||||
const { userinfo: userInfo } = response || {};
|
||||
dispatch({
|
||||
type: "userInfo load complete",
|
||||
userInfo,
|
||||
});
|
||||
return userInfo;
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
async function genesetsFetch(dispatch: any, config: any) {
|
||||
async function genesetsFetch(dispatch, config) {
|
||||
/* request genesets ONLY if the backend supports the feature */
|
||||
const defaultResponse = {
|
||||
genesets: [],
|
||||
@@ -92,32 +84,26 @@ async function genesetsFetch(dispatch: any, config: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function prefetchEmbeddings(annoMatrix: any) {
|
||||
function prefetchEmbeddings(annoMatrix) {
|
||||
/*
|
||||
prefetch requests for all embeddings
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const available = schema.layout.obs.map((v: any) => v.name);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
available.forEach((embName: any) => annoMatrix.prefetch("emb", embName));
|
||||
const available = schema.layout.obs.map((v) => v.name);
|
||||
available.forEach((embName) => annoMatrix.prefetch("emb", embName));
|
||||
}
|
||||
|
||||
/*
|
||||
Application bootstrap
|
||||
*/
|
||||
const doInitialDataLoad = (): ((
|
||||
dispatch: AppDispatch,
|
||||
getState: () => RootState
|
||||
) => void) =>
|
||||
catchErrorsWrap(async (dispatch: AppDispatch) => {
|
||||
const doInitialDataLoad = () =>
|
||||
catchErrorsWrap(async (dispatch) => {
|
||||
dispatch({ type: "initial data load start" });
|
||||
|
||||
try {
|
||||
const [config, schema] = await Promise.all([
|
||||
configFetch(dispatch),
|
||||
schemaFetch(),
|
||||
schemaFetch(dispatch),
|
||||
userColorsFetchAndLoad(dispatch),
|
||||
userInfoFetch(dispatch),
|
||||
]);
|
||||
@@ -140,7 +126,7 @@ const doInitialDataLoad = (): ((
|
||||
const layoutSchema = schema?.schema?.layout?.obs ?? [];
|
||||
if (
|
||||
defaultEmbedding &&
|
||||
layoutSchema.some((s: EmbeddingSchema) => s.name === defaultEmbedding)
|
||||
layoutSchema.some((s) => s.name === defaultEmbedding)
|
||||
) {
|
||||
dispatch(embActions.layoutChoiceAction(defaultEmbedding));
|
||||
}
|
||||
@@ -149,25 +135,21 @@ const doInitialDataLoad = (): ((
|
||||
}
|
||||
}, true);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
function requestSingleGeneExpressionCountsForColoringPOST(gene: any) {
|
||||
function requestSingleGeneExpressionCountsForColoringPOST(gene) {
|
||||
return {
|
||||
type: "color by expression",
|
||||
gene,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const requestUserDefinedGene = (gene: any) => ({
|
||||
const requestUserDefinedGene = (gene) => ({
|
||||
type: "request user defined gene success",
|
||||
|
||||
data: {
|
||||
genes: [gene],
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const dispatchDiffExpErrors = (dispatch: any, response: any) => {
|
||||
const dispatchDiffExpErrors = (dispatch, response) => {
|
||||
switch (response.status) {
|
||||
case 403:
|
||||
dispatchNetworkErrorMessageToUser(
|
||||
@@ -191,16 +173,8 @@ const dispatchDiffExpErrors = (dispatch: any, response: any) => {
|
||||
};
|
||||
|
||||
const requestDifferentialExpression =
|
||||
(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
set1: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
set2: any,
|
||||
num_genes = 50
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) =>
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
async (dispatch: any, getState: any) => {
|
||||
(set1, set2, num_genes = 50) =>
|
||||
async (dispatch, getState) => {
|
||||
dispatch({ type: "request differential expression started" });
|
||||
try {
|
||||
/*
|
||||
@@ -248,9 +222,7 @@ const requestDifferentialExpression =
|
||||
const varIndex = await annoMatrix.fetch("var", varIndexName);
|
||||
const diffexpLists = { negative: [], positive: [] };
|
||||
for (const polarity of Object.keys(diffexpLists)) {
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
diffexpLists[polarity] = response[polarity].map((v: any) => [
|
||||
diffexpLists[polarity] = response[polarity].map((v) => [
|
||||
varIndex.at(v[0], varIndexName),
|
||||
...v.slice(1),
|
||||
]);
|
||||
@@ -269,10 +241,10 @@ const requestDifferentialExpression =
|
||||
}
|
||||
};
|
||||
|
||||
function fetchJson<T>(pathAndQuery: string): Promise<T> {
|
||||
return doJsonRequest<T>(
|
||||
function fetchJson(pathAndQuery) {
|
||||
return doJsonRequest(
|
||||
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
|
||||
) as Promise<T>;
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
Action creators for selection
|
||||
*/
|
||||
export const selectContinuousMetadataAction =
|
||||
(type, query, range, oldProps = {}) =>
|
||||
async (dispatch, getState) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const selection = range
|
||||
? {
|
||||
mode: "range",
|
||||
lo: range[0],
|
||||
hi: range[1],
|
||||
inclusive: true, // [lo, hi] incluisve selection
|
||||
}
|
||||
: { mode: "all" };
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(...query, selection);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
range,
|
||||
...oldProps,
|
||||
});
|
||||
};
|
||||
|
||||
export const selectCategoricalMetadataAction =
|
||||
(
|
||||
type, // action type
|
||||
metadataField, // annotation category name
|
||||
labels,
|
||||
label, // the label being selected/deselected
|
||||
isSelected, // bool
|
||||
oldProps = {}
|
||||
) =>
|
||||
async (dispatch, getState) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter, categoricalSelection } =
|
||||
getState();
|
||||
|
||||
const labelSelectionState = new Map(categoricalSelection[metadataField]);
|
||||
labels.forEach(
|
||||
(l) => labelSelectionState.has(l) || labelSelectionState.set(l, true)
|
||||
);
|
||||
labelSelectionState.set(label, isSelected);
|
||||
|
||||
const values = Array.from(labelSelectionState.keys()).filter((k) =>
|
||||
labelSelectionState.get(k)
|
||||
);
|
||||
const selection = {
|
||||
mode: "exact",
|
||||
values,
|
||||
};
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"obs",
|
||||
metadataField,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
metadataField,
|
||||
labelSelectionState,
|
||||
...oldProps,
|
||||
});
|
||||
};
|
||||
|
||||
export const selectCategoricalAllMetadataAction =
|
||||
(
|
||||
type, // action type
|
||||
metadataField, // annotation category name
|
||||
labels,
|
||||
isSelected, // bool, select all or none
|
||||
oldProps = {}
|
||||
) =>
|
||||
async (dispatch, getState) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter, categoricalSelection } =
|
||||
getState();
|
||||
|
||||
const labelSelectionState = new Map(categoricalSelection[metadataField]);
|
||||
labels.forEach((label) => labelSelectionState.set(label, isSelected));
|
||||
|
||||
const selection = { mode: isSelected ? "all" : "none" };
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"obs",
|
||||
metadataField,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
metadataField,
|
||||
labelSelectionState,
|
||||
...oldProps,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
** Graph selection-related actions
|
||||
**/
|
||||
|
||||
export const graphBrushStartAction = () =>
|
||||
/* no change to crossfilter until a change fires */
|
||||
({ type: "graph brush start" });
|
||||
|
||||
const _graphBrushWithinRectAction =
|
||||
(type, embName, brushCoords) => async (dispatch, getState) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const selection = { mode: "within-rect", ...brushCoords };
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"emb",
|
||||
embName,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
brushCoords,
|
||||
});
|
||||
};
|
||||
|
||||
const _graphAllAction = (type, embName) => async (dispatch, getState) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, {
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
export const graphBrushChangeAction = (embName, brushCoords) =>
|
||||
_graphBrushWithinRectAction("graph brush change", embName, brushCoords);
|
||||
|
||||
export const graphBrushEndAction = (embName, brushCoords) =>
|
||||
_graphBrushWithinRectAction("graph brush end", embName, brushCoords);
|
||||
|
||||
export const graphBrushCancelAction = (embName) =>
|
||||
_graphAllAction("graph brush cancel", embName);
|
||||
export const graphBrushDeselectAction = (embName) =>
|
||||
_graphAllAction("graph brush deselect", embName);
|
||||
|
||||
export const graphLassoStartAction = () =>
|
||||
/* no change to crossfilter until a change fires */
|
||||
({ type: "graph lasso start" });
|
||||
|
||||
export const graphLassoCancelAction = (embName) =>
|
||||
_graphAllAction("graph lasso cancel", embName);
|
||||
|
||||
export const graphLassoDeselectAction = (embName) =>
|
||||
_graphAllAction("graph lasso cancel", embName);
|
||||
|
||||
export const graphLassoEndAction =
|
||||
(embName, polygon) => async (dispatch, getState) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const selection = {
|
||||
mode: "within-polygon",
|
||||
polygon,
|
||||
};
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"emb",
|
||||
embName,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "graph lasso end",
|
||||
obsCrossfilter,
|
||||
polygon,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Differential expression set selection
|
||||
*/
|
||||
export const setCellSetFromSelection = (cellSetId) => (dispatch, getState) => {
|
||||
const { obsCrossfilter } = getState();
|
||||
const selected = obsCrossfilter.allSelectedLabels();
|
||||
|
||||
dispatch({
|
||||
type: `store current cell selection as differential set ${cellSetId}`,
|
||||
data: selected.length > 0 ? selected : null,
|
||||
});
|
||||
};
|
||||
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
Action creators for selection
|
||||
*/
|
||||
export const selectContinuousMetadataAction = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
type: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
query: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
range: any,
|
||||
oldProps = {} // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const selection = range
|
||||
? {
|
||||
mode: "range",
|
||||
lo: range[0],
|
||||
hi: range[1],
|
||||
inclusive: true, // [lo, hi] incluisve selection
|
||||
}
|
||||
: { mode: "all" };
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(...query, selection);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
range,
|
||||
...oldProps,
|
||||
});
|
||||
};
|
||||
|
||||
export const selectCategoricalMetadataAction = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
type: any, // action type
|
||||
// annotation category name
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labels: any,
|
||||
// the label being selected/deselected
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
label: any,
|
||||
// bool
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
isSelected: any,
|
||||
oldProps = {}
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
const {
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
categoricalSelection,
|
||||
} = getState();
|
||||
|
||||
const labelSelectionState = new Map(categoricalSelection[metadataField]);
|
||||
labels.forEach(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(l: any) => labelSelectionState.has(l) || labelSelectionState.set(l, true)
|
||||
);
|
||||
labelSelectionState.set(label, isSelected);
|
||||
|
||||
const values = Array.from(labelSelectionState.keys()).filter((k) =>
|
||||
labelSelectionState.get(k)
|
||||
);
|
||||
const selection = {
|
||||
mode: "exact",
|
||||
values,
|
||||
};
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"obs",
|
||||
metadataField,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
metadataField,
|
||||
labelSelectionState,
|
||||
...oldProps,
|
||||
});
|
||||
};
|
||||
|
||||
export const selectCategoricalAllMetadataAction = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
type: any, // action type
|
||||
// annotation category name
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labels: any,
|
||||
// bool, select all or none
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
isSelected: any,
|
||||
oldProps = {}
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
const {
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
categoricalSelection,
|
||||
} = getState();
|
||||
|
||||
const labelSelectionState = new Map(categoricalSelection[metadataField]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
labels.forEach((label: any) => labelSelectionState.set(label, isSelected));
|
||||
|
||||
const selection = { mode: isSelected ? "all" : "none" };
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"obs",
|
||||
metadataField,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
metadataField,
|
||||
labelSelectionState,
|
||||
...oldProps,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
** Graph selection-related actions
|
||||
**/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphBrushStartAction = () =>
|
||||
/* no change to crossfilter until a change fires */
|
||||
({ type: "graph brush start" });
|
||||
|
||||
const _graphBrushWithinRectAction = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
embName: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
brushCoords: any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
) => async (dispatch: any, getState: any) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const selection = { mode: "within-rect", ...brushCoords };
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"emb",
|
||||
embName,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
brushCoords,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const _graphAllAction = (type: any, embName: any) => async (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, {
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
obsCrossfilter,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphBrushChangeAction = (embName: any, brushCoords: any) =>
|
||||
_graphBrushWithinRectAction("graph brush change", embName, brushCoords);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphBrushEndAction = (embName: any, brushCoords: any) =>
|
||||
_graphBrushWithinRectAction("graph brush end", embName, brushCoords);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphBrushCancelAction = (embName: any) =>
|
||||
_graphAllAction("graph brush cancel", embName);
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphBrushDeselectAction = (embName: any) =>
|
||||
_graphAllAction("graph brush deselect", embName);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphLassoStartAction = () =>
|
||||
/* no change to crossfilter until a change fires */
|
||||
({ type: "graph lasso start" });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphLassoCancelAction = (embName: any) =>
|
||||
_graphAllAction("graph lasso cancel", embName);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphLassoDeselectAction = (embName: any) =>
|
||||
_graphAllAction("graph lasso cancel", embName);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const graphLassoEndAction = (embName: any, polygon: any) => async (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||
|
||||
const selection = {
|
||||
mode: "within-polygon",
|
||||
polygon,
|
||||
};
|
||||
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||
"emb",
|
||||
embName,
|
||||
selection
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "graph lasso end",
|
||||
obsCrossfilter,
|
||||
polygon,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Differential expression set selection
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const setCellSetFromSelection = (cellSetId: any) => (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
const { obsCrossfilter } = getState();
|
||||
const selected = obsCrossfilter.allSelectedLabels();
|
||||
|
||||
dispatch({
|
||||
type: `store current cell selection as differential set ${cellSetId}`,
|
||||
data: selected.length > 0 ? selected : null,
|
||||
});
|
||||
};
|
||||
@@ -18,13 +18,7 @@ import {
|
||||
_userResetSubsetAnnoMatrix,
|
||||
} from "../util/stateManager/viewStackHelpers";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const clipAction = (min: any, max: any) => (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
dispatch: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getState: any
|
||||
) => {
|
||||
export const clipAction = (min, max) => (dispatch, getState) => {
|
||||
/*
|
||||
apply a clip to the current annoMatrix. By convention, the clip
|
||||
view is ALWAYS the top view.
|
||||
@@ -40,8 +34,7 @@ export const clipAction = (min: any, max: any) => (
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const subsetAction = () => (dispatch: any, getState: any) => {
|
||||
export const subsetAction = () => (dispatch, getState) => {
|
||||
/*
|
||||
Subset the annoMatrix to the current crossfilter selection by pushing a
|
||||
subset view.
|
||||
@@ -49,10 +42,8 @@ export const subsetAction = () => (dispatch: any, getState: any) => {
|
||||
By convention, a clip view is ALWAYS the top view, so if present, pop
|
||||
off and re-apply
|
||||
*/
|
||||
const {
|
||||
annoMatrix: prevAnnoMatrix,
|
||||
obsCrossfilter: prevObsCrossfilter,
|
||||
} = getState();
|
||||
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
|
||||
getState();
|
||||
const annoMatrix = _userSubsetAnnoMatrix(
|
||||
prevAnnoMatrix,
|
||||
prevObsCrossfilter.allSelectedMask()
|
||||
@@ -65,8 +56,7 @@ export const subsetAction = () => (dispatch: any, getState: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const resetSubsetAction = () => (dispatch: any, getState: any) => {
|
||||
export const resetSubsetAction = () => (dispatch, getState) => {
|
||||
/*
|
||||
Reset the annoMatrix to all data. Because we may have multiple views
|
||||
stacked, we pop them all. By convention, any clip transformation will
|
||||
@@ -2,9 +2,6 @@ import {
|
||||
Dataframe,
|
||||
IdentityInt32Index,
|
||||
dataframeMemo,
|
||||
LabelType,
|
||||
DataframeValue,
|
||||
DataframeValueArray,
|
||||
} from "../util/dataframe";
|
||||
import {
|
||||
_getColumnDimensionNames,
|
||||
@@ -13,71 +10,13 @@ import {
|
||||
_getWritableColumns,
|
||||
} from "./schema";
|
||||
import { indexEntireSchema } from "../util/stateManager/schemaHelpers";
|
||||
import {
|
||||
_whereCacheGet,
|
||||
_whereCacheMerge,
|
||||
WhereCache,
|
||||
WhereCacheColumnLabels,
|
||||
} from "./whereCache";
|
||||
import { _whereCacheGet, _whereCacheMerge } from "./whereCache";
|
||||
import _shallowClone from "./clone";
|
||||
import { _queryValidate, _queryCacheKey, Query } from "./query";
|
||||
import { GCHints } from "../common/types/entities";
|
||||
import {
|
||||
AnnotationColumnSchema,
|
||||
Category,
|
||||
Field,
|
||||
EmbeddingSchema,
|
||||
Schema,
|
||||
ArraySchema,
|
||||
RawSchema,
|
||||
} from "../common/types/schema";
|
||||
import { LabelArray } from "../util/dataframe/types";
|
||||
import { LabelIndexBase } from "../util/dataframe/labelIndex";
|
||||
import { _queryValidate, _queryCacheKey } from "./query";
|
||||
|
||||
const _dataframeCache = dataframeMemo(128);
|
||||
|
||||
interface Cache {
|
||||
[Field.obs]: Dataframe;
|
||||
[Field.var]: Dataframe;
|
||||
[Field.emb]: Dataframe;
|
||||
[Field.X]: Dataframe;
|
||||
}
|
||||
|
||||
interface PendingLoad {
|
||||
[Field.obs]: { [key: string]: Promise<void> };
|
||||
[Field.var]: { [key: string]: Promise<void> };
|
||||
[Field.emb]: { [key: string]: Promise<void> };
|
||||
[Field.X]: { [key: string]: Promise<void> };
|
||||
}
|
||||
|
||||
export interface UserFlags {
|
||||
isUserSubsetView?: boolean;
|
||||
isEmbSubsetView?: boolean;
|
||||
}
|
||||
|
||||
export default abstract class AnnoMatrix {
|
||||
public isView: boolean;
|
||||
|
||||
public nObs: number;
|
||||
|
||||
public nVar: number;
|
||||
|
||||
public rowIndex: LabelIndexBase;
|
||||
|
||||
public schema: Schema;
|
||||
|
||||
public userFlags: UserFlags;
|
||||
|
||||
public viewOf: AnnoMatrix;
|
||||
|
||||
public _cache: Cache;
|
||||
|
||||
private _pendingLoad: PendingLoad;
|
||||
|
||||
private _whereCache: WhereCache;
|
||||
|
||||
private _gcInfo: Map<string, number>;
|
||||
|
||||
export default class AnnoMatrix {
|
||||
/*
|
||||
Abstract base class for all AnnoMatrix objects. This class provides a proxy
|
||||
to the annotated matrix data authoritatively served by the server/back-end.
|
||||
@@ -108,19 +47,14 @@ export default abstract class AnnoMatrix {
|
||||
subset(annoMatrix, rowLabels) -> annoMatrix
|
||||
etc.
|
||||
*/
|
||||
static fields(): Field[] {
|
||||
static fields() {
|
||||
/*
|
||||
return the fields present in the AnnoMatrix instance.
|
||||
*/
|
||||
return [Field.obs, Field.var, Field.emb, Field.X];
|
||||
return ["obs", "var", "emb", "X"];
|
||||
}
|
||||
|
||||
constructor(
|
||||
schema: RawSchema,
|
||||
nObs: number,
|
||||
nVar: number,
|
||||
rowIndex: LabelIndexBase | null = null
|
||||
) {
|
||||
constructor(schema, nObs, nVar, rowIndex = null) {
|
||||
/*
|
||||
Private constructor - this is an abstract base class. Do not use.
|
||||
*/
|
||||
@@ -136,7 +70,7 @@ export default abstract class AnnoMatrix {
|
||||
* rowIndex - a rowIndex shared by all data on this view (ie, the list of cells).
|
||||
The row index labels are as defined by the base dataset from the server.
|
||||
* isView - true if this is a view, false if not.
|
||||
* viewOf - pointer to parent annomatrix if a view, self if not a view.
|
||||
* viewOf - pointer to parent annomatrix if a view, undefined/null if not a view.
|
||||
* userFlags - container for any additional state a user of this API wants to hang
|
||||
off of an annoMatrix, and have propagated by the (shallow) cloning protocol.
|
||||
*/
|
||||
@@ -145,17 +79,17 @@ export default abstract class AnnoMatrix {
|
||||
this.nVar = nVar;
|
||||
this.rowIndex = rowIndex || new IdentityInt32Index(nObs);
|
||||
this.isView = false;
|
||||
this.viewOf = this;
|
||||
this.viewOf = undefined;
|
||||
this.userFlags = {};
|
||||
|
||||
/*
|
||||
Private instance variables.
|
||||
Private instance variables.
|
||||
|
||||
These are caches - lazily loaded. The only guarantee is that if they
|
||||
are loaded, they will conform to the schema & dimensionality constraints.
|
||||
These are caches - lazily loaded. The only guarantee is that if they
|
||||
are loaded, they will conform to the schema & dimensionality constraints.
|
||||
|
||||
Do NOT use directly - instead, use the fetch() and preload() API.
|
||||
*/
|
||||
Do NOT use directly - instead, use the fetch() and preload() API.
|
||||
*/
|
||||
this._cache = {
|
||||
obs: Dataframe.empty(this.rowIndex),
|
||||
var: Dataframe.empty(this.rowIndex),
|
||||
@@ -168,14 +102,14 @@ export default abstract class AnnoMatrix {
|
||||
emb: {},
|
||||
X: {},
|
||||
};
|
||||
this._whereCache = {} as WhereCache;
|
||||
this._whereCache = {};
|
||||
this._gcInfo = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
** Schema helper/accessors
|
||||
**/
|
||||
getMatrixColumns(field: Field): string[] {
|
||||
getMatrixColumns(field) {
|
||||
/*
|
||||
Return array of column names in the field. ONLY supported on the
|
||||
obs, var and emb fields. X currently unimplemented and will throw.
|
||||
@@ -188,7 +122,7 @@ export default abstract class AnnoMatrix {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this -- need to be able to call this on instances
|
||||
getMatrixFields(): Field[] {
|
||||
getMatrixFields() {
|
||||
/*
|
||||
Return array of fields in this annoMatrix. Currently hard-wired to
|
||||
return: ["X", "obs", "var", "emb"].
|
||||
@@ -198,7 +132,7 @@ export default abstract class AnnoMatrix {
|
||||
return AnnoMatrix.fields();
|
||||
}
|
||||
|
||||
getColumnSchema(field: Field, col: LabelType): ArraySchema {
|
||||
getColumnSchema(field, col) {
|
||||
/*
|
||||
Return the schema for the field & column ,eg,
|
||||
|
||||
@@ -210,7 +144,7 @@ export default abstract class AnnoMatrix {
|
||||
return _getColumnSchema(this.schema, field, col);
|
||||
}
|
||||
|
||||
getColumnDimensions(field: Field, col: LabelType): LabelArray | undefined {
|
||||
getColumnDimensions(field, col) {
|
||||
/*
|
||||
Return the dimensions on this field / column. For most fields, which are 1D,
|
||||
this just return the column name. Multi-dimensional columns, such as embeddings,
|
||||
@@ -228,19 +162,19 @@ export default abstract class AnnoMatrix {
|
||||
/**
|
||||
** General utility methods
|
||||
**/
|
||||
base(): AnnoMatrix {
|
||||
base() {
|
||||
/*
|
||||
return the base of view, or `this` if not a view.
|
||||
*/
|
||||
let annoMatrix = this._getViewOf();
|
||||
while (annoMatrix.isView) annoMatrix = annoMatrix._getViewOf();
|
||||
let annoMatrix = this;
|
||||
while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
|
||||
return annoMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
** Load / read interfaces
|
||||
**/
|
||||
fetch(field: Field, q: Query | Query[]): Promise<Dataframe> {
|
||||
fetch(field, q) {
|
||||
/*
|
||||
Return the given query on a single matrix field as a single dataframe.
|
||||
Currently supports ONLY full column query.
|
||||
@@ -269,7 +203,7 @@ export default abstract class AnnoMatrix {
|
||||
1. Fetch the "n_genes" column the "obs":
|
||||
|
||||
const df = await fetch("obs", "n_genes")
|
||||
console.log("Largest number of genes is: ", df.summarizeContinuous().max);
|
||||
console.log("Largest number of genes is: ", df.summarize().max);
|
||||
|
||||
2. Fetch two separate columns from obs. Returns a single dataframe containing
|
||||
the columns:
|
||||
@@ -297,7 +231,7 @@ export default abstract class AnnoMatrix {
|
||||
return this._fetch(field, q);
|
||||
}
|
||||
|
||||
prefetch(field: Field, q: Query): void {
|
||||
prefetch(field, q) {
|
||||
/*
|
||||
Start a data fetch & cache fill. Identical to fetch() except it does
|
||||
not return a value.
|
||||
@@ -306,6 +240,7 @@ export default abstract class AnnoMatrix {
|
||||
overall component rendering latency.
|
||||
*/
|
||||
this._fetch(field, q);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,172 +261,176 @@ export default abstract class AnnoMatrix {
|
||||
** The actual implementation is in the sub-classes, which MUST override these.
|
||||
**/
|
||||
|
||||
/*
|
||||
Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix.
|
||||
Typical use is to add a new user-created label to a user-created obs categorical
|
||||
annotation.
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
addObsAnnoCategory(col, category) {
|
||||
/*
|
||||
Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix.
|
||||
Typical use is to add a new user-created label to a user-created obs categorical
|
||||
annotation.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix
|
||||
|
||||
*/
|
||||
abstract addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix;
|
||||
|
||||
/*
|
||||
Remove a category value from an obs column, reassign any obs having that value
|
||||
to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix.
|
||||
Typical use is to remove a user-created label from a user-created obs categorical
|
||||
annotation.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
An `unassignedCategory` value must be provided, for assignment to any obs/cells
|
||||
that had the now-delete category label as their value.
|
||||
|
||||
Example:
|
||||
await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix
|
||||
|
||||
NOTE: method is async as it may need to fetch data to provide the reassignment.
|
||||
*/
|
||||
abstract removeObsAnnoCategory(
|
||||
col: LabelType,
|
||||
category: Category,
|
||||
unassignedCategory: string
|
||||
): Promise<AnnoMatrix>;
|
||||
|
||||
/*
|
||||
Drop an entire writable column, eg a user-created obs annotation. Typical use
|
||||
is to provide the "Delete Category" implementation. Returns the new AnnoMatrix.
|
||||
Will throw if not a writable annotation.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
|
||||
dropObsColumn("old annotations") -> AnnoMatrix
|
||||
*/
|
||||
abstract dropObsColumn(col: LabelType): AnnoMatrix;
|
||||
|
||||
/*
|
||||
Add a new writable OBS annotation column, with the caller-specified schema, initial value
|
||||
type and value.
|
||||
|
||||
Value may be any one of:
|
||||
* an array of values
|
||||
* a primitive type, including null or undefined.
|
||||
If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'.
|
||||
If a primitive, 'Ctor' will be used to create the initial value, which will be filled
|
||||
with 'value'.
|
||||
|
||||
Throws if the name specified in 'colSchema' duplicates an existing obs column.
|
||||
|
||||
Returns a new AnnoMatrix.
|
||||
|
||||
Examples:
|
||||
|
||||
addObsColumn(
|
||||
{ name: "foo", type: "categorical", categories: "unassigned" },
|
||||
Array,
|
||||
"unassigned"
|
||||
) -> AnnoMatrix
|
||||
|
||||
*/
|
||||
abstract addObsColumn<T extends DataframeValueArray>(
|
||||
colSchema: AnnotationColumnSchema,
|
||||
Ctor: new (n: number) => T,
|
||||
value: T
|
||||
): AnnoMatrix;
|
||||
|
||||
/*
|
||||
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
|
||||
|
||||
Will throw column does not exist or is not writable, or if 'newCol' is not unique.
|
||||
|
||||
Example:
|
||||
|
||||
renameObsColumn('cell type', 'old cell type') -> AnnoMatrix.
|
||||
|
||||
*/
|
||||
abstract renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix;
|
||||
|
||||
/*
|
||||
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
|
||||
to set a group of cells to have a label on a user-created categorical annotation
|
||||
(eg set all selected cells to have a label).
|
||||
|
||||
NOTE: async method, as it may need to fetch.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix
|
||||
*/
|
||||
abstract setObsColumnValues(
|
||||
col: LabelType,
|
||||
obsLabels: Int32Array,
|
||||
value: DataframeValue
|
||||
): Promise<AnnoMatrix>;
|
||||
|
||||
/*
|
||||
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
|
||||
Async method - returns a promise for a new AnnoMatrix.
|
||||
|
||||
Typical use would be to set all labels of one value to another.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix
|
||||
addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix
|
||||
|
||||
*/
|
||||
abstract resetObsColumnValues<T extends DataframeValue>(
|
||||
col: LabelType,
|
||||
oldValue: T,
|
||||
newValue: T
|
||||
): Promise<AnnoMatrix>;
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
/*
|
||||
Add a new obs embedding to the AnnoMatrix, with provided schema.
|
||||
Returns a new annomatrix.
|
||||
|
||||
Typical use will be to add a re-embedding that the server has calculated.
|
||||
|
||||
Will throw if the column schema is invalid (eg, duplicate name).
|
||||
*/
|
||||
abstract addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix;
|
||||
|
||||
getCacheKeys(
|
||||
field: Field,
|
||||
query: Query
|
||||
): WhereCacheColumnLabels | [undefined] {
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
async removeObsAnnoCategory(col, category, unassignedCategory) {
|
||||
/*
|
||||
Return cache keys for columns associated with this query. May return
|
||||
[unknown] if no keys are known (ie, nothing is or was cached).
|
||||
*/
|
||||
Remove a category value from an obs column, reassign any obs having that value
|
||||
to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix.
|
||||
Typical use is to remove a user-created label from a user-created obs categorical
|
||||
annotation.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
An `unassignedCategory` value must be provided, for assignment to any obs/cells
|
||||
that had the now-delete category label as their value.
|
||||
|
||||
Example:
|
||||
await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix
|
||||
|
||||
NOTE: method is async as it may need to fetch data to provide the reassignment.
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
dropObsColumn(col) {
|
||||
/*
|
||||
Drop an entire writable column, eg a user-created obs annotation. Typical use
|
||||
is to provide the "Delete Category" implementation. Returns the new AnnoMatrix.
|
||||
Will throw if not a writable annotation.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
|
||||
dropObsColumn("old annotations") -> AnnoMatrix
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
addObsColumn(colSchema, Ctor, value) {
|
||||
/*
|
||||
Add a new writable OBS annotation column, with the caller-specified schema, initial value
|
||||
type and value.
|
||||
|
||||
Value may be any one of:
|
||||
* an array of values
|
||||
* a primitive type, including null or undefined.
|
||||
If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'.
|
||||
If a primitive, 'Ctor' will be used to create the initial value, which will be filled
|
||||
with 'value'.
|
||||
|
||||
Throws if the name specified in 'colSchema' duplicates an existing obs column.
|
||||
|
||||
Returns a new AnnoMatrix.
|
||||
|
||||
Examples:
|
||||
|
||||
addObsColumn(
|
||||
{ name: "foo", type: "categorical", categories: "unassigned" },
|
||||
Array,
|
||||
"unassigned"
|
||||
) -> AnnoMatrix
|
||||
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
renameObsColumn(oldCol, newCol) {
|
||||
/*
|
||||
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
|
||||
|
||||
Will throw column does not exist or is not writable, or if 'newCol' is not unique.
|
||||
|
||||
Example:
|
||||
|
||||
renameObsColumn('cell type', 'old cell type') -> AnnoMatrix.
|
||||
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
async setObsColumnValues(col, obsLabels, value) {
|
||||
/*
|
||||
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
|
||||
to set a group of cells to have a label on a user-created categorical anntoation
|
||||
(eg set all selected cells to have a label).
|
||||
|
||||
NOTE: async method, as it may need to fetch.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix
|
||||
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
async resetObsColumnValues(col, oldValue, newValue) {
|
||||
/*
|
||||
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
|
||||
Async method - returns a promise for a new AnnoMatrix.
|
||||
|
||||
Typical use would be to set all labels of one value to another.
|
||||
|
||||
Will throw column does not exist or is not writable.
|
||||
|
||||
Example:
|
||||
await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix
|
||||
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
||||
addEmbedding(colSchema) {
|
||||
/*
|
||||
Add a new obs embedding to the AnnoMatrix, with provided schema.
|
||||
Returns a new annomatrix.
|
||||
|
||||
Typical use will be to add a re-embedding that the server has calculated.
|
||||
|
||||
Will throw if the column schema is invalid (eg, duplicate name).
|
||||
*/
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
getCacheKeys(field, query) {
|
||||
/*
|
||||
Return cache keys for columns associated with this query. May return
|
||||
[unknown] if no keys are known (ie, nothing is or was cached).
|
||||
*/
|
||||
return _whereCacheGet(this._whereCache, this.schema, field, query);
|
||||
}
|
||||
|
||||
/**
|
||||
** Private interfaces below.
|
||||
**/
|
||||
_resolveCachedQueries(field: Field, queries: Query[]): LabelArray {
|
||||
_resolveCachedQueries(field, queries) {
|
||||
return queries
|
||||
.map((query: Query) =>
|
||||
// @ts-expect-error --- TODO revisit:
|
||||
// `filter`: This expression is not callable.
|
||||
.map((query) =>
|
||||
_whereCacheGet(this._whereCache, this.schema, field, query).filter(
|
||||
(cacheKey?: LabelType) =>
|
||||
(cacheKey) =>
|
||||
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
|
||||
)
|
||||
)
|
||||
.flat();
|
||||
}
|
||||
|
||||
async _fetch(field: Field, q: Query | Query[]): Promise<Dataframe> {
|
||||
if (!AnnoMatrix.fields().includes(field)) return Dataframe.empty();
|
||||
async _fetch(field, q) {
|
||||
if (!AnnoMatrix.fields().includes(field)) return undefined;
|
||||
const queries = Array.isArray(q) ? q : [q];
|
||||
queries.forEach(_queryValidate);
|
||||
|
||||
@@ -502,7 +441,7 @@ Return cache keys for columns associated with this query. May return
|
||||
/* find any query not already cached */
|
||||
const uncachedQueries = queries.filter((query) =>
|
||||
_whereCacheGet(this._whereCache, this.schema, field, query).some(
|
||||
(cacheKey?: LabelType) =>
|
||||
(cacheKey) =>
|
||||
cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
|
||||
)
|
||||
);
|
||||
@@ -511,19 +450,15 @@ Return cache keys for columns associated with this query. May return
|
||||
if (uncachedQueries.length > 0) {
|
||||
await Promise.all(
|
||||
uncachedQueries.map((query) =>
|
||||
this._getPendingLoad(
|
||||
field,
|
||||
query,
|
||||
async (_field: Field, _query: Query): Promise<void> => {
|
||||
/* fetch, then index. _doLoad is subclass interface */
|
||||
const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
|
||||
this._cache[_field] = this._cache[_field].withColsFrom(df);
|
||||
this._whereCache = _whereCacheMerge(
|
||||
this._whereCache,
|
||||
whereCacheUpdate
|
||||
);
|
||||
}
|
||||
)
|
||||
this._getPendingLoad(field, query, async (_field, _query) => {
|
||||
/* fetch, then index. _doLoad is subclass interface */
|
||||
const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
|
||||
this._cache[_field] = this._cache[_field].withColsFrom(df);
|
||||
this._whereCache = _whereCacheMerge(
|
||||
this._whereCache,
|
||||
whereCacheUpdate
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -537,11 +472,7 @@ Return cache keys for columns associated with this query. May return
|
||||
return response;
|
||||
}
|
||||
|
||||
async _getPendingLoad(
|
||||
field: Field,
|
||||
query: Query,
|
||||
fetchFn: (_field: Field, _query: Query) => Promise<void>
|
||||
): Promise<void> {
|
||||
async _getPendingLoad(field, query, fetchFn) {
|
||||
/*
|
||||
Given a query on a field, ensure that we only have a single outstanding
|
||||
fetch at any given time. If multiple requests occur while a fetch is
|
||||
@@ -562,22 +493,9 @@ Return cache keys for columns associated with this query. May return
|
||||
return this._pendingLoad[field][key];
|
||||
}
|
||||
|
||||
abstract _doLoad(
|
||||
field: Field,
|
||||
query: Query
|
||||
): Promise<[WhereCache | null, Dataframe]>;
|
||||
|
||||
/**
|
||||
* Determines viewOf for this annoMatrix.
|
||||
*
|
||||
* @internal
|
||||
* @returns - parent annoMatrix if this annoMatrix is a view, otherwise this annoMatrix if it's not a view.
|
||||
*/
|
||||
_getViewOf(): AnnoMatrix {
|
||||
if (this.isView) {
|
||||
return this.viewOf;
|
||||
}
|
||||
return this;
|
||||
// eslint-disable-next-line class-methods-use-this -- make sure subclass implements
|
||||
async _doLoad() {
|
||||
_subclassResponsibility();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,21 +527,20 @@ Return cache keys for columns associated with this query. May return
|
||||
To be effective, the GC callback needs to be invoked from the undo/redo code,
|
||||
as much of the cache is pinned by that data structure.
|
||||
*/
|
||||
_gcField(field: Field, isHot: boolean, pinnedColumns: LabelArray): void {
|
||||
const maxColumns = isHot ? 256 : 10;
|
||||
_gcField(field, isHot, pinnedColumns) {
|
||||
const maxColumns = isHot ? 256 : 10; // maybe to aggressive?
|
||||
|
||||
const cache = this._cache[field];
|
||||
if (cache.colIndex.size() < maxColumns) return; // trivial rejection
|
||||
|
||||
const candidates = cache.colIndex
|
||||
.labels()
|
||||
// @ts-expect-error --- TODO revisit:
|
||||
// `col`: Argument of type 'LabelType' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'.
|
||||
.filter((col: LabelType) => !pinnedColumns.includes(col));
|
||||
.filter((col) => !pinnedColumns.includes(col));
|
||||
|
||||
const excessCount = candidates.length + pinnedColumns.length - maxColumns;
|
||||
if (excessCount > 0) {
|
||||
const { _gcInfo } = this;
|
||||
candidates.sort((a: LabelType, b: LabelType) => {
|
||||
candidates.sort((a, b) => {
|
||||
let atime = _gcInfo.get(_columnCacheKey(field, a));
|
||||
if (atime === undefined) atime = 0;
|
||||
|
||||
@@ -640,49 +557,41 @@ Return cache keys for columns associated with this query. May return
|
||||
// ", "
|
||||
// )}]`
|
||||
// );
|
||||
// @ts-expect-error --- TODO revisit:
|
||||
// `reduce`: This expression is not callable.
|
||||
this._cache[field] = toDrop.reduce(
|
||||
(df: Dataframe, col: LabelType) => df.dropCol(col),
|
||||
(df, col) => df.dropCol(col),
|
||||
this._cache[field]
|
||||
);
|
||||
toDrop.forEach((col: LabelType) =>
|
||||
_gcInfo.delete(_columnCacheKey(field, col))
|
||||
);
|
||||
toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col)));
|
||||
}
|
||||
}
|
||||
|
||||
_gcFetchCleanup(field: Field, pinnedColumns: LabelArray): void {
|
||||
_gcFetchCleanup(field, pinnedColumns) {
|
||||
/*
|
||||
Called during data load/fetch. By definition, this is 'hot', so we
|
||||
only want to gc X.
|
||||
*/
|
||||
if (field === Field.X) {
|
||||
if (field === "X") {
|
||||
this._gcField(
|
||||
field,
|
||||
true,
|
||||
// @ts-expect-error --- TODO revisit:
|
||||
// Property 'concat' does not exist on type 'LabelArray'.
|
||||
pinnedColumns.concat(_getWritableColumns(this.schema, field))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_gc(hints: GCHints): void {
|
||||
_gc(hints) {
|
||||
/*
|
||||
Called from middleware, or elsewhere. isHot is true if we are in the active store,
|
||||
or false if we are in some other context (eg, history state).
|
||||
*/
|
||||
const { isHot } = hints;
|
||||
const candidateFields = isHot
|
||||
? [Field.X]
|
||||
: [Field.X, Field.emb, Field.var, Field.obs];
|
||||
const candidateFields = isHot ? ["X"] : ["X", "emb", "var", "obs"];
|
||||
candidateFields.forEach((field) =>
|
||||
this._gcField(field, isHot, _getWritableColumns(this.schema, field))
|
||||
);
|
||||
}
|
||||
|
||||
_gcUpdateStats(field: Field, dataframe: Dataframe): void {
|
||||
_gcUpdateStats(field, dataframe) {
|
||||
/*
|
||||
called each time a query is performed, allowing the gc to update any bookkeeping
|
||||
information. Currently, this is just a simple last-fetched timestamp, stored
|
||||
@@ -691,7 +600,7 @@ Return cache keys for columns associated with this query. May return
|
||||
const cols = dataframe.colIndex.labels();
|
||||
const { _gcInfo } = this;
|
||||
const now = Date.now();
|
||||
cols.forEach((c: LabelType) => {
|
||||
cols.forEach((c) => {
|
||||
_gcInfo.set(_columnCacheKey(field, c), now);
|
||||
});
|
||||
}
|
||||
@@ -708,7 +617,7 @@ Return cache keys for columns associated with this query. May return
|
||||
|
||||
Do not override _clone();
|
||||
**/
|
||||
_cloneDeeper(clone: AnnoMatrix): AnnoMatrix {
|
||||
_cloneDeeper(clone) {
|
||||
clone._cache = _shallowClone(this._cache);
|
||||
clone._gcInfo = new Map();
|
||||
clone._pendingLoad = {
|
||||
@@ -720,7 +629,7 @@ Return cache keys for columns associated with this query. May return
|
||||
return clone;
|
||||
}
|
||||
|
||||
_clone(): AnnoMatrix {
|
||||
_clone() {
|
||||
const clone = _shallowClone(this);
|
||||
this._cloneDeeper(clone);
|
||||
Object.seal(clone);
|
||||
@@ -731,6 +640,11 @@ Return cache keys for columns associated with this query. May return
|
||||
/*
|
||||
private utility functions below
|
||||
*/
|
||||
function _columnCacheKey(field: Field, column: LabelType): string {
|
||||
function _columnCacheKey(field, column) {
|
||||
return `${field}/${column}`;
|
||||
}
|
||||
|
||||
function _subclassResponsibility() {
|
||||
/* protect against bugs in subclass */
|
||||
throw new Error("subclass failed to implement required method");
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
Shallow clone an object, correctly handling prototype
|
||||
*/
|
||||
|
||||
export default function _shallowClone<T>(orig: T): T {
|
||||
export default function _shallowClone(orig) {
|
||||
return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig);
|
||||
}
|
||||
@@ -9,94 +9,56 @@ AnnoMatrix stay in sync:
|
||||
*/
|
||||
import Crossfilter from "../util/typedCrossfilter";
|
||||
import { _getColumnSchema } from "./schema";
|
||||
import {
|
||||
AnnotationColumnSchema,
|
||||
Field,
|
||||
EmbeddingSchema,
|
||||
} from "../common/types/schema";
|
||||
import AnnoMatrix from "./annoMatrix";
|
||||
import {
|
||||
Dataframe,
|
||||
DataframeValue,
|
||||
DataframeValueArray,
|
||||
LabelType,
|
||||
} from "../util/dataframe";
|
||||
import { Query } from "./query";
|
||||
import { TypedArray } from "../common/types/arraytypes";
|
||||
import { LabelArray } from "../util/dataframe/types";
|
||||
|
||||
type ObsDimensionParams =
|
||||
| [string, DataframeValueArray, DataframeValueArray]
|
||||
| [string, DataframeValueArray]
|
||||
| [string, DataframeValueArray, Int32ArrayConstructor]
|
||||
| [string, DataframeValueArray, Float32ArrayConstructor];
|
||||
|
||||
function _dimensionNameFromDf(field: Field, df: Dataframe): string {
|
||||
function _dimensionNameFromDf(field, df) {
|
||||
const colNames = df.colIndex.labels();
|
||||
return _dimensionName(field, colNames);
|
||||
}
|
||||
|
||||
function _dimensionName(
|
||||
field: Field,
|
||||
colNames: LabelType | LabelArray
|
||||
): string {
|
||||
function _dimensionName(field, colNames) {
|
||||
if (!Array.isArray(colNames)) return `${field}/${colNames}`;
|
||||
return `${field}/${colNames.join(":")}`;
|
||||
}
|
||||
|
||||
export default class AnnoMatrixObsCrossfilter {
|
||||
annoMatrix: AnnoMatrix;
|
||||
|
||||
obsCrossfilter: Crossfilter;
|
||||
|
||||
constructor(
|
||||
annoMatrix: AnnoMatrix,
|
||||
_obsCrossfilter: Crossfilter | null = null
|
||||
) {
|
||||
constructor(annoMatrix, _obsCrossfilter = null) {
|
||||
this.annoMatrix = annoMatrix;
|
||||
this.obsCrossfilter =
|
||||
_obsCrossfilter || new Crossfilter(annoMatrix._cache.obs);
|
||||
this.obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs);
|
||||
}
|
||||
|
||||
size(): number {
|
||||
size() {
|
||||
return this.obsCrossfilter.size();
|
||||
}
|
||||
|
||||
/**
|
||||
Managing the associated annoMatrix. These wrappers are necessary to
|
||||
Managing the associated annoMatrix. These wrappers are necessary to
|
||||
make coordinated changes to BOTH the crossfilter and annoMatrix, and
|
||||
ensure that all state stays synchronized.
|
||||
|
||||
See API documentation in annoMatrix.js.
|
||||
**/
|
||||
addObsColumn<T extends DataframeValueArray>(
|
||||
colSchema: AnnotationColumnSchema,
|
||||
Ctor: new (n: number) => T,
|
||||
value: T
|
||||
): AnnoMatrixObsCrossfilter {
|
||||
addObsColumn(colSchema, Ctor, value) {
|
||||
const annoMatrix = this.annoMatrix.addObsColumn(colSchema, Ctor, value);
|
||||
const obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs);
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
dropObsColumn(col: LabelType): AnnoMatrixObsCrossfilter {
|
||||
dropObsColumn(col) {
|
||||
const annoMatrix = this.annoMatrix.dropObsColumn(col);
|
||||
let { obsCrossfilter } = this;
|
||||
const dimName = _dimensionName(Field.obs, col);
|
||||
const dimName = _dimensionName("obs", col);
|
||||
if (obsCrossfilter.hasDimension(dimName)) {
|
||||
obsCrossfilter = obsCrossfilter.delDimension(dimName);
|
||||
}
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
renameObsColumn(
|
||||
oldCol: LabelType,
|
||||
newCol: LabelType
|
||||
): AnnoMatrixObsCrossfilter {
|
||||
renameObsColumn(oldCol, newCol) {
|
||||
const annoMatrix = this.annoMatrix.renameObsColumn(oldCol, newCol);
|
||||
const oldDimName = _dimensionName(Field.obs, oldCol);
|
||||
const newDimName = _dimensionName(Field.obs, newCol);
|
||||
const oldDimName = _dimensionName("obs", oldCol);
|
||||
const newDimName = _dimensionName("obs", newCol);
|
||||
let { obsCrossfilter } = this;
|
||||
if (obsCrossfilter.hasDimension(oldDimName)) {
|
||||
obsCrossfilter = obsCrossfilter.renameDimension(oldDimName, newDimName);
|
||||
@@ -104,12 +66,9 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
addObsAnnoCategory(
|
||||
col: LabelType,
|
||||
category: string
|
||||
): AnnoMatrixObsCrossfilter {
|
||||
addObsAnnoCategory(col, category) {
|
||||
const annoMatrix = this.annoMatrix.addObsAnnoCategory(col, category);
|
||||
const dimName = _dimensionName(Field.obs, col);
|
||||
const dimName = _dimensionName("obs", col);
|
||||
let { obsCrossfilter } = this;
|
||||
if (obsCrossfilter.hasDimension(dimName)) {
|
||||
obsCrossfilter = obsCrossfilter.delDimension(dimName);
|
||||
@@ -117,17 +76,13 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
async removeObsAnnoCategory(
|
||||
col: LabelType,
|
||||
category: string,
|
||||
unassignedCategory: string
|
||||
): Promise<AnnoMatrixObsCrossfilter> {
|
||||
async removeObsAnnoCategory(col, category, unassignedCategory) {
|
||||
const annoMatrix = await this.annoMatrix.removeObsAnnoCategory(
|
||||
col,
|
||||
category,
|
||||
unassignedCategory
|
||||
);
|
||||
const dimName = _dimensionName(Field.obs, col);
|
||||
const dimName = _dimensionName("obs", col);
|
||||
let { obsCrossfilter } = this;
|
||||
if (obsCrossfilter.hasDimension(dimName)) {
|
||||
obsCrossfilter = obsCrossfilter.delDimension(dimName);
|
||||
@@ -135,17 +90,13 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
async setObsColumnValues(
|
||||
col: LabelType,
|
||||
rowLabels: Int32Array,
|
||||
value: DataframeValue
|
||||
): Promise<AnnoMatrixObsCrossfilter> {
|
||||
async setObsColumnValues(col, rowLabels, value) {
|
||||
const annoMatrix = await this.annoMatrix.setObsColumnValues(
|
||||
col,
|
||||
rowLabels,
|
||||
value
|
||||
);
|
||||
const dimName = _dimensionName(Field.obs, col);
|
||||
const dimName = _dimensionName("obs", col);
|
||||
let { obsCrossfilter } = this;
|
||||
if (obsCrossfilter.hasDimension(dimName)) {
|
||||
obsCrossfilter = obsCrossfilter.delDimension(dimName);
|
||||
@@ -153,17 +104,13 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
async resetObsColumnValues<T extends DataframeValue>(
|
||||
col: LabelType,
|
||||
oldValue: T,
|
||||
newValue: T
|
||||
): Promise<AnnoMatrixObsCrossfilter> {
|
||||
async resetObsColumnValues(col, oldValue, newValue) {
|
||||
const annoMatrix = await this.annoMatrix.resetObsColumnValues(
|
||||
col,
|
||||
oldValue,
|
||||
newValue
|
||||
);
|
||||
const dimName = _dimensionName(Field.obs, col);
|
||||
const dimName = _dimensionName("obs", col);
|
||||
let { obsCrossfilter } = this;
|
||||
if (obsCrossfilter.hasDimension(dimName)) {
|
||||
obsCrossfilter = obsCrossfilter.delDimension(dimName);
|
||||
@@ -171,25 +118,23 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrixObsCrossfilter {
|
||||
addEmbedding(colSchema) {
|
||||
const annoMatrix = this.annoMatrix.addEmbedding(colSchema);
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, this.obsCrossfilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the crossfilter dimension. Do not change the annoMatrix. Useful when we
|
||||
* want to stop tracking the selection state, but aren't sure we want to blow the
|
||||
* want to stop trackin the selection state, but aren't sure we want to blow the
|
||||
* annomatrix cache.
|
||||
*/
|
||||
dropDimension(field: Field, query: Query): AnnoMatrixObsCrossfilter {
|
||||
dropDimension(field, query) {
|
||||
const { annoMatrix } = this;
|
||||
let { obsCrossfilter } = this;
|
||||
const keys = annoMatrix
|
||||
.getCacheKeys(field, query)
|
||||
// @ts-expect-error ts-migrate --- suppressing TS defect (https://github.com/microsoft/TypeScript/issues/44373).
|
||||
// Compiler is complaining that expression is not callable on array union types. Remove suppression once fixed.
|
||||
.filter((k?: string | number) => k !== undefined);
|
||||
const dimName = _dimensionName(field, keys as string[]);
|
||||
.filter((k) => k !== undefined);
|
||||
const dimName = _dimensionName(field, keys);
|
||||
if (obsCrossfilter.hasDimension(dimName)) {
|
||||
obsCrossfilter = obsCrossfilter.delDimension(dimName);
|
||||
}
|
||||
@@ -201,12 +146,7 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
are just wrappers to lazy create indices.
|
||||
**/
|
||||
|
||||
async select(
|
||||
field: Field,
|
||||
query: Query,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from util/typedCrossfilter
|
||||
spec: any
|
||||
): Promise<AnnoMatrixObsCrossfilter> {
|
||||
async select(field, query, spec) {
|
||||
const { annoMatrix } = this;
|
||||
let { obsCrossfilter } = this;
|
||||
|
||||
@@ -219,9 +159,7 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
|
||||
// grab the data, so we can grab the index.
|
||||
const df = await annoMatrix.fetch(field, query);
|
||||
if (!df) {
|
||||
throw new Error("Dataframe cannot be `undefined`");
|
||||
}
|
||||
|
||||
const dimName = _dimensionNameFromDf(field, df);
|
||||
if (!obsCrossfilter.hasDimension(dimName)) {
|
||||
// lazy index generation - add dimension when first used
|
||||
@@ -238,26 +176,23 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
selectAll(): AnnoMatrixObsCrossfilter {
|
||||
selectAll() {
|
||||
/*
|
||||
Select all on any dimension in this field.
|
||||
*/
|
||||
const { annoMatrix } = this;
|
||||
const currentDims = this.obsCrossfilter.dimensionNames();
|
||||
const obsCrossfilter = currentDims.reduce(
|
||||
(xfltr, dim) => xfltr.select(dim, { mode: "all" }),
|
||||
this.obsCrossfilter
|
||||
);
|
||||
const obsCrossfilter = currentDims.reduce((xfltr, dim) => xfltr.select(dim, { mode: "all" }), this.obsCrossfilter);
|
||||
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
|
||||
}
|
||||
|
||||
countSelected(): number {
|
||||
countSelected() {
|
||||
/* if no data yet indexed in the crossfilter, just say everything is selected */
|
||||
if (this.obsCrossfilter.size() === 0) return this.annoMatrix.nObs;
|
||||
return this.obsCrossfilter.countSelected();
|
||||
}
|
||||
|
||||
allSelectedMask(): Uint8Array {
|
||||
allSelectedMask() {
|
||||
/* if no data yet indexed in the crossfilter, just say everything is selected */
|
||||
if (
|
||||
this.obsCrossfilter.size() === 0 ||
|
||||
@@ -269,7 +204,7 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return this.obsCrossfilter.allSelectedMask();
|
||||
}
|
||||
|
||||
allSelectedLabels(): LabelArray {
|
||||
allSelectedLabels() {
|
||||
/* if no data yet indexed in the crossfilter, just say everything is selected */
|
||||
if (
|
||||
this.obsCrossfilter.size() === 0 ||
|
||||
@@ -283,18 +218,12 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
return index.labels();
|
||||
}
|
||||
|
||||
fillByIsSelected<A extends TypedArray>(
|
||||
array: A,
|
||||
selectedValue: A[0],
|
||||
deselectedValue: A[0]
|
||||
): A {
|
||||
fillByIsSelected(array, selectedValue, deselectedValue) {
|
||||
/* if no data yet indexed in the crossfilter, just say everything is selected */
|
||||
if (
|
||||
this.obsCrossfilter.size() === 0 ||
|
||||
this.obsCrossfilter.dimensionNames().length === 0
|
||||
) {
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// Type 'Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array' is not assignable to type 'A'...
|
||||
return array.fill(selectedValue);
|
||||
}
|
||||
return this.obsCrossfilter.fillByIsSelected(
|
||||
@@ -308,35 +237,25 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
** Private below
|
||||
**/
|
||||
|
||||
_addObsCrossfilterDimension(
|
||||
annoMatrix: AnnoMatrix,
|
||||
obsCrossfilter: Crossfilter,
|
||||
field: Field,
|
||||
df: Dataframe
|
||||
): Crossfilter {
|
||||
_addObsCrossfilterDimension(annoMatrix, obsCrossfilter, field, df) {
|
||||
if (field === "var") return obsCrossfilter;
|
||||
const dimName = _dimensionNameFromDf(field, df);
|
||||
const dimParams = this._getObsDimensionParams(field, df);
|
||||
obsCrossfilter = obsCrossfilter.setData(annoMatrix._cache.obs);
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `...dimParams`: A spread argument must either have a tuple type or be passed to a rest parameter.
|
||||
obsCrossfilter = obsCrossfilter.addDimension(dimName, ...dimParams);
|
||||
return obsCrossfilter;
|
||||
}
|
||||
|
||||
_getColumnBaseType(field: Field, col: LabelType): string {
|
||||
_getColumnBaseType(field, col) {
|
||||
/* Look up the primitive type for this field/col */
|
||||
const colSchema = _getColumnSchema(this.annoMatrix.schema, field, col);
|
||||
return colSchema.type;
|
||||
}
|
||||
|
||||
_getObsDimensionParams(
|
||||
field: Field,
|
||||
df: Dataframe
|
||||
): ObsDimensionParams | undefined {
|
||||
_getObsDimensionParams(field, df) {
|
||||
/* return the crossfilter dimensiontype type and params for this field/dataframe */
|
||||
|
||||
if (field === Field.emb) {
|
||||
if (field === "emb") {
|
||||
/* assumed to be 2D */
|
||||
return ["spatial", df.icol(0).asArray(), df.icol(1).asArray()];
|
||||
}
|
||||
@@ -344,8 +263,6 @@ export default class AnnoMatrixObsCrossfilter {
|
||||
/* assumed to be 1D */
|
||||
const col = df.icol(0);
|
||||
const colName = df.colIndex.getLabel(0);
|
||||
// @ts-expect-error --- TODO revisit:
|
||||
// `colName` Argument of type 'LabelType | undefined' is not assignable to parameter of type 'LabelType'. Type 'undefined' is not assignable to type 'LabelType'.
|
||||
const type = this._getColumnBaseType(field, colName);
|
||||
if (type === "string" || type === "categorical" || type === "boolean") {
|
||||
return ["enum", col.asArray()];
|
||||
@@ -0,0 +1,25 @@
|
||||
export { doBinaryRequest, doFetch } from "../util/actionHelpers";
|
||||
|
||||
/* double URI encode - needed for query-param filters */
|
||||
export function _dubEncURIComp(s) {
|
||||
return encodeURIComponent(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
/* currently unused, consider deleting */
|
||||
export function _fetchResult(promise) {
|
||||
let _status = "pending";
|
||||
const res = promise.then(
|
||||
(r) => {
|
||||
_status = "success";
|
||||
return r;
|
||||
},
|
||||
(e) => {
|
||||
_status = "error";
|
||||
throw e;
|
||||
}
|
||||
);
|
||||
|
||||
res.status = () => _status;
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
export { doBinaryRequest, doFetch } from "../util/actionHelpers";
|
||||
|
||||
/* double URI encode - needed for query-param filters */
|
||||
export function _dubEncURIComp(s: string | number | boolean): string {
|
||||
return encodeURIComponent(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
/* currently unused, consider deleting */
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function _fetchResult(promise: any) {
|
||||
let _status = "pending";
|
||||
const res = promise.then(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(r: any) => {
|
||||
_status = "success";
|
||||
return r;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(e: any) => {
|
||||
_status = "error";
|
||||
throw e;
|
||||
}
|
||||
);
|
||||
|
||||
res.status = () => _status;
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -2,47 +2,31 @@ import { doBinaryRequest, doFetch } from "./fetchHelpers";
|
||||
import { matrixFBSToDataframe } from "../util/stateManager/matrix";
|
||||
import { _getColumnSchema } from "./schema";
|
||||
import {
|
||||
addObsAnnoCategory,
|
||||
addObsAnnoColumn,
|
||||
addObsLayout,
|
||||
removeObsAnnoCategory,
|
||||
removeObsAnnoColumn,
|
||||
addObsAnnoCategory,
|
||||
removeObsAnnoCategory,
|
||||
addObsLayout,
|
||||
} from "../util/stateManager/schemaHelpers";
|
||||
import { isAnyArray } from "../common/types/arraytypes";
|
||||
import { _whereCacheCreate, WhereCache } from "./whereCache";
|
||||
import { isArrayOrTypedArray } from "../util/typeHelpers";
|
||||
import { _whereCacheCreate } from "./whereCache";
|
||||
import AnnoMatrix from "./annoMatrix";
|
||||
import PromiseLimit from "../util/promiseLimit";
|
||||
import {
|
||||
_expectComplexQuery,
|
||||
_expectSimpleQuery,
|
||||
_hashStringValues,
|
||||
_urlEncodeComplexQuery,
|
||||
_expectComplexQuery,
|
||||
_urlEncodeLabelQuery,
|
||||
ComplexQuery,
|
||||
Query,
|
||||
_urlEncodeComplexQuery,
|
||||
_hashStringValues,
|
||||
} from "./query";
|
||||
import {
|
||||
normalizeResponse,
|
||||
normalizeWritableCategoricalSchema,
|
||||
} from "./normalize";
|
||||
import {
|
||||
AnnotationColumnSchema,
|
||||
Field,
|
||||
EmbeddingSchema,
|
||||
RawSchema,
|
||||
} from "../common/types/schema";
|
||||
import {
|
||||
Dataframe,
|
||||
DataframeValue,
|
||||
DataframeValueArray,
|
||||
LabelType,
|
||||
} from "../util/dataframe";
|
||||
|
||||
const promiseThrottle = new PromiseLimit(5);
|
||||
|
||||
export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
baseURL: string;
|
||||
|
||||
/*
|
||||
AnnoMatrix implementation which proxies to HTTP server using the CXG REST API.
|
||||
Used as the base (non-view) instance.
|
||||
@@ -53,7 +37,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
new AnnoMatrixLoader(serverBaseURL, schema) -> instance
|
||||
|
||||
*/
|
||||
constructor(baseURL: string, schema: RawSchema) {
|
||||
constructor(baseURL, schema) {
|
||||
const { nObs, nVar } = schema.dataframe;
|
||||
super(schema, nObs, nVar);
|
||||
|
||||
@@ -68,36 +52,24 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
/**
|
||||
** Public. API described in base class.
|
||||
**/
|
||||
addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix {
|
||||
addObsAnnoCategory(col, category) {
|
||||
/*
|
||||
Add a new category (aka label) to the schema for an obs column.
|
||||
*/
|
||||
const colSchema = _getColumnSchema(
|
||||
this.schema,
|
||||
Field.obs,
|
||||
col
|
||||
) as AnnotationColumnSchema;
|
||||
_writableObsCategoryTypeCheck(colSchema); // throws on error
|
||||
const colSchema = _getColumnSchema(this.schema, "obs", col);
|
||||
_writableCategoryTypeCheck(colSchema); // throws on error
|
||||
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, category);
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
async removeObsAnnoCategory(
|
||||
col: LabelType,
|
||||
category: string,
|
||||
unassignedCategory: string
|
||||
): Promise<AnnoMatrix> {
|
||||
async removeObsAnnoCategory(col, category, unassignedCategory) {
|
||||
/*
|
||||
Remove a single "category" (aka "label") from the data & schema of an obs column.
|
||||
*/
|
||||
const colSchema = _getColumnSchema(
|
||||
this.schema,
|
||||
Field.obs,
|
||||
col
|
||||
) as AnnotationColumnSchema;
|
||||
_writableObsCategoryTypeCheck(colSchema); // throws on error
|
||||
const colSchema = _getColumnSchema(this.schema, "obs", col);
|
||||
_writableCategoryTypeCheck(colSchema); // throws on error
|
||||
|
||||
const newAnnoMatrix = await this.resetObsColumnValues(
|
||||
col,
|
||||
@@ -112,16 +84,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
dropObsColumn(col: LabelType): AnnoMatrix {
|
||||
dropObsColumn(col) {
|
||||
/*
|
||||
drop column from field
|
||||
*/
|
||||
const colSchema = _getColumnSchema(
|
||||
this.schema,
|
||||
Field.obs,
|
||||
col
|
||||
) as AnnotationColumnSchema;
|
||||
_writableObsCheck(colSchema); // throws on error
|
||||
const colSchema = _getColumnSchema(this.schema, "obs", col);
|
||||
_writableCheck(colSchema); // throws on error
|
||||
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col);
|
||||
@@ -129,11 +97,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
addObsColumn<T extends DataframeValueArray>(
|
||||
colSchema: AnnotationColumnSchema,
|
||||
Ctor: new (n: number) => T,
|
||||
value: T
|
||||
): AnnoMatrix {
|
||||
addObsColumn(colSchema, Ctor, value) {
|
||||
/*
|
||||
add a column to field, initializing with value. Value may
|
||||
be one of:
|
||||
@@ -144,7 +108,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
colSchema.writable = true;
|
||||
const colName = colSchema.name;
|
||||
if (
|
||||
_getColumnSchema(this.schema, Field.obs, colName) ||
|
||||
_getColumnSchema(this.schema, "obs", colName) ||
|
||||
this._cache.obs.hasCol(colName)
|
||||
) {
|
||||
throw new Error("column already exists");
|
||||
@@ -152,7 +116,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
|
||||
const newAnnoMatrix = this._clone();
|
||||
let data;
|
||||
if (isAnyArray(value)) {
|
||||
if (isArrayOrTypedArray(value)) {
|
||||
if (value.constructor !== Ctor)
|
||||
throw new Error("Mismatched value array type");
|
||||
if (value.length !== this.nObs)
|
||||
@@ -170,50 +134,35 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix {
|
||||
renameObsColumn(oldCol, newCol) {
|
||||
/*
|
||||
Rename the obs oldColName to newColName. oldCol must be writable.
|
||||
*/
|
||||
const oldColSchema = _getColumnSchema(
|
||||
this.schema,
|
||||
Field.obs,
|
||||
oldCol
|
||||
) as AnnotationColumnSchema;
|
||||
_writableObsCheck(oldColSchema);
|
||||
const oldColSchema = _getColumnSchema(this.schema, "obs", oldCol);
|
||||
_writableCheck(oldColSchema); // throws on error
|
||||
|
||||
const value = this._cache.obs.hasCol(oldCol)
|
||||
? this._cache.obs.col(oldCol).asArray()
|
||||
: undefined;
|
||||
return this.dropObsColumn(oldCol).addObsColumn(
|
||||
{
|
||||
...oldColSchema,
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `name`: Type 'LabelType' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'.
|
||||
name: newCol,
|
||||
},
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `value`: Object is possibly 'undefined'.
|
||||
value.constructor,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
async setObsColumnValues(
|
||||
col: LabelType,
|
||||
rowLabels: Int32Array,
|
||||
value: DataframeValue
|
||||
): Promise<AnnoMatrix> {
|
||||
async setObsColumnValues(col, rowLabels, value) {
|
||||
/*
|
||||
Set all rows identified by rowLabels to value.
|
||||
*/
|
||||
const colSchema = _getColumnSchema(
|
||||
this.schema,
|
||||
Field.obs,
|
||||
col
|
||||
) as AnnotationColumnSchema;
|
||||
_writableObsCategoryTypeCheck(colSchema); // throws on error
|
||||
const colSchema = _getColumnSchema(this.schema, "obs", col);
|
||||
_writableCategoryTypeCheck(colSchema); // throws on error
|
||||
|
||||
// ensure that we have the data in cache before we manipulate it
|
||||
await this.fetch(Field.obs, col);
|
||||
await this.fetch("obs", col);
|
||||
if (!this._cache.obs.hasCol(col))
|
||||
throw new Error("Internal error - user annotation data missing");
|
||||
|
||||
@@ -221,7 +170,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
const data = this._cache.obs.col(col).asArray().slice();
|
||||
for (let i = 0, len = rowIndices.length; i < len; i += 1) {
|
||||
const idx = rowIndices[i];
|
||||
if (idx === -1) throw new Error("Unknown row label");
|
||||
if (idx === undefined) throw new Error("Unknown row label");
|
||||
data[idx] = value;
|
||||
}
|
||||
|
||||
@@ -234,29 +183,19 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
async resetObsColumnValues<T extends DataframeValue>(
|
||||
col: LabelType,
|
||||
oldValue: T,
|
||||
newValue: T
|
||||
): Promise<AnnoMatrix> {
|
||||
async resetObsColumnValues(col, oldValue, newValue) {
|
||||
/*
|
||||
Set all rows with value 'oldValue' to 'newValue'.
|
||||
*/
|
||||
const colSchema = _getColumnSchema(
|
||||
this.schema,
|
||||
Field.obs,
|
||||
col
|
||||
) as AnnotationColumnSchema;
|
||||
_writableObsCategoryTypeCheck(colSchema); // throws on error
|
||||
const colSchema = _getColumnSchema(this.schema, "obs", col);
|
||||
_writableCategoryTypeCheck(colSchema); // throws on error
|
||||
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `colSchema.categories`: Object is possibly 'undefined'.
|
||||
if (!colSchema.categories.includes(oldValue)) {
|
||||
throw new Error("unknown category");
|
||||
}
|
||||
|
||||
// ensure that we have the data in cache before we manipulate it
|
||||
await this.fetch(Field.obs, col);
|
||||
await this.fetch("obs", col);
|
||||
if (!this._cache.obs.hasCol(col))
|
||||
throw new Error("Internal error - user annotation data missing");
|
||||
|
||||
@@ -274,12 +213,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix {
|
||||
addEmbedding(colSchema) {
|
||||
/*
|
||||
add new layout to the obs embeddings
|
||||
*/
|
||||
const { name: colName } = colSchema;
|
||||
if (_getColumnSchema(this.schema, Field.emb, colName)) {
|
||||
if (_getColumnSchema(this.schema, "emb", colName)) {
|
||||
throw new Error("column already exists");
|
||||
}
|
||||
|
||||
@@ -291,10 +230,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
/**
|
||||
** Private below
|
||||
**/
|
||||
async _doLoad(
|
||||
field: Field,
|
||||
query: Query
|
||||
): Promise<[WhereCache | null, Dataframe]> {
|
||||
async _doLoad(field, query) {
|
||||
/*
|
||||
_doLoad - evaluates the query against the field. Returns:
|
||||
* whereCache update: column query map mapping the query to the column labels
|
||||
@@ -321,9 +257,8 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
default:
|
||||
throw new Error("Unknown field name");
|
||||
}
|
||||
|
||||
const buffer = await promiseThrottle.priorityAdd(priority, doRequest);
|
||||
// @ts-expect-error --- TODO revisit:
|
||||
// `buffer`: Argument of type 'unknown' is not assignable to parameter of type 'ArrayBuffer | ArrayBuffer[]'. Type 'unknown' is not assignable to type 'ArrayBuffer[]'.
|
||||
let result = matrixFBSToDataframe(buffer);
|
||||
if (!result || result.isEmpty()) throw Error("Unknown field/col");
|
||||
|
||||
@@ -333,7 +268,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
result.colIndex.labels()
|
||||
);
|
||||
|
||||
result = normalizeResponse(field, this.schema, result);
|
||||
result = normalizeResponse(field, query, this.schema, result);
|
||||
|
||||
return [whereCacheUpdate, result];
|
||||
}
|
||||
@@ -343,26 +278,20 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
|
||||
Utility functions below
|
||||
*/
|
||||
|
||||
function _writableObsCheck(obsColSchema: AnnotationColumnSchema): void {
|
||||
if (!obsColSchema?.writable) {
|
||||
function _writableCheck(colSchema) {
|
||||
if (!colSchema?.writable) {
|
||||
throw new Error("Unknown or readonly obs column");
|
||||
}
|
||||
}
|
||||
|
||||
function _writableObsCategoryTypeCheck(
|
||||
obsColSchema: AnnotationColumnSchema
|
||||
): void {
|
||||
_writableObsCheck(obsColSchema);
|
||||
if (obsColSchema.type !== "categorical") {
|
||||
function _writableCategoryTypeCheck(colSchema) {
|
||||
_writableCheck(colSchema);
|
||||
if (colSchema.type !== "categorical") {
|
||||
throw new Error("column must be categorical");
|
||||
}
|
||||
}
|
||||
|
||||
function _embLoader(
|
||||
baseURL: string,
|
||||
_field: Field,
|
||||
query: Query
|
||||
): () => Promise<ArrayBuffer> {
|
||||
function _embLoader(baseURL, _field, query) {
|
||||
_expectSimpleQuery(query);
|
||||
|
||||
const urlBase = `${baseURL}layout/obs`;
|
||||
@@ -371,11 +300,7 @@ function _embLoader(
|
||||
return () => doBinaryRequest(url);
|
||||
}
|
||||
|
||||
function _obsOrVarLoader(
|
||||
baseURL: string,
|
||||
field: Field,
|
||||
query: Query
|
||||
): () => Promise<ArrayBuffer> {
|
||||
function _obsOrVarLoader(baseURL, field, query) {
|
||||
_expectSimpleQuery(query);
|
||||
|
||||
const urlBase = `${baseURL}annotations/${field}`;
|
||||
@@ -384,26 +309,19 @@ function _obsOrVarLoader(
|
||||
return () => doBinaryRequest(url);
|
||||
}
|
||||
|
||||
function _XLoader(
|
||||
baseURL: string,
|
||||
_field: Field,
|
||||
query: Query
|
||||
): () => Promise<ArrayBuffer> {
|
||||
function _XLoader(baseURL, field, query) {
|
||||
_expectComplexQuery(query);
|
||||
|
||||
// Casting here as query is validated to be complex in _expectComplexQuery above.
|
||||
const complexQuery = query as ComplexQuery;
|
||||
|
||||
if ("where" in complexQuery) {
|
||||
if (query.where) {
|
||||
const urlBase = `${baseURL}data/var`;
|
||||
const urlQuery = _urlEncodeComplexQuery(complexQuery);
|
||||
const urlQuery = _urlEncodeComplexQuery(query);
|
||||
const url = `${urlBase}?${urlQuery}`;
|
||||
return () => doBinaryRequest(url);
|
||||
}
|
||||
|
||||
if ("summarize" in complexQuery) {
|
||||
if (query.summarize) {
|
||||
const urlBase = `${baseURL}summarize/var`;
|
||||
const urlQuery = _urlEncodeComplexQuery(complexQuery);
|
||||
const urlQuery = _urlEncodeComplexQuery(query);
|
||||
|
||||
if (urlBase.length + urlQuery.length < 2000) {
|
||||
const url = `${urlBase}?${urlQuery}`;
|
||||
@@ -11,24 +11,16 @@ Undoable metareducer and the AnnoMatrix private API. It would be helpful
|
||||
to make the Undoable interface better factored.
|
||||
*/
|
||||
|
||||
import { Action, Dispatch, MiddlewareAPI } from "redux";
|
||||
import AnnoMatrix from "./annoMatrix";
|
||||
import { GCHints } from "../common/types/entities";
|
||||
|
||||
const annoMatrixGC =
|
||||
(store: MiddlewareAPI) =>
|
||||
// GC middleware doesn't add any extra types to dispatch; it just executes GC and continues.
|
||||
(next: Dispatch) =>
|
||||
(action: Action): Action => {
|
||||
if (_itIsTimeForGC()) {
|
||||
_doGC(store);
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
const annoMatrixGC = (store) => (next) => (action) => {
|
||||
if (_itIsTimeForGC()) {
|
||||
_doGC(store);
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
|
||||
let lastGCTime = 0;
|
||||
const InterGCDelayMS = 30 * 1000; // 30 seconds
|
||||
function _itIsTimeForGC(): boolean {
|
||||
function _itIsTimeForGC() {
|
||||
/*
|
||||
we don't want to run GC on every dispatch, so throttle it a bit.
|
||||
|
||||
@@ -42,22 +34,17 @@ function _itIsTimeForGC(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function _doGC(store: MiddlewareAPI): void {
|
||||
function _doGC(store) {
|
||||
const state = store.getState();
|
||||
|
||||
// these should probably be a function imported from undoable.js, etc, as
|
||||
// they have overly intimate knowledge of our reducers.
|
||||
// they have overly intimiate knowledge of our reducers.
|
||||
const undoablePast = state["@@undoable/past"];
|
||||
const undoableFuture = state["@@undoable/future"];
|
||||
const undoableStack = undoablePast
|
||||
.concat(undoableFuture)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers
|
||||
.flatMap((snapshot: any) =>
|
||||
snapshot
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers
|
||||
.filter((v: any) => v[0] === "annoMatrix")
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers
|
||||
.map((v: any) => v[1])
|
||||
.flatMap((snapshot) =>
|
||||
snapshot.filter((v) => v[0] === "annoMatrix").map((v) => v[1])
|
||||
);
|
||||
const currentAnnoMatrix = state.annoMatrix;
|
||||
|
||||
@@ -65,17 +52,15 @@ function _doGC(store: MiddlewareAPI): void {
|
||||
We want to identify those matrixes currently "hot", ie, linked from the current annoMatrix,
|
||||
as our current gc algo is more aggressive with those not hot.
|
||||
*/
|
||||
const allAnnoMatrices = new Map<AnnoMatrix, GCHints>(
|
||||
undoableStack.map((m: AnnoMatrix) => [m, { isHot: false }])
|
||||
const allAnnoMatrices = new Map(
|
||||
undoableStack.map((m) => [m, { isHot: false }])
|
||||
);
|
||||
let am = currentAnnoMatrix;
|
||||
while (am?.isView) {
|
||||
while (am) {
|
||||
allAnnoMatrices.set(am, { isHot: true });
|
||||
am = am.viewOf;
|
||||
}
|
||||
allAnnoMatrices.forEach((hints, annoMatrix: AnnoMatrix) =>
|
||||
annoMatrix._gc(hints)
|
||||
);
|
||||
allAnnoMatrices.forEach((hints, annoMatrix) => annoMatrix._gc(hints));
|
||||
}
|
||||
|
||||
export default annoMatrixGC;
|
||||
@@ -5,19 +5,8 @@ import {
|
||||
overflowCategoryLabel,
|
||||
globalConfig,
|
||||
} from "../globals";
|
||||
import { Dataframe, LabelType, DataframeColumn } from "../util/dataframe";
|
||||
import {
|
||||
AnnotationColumnSchema,
|
||||
ArraySchema,
|
||||
Field,
|
||||
Schema,
|
||||
} from "../common/types/schema";
|
||||
|
||||
export function normalizeResponse(
|
||||
field: Field,
|
||||
schema: Schema,
|
||||
response: Dataframe
|
||||
): Dataframe {
|
||||
export function normalizeResponse(field, query, schema, response) {
|
||||
/**
|
||||
* There are a number of assumptions in the front-end about data typing and data
|
||||
* characteristics. This routine will normalize a server response dataframe
|
||||
@@ -42,15 +31,11 @@ export function normalizeResponse(
|
||||
*/
|
||||
|
||||
// currently no data or schema normalization necessary for X or emb
|
||||
if (field !== Field.obs && field !== Field.var) return response;
|
||||
if (field !== "obs" && field !== "var") return response;
|
||||
|
||||
const colLabels = response.colIndex.labels();
|
||||
for (const colLabel of colLabels) {
|
||||
const colSchema = _getColumnSchema(
|
||||
schema,
|
||||
field,
|
||||
colLabel
|
||||
) as AnnotationColumnSchema;
|
||||
const colSchema = _getColumnSchema(schema, field, colLabel);
|
||||
const isIndex = _isIndex(schema, field, colLabel);
|
||||
const { type, writable } = colSchema;
|
||||
|
||||
@@ -74,7 +59,7 @@ export function normalizeResponse(
|
||||
return response;
|
||||
}
|
||||
|
||||
function castColumnToBoolean(df: Dataframe, label: LabelType): Dataframe {
|
||||
function castColumnToBoolean(df, label) {
|
||||
const colData = df.col(label).asArray();
|
||||
const newColData = new Array(colData.length);
|
||||
for (let i = 0; i < colData.length; i += 1) newColData[i] = !!colData[i];
|
||||
@@ -82,16 +67,13 @@ function castColumnToBoolean(df: Dataframe, label: LabelType): Dataframe {
|
||||
return df;
|
||||
}
|
||||
|
||||
export function normalizeWritableCategoricalSchema(
|
||||
colSchema: AnnotationColumnSchema, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
col: DataframeColumn
|
||||
): ArraySchema {
|
||||
export function normalizeWritableCategoricalSchema(colSchema, col) {
|
||||
/*
|
||||
Ensure all enum writable / categorical schema have a categories array, that
|
||||
the categories array contains all unique values in the data array, AND that
|
||||
the array is UI sorted.
|
||||
*/
|
||||
const categorySet = new Set<string>(
|
||||
const categorySet = new Set(
|
||||
col.summarizeCategorical().categories.concat(colSchema.categories ?? [])
|
||||
);
|
||||
if (!categorySet.has(unassignedCategoryLabel)) {
|
||||
@@ -101,19 +83,15 @@ export function normalizeWritableCategoricalSchema(
|
||||
return colSchema;
|
||||
}
|
||||
|
||||
export function normalizeCategorical(
|
||||
df: Dataframe,
|
||||
colLabel: LabelType,
|
||||
colSchema: AnnotationColumnSchema
|
||||
): Dataframe {
|
||||
export function normalizeCategorical(df, colLabel, colSchema) {
|
||||
/*
|
||||
If writable, ensure schema matches data and we have an unassigned label
|
||||
|
||||
If not writable, ensure schema matches data and that we consolidate labels in excess
|
||||
of "top N" into an overflow labels.
|
||||
*/
|
||||
const { writable } = colSchema;
|
||||
const col = df.col(colLabel);
|
||||
|
||||
if (writable) {
|
||||
// writable (aka user) annotations
|
||||
normalizeWritableCategoricalSchema(colSchema, col);
|
||||
@@ -125,7 +103,7 @@ export function normalizeCategorical(
|
||||
|
||||
// consolidate all categories from data and schema into a single list
|
||||
const colDataSummary = col.summarizeCategorical();
|
||||
const allCategories = new Set<string>(
|
||||
const allCategories = new Set(
|
||||
colDataSummary.categories.concat(colSchema.categories ?? [])
|
||||
);
|
||||
|
||||
@@ -1,52 +1,21 @@
|
||||
import sha1 from "sha1";
|
||||
import { _dubEncURIComp } from "./fetchHelpers";
|
||||
import { Field } from "../common/types/schema";
|
||||
import { LabelType } from "../util/dataframe";
|
||||
|
||||
/**
|
||||
* Query utilities, mostly for debugging support and validation.
|
||||
*/
|
||||
|
||||
export type ComplexQuery = SummarizeQuery | WhereQuery;
|
||||
|
||||
export type Query = LabelType | ComplexQuery;
|
||||
|
||||
interface SummarizeQuery {
|
||||
summarize: SummarizeQueryTerm;
|
||||
}
|
||||
|
||||
interface SummarizeQueryTerm {
|
||||
column: string;
|
||||
field: string;
|
||||
method: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
interface WhereQuery {
|
||||
where: WhereQueryTerm;
|
||||
}
|
||||
|
||||
interface WhereQueryTerm {
|
||||
column: string;
|
||||
field: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function _expectSimpleQuery(query: Query): void {
|
||||
if (typeof query === "object") throw new Error("expected simple query");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize & error check the query.
|
||||
* @param {Query} query - the query
|
||||
* @returns {Query} - the normalized query
|
||||
* @param {object | string} query - the query
|
||||
* @returns {object | string} - the normalized query
|
||||
*/
|
||||
export function _queryValidate(query: Query): Query {
|
||||
export function _queryValidate(query) {
|
||||
if (typeof query !== "object") return query;
|
||||
|
||||
if ("where" in query && "summarize" in query)
|
||||
if (query.where && query.summarize)
|
||||
throw new Error("query may not specify both where and summarize");
|
||||
if ("where" in query) {
|
||||
if (query.where) {
|
||||
const {
|
||||
field: queryField,
|
||||
column: queryColumn,
|
||||
@@ -56,7 +25,7 @@ export function _queryValidate(query: Query): Query {
|
||||
throw new Error("Incomplete where query");
|
||||
return query;
|
||||
}
|
||||
if ("summarize" in query) {
|
||||
if (query.summarize) {
|
||||
const {
|
||||
field: queryField,
|
||||
column: queryColumn,
|
||||
@@ -71,7 +40,11 @@ export function _queryValidate(query: Query): Query {
|
||||
throw new Error("query must specify one of where or summarize");
|
||||
}
|
||||
|
||||
export function _expectComplexQuery(query: Query): void {
|
||||
export function _expectSimpleQuery(query) {
|
||||
if (typeof query === "object") throw new Error("expected simple query");
|
||||
}
|
||||
|
||||
export function _expectComplexQuery(query) {
|
||||
if (typeof query !== "object") throw new Error("expected complex query");
|
||||
}
|
||||
|
||||
@@ -80,12 +53,12 @@ export function _expectComplexQuery(query: Query): void {
|
||||
*
|
||||
* @param {string} field
|
||||
* @param {string|object} query
|
||||
* @returns {string} the key
|
||||
* @returns the key
|
||||
*/
|
||||
export function _queryCacheKey(field: Field, query: Query): string {
|
||||
export function _queryCacheKey(field, query) {
|
||||
if (typeof query === "object") {
|
||||
// complex query
|
||||
if ("where" in query) {
|
||||
if (query.where) {
|
||||
const {
|
||||
field: queryField,
|
||||
column: queryColumn,
|
||||
@@ -93,7 +66,7 @@ export function _queryCacheKey(field: Field, query: Query): string {
|
||||
} = query.where;
|
||||
return `${field}/${queryField}/${queryColumn}/${queryValue}`;
|
||||
}
|
||||
if ("summarize" in query) {
|
||||
if (query.summarize) {
|
||||
const {
|
||||
method,
|
||||
field: queryField,
|
||||
@@ -111,34 +84,34 @@ export function _queryCacheKey(field: Field, query: Query): string {
|
||||
return `${field}/${query}`;
|
||||
}
|
||||
|
||||
function _urlEncodeWhereQuery(q: WhereQueryTerm): string {
|
||||
function _urlEncodeWhereQuery(q) {
|
||||
const { field: queryField, column: queryColumn, value: queryValue } = q;
|
||||
return `${_dubEncURIComp(queryField)}:${_dubEncURIComp(
|
||||
queryColumn
|
||||
)}=${_dubEncURIComp(queryValue)}`;
|
||||
}
|
||||
|
||||
function _urlEncodeSummarizeQuery(q: SummarizeQueryTerm): string {
|
||||
function _urlEncodeSummarizeQuery(q) {
|
||||
const { method, field, column, values } = q;
|
||||
const filter = values
|
||||
.map((value: string) => _urlEncodeWhereQuery({ field, column, value }))
|
||||
.map((value) => _urlEncodeWhereQuery({ field, column, value }))
|
||||
.join("&");
|
||||
return `method=${method}&${filter}`;
|
||||
}
|
||||
|
||||
export function _urlEncodeComplexQuery(q: ComplexQuery): string {
|
||||
export function _urlEncodeComplexQuery(q) {
|
||||
if (typeof q === "object") {
|
||||
if ("where" in q) {
|
||||
if (q.where) {
|
||||
return _urlEncodeWhereQuery(q.where);
|
||||
}
|
||||
if ("summarize" in q) {
|
||||
if (q.summarize) {
|
||||
return _urlEncodeSummarizeQuery(q.summarize);
|
||||
}
|
||||
}
|
||||
throw new Error("Unrecognized complex query type");
|
||||
}
|
||||
|
||||
export function _urlEncodeLabelQuery(colKey: string, q: Query): string {
|
||||
export function _urlEncodeLabelQuery(colKey, q) {
|
||||
if (!colKey) throw new Error("Unsupported query by name");
|
||||
if (typeof q !== "string") throw new Error("Query must be a simple label.");
|
||||
return `${colKey}=${encodeURIComponent(q)}`;
|
||||
@@ -147,6 +120,7 @@ export function _urlEncodeLabelQuery(colKey: string, q: Query): string {
|
||||
/**
|
||||
* Generate the column key the server will send us for this query.
|
||||
*/
|
||||
export function _hashStringValues(arrayOfString: string[]): string {
|
||||
return sha1(arrayOfString.join(""));
|
||||
export function _hashStringValues(arrayOfString) {
|
||||
const hash = sha1(arrayOfString.join(""));
|
||||
return hash;
|
||||
}
|
||||
@@ -1,54 +1,34 @@
|
||||
/*
|
||||
Private helper functions related to schema
|
||||
*/
|
||||
import {
|
||||
AnnotationColumnSchema,
|
||||
ArraySchema,
|
||||
Field,
|
||||
Schema,
|
||||
} from "../common/types/schema";
|
||||
import { LabelArray, LabelType } from "../util/dataframe/types";
|
||||
|
||||
export function _getColumnSchema(
|
||||
schema: Schema,
|
||||
field: Field,
|
||||
col: LabelType
|
||||
): ArraySchema {
|
||||
export function _getColumnSchema(schema, field, col) {
|
||||
/* look up the column definition */
|
||||
switch (field) {
|
||||
case Field.obs:
|
||||
case "obs":
|
||||
if (typeof col === "object")
|
||||
throw new Error("unable to get column schema by query");
|
||||
return schema.annotations.obsByName[col];
|
||||
case Field.var:
|
||||
case "var":
|
||||
if (typeof col === "object")
|
||||
throw new Error("unable to get column schema by query");
|
||||
return schema.annotations.varByName[col];
|
||||
case Field.emb:
|
||||
case "emb":
|
||||
if (typeof col === "object")
|
||||
throw new Error("unable to get column schema by query");
|
||||
return schema.layout.obsByName[col];
|
||||
case Field.X:
|
||||
case "X":
|
||||
return schema.dataframe;
|
||||
default:
|
||||
throw new Error(`unknown field name: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function _isIndex(
|
||||
schema: Schema,
|
||||
field: Field.obs | Field.var,
|
||||
col: LabelType
|
||||
): boolean {
|
||||
export function _isIndex(schema, field, col) {
|
||||
const index = schema.annotations?.[field].index;
|
||||
return !!(index && index === col);
|
||||
return index && index === col;
|
||||
}
|
||||
|
||||
export function _getColumnDimensionNames(
|
||||
schema: Schema,
|
||||
field: Field,
|
||||
col: LabelType
|
||||
): LabelArray | undefined {
|
||||
export function _getColumnDimensionNames(schema, field, col) {
|
||||
/*
|
||||
field/col may be an alias for multiple columns. Currently used to map ND
|
||||
values to 1D dataframe columns for embeddings/layout. Signified by the presence
|
||||
@@ -58,33 +38,30 @@ export function _getColumnDimensionNames(
|
||||
if (!colSchema) {
|
||||
return undefined;
|
||||
}
|
||||
if ("dims" in colSchema) {
|
||||
return colSchema.dims;
|
||||
}
|
||||
return [col];
|
||||
return colSchema.dims || [col];
|
||||
}
|
||||
|
||||
export function _schemaColumns(schema: Schema, field: Field): string[] {
|
||||
export function _schemaColumns(schema, field) {
|
||||
switch (field) {
|
||||
case Field.obs:
|
||||
case "obs":
|
||||
return Object.keys(schema.annotations.obsByName);
|
||||
case Field.var:
|
||||
case "var":
|
||||
return Object.keys(schema.annotations.varByName);
|
||||
case Field.emb:
|
||||
case "emb":
|
||||
return Object.keys(schema.layout.obsByName);
|
||||
default:
|
||||
throw new Error(`unknown field name: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function _getWritableColumns(schema: Schema, field: Field): string[] {
|
||||
if (field !== Field.obs) return [];
|
||||
export function _getWritableColumns(schema, field) {
|
||||
if (field !== "obs") return [];
|
||||
return schema.annotations.obs.columns
|
||||
.filter((v: AnnotationColumnSchema) => v.writable)
|
||||
.map((v: AnnotationColumnSchema) => v.name);
|
||||
.filter((v) => v.writable)
|
||||
.map((v) => v.name);
|
||||
}
|
||||
|
||||
export function _isContinuousType(schema: ArraySchema): boolean {
|
||||
export function _isContinuousType(schema) {
|
||||
const { type } = schema;
|
||||
return !(type === "string" || type === "boolean" || type === "categorical");
|
||||
}
|
||||
@@ -4,18 +4,8 @@ instances of AnnoMatrix, implementing common UI functions.
|
||||
*/
|
||||
|
||||
import { AnnoMatrixRowSubsetView, AnnoMatrixClipView } from "./views";
|
||||
import AnnoMatrix from "./annoMatrix";
|
||||
import {
|
||||
DenseInt32Index,
|
||||
IdentityInt32Index,
|
||||
KeyIndex,
|
||||
} from "../util/dataframe";
|
||||
import { OffsetArray } from "../util/dataframe/types";
|
||||
|
||||
export function isubsetMask(
|
||||
annoMatrix: AnnoMatrix,
|
||||
obsMask: Uint8Array
|
||||
): AnnoMatrixRowSubsetView {
|
||||
export function isubsetMask(annoMatrix, obsMask) {
|
||||
/*
|
||||
Subset annomatrix to contain the rows which have truish value in the mask.
|
||||
Maks length must equal annoMatrix.nObs (row count).
|
||||
@@ -23,10 +13,7 @@ export function isubsetMask(
|
||||
return isubset(annoMatrix, _maskToList(obsMask));
|
||||
}
|
||||
|
||||
export function isubset(
|
||||
annoMatrix: AnnoMatrix,
|
||||
obsOffsets: OffsetArray
|
||||
): AnnoMatrixRowSubsetView {
|
||||
export function isubset(annoMatrix, obsOffsets) {
|
||||
/*
|
||||
Subset annomatrix to contain the positions contained in the obsOffsets array
|
||||
|
||||
@@ -38,10 +25,7 @@ export function isubset(
|
||||
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
|
||||
}
|
||||
|
||||
export function subset(
|
||||
annoMatrix: AnnoMatrix,
|
||||
obsLabels: Int32Array
|
||||
): AnnoMatrixRowSubsetView {
|
||||
export function subset(annoMatrix, obsLabels) {
|
||||
/*
|
||||
subset based on labels
|
||||
*/
|
||||
@@ -49,21 +33,14 @@ export function subset(
|
||||
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
|
||||
}
|
||||
|
||||
export function subsetByIndex(
|
||||
annoMatrix: AnnoMatrix,
|
||||
obsIndex: DenseInt32Index | IdentityInt32Index | KeyIndex
|
||||
): AnnoMatrixRowSubsetView {
|
||||
export function subsetByIndex(annoMatrix, obsIndex) {
|
||||
/*
|
||||
subset based upon the new obs index.
|
||||
*/
|
||||
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
|
||||
}
|
||||
|
||||
export function clip(
|
||||
annoMatrix: AnnoMatrix,
|
||||
qmin: number,
|
||||
qmax: number
|
||||
): AnnoMatrix {
|
||||
export function clip(annoMatrix, qmin, qmax) {
|
||||
/*
|
||||
Create a view that clips all continuous data to the [min, max] range.
|
||||
The matrix shape does not change, but the continuous values outside the
|
||||
@@ -76,8 +53,11 @@ export function clip(
|
||||
Private utility functions below
|
||||
*/
|
||||
|
||||
function _maskToList(mask: Uint8Array): OffsetArray {
|
||||
function _maskToList(mask) {
|
||||
/* convert masks to lists - method wastes space, but is fast */
|
||||
if (!mask) {
|
||||
return null;
|
||||
}
|
||||
const list = new Int32Array(mask.length);
|
||||
let elems = 0;
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
@@ -5,51 +5,25 @@ Views on the annomatrix. all API here is defined in viewCreators.js and annoMat
|
||||
*/
|
||||
import clip from "../util/clip";
|
||||
import AnnoMatrix from "./annoMatrix";
|
||||
import { _whereCacheCreate, WhereCache } from "./whereCache";
|
||||
import { _whereCacheCreate } from "./whereCache";
|
||||
import { _isContinuousType, _getColumnSchema } from "./schema";
|
||||
import {
|
||||
Dataframe,
|
||||
DataframeValue,
|
||||
DataframeValueArray,
|
||||
LabelType,
|
||||
} from "../util/dataframe";
|
||||
import { Query } from "./query";
|
||||
import {
|
||||
AnnotationColumnSchema,
|
||||
ArraySchema,
|
||||
Field,
|
||||
EmbeddingSchema,
|
||||
} from "../common/types/schema";
|
||||
import { LabelIndexBase } from "../util/dataframe/labelIndex";
|
||||
|
||||
type MapFn = (
|
||||
field: Field,
|
||||
colLabel: LabelType,
|
||||
colSchema: ArraySchema,
|
||||
colData: DataframeValueArray,
|
||||
df: Dataframe
|
||||
) => DataframeValueArray;
|
||||
|
||||
abstract class AnnoMatrixView extends AnnoMatrix {
|
||||
constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase | null = null) {
|
||||
class AnnoMatrixView extends AnnoMatrix {
|
||||
constructor(viewOf, rowIndex = null) {
|
||||
const nObs = rowIndex ? rowIndex.size() : viewOf.nObs;
|
||||
super(viewOf.schema, nObs, viewOf.nVar, rowIndex || viewOf.rowIndex);
|
||||
this.viewOf = viewOf;
|
||||
this.isView = true;
|
||||
}
|
||||
|
||||
addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix {
|
||||
addObsAnnoCategory(col, category) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = this.viewOf.addObsAnnoCategory(col, category);
|
||||
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
async removeObsAnnoCategory(
|
||||
col: LabelType,
|
||||
category: string,
|
||||
unassignedCategory: string
|
||||
): Promise<AnnoMatrix> {
|
||||
async removeObsAnnoCategory(col, category, unassignedCategory) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = await this.viewOf.removeObsAnnoCategory(
|
||||
col,
|
||||
@@ -60,7 +34,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
dropObsColumn(col: LabelType): AnnoMatrix {
|
||||
dropObsColumn(col) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = this.viewOf.dropObsColumn(col);
|
||||
newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col);
|
||||
@@ -68,29 +42,21 @@ abstract class AnnoMatrixView extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
addObsColumn<T extends DataframeValueArray>(
|
||||
colSchema: AnnotationColumnSchema,
|
||||
Ctor: new (n: number) => T,
|
||||
value: T
|
||||
): AnnoMatrix {
|
||||
addObsColumn(colSchema, Ctor, value) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = this.viewOf.addObsColumn(colSchema, Ctor, value);
|
||||
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix {
|
||||
renameObsColumn(oldCol, newCol) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = this.viewOf.renameObsColumn(oldCol, newCol);
|
||||
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
async setObsColumnValues(
|
||||
col: LabelType,
|
||||
rowLabels: Int32Array,
|
||||
value: DataframeValue
|
||||
): Promise<AnnoMatrix> {
|
||||
async setObsColumnValues(col, rowLabels, value) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = await this.viewOf.setObsColumnValues(
|
||||
col,
|
||||
@@ -102,11 +68,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
async resetObsColumnValues<T extends DataframeValue>(
|
||||
col: LabelType,
|
||||
oldValue: T,
|
||||
newValue: T
|
||||
): Promise<AnnoMatrix> {
|
||||
async resetObsColumnValues(col, oldValue, newValue) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = await this.viewOf.resetObsColumnValues(
|
||||
col,
|
||||
@@ -118,7 +80,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
|
||||
return newAnnoMatrix;
|
||||
}
|
||||
|
||||
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix {
|
||||
addEmbedding(colSchema) {
|
||||
const newAnnoMatrix = this._clone();
|
||||
newAnnoMatrix.viewOf = this.viewOf.addEmbedding(colSchema);
|
||||
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
|
||||
@@ -127,32 +89,21 @@ abstract class AnnoMatrixView extends AnnoMatrix {
|
||||
}
|
||||
|
||||
class AnnoMatrixMapView extends AnnoMatrixView {
|
||||
mapFn: MapFn;
|
||||
|
||||
/*
|
||||
A view which knows how to transform its data.
|
||||
*/
|
||||
constructor(viewOf: AnnoMatrix, mapFn: MapFn) {
|
||||
A view which knows how to transform its data.
|
||||
*/
|
||||
constructor(viewOf, mapFn) {
|
||||
super(viewOf);
|
||||
this.mapFn = mapFn;
|
||||
}
|
||||
|
||||
async _doLoad(
|
||||
field: Field,
|
||||
query: Query
|
||||
): Promise<[WhereCache | null, Dataframe]> {
|
||||
async _doLoad(field, query) {
|
||||
const df = await this.viewOf._fetch(field, query);
|
||||
const dfMapped = df.mapColumns(
|
||||
(colData: DataframeValueArray, colIdx: number) => {
|
||||
const colLabel = df.colIndex.getLabel(colIdx);
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `colLabel`: Argument of type 'LabelType | undefined' is not assignable to parameter of type 'LabelType'.
|
||||
const colSchema = _getColumnSchema(this.schema, field, colLabel);
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `colLabel`: Argument of type 'LabelType | undefined' is not assignable to parameter of type 'LabelType'.
|
||||
return this.mapFn(field, colLabel, colSchema, colData, df);
|
||||
}
|
||||
);
|
||||
const dfMapped = df.mapColumns((colData, colIdx) => {
|
||||
const colLabel = df.colIndex.getLabel(colIdx);
|
||||
const colSchema = _getColumnSchema(this.schema, field, colLabel);
|
||||
return this.mapFn(field, colLabel, colSchema, colData, df);
|
||||
});
|
||||
const whereCacheUpdate = _whereCacheCreate(
|
||||
field,
|
||||
query,
|
||||
@@ -163,23 +114,12 @@ class AnnoMatrixMapView extends AnnoMatrixView {
|
||||
}
|
||||
|
||||
export class AnnoMatrixClipView extends AnnoMatrixMapView {
|
||||
clipRange: [number, number];
|
||||
|
||||
isClipped: boolean;
|
||||
|
||||
/*
|
||||
A view which is a clipped transformation of its parent
|
||||
*/
|
||||
constructor(viewOf: AnnoMatrix, qmin: number, qmax: number) {
|
||||
super(
|
||||
viewOf,
|
||||
(
|
||||
field: Field,
|
||||
colLabel: LabelType,
|
||||
colSchema: ArraySchema,
|
||||
colData: DataframeValueArray,
|
||||
df: Dataframe
|
||||
) => _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax)
|
||||
A view which is a clipped transformation of its parent
|
||||
*/
|
||||
constructor(viewOf, qmin, qmax) {
|
||||
super(viewOf, (field, colLabel, colSchema, colData, df) =>
|
||||
_clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax)
|
||||
);
|
||||
this.isClipped = true;
|
||||
this.clipRange = [qmin, qmax];
|
||||
@@ -189,21 +129,18 @@ export class AnnoMatrixClipView extends AnnoMatrixMapView {
|
||||
|
||||
export class AnnoMatrixRowSubsetView extends AnnoMatrixView {
|
||||
/*
|
||||
A view which is a subset of total rows.
|
||||
*/
|
||||
constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase) {
|
||||
A view which is a subset of total rows.
|
||||
*/
|
||||
constructor(viewOf, rowIndex) {
|
||||
super(viewOf, rowIndex);
|
||||
Object.seal(this);
|
||||
}
|
||||
|
||||
async _doLoad(
|
||||
field: Field,
|
||||
query: Query
|
||||
): Promise<[WhereCache | null, Dataframe]> {
|
||||
async _doLoad(field, query) {
|
||||
const df = await this.viewOf._fetch(field, query);
|
||||
|
||||
// don't try to row-subset the var dimension.
|
||||
if (field === Field.var) {
|
||||
if (field === "var") {
|
||||
return [null, df];
|
||||
}
|
||||
|
||||
@@ -221,23 +158,15 @@ export class AnnoMatrixRowSubsetView extends AnnoMatrixView {
|
||||
Utility functions below
|
||||
*/
|
||||
|
||||
function _clipAnnoMatrix(
|
||||
field: Field,
|
||||
colLabel: LabelType,
|
||||
colSchema: ArraySchema,
|
||||
colData: DataframeValueArray,
|
||||
df: Dataframe,
|
||||
qmin: number,
|
||||
qmax: number
|
||||
): DataframeValueArray {
|
||||
function _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) {
|
||||
/* only clip obs and var scalar columns */
|
||||
if (field !== Field.obs && field !== Field.X) return colData;
|
||||
if (field !== "obs" && field !== "X") return colData;
|
||||
if (!_isContinuousType(colSchema)) return colData;
|
||||
if (qmin < 0) qmin = 0;
|
||||
if (qmax > 1) qmax = 1;
|
||||
if (qmin === 0 && qmax === 1) return colData;
|
||||
|
||||
const quantiles = df.col(colLabel).summarizeContinuous().percentiles;
|
||||
const quantiles = df.col(colLabel).summarize().percentiles;
|
||||
const lower = quantiles[100 * qmin];
|
||||
const upper = quantiles[100 * qmax];
|
||||
const clippedData = clip(colData.slice(), lower, upper, Number.NaN);
|
||||
@@ -2,7 +2,7 @@
|
||||
Private support functions.
|
||||
|
||||
This implements a query resolver cache, mapping a query onto the column labels
|
||||
resolved by that query. These labels are then used to manage the actual data cache,
|
||||
resolved by that query. These labels are then used to manage the acutal data cache,
|
||||
which stores data by the resolved label.
|
||||
|
||||
There are three query forms:
|
||||
@@ -49,33 +49,9 @@ creates a cache entry of:
|
||||
}
|
||||
*/
|
||||
import { _getColumnDimensionNames } from "./schema";
|
||||
import { _hashStringValues, Query } from "./query";
|
||||
import { Field, Schema } from "../common/types/schema";
|
||||
import { LabelArray } from "../util/dataframe/types";
|
||||
import { _hashStringValues } from "./query";
|
||||
|
||||
export interface WhereCache {
|
||||
summarize?: {
|
||||
[key: string]: {
|
||||
[key: string]: WhereCacheTerms;
|
||||
};
|
||||
};
|
||||
where?: {
|
||||
[key: string]: WhereCacheTerms;
|
||||
};
|
||||
}
|
||||
|
||||
export type WhereCacheColumnLabels = LabelArray;
|
||||
|
||||
interface WhereCacheTerms {
|
||||
[key: string]: Map<string, Map<string, WhereCacheColumnLabels>>;
|
||||
}
|
||||
|
||||
export function _whereCacheGet(
|
||||
whereCache: WhereCache,
|
||||
schema: Schema,
|
||||
field: Field,
|
||||
query: Query
|
||||
): WhereCacheColumnLabels | [undefined] {
|
||||
export function _whereCacheGet(whereCache, schema, field, query) {
|
||||
/*
|
||||
query will either be an where query (object) or a column name (string).
|
||||
|
||||
@@ -83,7 +59,7 @@ export function _whereCacheGet(
|
||||
*/
|
||||
|
||||
if (typeof query === "object") {
|
||||
if ("where" in query) {
|
||||
if (query.where) {
|
||||
const {
|
||||
field: queryField,
|
||||
column: queryColumn,
|
||||
@@ -92,7 +68,7 @@ export function _whereCacheGet(
|
||||
const columnMap = whereCache?.where?.[field]?.[queryField];
|
||||
return columnMap?.get(queryColumn)?.get(queryValue) ?? [undefined];
|
||||
}
|
||||
if ("summarize" in query) {
|
||||
if (query.summarize) {
|
||||
const {
|
||||
method,
|
||||
field: queryField,
|
||||
@@ -109,17 +85,13 @@ export function _whereCacheGet(
|
||||
return _getColumnDimensionNames(schema, field, query) ?? [undefined];
|
||||
}
|
||||
|
||||
export function _whereCacheCreate(
|
||||
field: Field,
|
||||
query: Query,
|
||||
columnLabels: LabelArray
|
||||
): WhereCache | null {
|
||||
export function _whereCacheCreate(field, query, columnLabels) {
|
||||
/*
|
||||
Create a new whereCache
|
||||
*/
|
||||
if (typeof query !== "object") return null;
|
||||
|
||||
if ("where" in query) {
|
||||
if (query.where) {
|
||||
const {
|
||||
field: queryField,
|
||||
column: queryColumn,
|
||||
@@ -135,7 +107,7 @@ export function _whereCacheCreate(
|
||||
},
|
||||
};
|
||||
}
|
||||
if ("summarize" in query) {
|
||||
if (query.summarize) {
|
||||
const {
|
||||
method,
|
||||
field: queryField,
|
||||
@@ -159,25 +131,20 @@ export function _whereCacheCreate(
|
||||
return {};
|
||||
}
|
||||
|
||||
function __mergeQueries(dst: WhereCacheTerms, src: WhereCacheTerms) {
|
||||
function __mergeQueries(dst, src) {
|
||||
for (const [queryField, columnMap] of Object.entries(src)) {
|
||||
dst[queryField] = dst[queryField] || new Map();
|
||||
for (const [queryColumn, valueMap] of columnMap) {
|
||||
if (!dst[queryField].has(queryColumn))
|
||||
dst[queryField].set(queryColumn, new Map());
|
||||
for (const [queryValue, columnLabels] of valueMap) {
|
||||
// @ts-expect-error ts-migrate --- TODO revisit:
|
||||
// `dst[queryField].get(queryColumn)` Object is possibly 'undefined'.
|
||||
dst[queryField].get(queryColumn).set(queryValue, columnLabels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __whereCacheMerge(
|
||||
dst: WhereCache,
|
||||
src: WhereCache | null
|
||||
): WhereCache {
|
||||
function __whereCacheMerge(dst, src) {
|
||||
/*
|
||||
merge src into dst (modifies dst)
|
||||
*/
|
||||
@@ -204,6 +171,6 @@ function __whereCacheMerge(
|
||||
return dst;
|
||||
}
|
||||
|
||||
export function _whereCacheMerge(...caches: (WhereCache | null)[]): WhereCache {
|
||||
return caches.reduce(__whereCacheMerge, {} as WhereCache);
|
||||
export function _whereCacheMerge(...caches) {
|
||||
return caches.reduce(__whereCacheMerge, {});
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* Utility type and interface definitions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TypedArrays that can be assigned to a number.
|
||||
*/
|
||||
export type TypedArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array;
|
||||
|
||||
export type UnsignedTypedArray = Uint8Array | Uint16Array | Uint32Array;
|
||||
export type FloatTypedArray = Float32Array | Float64Array;
|
||||
|
||||
export type TypedArrayConstructor =
|
||||
| Int8ArrayConstructor
|
||||
| Uint8ArrayConstructor
|
||||
| Int16ArrayConstructor
|
||||
| Uint16ArrayConstructor
|
||||
| Int32ArrayConstructor
|
||||
| Uint32ArrayConstructor
|
||||
| Float32ArrayConstructor
|
||||
| Float64ArrayConstructor;
|
||||
|
||||
export type AnyArray = Array<unknown> | TypedArray;
|
||||
|
||||
export interface GenericArrayConstructor<T extends AnyArray> {
|
||||
new (
|
||||
...args: ConstructorParameters<
|
||||
typeof Int8Array &
|
||||
typeof Uint8Array &
|
||||
typeof Int16Array &
|
||||
typeof Uint16Array &
|
||||
typeof Int32Array &
|
||||
typeof Uint32Array &
|
||||
typeof Float32Array &
|
||||
typeof Float64Array &
|
||||
typeof Array
|
||||
>
|
||||
): T;
|
||||
}
|
||||
|
||||
export type NumberArray = Array<number> | TypedArray;
|
||||
|
||||
export type Int8 = Int8Array[0];
|
||||
export type Uint8 = Uint8Array[0];
|
||||
export type Int16 = Int16Array[0];
|
||||
export type Uint16 = Uint16Array[0];
|
||||
export type Int32 = Int32Array[0];
|
||||
export type Uint32 = Uint32Array[0];
|
||||
export type Float32 = Float32Array[0];
|
||||
export type Float64 = Float64Array[0];
|
||||
|
||||
/**
|
||||
* Test if the parameter is a TypedArray.
|
||||
* @param tbd - value to be tested
|
||||
* @returns true if `tbd` is a TypedArray, false if not.
|
||||
*/
|
||||
export function isTypedArray(tbd: unknown): tbd is TypedArray {
|
||||
return (
|
||||
ArrayBuffer.isView(tbd) &&
|
||||
Object.prototype.toString.call(tbd) !== "[object DataView]"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the paramter is a float TypedArray
|
||||
* @param tbd - value to be tested
|
||||
* @returns - true if `tbd` is a float typed array.
|
||||
*/
|
||||
export function isFloatTypedArray(tbd: unknown): tbd is FloatTypedArray {
|
||||
return tbd instanceof Float32Array || tbd instanceof Float64Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the paramter is a float TypedArray
|
||||
* @param tbd - value to be tested
|
||||
* @returns - true if `tbd` is a float typed array.
|
||||
*/
|
||||
export function isUnsignedTypedArray(tbd: unknown): tbd is UnsignedTypedArray {
|
||||
return (
|
||||
tbd instanceof Uint8Array ||
|
||||
tbd instanceof Uint16Array ||
|
||||
tbd instanceof Uint32Array
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the parameter is a TypedArray or Array
|
||||
* @param tbd - value to be tested
|
||||
* @returns - true if `tbd` is a TypedArray or Array
|
||||
*/
|
||||
export function isAnyArray(tbd: unknown): tbd is AnyArray {
|
||||
return Array.isArray(tbd) || isTypedArray(tbd);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// If a globally shared type or interface doesn't have a clear owner, put it here
|
||||
|
||||
/**
|
||||
* Flags informing garbage collection-related logic.
|
||||
*/
|
||||
export interface GCHints {
|
||||
isHot: boolean;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
export type Category = number | string | boolean;
|
||||
|
||||
export interface AnnotationColumnSchema {
|
||||
categories?: Category[];
|
||||
name: string;
|
||||
type: "string" | "float32" | "int32" | "categorical" | "boolean";
|
||||
writable: boolean;
|
||||
}
|
||||
|
||||
export interface XMatrixSchema {
|
||||
nObs: number;
|
||||
nVar: number;
|
||||
// TODO(thuang): Not sure what other types are available
|
||||
type: "float32";
|
||||
}
|
||||
|
||||
export interface EmbeddingSchema {
|
||||
dims: string[];
|
||||
name: string;
|
||||
// TODO(thuang): Not sure what other types are available
|
||||
type: "float32";
|
||||
}
|
||||
interface RawLayoutSchema {
|
||||
obs: EmbeddingSchema[];
|
||||
var?: EmbeddingSchema[];
|
||||
}
|
||||
|
||||
interface RawAnnotationsSchema {
|
||||
obs: {
|
||||
columns: AnnotationColumnSchema[];
|
||||
index: string;
|
||||
};
|
||||
var: {
|
||||
columns: AnnotationColumnSchema[];
|
||||
index: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RawSchema {
|
||||
annotations: RawAnnotationsSchema;
|
||||
dataframe: XMatrixSchema;
|
||||
layout: RawLayoutSchema;
|
||||
}
|
||||
|
||||
interface AnnotationsSchema extends RawAnnotationsSchema {
|
||||
obsByName: { [name: string]: AnnotationColumnSchema };
|
||||
varByName: { [name: string]: AnnotationColumnSchema };
|
||||
}
|
||||
|
||||
interface LayoutSchema extends RawLayoutSchema {
|
||||
obsByName: { [name: string]: EmbeddingSchema };
|
||||
varByName: { [name: string]: EmbeddingSchema };
|
||||
}
|
||||
|
||||
export interface Schema extends RawSchema {
|
||||
annotations: AnnotationsSchema;
|
||||
layout: LayoutSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-schema objects describing the schema for a primitive Array or Matrix in one of the fields.
|
||||
*/
|
||||
export type ArraySchema =
|
||||
| AnnotationColumnSchema
|
||||
| EmbeddingSchema
|
||||
| XMatrixSchema;
|
||||
|
||||
/**
|
||||
* Set of data / metadata objects that must be specified in a CXG.
|
||||
*/
|
||||
export enum Field {
|
||||
"obs" = "obs",
|
||||
"var" = "var",
|
||||
"emb" = "emb",
|
||||
"X" = "X",
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from "react";
|
||||
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
|
||||
|
||||
class AnnoDialog extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isActive,
|
||||
text,
|
||||
title,
|
||||
instruction,
|
||||
cancelTooltipContent,
|
||||
errorMessage,
|
||||
validationError,
|
||||
annoSelect,
|
||||
annoInput,
|
||||
secondaryInstructions,
|
||||
secondaryInput,
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
primaryButtonText,
|
||||
secondaryButtonText,
|
||||
handleSecondaryButtonSubmit,
|
||||
primaryButtonProps,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<p>{instruction}</p>
|
||||
{annoInput || null}
|
||||
<p
|
||||
style={{
|
||||
marginTop: 7,
|
||||
visibility: validationError ? "visible" : "hidden",
|
||||
color: Colors.ORANGE3,
|
||||
}}
|
||||
>
|
||||
{errorMessage}
|
||||
</p>
|
||||
{/* we might rename, secondary button and secondary input are not related */}
|
||||
{secondaryInstructions && (
|
||||
<p style={{ marginTop: secondaryInstructions ? 20 : 0 }}>
|
||||
{secondaryInstructions}
|
||||
</p>
|
||||
)}
|
||||
{secondaryInput || null}
|
||||
</div>
|
||||
{annoSelect || null}
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Tooltip content={cancelTooltipContent}>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
</Tooltip>
|
||||
{/* we might rename, secondary button and secondary input are not related */}
|
||||
{handleSecondaryButtonSubmit && secondaryButtonText ? (
|
||||
<Button
|
||||
onClick={handleSecondaryButtonSubmit}
|
||||
disabled={!text || validationError}
|
||||
intent="none"
|
||||
type="button"
|
||||
>
|
||||
{secondaryButtonText}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
{...primaryButtonProps} // eslint-disable-line react/jsx-props-no-spreading -- Spreading props allows for modularity
|
||||
onClick={handleSubmit}
|
||||
disabled={!text || validationError}
|
||||
intent="primary"
|
||||
type="submit"
|
||||
>
|
||||
{primaryButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AnnoDialog;
|
||||
@@ -1,117 +0,0 @@
|
||||
import React from "react";
|
||||
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class AnnoDialog extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isActive' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
isActive,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
text,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
title,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'instruction' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
instruction,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'cancelTooltipContent' does not exist on ... Remove this comment to see the full error message
|
||||
cancelTooltipContent,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'errorMessage' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
errorMessage,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'validationError' does not exist on type ... Remove this comment to see the full error message
|
||||
validationError,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoSelect' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
annoSelect,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoInput' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
annoInput,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'secondaryInstructions' does not exist on... Remove this comment to see the full error message
|
||||
secondaryInstructions,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'secondaryInput' does not exist on type '... Remove this comment to see the full error message
|
||||
secondaryInput,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleCancel' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
handleCancel,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleSubmit' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
handleSubmit,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'primaryButtonText' does not exist on typ... Remove this comment to see the full error message
|
||||
primaryButtonText,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'secondaryButtonText' does not exist on t... Remove this comment to see the full error message
|
||||
secondaryButtonText,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleSecondaryButtonSubmit' does not ex... Remove this comment to see the full error message
|
||||
handleSecondaryButtonSubmit,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'primaryButtonProps' does not exist on ty... Remove this comment to see the full error message
|
||||
primaryButtonProps,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<p>{instruction}</p>
|
||||
{annoInput || null}
|
||||
<p
|
||||
style={{
|
||||
marginTop: 7,
|
||||
visibility: validationError ? "visible" : "hidden",
|
||||
color: Colors.ORANGE3,
|
||||
}}
|
||||
>
|
||||
{errorMessage}
|
||||
</p>
|
||||
{/* we might rename, secondary button and secondary input are not related */}
|
||||
{secondaryInstructions && (
|
||||
<p style={{ marginTop: secondaryInstructions ? 20 : 0 }}>
|
||||
{secondaryInstructions}
|
||||
</p>
|
||||
)}
|
||||
{secondaryInput || null}
|
||||
</div>
|
||||
{annoSelect || null}
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Tooltip content={cancelTooltipContent}>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
</Tooltip>
|
||||
{/* we might rename, secondary button and secondary input are not related */}
|
||||
{handleSecondaryButtonSubmit && secondaryButtonText ? (
|
||||
<Button
|
||||
onClick={handleSecondaryButtonSubmit}
|
||||
disabled={!text || validationError}
|
||||
intent="none"
|
||||
type="button"
|
||||
>
|
||||
{secondaryButtonText}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
{...primaryButtonProps} // eslint-disable-line react/jsx-props-no-spreading -- Spreading props allows for modularity
|
||||
onClick={handleSubmit}
|
||||
disabled={!text || validationError}
|
||||
intent="primary"
|
||||
type="submit"
|
||||
>
|
||||
{primaryButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AnnoDialog;
|
||||
@@ -15,38 +15,30 @@ import TermsOfServicePrompt from "./termsPrompt";
|
||||
|
||||
import actions from "../actions";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
loading: (state as any).controls.loading,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
error: (state as any).controls.error,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
graphRenderCounter: (state as any).controls.graphRenderCounter,
|
||||
loading: state.controls.loading,
|
||||
error: state.controls.error,
|
||||
graphRenderCounter: state.controls.graphRenderCounter,
|
||||
}))
|
||||
class App extends React.Component {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidMount() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
|
||||
/* listen for url changes, fire one when we start the app up */
|
||||
window.addEventListener("popstate", this._onURLChanged);
|
||||
this._onURLChanged();
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||
|
||||
dispatch(actions.doInitialDataLoad(window.location.search));
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
_onURLChanged() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch({ type: "url changed", url: document.location.href });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'loading' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
const { loading, error, graphRenderCounter } = this.props;
|
||||
return (
|
||||
<Container>
|
||||
@@ -78,16 +70,13 @@ class App extends React.Component {
|
||||
{loading || error ? null : (
|
||||
<Layout>
|
||||
<LeftSideBar />
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{(viewportRef: any) => (
|
||||
{(viewportRef) => (
|
||||
<>
|
||||
<MenuBar />
|
||||
<Embedding />
|
||||
<Autosave />
|
||||
<TermsOfServicePrompt />
|
||||
{/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}
|
||||
<Legend viewportRef={viewportRef} />
|
||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: any; viewportRef: any; }' is not assi... Remove this comment to see the full error message */}
|
||||
<Graph key={graphRenderCounter} viewportRef={viewportRef} />
|
||||
</>
|
||||
)}
|
||||
+18
-46
@@ -11,78 +11,60 @@ import {
|
||||
Tooltip,
|
||||
} from "@blueprintjs/core";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
idhash:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).config?.parameters?.["annotations-user-data-idhash"] ?? null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
auth: (state as any).config?.authentication,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userInfo: (state as any).userInfo,
|
||||
writableCategoriesEnabled:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).config?.parameters?.annotations ?? false,
|
||||
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
|
||||
annotations: state.annotations,
|
||||
auth: state.config?.authentication,
|
||||
userInfo: state.userInfo,
|
||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||
writableGenesetsEnabled: !(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
((state as any).config?.parameters?.annotations_genesets_readonly ?? true)
|
||||
state.config?.parameters?.annotations_genesets_readonly ?? true
|
||||
),
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class FilenameDialog extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class FilenameDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filenameText: "",
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
dismissFilenameDialog = () => {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleCreateFilename = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
const { filenameText } = this.state;
|
||||
|
||||
dispatch({
|
||||
type: "set annotations collection name",
|
||||
data: filenameText,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
filenameError = () => {
|
||||
const legalNames = /^\w+$/;
|
||||
const { filenameText } = this.state;
|
||||
let err = false;
|
||||
|
||||
if (filenameText === "") {
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'.
|
||||
err = "empty_string";
|
||||
} else if (!legalNames.test(filenameText)) {
|
||||
/*
|
||||
IMPORTANT: this test must ultimately match the test applied by the
|
||||
backend, which is designed to ensure a safe file name can be created
|
||||
from the data collection name. If you change this, you will also need
|
||||
to change the validation code in the backend, or it will have no effect.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'.
|
||||
IMPORTANT: this test must ultimately match the test applied by the
|
||||
backend, which is designed to ensure a safe file name can be created
|
||||
from the data collection name. If you change this, you will also need
|
||||
to change the validation code in the backend, or it will have no effect.
|
||||
*/
|
||||
err = "characters";
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
filenameErrorMessage = () => {
|
||||
const err = this.filenameError();
|
||||
let markup = null;
|
||||
// @ts-expect-error ts-migrate(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message
|
||||
|
||||
if (err === "empty_string") {
|
||||
markup = (
|
||||
<span
|
||||
@@ -96,7 +78,6 @@ class FilenameDialog extends React.Component<{}, State> {
|
||||
Name cannot be blank
|
||||
</span>
|
||||
);
|
||||
// @ts-expect-error ts-migrate(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message
|
||||
} else if (err === "characters") {
|
||||
markup = (
|
||||
<span
|
||||
@@ -114,21 +95,16 @@ class FilenameDialog extends React.Component<{}, State> {
|
||||
return markup;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
|
||||
writableCategoriesEnabled,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableGenesetsEnabled' does not exist ... Remove this comment to see the full error message
|
||||
writableGenesetsEnabled,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
annotations,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'idhash' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
idhash,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'userInfo' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
userInfo,
|
||||
} = this.props;
|
||||
const { filenameText } = this.state;
|
||||
|
||||
return (writableCategoriesEnabled || writableGenesetsEnabled) &&
|
||||
annotations.promptForFilename &&
|
||||
!annotations.dataCollectionNameIsReadOnly &&
|
||||
@@ -152,7 +128,6 @@ class FilenameDialog extends React.Component<{}, State> {
|
||||
<InputGroup
|
||||
autoFocus
|
||||
value={filenameText}
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||
intent={this.filenameError(filenameText) ? "warning" : "none"}
|
||||
onChange={(e) =>
|
||||
this.setState({ filenameText: e.target.value })
|
||||
@@ -163,14 +138,12 @@ class FilenameDialog extends React.Component<{}, State> {
|
||||
<p
|
||||
style={{
|
||||
marginTop: 7,
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||
visibility: this.filenameError(filenameText)
|
||||
? "visible"
|
||||
: "hidden",
|
||||
color: Colors.ORANGE3,
|
||||
}}
|
||||
>
|
||||
{/* @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1. */}
|
||||
{this.filenameErrorMessage(filenameText)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -198,7 +171,6 @@ class FilenameDialog extends React.Component<{}, State> {
|
||||
<Button onClick={this.dismissFilenameDialog}>Cancel</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||
disabled={!filenameText || this.filenameError(filenameText)}
|
||||
onClick={this.handleCreateFilename}
|
||||
intent="primary"
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import actions from "../../actions";
|
||||
import FilenameDialog from "./filenameDialog";
|
||||
|
||||
@connect((state) => ({
|
||||
annotations: state.annotations,
|
||||
obsAnnotationSaveInProgress:
|
||||
state.autosave?.obsAnnotationSaveInProgress ?? false,
|
||||
genesetSaveInProgress: state.autosave?.genesetSaveInProgress ?? false,
|
||||
error: state.autosave?.error,
|
||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||
writableGenesetsEnabled: !(
|
||||
state.config?.parameters?.annotations_genesets_readonly ?? true
|
||||
),
|
||||
annoMatrix: state.annoMatrix,
|
||||
genesets: state.genesets,
|
||||
lastSavedAnnoMatrix: state.autosave?.lastSavedAnnoMatrix,
|
||||
lastSavedGenesets: state.autosave?.lastSavedGenesets,
|
||||
}))
|
||||
class Autosave extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
timer: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { writableCategoriesEnabled, writableGenesetsEnabled } = this.props;
|
||||
|
||||
let { timer } = this.state;
|
||||
if (timer) clearInterval(timer);
|
||||
if (writableCategoriesEnabled || writableGenesetsEnabled) {
|
||||
timer = setInterval(this.tick, 2500);
|
||||
} else {
|
||||
timer = null;
|
||||
}
|
||||
this.setState({ timer });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { timer } = this.state;
|
||||
if (timer) this.clearInterval(timer);
|
||||
}
|
||||
|
||||
tick = () => {
|
||||
const { dispatch, obsAnnotationSaveInProgress, genesetSaveInProgress } =
|
||||
this.props;
|
||||
if (!obsAnnotationSaveInProgress && this.needToSaveObsAnnotations()) {
|
||||
dispatch(actions.saveObsAnnotationsAction());
|
||||
}
|
||||
if (!genesetSaveInProgress && this.needToSaveGenesets()) {
|
||||
dispatch(actions.saveGenesetsAction());
|
||||
}
|
||||
};
|
||||
|
||||
needToSaveObsAnnotations = () => {
|
||||
/* return true if we need to save obs cell labels, false if we don't */
|
||||
const { annoMatrix, lastSavedAnnoMatrix } = this.props;
|
||||
return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix);
|
||||
};
|
||||
|
||||
needToSaveGenesets = () => {
|
||||
/* return true if we need to save gene ses, false if we do not */
|
||||
const { genesets, lastSavedGenesets } = this.props;
|
||||
return genesets.initialized && genesets.genesets !== lastSavedGenesets;
|
||||
};
|
||||
|
||||
needToSave() {
|
||||
return this.needToSaveGenesets() || this.needToSaveObsAnnotations();
|
||||
}
|
||||
|
||||
saveInProgress() {
|
||||
const { obsAnnotationSaveInProgress, genesetSaveInProgress } = this.props;
|
||||
return obsAnnotationSaveInProgress || genesetSaveInProgress;
|
||||
}
|
||||
|
||||
statusMessage() {
|
||||
const { error } = this.props;
|
||||
if (error) {
|
||||
return `Autosave error: ${error}`;
|
||||
}
|
||||
return this.needToSave() ? "Unsaved" : "All saved";
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
writableCategoriesEnabled,
|
||||
writableGenesetsEnabled,
|
||||
lastSavedAnnoMatrix,
|
||||
} = this.props;
|
||||
const initialDataLoadComplete = lastSavedAnnoMatrix;
|
||||
|
||||
if (!writableCategoriesEnabled && !writableGenesetsEnabled) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
id="autosave"
|
||||
data-testclass={
|
||||
!initialDataLoadComplete
|
||||
? "autosave-init"
|
||||
: this.saveInProgress() || this.needToSave()
|
||||
? "autosave-incomplete"
|
||||
: "autosave-complete"
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
display: "inherit",
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{this.statusMessage()}
|
||||
<FilenameDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Autosave;
|
||||
@@ -1,160 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import actions from "../../actions";
|
||||
import FilenameDialog from "./filenameDialog";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
obsAnnotationSaveInProgress:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).autosave?.obsAnnotationSaveInProgress ?? false,
|
||||
genesetSaveInProgress:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).autosave?.genesetSaveInProgress ?? false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
error: (state as any).autosave?.error,
|
||||
writableCategoriesEnabled:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).config?.parameters?.annotations ?? false,
|
||||
writableGenesetsEnabled: !(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
((state as any).config?.parameters?.annotations_genesets_readonly ?? true)
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
lastSavedAnnoMatrix: (state as any).autosave?.lastSavedAnnoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
lastSavedGenesets: (state as any).autosave?.lastSavedGenesets,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class Autosave extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
timer: null,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidMount() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
|
||||
const { writableCategoriesEnabled, writableGenesetsEnabled } = this.props;
|
||||
let { timer } = this.state;
|
||||
if (timer) clearInterval(timer);
|
||||
if (writableCategoriesEnabled || writableGenesetsEnabled) {
|
||||
timer = setInterval(this.tick, 2500);
|
||||
} else {
|
||||
timer = null;
|
||||
}
|
||||
this.setState({ timer });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentWillUnmount() {
|
||||
const { timer } = this.state;
|
||||
if (timer) clearInterval(timer);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
tick = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'obsAnnotationSaveInProgress' does not ex... Remove this comment to see the full error message
|
||||
obsAnnotationSaveInProgress,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesetSaveInProgress' does not exist on... Remove this comment to see the full error message
|
||||
genesetSaveInProgress,
|
||||
} = this.props;
|
||||
if (!obsAnnotationSaveInProgress && this.needToSaveObsAnnotations()) {
|
||||
dispatch(actions.saveObsAnnotationsAction());
|
||||
}
|
||||
if (!genesetSaveInProgress && this.needToSaveGenesets()) {
|
||||
dispatch(actions.saveGenesetsAction());
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
needToSaveObsAnnotations = () => {
|
||||
/* return true if we need to save obs cell labels, false if we don't */
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { annoMatrix, lastSavedAnnoMatrix } = this.props;
|
||||
return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
needToSaveGenesets = () => {
|
||||
/* return true if we need to save gene ses, false if we do not */
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { genesets, lastSavedGenesets } = this.props;
|
||||
return genesets.initialized && genesets.genesets !== lastSavedGenesets;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
needToSave() {
|
||||
return this.needToSaveGenesets() || this.needToSaveObsAnnotations();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
saveInProgress() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'obsAnnotationSaveInProgress' does not ex... Remove this comment to see the full error message
|
||||
const { obsAnnotationSaveInProgress, genesetSaveInProgress } = this.props;
|
||||
return obsAnnotationSaveInProgress || genesetSaveInProgress;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
statusMessage() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'error' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
const { error } = this.props;
|
||||
if (error) {
|
||||
return `Autosave error: ${error}`;
|
||||
}
|
||||
return this.needToSave() ? "Unsaved" : "All saved";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
|
||||
writableCategoriesEnabled,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableGenesetsEnabled' does not exist ... Remove this comment to see the full error message
|
||||
writableGenesetsEnabled,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'lastSavedAnnoMatrix' does not exist on t... Remove this comment to see the full error message
|
||||
lastSavedAnnoMatrix,
|
||||
} = this.props;
|
||||
const initialDataLoadComplete = lastSavedAnnoMatrix;
|
||||
if (!writableCategoriesEnabled && !writableGenesetsEnabled) return null;
|
||||
return (
|
||||
<div
|
||||
id="autosave"
|
||||
data-testclass={
|
||||
!initialDataLoadComplete
|
||||
? "autosave-init"
|
||||
: this.saveInProgress() || this.needToSave()
|
||||
? "autosave-incomplete"
|
||||
: "autosave-complete"
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
display: "inherit",
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{this.statusMessage()}
|
||||
<FilenameDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Autosave;
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
const ErrorLoading = ({ displayName, zebra }) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
<span>{`Failure loading ${displayName}`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ErrorLoading;
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const ErrorLoading = ({ displayName, zebra }: any) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
<span>{`Failure loading ${displayName}`}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ErrorLoading;
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
|
||||
const HistogramFooter = React.memo(
|
||||
({
|
||||
displayName,
|
||||
hideRanges,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
rangeColorMin,
|
||||
rangeColorMax,
|
||||
isObs,
|
||||
isGeneSetSummary,
|
||||
}) =>
|
||||
/*
|
||||
Footer of each histogram. Will render range and title.
|
||||
|
||||
Required props:
|
||||
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
|
||||
* hideRanges - true/false, enables/disable rendering of ranges
|
||||
* range - length two array, [min, max], containing the range values to display
|
||||
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
|
||||
*/
|
||||
(
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: hideRanges ? "center" : "space-between",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMin,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
min {rangeMin.toPrecision(4)}
|
||||
</span>
|
||||
<span
|
||||
data-testclass="brushable-histogram-field-name"
|
||||
style={{ fontStyle: "italic" }}
|
||||
>
|
||||
{isObs && displayName}
|
||||
{isGeneSetSummary && "gene set mean expression"}
|
||||
</span>
|
||||
<div style={{ display: hideRanges ? "block" : "none" }}>
|
||||
: {rangeMin}
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMax,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
max {rangeMax.toPrecision(4)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
export default HistogramFooter;
|
||||
@@ -1,69 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const HistogramFooter = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'displayName' does not exist on type '{ c... Remove this comment to see the full error message
|
||||
displayName,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'hideRanges' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
hideRanges,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeMin' does not exist on type '{ chil... Remove this comment to see the full error message
|
||||
rangeMin,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeMax' does not exist on type '{ chil... Remove this comment to see the full error message
|
||||
rangeMax,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeColorMin' does not exist on type '{... Remove this comment to see the full error message
|
||||
rangeColorMin,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeColorMax' does not exist on type '{... Remove this comment to see the full error message
|
||||
rangeColorMax,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{ childre... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
}) => (
|
||||
/*
|
||||
Footer of each histogram. Will render range and title.
|
||||
|
||||
Required props:
|
||||
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
|
||||
* hideRanges - true/false, enables/disable rendering of ranges
|
||||
* range - length two array, [min, max], containing the range values to display
|
||||
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
|
||||
*/
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: hideRanges ? "center" : "space-between",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMin,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
min {rangeMin.toPrecision(4)}
|
||||
</span>
|
||||
<span
|
||||
data-testclass="brushable-histogram-field-name"
|
||||
style={{ fontStyle: "italic" }}
|
||||
>
|
||||
{isObs && displayName}
|
||||
{isGeneSetSummary && "gene set mean expression"}
|
||||
</span>
|
||||
<div style={{ display: hideRanges ? "block" : "none" }}>
|
||||
: {rangeMin}
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
color: rangeColorMax,
|
||||
display: hideRanges ? "none" : "block",
|
||||
}}
|
||||
>
|
||||
max {rangeMax.toPrecision(4)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
export default HistogramFooter;
|
||||
-9
@@ -5,23 +5,14 @@ import * as globals from "../../globals";
|
||||
|
||||
const HistogramHeader = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'fieldId' does not exist on type '{ child... Remove this comment to see the full error message
|
||||
fieldId,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorBy' does not exist on type '{ chi... Remove this comment to see the full error message
|
||||
isColorBy,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onColorByClick' does not exist on type '... Remove this comment to see the full error message
|
||||
onColorByClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onRemoveClick' does not exist on type '{... Remove this comment to see the full error message
|
||||
onRemoveClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotX' does not exist on type '... Remove this comment to see the full error message
|
||||
isScatterPlotX,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotY' does not exist on type '... Remove this comment to see the full error message
|
||||
isScatterPlotY,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotXClick' does not exist on t... Remove this comment to see the full error message
|
||||
onScatterPlotXClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotYClick' does not exist on t... Remove this comment to see the full error message
|
||||
onScatterPlotYClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{ childre... Remove this comment to see the full error message
|
||||
isObs,
|
||||
}) => {
|
||||
/*
|
||||
+5
-24
@@ -2,11 +2,9 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { interpolateCool } from "d3-scale-chromatic";
|
||||
import * as d3 from "d3";
|
||||
|
||||
import { AxisDomain } from "d3";
|
||||
import maybeScientific from "../../util/maybeScientific";
|
||||
import clamp from "../../util/clamp";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
const Histogram = ({
|
||||
field,
|
||||
fieldForId,
|
||||
@@ -19,8 +17,8 @@ const Histogram = ({
|
||||
margin,
|
||||
isColorBy,
|
||||
selectionRange,
|
||||
mini, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
}: any) => {
|
||||
mini,
|
||||
}) => {
|
||||
const svgRef = useRef(null);
|
||||
const [brush, setBrush] = useState(null);
|
||||
|
||||
@@ -71,19 +69,14 @@ const Histogram = ({
|
||||
.data(bins)
|
||||
.enter()
|
||||
.append("rect")
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
|
||||
.attr("x", (d, i) => x(binStart(i)) + 1)
|
||||
.attr("y", (d) => y(d))
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
|
||||
.attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - binPadding)
|
||||
.attr("height", (d) => y(0) - y(d))
|
||||
.style(
|
||||
"fill",
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
isColorBy
|
||||
? // @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d: any, i: any) => colorScale(histogramScale(binStart(i)))
|
||||
? (d, i) => colorScale(histogramScale(binStart(i)))
|
||||
: defaultBarColor
|
||||
);
|
||||
}
|
||||
@@ -108,7 +101,6 @@ const Histogram = ({
|
||||
const brushXselection = container
|
||||
.insert("g")
|
||||
.attr("class", "brush")
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
.attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
|
||||
.call(brushX);
|
||||
|
||||
@@ -121,12 +113,7 @@ const Histogram = ({
|
||||
d3
|
||||
.axisBottom(x)
|
||||
.ticks(4)
|
||||
.tickFormat(
|
||||
d3.format(maybeScientific(x)) as (
|
||||
dv: AxisDomain,
|
||||
i: number
|
||||
) => string
|
||||
)
|
||||
.tickFormat(d3.format(maybeScientific(x)))
|
||||
);
|
||||
|
||||
/* Y AXIS */
|
||||
@@ -139,10 +126,8 @@ const Histogram = ({
|
||||
.axisRight(y)
|
||||
.ticks(3)
|
||||
.tickFormat(
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
d3.format(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
y.domain().some((n: any) => Math.abs(n) >= 10000) ? ".0e" : ","
|
||||
y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -152,7 +137,6 @@ const Histogram = ({
|
||||
svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
|
||||
svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
|
||||
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ brushX: d3.BrushBehavior<unkno... Remove this comment to see the full error message
|
||||
setBrush({ brushX, brushXselection });
|
||||
}
|
||||
}, [histogram, isColorBy]);
|
||||
@@ -162,7 +146,6 @@ const Histogram = ({
|
||||
paint/update selection brush
|
||||
*/
|
||||
if (!brush) return;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'brushX' does not exist on type 'null'.
|
||||
const { brushX, brushXselection } = brush;
|
||||
const selection = d3.brushSelection(brushXselection.node());
|
||||
if (!selectionRange && selection) {
|
||||
@@ -179,9 +162,7 @@ const Histogram = ({
|
||||
} else {
|
||||
/* there is an active selection and a brush - make sure they match */
|
||||
const moveDeltaThreshold = 1;
|
||||
// @ts-expect-error ts-migrate(2363) FIXME: The right-hand side of an arithmetic operation mus... Remove this comment to see the full error message
|
||||
const dX0 = Math.abs(x0 - selection[0]);
|
||||
// @ts-expect-error ts-migrate(2363) FIXME: The right-hand side of an arithmetic operation mus... Remove this comment to see the full error message
|
||||
const dX1 = Math.abs(x1 - selection[1]);
|
||||
/*
|
||||
only update the brush if it is grossly incorrect,
|
||||
@@ -0,0 +1,440 @@
|
||||
import React from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import * as d3 from "d3";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import { makeContinuousDimensionName } from "../../util/nameCreators";
|
||||
import HistogramHeader from "./header";
|
||||
import Histogram from "./histogram";
|
||||
import HistogramFooter from "./footer";
|
||||
import StillLoading from "./loading";
|
||||
import ErrorLoading from "./error";
|
||||
|
||||
const MARGIN = {
|
||||
LEFT: 10, // Space for 0 tick label on X axis
|
||||
RIGHT: 54, // space for Y axis & labels
|
||||
BOTTOM: 25, // space for X axis & labels
|
||||
TOP: 3,
|
||||
};
|
||||
const WIDTH = 340 - MARGIN.LEFT - MARGIN.RIGHT;
|
||||
const HEIGHT = 135 - MARGIN.TOP - MARGIN.BOTTOM;
|
||||
const MARGIN_MINI = {
|
||||
LEFT: 0, // Space for 0 tick label on X axis
|
||||
RIGHT: 0, // space for Y axis & labels
|
||||
BOTTOM: 0, // space for X axis & labels
|
||||
TOP: 0,
|
||||
};
|
||||
const WIDTH_MINI = 120 - MARGIN_MINI.LEFT - MARGIN_MINI.RIGHT;
|
||||
const HEIGHT_MINI = 15 - MARGIN_MINI.TOP - MARGIN_MINI.BOTTOM;
|
||||
|
||||
@connect((state, ownProps) => {
|
||||
const { isObs, isUserDefined, isGeneSetSummary, field } = ownProps;
|
||||
const myName = makeContinuousDimensionName(
|
||||
{ isObs, isUserDefined, isGeneSetSummary },
|
||||
field
|
||||
);
|
||||
return {
|
||||
annoMatrix: state.annoMatrix,
|
||||
isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === field,
|
||||
isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === field,
|
||||
continuousSelectionRange: state.continuousSelection[myName],
|
||||
isColorAccessor: state.colors.colorAccessor === field,
|
||||
};
|
||||
})
|
||||
class HistogramBrush extends React.PureComponent {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
/* memoized closure to prevent HistogramHeader unecessary repaint */
|
||||
handleColorAction = memoize((dispatch) => (field, isObs) => {
|
||||
if (isObs) {
|
||||
dispatch({
|
||||
type: "color by continuous metadata",
|
||||
colorAccessor: field,
|
||||
});
|
||||
} else {
|
||||
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field));
|
||||
}
|
||||
});
|
||||
|
||||
onBrush = (selection, x, eventType) => {
|
||||
const type = `continuous metadata histogram ${eventType}`;
|
||||
return () => {
|
||||
const { dispatch, field, isObs, isUserDefined, isGeneSetSummary } =
|
||||
this.props;
|
||||
|
||||
// ignore programmatically generated events
|
||||
if (!d3.event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
if (d3.event.sourceEvent.sourceEvent) return;
|
||||
|
||||
const query = this.createQuery();
|
||||
const range = d3.event.selection
|
||||
? [x(d3.event.selection[0]), x(d3.event.selection[1])]
|
||||
: null;
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
onBrushEnd = (selection, x) => () => {
|
||||
const { dispatch, field, isObs, isUserDefined, isGeneSetSummary } =
|
||||
this.props;
|
||||
const minAllowedBrushSize = 10;
|
||||
const smallAmountToAvoidInfiniteLoop = 0.1;
|
||||
|
||||
// ignore programmatically generated events
|
||||
if (!d3.event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
if (d3.event.sourceEvent.sourceEvent) return;
|
||||
|
||||
let type;
|
||||
let range = null;
|
||||
if (d3.event.selection) {
|
||||
type = "continuous metadata histogram end";
|
||||
if (
|
||||
d3.event.selection[1] - d3.event.selection[0] >
|
||||
minAllowedBrushSize
|
||||
) {
|
||||
range = [x(d3.event.selection[0]), x(d3.event.selection[1])];
|
||||
} else {
|
||||
/* the user selected range is too small and will be hidden #587, so take control of it procedurally */
|
||||
/* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */
|
||||
|
||||
const procedurallyResizedBrushWidth =
|
||||
d3.event.selection[0] +
|
||||
minAllowedBrushSize +
|
||||
smallAmountToAvoidInfiniteLoop; //
|
||||
|
||||
range = [x(d3.event.selection[0]), x(procedurallyResizedBrushWidth)];
|
||||
}
|
||||
} else {
|
||||
type = "continuous metadata histogram cancel";
|
||||
}
|
||||
|
||||
const query = this.createQuery();
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
|
||||
handleSetGeneAsScatterplotX = () => {
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
handleSetGeneAsScatterplotY = () => {
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
removeHistogram = () => {
|
||||
const {
|
||||
dispatch,
|
||||
field,
|
||||
isColorAccessor,
|
||||
isScatterplotXXaccessor,
|
||||
isScatterplotYYaccessor,
|
||||
} = this.props;
|
||||
dispatch({
|
||||
type: "clear user defined gene",
|
||||
data: field,
|
||||
});
|
||||
if (isColorAccessor) {
|
||||
dispatch({
|
||||
type: "reset colorscale",
|
||||
});
|
||||
}
|
||||
if (isScatterplotXXaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
if (isScatterplotYYaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchAsyncProps = async () => {
|
||||
const { annoMatrix, width } = this.props;
|
||||
|
||||
const { isClipped } = annoMatrix;
|
||||
|
||||
const query = this.createQuery();
|
||||
const df = await annoMatrix.fetch(...query);
|
||||
const column = df.icol(0);
|
||||
|
||||
// if we are clipped, fetch both our value and our unclipped value,
|
||||
// as we need the absolute min/max range, not just the clipped min/max.
|
||||
const summary = column.summarize();
|
||||
const range = [summary.min, summary.max];
|
||||
|
||||
let unclippedRange = [...range];
|
||||
if (isClipped) {
|
||||
const parent = await annoMatrix.viewOf.fetch(...query);
|
||||
const { min, max } = parent.icol(0).summarize();
|
||||
unclippedRange = [min, max];
|
||||
}
|
||||
|
||||
const unclippedRangeColor = [
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[0] === 0
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[1] === 1
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
];
|
||||
|
||||
const histogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN,
|
||||
width || WIDTH,
|
||||
HEIGHT
|
||||
);
|
||||
const miniHistogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN_MINI,
|
||||
width || WIDTH_MINI,
|
||||
HEIGHT_MINI
|
||||
);
|
||||
|
||||
const isSingleValue = summary.min === summary.max;
|
||||
const nonFiniteExtent =
|
||||
summary.min === undefined ||
|
||||
summary.max === undefined ||
|
||||
Number.isNaN(summary.min) ||
|
||||
Number.isNaN(summary.max);
|
||||
|
||||
const OK2Render = !summary.categorical && !nonFiniteExtent;
|
||||
|
||||
return {
|
||||
histogram,
|
||||
miniHistogram,
|
||||
range,
|
||||
unclippedRange,
|
||||
unclippedRangeColor,
|
||||
isSingleValue,
|
||||
OK2Render,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this -- instance method allows for memoization per annotation
|
||||
calcHistogramCache(col, newMargin, newWidth, newHeight) {
|
||||
/*
|
||||
recalculate expensive stuff, notably bins, summaries, etc.
|
||||
*/
|
||||
const histogramCache = {}; /* maybe change this so that it computes ... */
|
||||
const summary =
|
||||
col.summarize(); /* this is memoized, so it's free the second time you call it */
|
||||
const { min: domainMin, max: domainMax } = summary;
|
||||
const numBins = 40;
|
||||
const { TOP: topMargin, LEFT: leftMargin } =
|
||||
newMargin; /* changes with mini */
|
||||
|
||||
histogramCache.domain = [
|
||||
domainMin,
|
||||
domainMax,
|
||||
]; /* doesn't change with mini */
|
||||
|
||||
histogramCache.x = d3
|
||||
.scaleLinear()
|
||||
.domain([domainMin, domainMax])
|
||||
.range([leftMargin, leftMargin + newWidth]);
|
||||
|
||||
histogramCache.bins = col.histogram(numBins, [
|
||||
domainMin,
|
||||
domainMax,
|
||||
]); /* memoized */
|
||||
|
||||
histogramCache.binWidth = (domainMax - domainMin) / numBins;
|
||||
|
||||
histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth;
|
||||
histogramCache.binEnd = (i) =>
|
||||
domainMin + (i + 1) * histogramCache.binWidth;
|
||||
|
||||
const yMax = histogramCache.bins.reduce((l, r) => (l > r ? l : r));
|
||||
|
||||
histogramCache.y = d3
|
||||
.scaleLinear()
|
||||
.domain([0, yMax])
|
||||
.range([topMargin + newHeight, topMargin]);
|
||||
|
||||
return histogramCache;
|
||||
}
|
||||
|
||||
createQuery() {
|
||||
const { isObs, isGeneSetSummary, field, setGenes, annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
if (isObs) {
|
||||
return ["obs", field];
|
||||
}
|
||||
const varIndex = schema?.annotations?.var?.index;
|
||||
if (!varIndex) return null;
|
||||
|
||||
if (isGeneSetSummary) {
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
summarize: {
|
||||
method: "mean",
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
values: [...setGenes.keys()],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// else, we assume it is a gene expression
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
where: {
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: field,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatch,
|
||||
annoMatrix,
|
||||
field,
|
||||
isColorAccessor,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
isScatterplotXXaccessor,
|
||||
isScatterplotYYaccessor,
|
||||
zebra,
|
||||
continuousSelectionRange,
|
||||
isObs,
|
||||
mini,
|
||||
setGenes,
|
||||
} = this.props;
|
||||
|
||||
let { width } = this.props;
|
||||
if (!width) {
|
||||
width = mini ? WIDTH_MINI : WIDTH;
|
||||
}
|
||||
|
||||
const fieldForId = field.replace(/\s/g, "_");
|
||||
const showScatterPlot = isUserDefined;
|
||||
|
||||
let testClass = "histogram-continuous-metadata";
|
||||
if (isUserDefined) testClass = "histogram-user-gene";
|
||||
else if (isGeneSetSummary) testClass = "histogram-gene-set-summary";
|
||||
|
||||
return (
|
||||
<Async
|
||||
watchFn={HistogramBrush.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{ annoMatrix, setGenes }}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading displayName={field} zebra={zebra} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading zebra={zebra} error={error} displayName={field} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) =>
|
||||
asyncProps.OK2Render ? (
|
||||
<div
|
||||
id={`histogram_${fieldForId}`}
|
||||
data-testid={`histogram-${field}`}
|
||||
data-testclass={testClass}
|
||||
style={{
|
||||
padding: mini ? 0 : globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
{!mini && isObs ? (
|
||||
<HistogramHeader
|
||||
fieldId={field}
|
||||
isColorBy={isColorAccessor}
|
||||
isObs={isObs}
|
||||
onColorByClick={this.handleColorAction(dispatch)}
|
||||
onRemoveClick={isUserDefined ? this.removeHistogram : null}
|
||||
isScatterPlotX={isScatterplotXXaccessor}
|
||||
isScatterPlotY={isScatterplotYYaccessor}
|
||||
onScatterPlotXClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
|
||||
}
|
||||
onScatterPlotYClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Histogram
|
||||
field={field}
|
||||
fieldForId={fieldForId}
|
||||
display={asyncProps.isSingleValue ? "none" : "block"}
|
||||
histogram={
|
||||
mini ? asyncProps.miniHistogram : asyncProps.histogram
|
||||
}
|
||||
width={width}
|
||||
height={mini ? HEIGHT_MINI : HEIGHT}
|
||||
onBrush={this.onBrush}
|
||||
onBrushEnd={this.onBrushEnd}
|
||||
margin={mini ? MARGIN_MINI : MARGIN}
|
||||
isColorBy={isColorAccessor}
|
||||
selectionRange={continuousSelectionRange}
|
||||
mini={mini}
|
||||
/>
|
||||
{!mini && (
|
||||
<HistogramFooter
|
||||
isGeneSetSummary={isGeneSetSummary}
|
||||
isObs={isObs}
|
||||
displayName={field}
|
||||
hideRanges={asyncProps.isSingleValue}
|
||||
rangeMin={asyncProps.unclippedRange[0]}
|
||||
rangeMax={asyncProps.unclippedRange[1]}
|
||||
rangeColorMin={asyncProps.unclippedRangeColor[0]}
|
||||
rangeColorMax={asyncProps.unclippedRangeColor[1]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HistogramBrush;
|
||||
@@ -1,547 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import * as d3 from "d3";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import { makeContinuousDimensionName } from "../../util/nameCreators";
|
||||
import HistogramHeader from "./header";
|
||||
import Histogram from "./histogram";
|
||||
import HistogramFooter from "./footer";
|
||||
import StillLoading from "./loading";
|
||||
import ErrorLoading from "./error";
|
||||
import { Dataframe } from "../../util/dataframe";
|
||||
|
||||
const MARGIN = {
|
||||
LEFT: 10, // Space for 0 tick label on X axis
|
||||
RIGHT: 54, // space for Y axis & labels
|
||||
BOTTOM: 25, // space for X axis & labels
|
||||
TOP: 3,
|
||||
};
|
||||
const WIDTH = 340 - MARGIN.LEFT - MARGIN.RIGHT;
|
||||
const HEIGHT = 135 - MARGIN.TOP - MARGIN.BOTTOM;
|
||||
const MARGIN_MINI = {
|
||||
LEFT: 0, // Space for 0 tick label on X axis
|
||||
RIGHT: 0, // space for Y axis & labels
|
||||
BOTTOM: 0, // space for X axis & labels
|
||||
TOP: 0,
|
||||
};
|
||||
const WIDTH_MINI = 120 - MARGIN_MINI.LEFT - MARGIN_MINI.RIGHT;
|
||||
const HEIGHT_MINI = 15 - MARGIN_MINI.TOP - MARGIN_MINI.BOTTOM;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state, ownProps) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{}'.
|
||||
const { isObs, isUserDefined, isGeneSetSummary, field } = ownProps;
|
||||
const myName = makeContinuousDimensionName(
|
||||
{ isObs, isUserDefined, isGeneSetSummary },
|
||||
field
|
||||
);
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
isScatterplotXXaccessor:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).controls.scatterplotXXaccessor === field,
|
||||
isScatterplotYYaccessor:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).controls.scatterplotYYaccessor === field,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
continuousSelectionRange: (state as any).continuousSelection[myName],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
isColorAccessor: (state as any).colors.colorAccessor === field,
|
||||
};
|
||||
})
|
||||
class HistogramBrush extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static watchAsync(props: any, prevProps: any) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
/* memoized closure to prevent HistogramHeader unecessary repaint */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleColorAction = memoize((dispatch) => (field: any, isObs: any) => {
|
||||
if (isObs) {
|
||||
dispatch({
|
||||
type: "color by continuous metadata",
|
||||
colorAccessor: field,
|
||||
});
|
||||
} else {
|
||||
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field));
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'selection' is declared but its value is never rea... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
onBrush = (selection: any, x: any, eventType: any) => {
|
||||
const type = `continuous metadata histogram ${eventType}`;
|
||||
return () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
|
||||
isUserDefined,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
} = this.props;
|
||||
|
||||
// ignore programmatically generated events
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if (!(d3 as any).event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if ((d3 as any).event.sourceEvent.sourceEvent) return;
|
||||
|
||||
const query = this.createQuery();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const range = (d3 as any).event.selection
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
[x((d3 as any).event.selection[0]), x((d3 as any).event.selection[1])]
|
||||
: null;
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
onBrushEnd =
|
||||
(
|
||||
_selection: any, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
x: any // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
) =>
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
() => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
|
||||
isUserDefined,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
} = this.props;
|
||||
const minAllowedBrushSize = 10;
|
||||
const smallAmountToAvoidInfiniteLoop = 0.1;
|
||||
|
||||
// ignore programmatically generated events
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if (!(d3 as any).event.sourceEvent) return;
|
||||
// ignore cascading events, which are programmatically generated
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if ((d3 as any).event.sourceEvent.sourceEvent) return;
|
||||
|
||||
let type;
|
||||
let range = null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if ((d3 as any).event.selection) {
|
||||
type = "continuous metadata histogram end";
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d3 as any).event.selection[1] - (d3 as any).event.selection[0] >
|
||||
minAllowedBrushSize
|
||||
) {
|
||||
range = [
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
x((d3 as any).event.selection[0]),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
x((d3 as any).event.selection[1]),
|
||||
];
|
||||
} else {
|
||||
/* the user selected range is too small and will be hidden #587, so take control of it procedurally */
|
||||
/* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */
|
||||
const procedurallyResizedBrushWidth =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d3 as any).event.selection[0] +
|
||||
minAllowedBrushSize +
|
||||
smallAmountToAvoidInfiniteLoop; //
|
||||
range = [
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
x((d3 as any).event.selection[0]),
|
||||
x(procedurallyResizedBrushWidth),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
type = "continuous metadata histogram cancel";
|
||||
}
|
||||
|
||||
const query = this.createQuery();
|
||||
const otherProps = {
|
||||
selection: field,
|
||||
continuousNamespace: {
|
||||
isObs,
|
||||
isUserDefined,
|
||||
isGeneSetSummary,
|
||||
},
|
||||
};
|
||||
dispatch(
|
||||
actions.selectContinuousMetadataAction(type, query, range, otherProps)
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleSetGeneAsScatterplotX = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleSetGeneAsScatterplotY = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, field } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: field,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
removeHistogram = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotXXaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotYYaccessor,
|
||||
} = this.props;
|
||||
dispatch({
|
||||
type: "clear user defined gene",
|
||||
data: field,
|
||||
});
|
||||
if (isColorAccessor) {
|
||||
dispatch({
|
||||
type: "reset colorscale",
|
||||
});
|
||||
}
|
||||
if (isScatterplotXXaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
if (isScatterplotYYaccessor) {
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
fetchAsyncProps = async () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { annoMatrix, width } = this.props;
|
||||
|
||||
const { isClipped } = annoMatrix;
|
||||
|
||||
const query = this.createQuery();
|
||||
// @ts-expect-error ts-migrate(2488) FIXME: Type 'any[] | null' must have a '[Symbol.iterator]... Remove this comment to see the full error message
|
||||
const df: Dataframe = await annoMatrix.fetch(...query);
|
||||
const column = df.icol(0);
|
||||
|
||||
// if we are clipped, fetch both our value and our unclipped value,
|
||||
// as we need the absolute min/max range, not just the clipped min/max.
|
||||
const summary = column.summarizeContinuous();
|
||||
const range = [summary.min, summary.max];
|
||||
|
||||
let unclippedRange = [...range];
|
||||
if (isClipped) {
|
||||
const parent: Dataframe = await annoMatrix.viewOf.fetch(...query);
|
||||
const { min, max } = parent.icol(0).summarizeContinuous();
|
||||
unclippedRange = [min, max];
|
||||
}
|
||||
|
||||
const unclippedRangeColor = [
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[0] === 0
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
!annoMatrix.isClipped || annoMatrix.clipRange[1] === 1
|
||||
? "#bbb"
|
||||
: globals.blue,
|
||||
];
|
||||
|
||||
const histogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN,
|
||||
width || WIDTH,
|
||||
HEIGHT
|
||||
);
|
||||
const miniHistogram = this.calcHistogramCache(
|
||||
column,
|
||||
MARGIN_MINI,
|
||||
width || WIDTH_MINI,
|
||||
HEIGHT_MINI
|
||||
);
|
||||
|
||||
const isSingleValue = summary.min === summary.max;
|
||||
const nonFiniteExtent =
|
||||
summary.min === undefined ||
|
||||
summary.max === undefined ||
|
||||
Number.isNaN(summary.min) ||
|
||||
Number.isNaN(summary.max);
|
||||
|
||||
const OK2Render = !summary.categorical && !nonFiniteExtent;
|
||||
|
||||
return {
|
||||
histogram,
|
||||
miniHistogram,
|
||||
range,
|
||||
unclippedRange,
|
||||
unclippedRangeColor,
|
||||
isSingleValue,
|
||||
OK2Render,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- instance method allows for memoization per annotation
|
||||
calcHistogramCache(col: any, newMargin: any, newWidth: any, newHeight: any) {
|
||||
/*
|
||||
recalculate expensive stuff, notably bins, summaries, etc.
|
||||
*/
|
||||
const histogramCache = {}; /* maybe change this so that it computes ... */
|
||||
const summary =
|
||||
col.summarizeContinuous(); /* this is memoized, so it's free the second time you call it */
|
||||
const { min: domainMin, max: domainMax } = summary;
|
||||
const numBins = 40;
|
||||
const { TOP: topMargin, LEFT: leftMargin } = newMargin;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).domain = [domainMin, domainMax];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
/* doesn't change with mini */ (histogramCache as any).x = d3
|
||||
.scaleLinear()
|
||||
.domain([domainMin, domainMax])
|
||||
.range([leftMargin, leftMargin + newWidth]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).bins = col.histogramContinuous(numBins, [
|
||||
domainMin,
|
||||
domainMax,
|
||||
]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
/* memoized */ (histogramCache as any).binWidth =
|
||||
(domainMax - domainMin) / numBins;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).binStart = (i: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
domainMin + i * (histogramCache as any).binWidth;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).binEnd = (i: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
domainMin + (i + 1) * (histogramCache as any).binWidth;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const yMax = (histogramCache as any).bins.reduce((l: any, r: any) =>
|
||||
l > r ? l : r
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(histogramCache as any).y = d3
|
||||
.scaleLinear()
|
||||
.domain([0, yMax])
|
||||
.range([topMargin + newHeight, topMargin]);
|
||||
|
||||
return histogramCache;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
createQuery() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
const { isObs, isGeneSetSummary, field, setGenes, annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
if (isObs) {
|
||||
return ["obs", field];
|
||||
}
|
||||
const varIndex = schema?.annotations?.var?.index;
|
||||
if (!varIndex) return null;
|
||||
|
||||
if (isGeneSetSummary) {
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
summarize: {
|
||||
method: "mean",
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
values: [...setGenes.keys()],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// else, we assume it is a gene expression
|
||||
return [
|
||||
"X",
|
||||
{
|
||||
where: {
|
||||
field: "var",
|
||||
column: varIndex,
|
||||
value: field,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
annoMatrix,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
field,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
|
||||
isUserDefined,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
|
||||
isGeneSetSummary,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotXXaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotYYaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'zebra' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
zebra,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'continuousSelectionRange' does not exist... Remove this comment to see the full error message
|
||||
continuousSelectionRange,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
isObs,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'mini' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
mini,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'setGenes' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
setGenes,
|
||||
} = this.props;
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'width' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
let { width } = this.props;
|
||||
if (!width) {
|
||||
width = mini ? WIDTH_MINI : WIDTH;
|
||||
}
|
||||
|
||||
const fieldForId = field.replace(/\s/g, "_");
|
||||
const showScatterPlot = isUserDefined;
|
||||
|
||||
let testClass = "histogram-continuous-metadata";
|
||||
if (isUserDefined) testClass = "histogram-user-gene";
|
||||
else if (isGeneSetSummary) testClass = "histogram-gene-set-summary";
|
||||
|
||||
return (
|
||||
<Async
|
||||
watchFn={HistogramBrush.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{ annoMatrix, setGenes }}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading displayName={field} zebra={zebra} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading zebra={zebra} error={error} displayName={field} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(asyncProps as any).OK2Render ? (
|
||||
<div
|
||||
id={`histogram_${fieldForId}`}
|
||||
data-testid={`histogram-${field}`}
|
||||
data-testclass={testClass}
|
||||
style={{
|
||||
padding: mini ? 0 : globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
{!mini && isObs ? (
|
||||
<HistogramHeader
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ fieldId: any; isColorBy: any; isObs: any; ... Remove this comment to see the full error message
|
||||
fieldId={field}
|
||||
isColorBy={isColorAccessor}
|
||||
isObs={isObs}
|
||||
onColorByClick={this.handleColorAction(dispatch)}
|
||||
onRemoveClick={isUserDefined ? this.removeHistogram : null}
|
||||
isScatterPlotX={isScatterplotXXaccessor}
|
||||
isScatterPlotY={isScatterplotYYaccessor}
|
||||
onScatterPlotXClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
|
||||
}
|
||||
onScatterPlotYClick={
|
||||
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Histogram
|
||||
field={field}
|
||||
fieldForId={fieldForId}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
display={(asyncProps as any).isSingleValue ? "none" : "block"}
|
||||
histogram={
|
||||
mini
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(asyncProps as any).miniHistogram
|
||||
: // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(asyncProps as any).histogram
|
||||
}
|
||||
width={width}
|
||||
height={mini ? HEIGHT_MINI : HEIGHT}
|
||||
onBrush={this.onBrush}
|
||||
onBrushEnd={this.onBrushEnd}
|
||||
margin={mini ? MARGIN_MINI : MARGIN}
|
||||
isColorBy={isColorAccessor}
|
||||
selectionRange={continuousSelectionRange}
|
||||
mini={mini}
|
||||
/>
|
||||
{!mini && (
|
||||
<HistogramFooter
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isGeneSetSummary: any; isObs: any; display... Remove this comment to see the full error message
|
||||
isGeneSetSummary={isGeneSetSummary}
|
||||
isObs={isObs}
|
||||
displayName={field}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
hideRanges={(asyncProps as any).isSingleValue}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeMin={(asyncProps as any).unclippedRange[0]}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeMax={(asyncProps as any).unclippedRange[1]}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeColorMin={(asyncProps as any).unclippedRangeColor[0]}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rangeColorMax={(asyncProps as any).unclippedRangeColor[1]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HistogramBrush;
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
|
||||
const StillLoading = ({ zebra, displayName }) =>
|
||||
/*
|
||||
Render a loading indicator for the field.
|
||||
*/
|
||||
(
|
||||
<div
|
||||
data-testclass="gene-loading-spinner"
|
||||
style={{
|
||||
padding: globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 30 }} />
|
||||
<div style={{ display: "flex", alignSelf: "center" }}>
|
||||
<span style={{ fontStyle: "italic" }}>{displayName}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button minimal loading intent="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
;
|
||||
|
||||
export default StillLoading;
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from "react";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const StillLoading = ({ zebra, displayName }: any) => (
|
||||
/*
|
||||
Render a loading indicator for the field.
|
||||
*/
|
||||
<div
|
||||
data-testclass="gene-loading-spinner"
|
||||
style={{
|
||||
padding: globals.leftSidebarSectionPadding,
|
||||
backgroundColor: zebra ? globals.lightestGrey : "white",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 30 }} />
|
||||
<div style={{ display: "flex", alignSelf: "center" }}>
|
||||
<span style={{ fontStyle: "italic" }}>{displayName}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button minimal loading intent="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default StillLoading;
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import { Button, MenuItem } from "@blueprintjs/core";
|
||||
import { Select } from "@blueprintjs/select";
|
||||
|
||||
class DuplicateCategorySelect extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
allCategoryNames,
|
||||
categoryToDuplicate,
|
||||
handleModalDuplicateCategorySelection,
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Optionally duplicate all labels & cell assignments from existing
|
||||
category into new category:
|
||||
</p>
|
||||
<Select
|
||||
items={
|
||||
allCategoryNames ||
|
||||
[] /* this is a placeholder, could be a subcomponent to avoid this */
|
||||
}
|
||||
filterable={false}
|
||||
itemRenderer={(d, { handleClick }) => (
|
||||
<MenuItem
|
||||
data-testclass="duplicate-category-dropdown-option"
|
||||
onClick={handleClick}
|
||||
key={d}
|
||||
text={d}
|
||||
/>
|
||||
)}
|
||||
noResults={<MenuItem disabled text="No results." />}
|
||||
onItemSelect={(d) => {
|
||||
handleModalDuplicateCategorySelection(d);
|
||||
}}
|
||||
>
|
||||
{/* children become the popover target; render value here */}
|
||||
<Button
|
||||
data-testid="duplicate-category-dropdown"
|
||||
text={categoryToDuplicate || "None (all cells 'unassigned')"}
|
||||
rightIcon="double-caret-vertical"
|
||||
/>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DuplicateCategorySelect;
|
||||
@@ -1,65 +0,0 @@
|
||||
import React from "react";
|
||||
import { Button, MenuItem } from "@blueprintjs/core";
|
||||
import { Select } from "@blueprintjs/select";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class DuplicateCategorySelect extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'allCategoryNames' does not exist on type... Remove this comment to see the full error message
|
||||
allCategoryNames,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryToDuplicate' does not exist on t... Remove this comment to see the full error message
|
||||
categoryToDuplicate,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleModalDuplicateCategorySelection' d... Remove this comment to see the full error message
|
||||
handleModalDuplicateCategorySelection,
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Optionally duplicate all labels & cell assignments from existing
|
||||
category into new category:
|
||||
</p>
|
||||
<Select
|
||||
items={
|
||||
allCategoryNames ||
|
||||
[] /* this is a placeholder, could be a subcomponent to avoid this */
|
||||
}
|
||||
filterable={false}
|
||||
itemRenderer={(d, { handleClick }) => (
|
||||
<MenuItem
|
||||
data-testclass="duplicate-category-dropdown-option"
|
||||
onClick={handleClick}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'Key | nu... Remove this comment to see the full error message
|
||||
key={d}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'ReactNod... Remove this comment to see the full error message
|
||||
text={d}
|
||||
/>
|
||||
)}
|
||||
noResults={<MenuItem disabled text="No results." />}
|
||||
onItemSelect={(d) => {
|
||||
handleModalDuplicateCategorySelection(d);
|
||||
}}
|
||||
>
|
||||
{/* children become the popover target; render value here */}
|
||||
<Button
|
||||
data-testid="duplicate-category-dropdown"
|
||||
text={categoryToDuplicate || "None (all cells 'unassigned')"}
|
||||
rightIcon="double-caret-vertical"
|
||||
/>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DuplicateCategorySelect;
|
||||
@@ -0,0 +1,113 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
import { labelPrompt, isLabelErroneous } from "../labelUtil";
|
||||
import actions from "../../../actions";
|
||||
|
||||
@connect((state) => ({
|
||||
annotations: state.annotations,
|
||||
schema: state.annoMatrix?.schema,
|
||||
obsCrossfilter: state.obsCrossfilter,
|
||||
}))
|
||||
class Category extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
newLabelText: "",
|
||||
};
|
||||
}
|
||||
|
||||
disableAddNewLabelMode = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
this.setState({
|
||||
newLabelText: "",
|
||||
});
|
||||
dispatch({
|
||||
type: "annotation: disable add new label mode",
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
handleAddNewLabelToCategory = (e) => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { newLabelText } = this.state;
|
||||
|
||||
this.disableAddNewLabelMode();
|
||||
dispatch(
|
||||
actions.annotationCreateLabelInCategory(
|
||||
metadataField,
|
||||
newLabelText,
|
||||
false
|
||||
)
|
||||
);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
addLabelAndAssignCells = (e) => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { newLabelText } = this.state;
|
||||
|
||||
this.disableAddNewLabelMode();
|
||||
dispatch(
|
||||
actions.annotationCreateLabelInCategory(metadataField, newLabelText, true)
|
||||
);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
labelNameError = (name) => {
|
||||
const { metadataField, schema } = this.props;
|
||||
return isLabelErroneous(name, metadataField, schema);
|
||||
};
|
||||
|
||||
instruction = (label) => labelPrompt(this.labelNameError(label), "New, unique label", ":");
|
||||
|
||||
handleChangeOrSelect = (label) => {
|
||||
this.setState({ newLabelText: label });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { newLabelText } = this.state;
|
||||
const { metadataField, annotations, obsCrossfilter } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
isActive={
|
||||
annotations.isAddingNewLabel &&
|
||||
annotations.categoryAddingNewLabel === metadataField
|
||||
}
|
||||
inputProps={{ "data-testid": `${metadataField}:create-label-dialog` }}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${metadataField}:submit-label`,
|
||||
}}
|
||||
title="Add new label to category"
|
||||
instruction={this.instruction(newLabelText)}
|
||||
cancelTooltipContent="Close this dialog without adding a label."
|
||||
primaryButtonText="Add label"
|
||||
secondaryButtonText={`Add label & assign ${obsCrossfilter.countSelected()} selected cells`}
|
||||
handleSecondaryButtonSubmit={this.addLabelAndAssignCells}
|
||||
text={newLabelText}
|
||||
validationError={this.labelNameError(newLabelText)}
|
||||
handleSubmit={this.handleAddNewLabelToCategory}
|
||||
handleCancel={this.disableAddNewLabelMode}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
labelSuggestions={null}
|
||||
onChange={this.handleChangeOrSelect}
|
||||
onSelect={this.handleChangeOrSelect}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:new-label-name`,
|
||||
leftIcon: "tag",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Category;
|
||||
@@ -1,136 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
import { labelPrompt, isLabelErroneous } from "../labelUtil";
|
||||
import actions from "../../../actions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
obsCrossfilter: (state as any).obsCrossfilter,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class Category extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
newLabelText: "",
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
disableAddNewLabelMode = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
this.setState({
|
||||
newLabelText: "",
|
||||
});
|
||||
dispatch({
|
||||
type: "annotation: disable add new label mode",
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleAddNewLabelToCategory = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { newLabelText } = this.state;
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
this.disableAddNewLabelMode();
|
||||
dispatch(
|
||||
actions.annotationCreateLabelInCategory(
|
||||
metadataField,
|
||||
newLabelText,
|
||||
false
|
||||
)
|
||||
);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
addLabelAndAssignCells = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { newLabelText } = this.state;
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
this.disableAddNewLabelMode();
|
||||
dispatch(
|
||||
actions.annotationCreateLabelInCategory(metadataField, newLabelText, true)
|
||||
);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labelNameError = (name: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { metadataField, schema } = this.props;
|
||||
return isLabelErroneous(name, metadataField, schema);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
instruction = (label: any) =>
|
||||
labelPrompt(this.labelNameError(label), "New, unique label", ":");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChangeOrSelect = (label: any) => {
|
||||
this.setState({ newLabelText: label });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { newLabelText } = this.state;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { metadataField, annotations, obsCrossfilter } = this.props;
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isActive: any; inputProps: { "data-testid"... Remove this comment to see the full error message
|
||||
isActive={
|
||||
annotations.isAddingNewLabel &&
|
||||
annotations.categoryAddingNewLabel === metadataField
|
||||
}
|
||||
inputProps={{ "data-testid": `${metadataField}:create-label-dialog` }}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${metadataField}:submit-label`,
|
||||
}}
|
||||
title="Add new label to category"
|
||||
instruction={this.instruction(newLabelText)}
|
||||
cancelTooltipContent="Close this dialog without adding a label."
|
||||
primaryButtonText="Add label"
|
||||
secondaryButtonText={`Add label & assign ${obsCrossfilter.countSelected()} selected cells`}
|
||||
handleSecondaryButtonSubmit={this.addLabelAndAssignCells}
|
||||
text={newLabelText}
|
||||
validationError={this.labelNameError(newLabelText)}
|
||||
handleSubmit={this.handleAddNewLabelToCategory}
|
||||
handleCancel={this.disableAddNewLabelMode}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ labelSuggestions: null; onChange: (label: ... Remove this comment to see the full error message
|
||||
labelSuggestions={null}
|
||||
onChange={this.handleChangeOrSelect}
|
||||
onSelect={this.handleChangeOrSelect}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:new-label-name`,
|
||||
leftIcon: "tag",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Category;
|
||||
@@ -0,0 +1,149 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
import { labelPrompt } from "../labelUtil";
|
||||
|
||||
import { AnnotationsHelpers } from "../../../util/stateManager";
|
||||
import actions from "../../../actions";
|
||||
|
||||
@connect((state) => ({
|
||||
annotations: state.annotations,
|
||||
schema: state.annoMatrix?.schema,
|
||||
}))
|
||||
class AnnoDialogEditCategoryName extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
newCategoryText: props.metadataField,
|
||||
};
|
||||
}
|
||||
|
||||
handleChangeOrSelect = (name) => {
|
||||
this.setState({
|
||||
newCategoryText: name,
|
||||
});
|
||||
};
|
||||
|
||||
disableEditCategoryMode = () => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch({
|
||||
type: "annotation: disable category edit mode",
|
||||
});
|
||||
this.setState({ newCategoryText: metadataField });
|
||||
};
|
||||
|
||||
handleEditCategory = (e) => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { newCategoryText } = this.state;
|
||||
|
||||
/*
|
||||
test for uniqueness against *all* annotation names, not just the subset
|
||||
we render as categorical.
|
||||
*/
|
||||
const { schema } = this.props;
|
||||
const allCategoryNames = schema.annotations.obs.columns.map((c) => c.name);
|
||||
|
||||
if (
|
||||
(allCategoryNames.indexOf(newCategoryText) > -1 &&
|
||||
newCategoryText !== metadataField) ||
|
||||
newCategoryText === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disableEditCategoryMode();
|
||||
|
||||
if (metadataField !== newCategoryText)
|
||||
dispatch(
|
||||
actions.annotationRenameCategoryAction(metadataField, newCategoryText)
|
||||
);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
editedCategoryNameError = (name) => {
|
||||
const { metadataField } = this.props;
|
||||
|
||||
/* check for syntax errors in category name */
|
||||
const error = AnnotationsHelpers.annotationNameIsErroneous(name);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
/* check for duplicative categories */
|
||||
|
||||
/*
|
||||
test for uniqueness against *all* annotation names, not just the subset
|
||||
we render as categorical.
|
||||
*/
|
||||
const { schema } = this.props;
|
||||
const allCategoryNames = schema.annotations.obs.columns.map((c) => c.name);
|
||||
|
||||
const categoryNameAlreadyExists = allCategoryNames.indexOf(name) > -1;
|
||||
const sameName = name === metadataField;
|
||||
if (categoryNameAlreadyExists && !sameName) {
|
||||
return "duplicate";
|
||||
}
|
||||
|
||||
/* otherwise, no error */
|
||||
return false;
|
||||
};
|
||||
|
||||
instruction = (name) => labelPrompt(
|
||||
this.editedCategoryNameError(name),
|
||||
"New, unique category name",
|
||||
":"
|
||||
);
|
||||
|
||||
allCategoryNames() {
|
||||
const { schema } = this.props;
|
||||
return schema.annotations.obs.columns.map((c) => c.name);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { newCategoryText } = this.state;
|
||||
const { metadataField, annotations } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
isActive={
|
||||
annotations.isEditingCategoryName &&
|
||||
annotations.categoryBeingEdited === metadataField
|
||||
}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:edit-category-name-dialog`,
|
||||
}}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${metadataField}:submit-category-edit`,
|
||||
}}
|
||||
title="Edit category name"
|
||||
instruction={this.instruction(newCategoryText)}
|
||||
cancelTooltipContent="Close this dialog without editing this category."
|
||||
primaryButtonText="Edit category name"
|
||||
text={newCategoryText}
|
||||
validationError={this.editedCategoryNameError(newCategoryText)}
|
||||
handleSubmit={this.handleEditCategory}
|
||||
handleCancel={this.disableEditCategoryMode}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
label={newCategoryText}
|
||||
labelSuggestions={null}
|
||||
onChange={this.handleChangeOrSelect}
|
||||
onSelect={this.handleChangeOrSelect}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:edit-category-name-text`,
|
||||
leftIcon: "tag",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="New category"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AnnoDialogEditCategoryName;
|
||||
@@ -1,172 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
import { labelPrompt } from "../labelUtil";
|
||||
|
||||
import { AnnotationsHelpers } from "../../../util/stateManager";
|
||||
import actions from "../../../actions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class AnnoDialogEditCategoryName extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type '{... Remove this comment to see the full error message
|
||||
newCategoryText: props.metadataField,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChangeOrSelect = (name: any) => {
|
||||
this.setState({
|
||||
newCategoryText: name,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
disableEditCategoryMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch({
|
||||
type: "annotation: disable category edit mode",
|
||||
});
|
||||
this.setState({ newCategoryText: metadataField });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleEditCategory = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
const { newCategoryText } = this.state;
|
||||
/*
|
||||
test for uniqueness against *all* annotation names, not just the subset
|
||||
we render as categorical.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
const { schema } = this.props;
|
||||
const allCategoryNames = schema.annotations.obs.columns.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(c: any) => c.name
|
||||
);
|
||||
if (
|
||||
(allCategoryNames.indexOf(newCategoryText) > -1 &&
|
||||
newCategoryText !== metadataField) ||
|
||||
newCategoryText === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.disableEditCategoryMode();
|
||||
if (metadataField !== newCategoryText)
|
||||
dispatch(
|
||||
actions.annotationRenameCategoryAction(metadataField, newCategoryText)
|
||||
);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
editedCategoryNameError = (name: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { metadataField } = this.props;
|
||||
/* check for syntax errors in category name */
|
||||
const error = AnnotationsHelpers.annotationNameIsErroneous(name);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
/* check for duplicative categories */
|
||||
/*
|
||||
test for uniqueness against *all* annotation names, not just the subset
|
||||
we render as categorical.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
const { schema } = this.props;
|
||||
const allCategoryNames = schema.annotations.obs.columns.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(c: any) => c.name
|
||||
);
|
||||
const categoryNameAlreadyExists = allCategoryNames.indexOf(name) > -1;
|
||||
const sameName = name === metadataField;
|
||||
if (categoryNameAlreadyExists && !sameName) {
|
||||
return "duplicate";
|
||||
}
|
||||
/* otherwise, no error */
|
||||
return false;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
instruction = (name: any) =>
|
||||
labelPrompt(
|
||||
this.editedCategoryNameError(name),
|
||||
"New, unique category name",
|
||||
":"
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
allCategoryNames() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
const { schema } = this.props;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
return schema.annotations.obs.columns.map((c: any) => c.name);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { newCategoryText } = this.state;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { metadataField, annotations } = this.props;
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isActive: any; inputProps: { "data-testid"... Remove this comment to see the full error message
|
||||
isActive={
|
||||
annotations.isEditingCategoryName &&
|
||||
annotations.categoryBeingEdited === metadataField
|
||||
}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:edit-category-name-dialog`,
|
||||
}}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${metadataField}:submit-category-edit`,
|
||||
}}
|
||||
title="Edit category name"
|
||||
instruction={this.instruction(newCategoryText)}
|
||||
cancelTooltipContent="Close this dialog without editing this category."
|
||||
primaryButtonText="Edit category name"
|
||||
text={newCategoryText}
|
||||
validationError={this.editedCategoryNameError(newCategoryText)}
|
||||
handleSubmit={this.handleEditCategory}
|
||||
handleCancel={this.disableEditCategoryMode}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ label: any; labelSuggestions: null; onChan... Remove this comment to see the full error message
|
||||
label={newCategoryText}
|
||||
labelSuggestions={null}
|
||||
onChange={this.handleChangeOrSelect}
|
||||
onSelect={this.handleChangeOrSelect}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:edit-category-name-text`,
|
||||
leftIcon: "tag",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="New category"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AnnoDialogEditCategoryName;
|
||||
+5
-23
@@ -16,25 +16,16 @@ import { IconNames } from "@blueprintjs/icons";
|
||||
import * as globals from "../../../globals";
|
||||
import actions from "../../../actions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
annotations: state.annotations,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class AnnoMenuCategory extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class AnnoMenuCategory extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
activateAddNewLabelMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch({
|
||||
type: "annotation: activate add new label mode",
|
||||
@@ -42,39 +33,30 @@ class AnnoMenuCategory extends React.PureComponent<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
activateEditCategoryMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
|
||||
dispatch({
|
||||
type: "annotation: activate category edit mode",
|
||||
data: metadataField,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleDeleteCategory = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch(actions.annotationDeleteCategoryAction(metadataField));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
annotations,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserAnno' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
isUserAnno,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'createText' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
createText,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'editText' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
editText,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'deleteText' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
deleteText,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isUserAnno ? (
|
||||
@@ -0,0 +1,617 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||
import {
|
||||
AnchorButton,
|
||||
Button,
|
||||
Classes,
|
||||
Position,
|
||||
Tooltip,
|
||||
} from "@blueprintjs/core";
|
||||
import { Flipper, Flipped } from "react-flip-toolkit";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
|
||||
import Value from "../value";
|
||||
import AnnoMenu from "./annoMenuCategory";
|
||||
import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName";
|
||||
import AnnoDialogAddLabel from "./annoDialogAddLabel";
|
||||
import Truncate from "../../util/truncate";
|
||||
import { CategoryCrossfilterContext } from "../categoryContext";
|
||||
|
||||
import * as globals from "../../../globals";
|
||||
import { createCategorySummaryFromDfCol } from "../../../util/stateManager/controlsHelpers";
|
||||
import {
|
||||
createColorTable,
|
||||
createColorQuery,
|
||||
} from "../../../util/stateManager/colorHelpers";
|
||||
import actions from "../../../actions";
|
||||
|
||||
const LABEL_WIDTH = globals.leftSidebarWidth - 100;
|
||||
const ANNO_BUTTON_WIDTH = 50;
|
||||
const LABEL_WIDTH_ANNO = LABEL_WIDTH - ANNO_BUTTON_WIDTH;
|
||||
|
||||
@connect((state, ownProps) => {
|
||||
const schema = state.annoMatrix?.schema;
|
||||
const { metadataField } = ownProps;
|
||||
const isUserAnno = schema?.annotations?.obsByName[metadataField]?.writable;
|
||||
const categoricalSelection = state.categoricalSelection?.[metadataField];
|
||||
return {
|
||||
colors: state.colors,
|
||||
categoricalSelection,
|
||||
annotations: state.annotations,
|
||||
annoMatrix: state.annoMatrix,
|
||||
schema,
|
||||
crossfilter: state.obsCrossfilter,
|
||||
isUserAnno,
|
||||
genesets: state.genesets.genesets,
|
||||
};
|
||||
})
|
||||
class Category extends React.PureComponent {
|
||||
static getSelectionState(
|
||||
categoricalSelection,
|
||||
metadataField,
|
||||
categorySummary
|
||||
) {
|
||||
// total number of categories in this dimension
|
||||
const totalCatCount = categorySummary.numCategoryValues;
|
||||
// number of selected options in this category
|
||||
const selectedCatCount = categorySummary.categoryValues.reduce(
|
||||
(res, label) => (categoricalSelection.get(label) ?? true ? res + 1 : res),
|
||||
0
|
||||
);
|
||||
return selectedCatCount === totalCatCount
|
||||
? "all"
|
||||
: selectedCatCount === 0
|
||||
? "none"
|
||||
: "some";
|
||||
}
|
||||
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
createCategorySummaryFromDfCol = memoize(createCategorySummaryFromDfCol);
|
||||
|
||||
getSelectionState(categorySummary) {
|
||||
const { categoricalSelection, metadataField } = this.props;
|
||||
return Category.getSelectionState(
|
||||
categoricalSelection,
|
||||
metadataField,
|
||||
categorySummary
|
||||
);
|
||||
}
|
||||
|
||||
handleColorChange = () => {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch({
|
||||
type: "color by categorical metadata",
|
||||
colorAccessor: metadataField,
|
||||
});
|
||||
};
|
||||
|
||||
handleCategoryClick = () => {
|
||||
const { annotations, metadataField, onExpansionChange } = this.props;
|
||||
const editingCategory =
|
||||
annotations.isEditingCategoryName &&
|
||||
annotations.categoryBeingEdited === metadataField;
|
||||
if (!editingCategory) {
|
||||
onExpansionChange(metadataField);
|
||||
}
|
||||
};
|
||||
|
||||
handleCategoryKeyPress = (e) => {
|
||||
if (e.key === "Enter") {
|
||||
this.handleCategoryClick();
|
||||
}
|
||||
};
|
||||
|
||||
handleToggleAllClick = (categorySummary) => {
|
||||
const isChecked = this.getSelectionState(categorySummary);
|
||||
if (isChecked === "all") {
|
||||
this.toggleNone(categorySummary);
|
||||
} else {
|
||||
this.toggleAll(categorySummary);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAsyncProps = async (props) => {
|
||||
const { annoMatrix, metadataField, colors } = props.watchProps;
|
||||
const { crossfilter } = this.props;
|
||||
|
||||
const [categoryData, categorySummary, colorData] = await this.fetchData(
|
||||
annoMatrix,
|
||||
metadataField,
|
||||
colors
|
||||
);
|
||||
|
||||
return {
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorData,
|
||||
crossfilter,
|
||||
...this.updateColorTable(colorData),
|
||||
handleCategoryToggleAllClick: () =>
|
||||
this.handleToggleAllClick(categorySummary),
|
||||
};
|
||||
};
|
||||
|
||||
async fetchData(annoMatrix, metadataField, colors) {
|
||||
/*
|
||||
fetch our data and the color-by data if appropriate, and then build a summary
|
||||
of our category and a color table for the color-by annotation.
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor, colorMode } = colors;
|
||||
const { genesets } = this.props;
|
||||
let colorDataPromise = Promise.resolve(null);
|
||||
if (colorAccessor) {
|
||||
const query = createColorQuery(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
schema,
|
||||
genesets
|
||||
);
|
||||
if (query) colorDataPromise = annoMatrix.fetch(...query);
|
||||
}
|
||||
const [categoryData, colorData] = await Promise.all([
|
||||
annoMatrix.fetch("obs", metadataField),
|
||||
colorDataPromise,
|
||||
]);
|
||||
|
||||
// our data
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[metadataField];
|
||||
const categorySummary = this.createCategorySummaryFromDfCol(
|
||||
column,
|
||||
colSchema
|
||||
);
|
||||
return [categoryData, categorySummary, colorData];
|
||||
}
|
||||
|
||||
updateColorTable(colorData) {
|
||||
// color table, which may be null
|
||||
const { schema, colors, metadataField } = this.props;
|
||||
const { colorAccessor, userColors, colorMode } = colors;
|
||||
return {
|
||||
isColorAccessor: colorAccessor === metadataField,
|
||||
colorAccessor,
|
||||
colorMode,
|
||||
colorTable: createColorTable(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
schema,
|
||||
userColors
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
toggleNone(categorySummary) {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch(
|
||||
actions.selectCategoricalAllMetadataAction(
|
||||
"categorical metadata filter none of these",
|
||||
metadataField,
|
||||
categorySummary.allCategoryValues,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
toggleAll(categorySummary) {
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch(
|
||||
actions.selectCategoricalAllMetadataAction(
|
||||
"categorical metadata filter all of these",
|
||||
metadataField,
|
||||
categorySummary.allCategoryValues,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
metadataField,
|
||||
isExpanded,
|
||||
categoricalSelection,
|
||||
crossfilter,
|
||||
colors,
|
||||
annoMatrix,
|
||||
isUserAnno,
|
||||
} = this.props;
|
||||
|
||||
const checkboxID = `category-select-${metadataField}`;
|
||||
|
||||
return (
|
||||
<CategoryCrossfilterContext.Provider value={crossfilter}>
|
||||
<Async
|
||||
watchFn={Category.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{
|
||||
metadataField,
|
||||
annoMatrix,
|
||||
categoricalSelection,
|
||||
colors,
|
||||
}}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
/>
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading metadataField={metadataField} error={error} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled persist>
|
||||
{(asyncProps) => {
|
||||
const {
|
||||
colorAccessor,
|
||||
colorTable,
|
||||
colorData,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
isColorAccessor,
|
||||
handleCategoryToggleAllClick,
|
||||
} = asyncProps;
|
||||
const selectionState = this.getSelectionState(categorySummary);
|
||||
return (
|
||||
<CategoryRender
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={selectionState}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
onColorChangeClick={this.handleColorChange}
|
||||
onCategoryToggleAllClick={handleCategoryToggleAllClick}
|
||||
onCategoryMenuClick={this.handleCategoryClick}
|
||||
onCategoryMenuKeyPress={this.handleCategoryKeyPress}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
</CategoryCrossfilterContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Category;
|
||||
|
||||
const StillLoading = ({ metadataField, checkboxID }) =>
|
||||
/*
|
||||
We are still loading this category, so render a "busy" signal.
|
||||
*/
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
maxWidth: globals.maxControlsWidth,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
htmlFor={checkboxID}
|
||||
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||
>
|
||||
<input disabled id={checkboxID} checked type="checkbox" />
|
||||
<span className={Classes.CONTROL_INDICATOR} />
|
||||
</label>
|
||||
<Truncate>
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
width: LABEL_WIDTH,
|
||||
}}
|
||||
>
|
||||
{metadataField}
|
||||
</span>
|
||||
</Truncate>
|
||||
</div>
|
||||
<div>
|
||||
<Button minimal loading intent="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
;
|
||||
|
||||
const ErrorLoading = ({ metadataField, error }) => {
|
||||
console.error(error); // log error to console as it is unexpected.
|
||||
return (
|
||||
<div style={{ marginBottom: 10, marginTop: 4 }}>
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
width: LABEL_WIDTH,
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
{`Failure loading ${metadataField}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CategoryHeader = React.memo(
|
||||
({
|
||||
metadataField,
|
||||
checkboxID,
|
||||
isUserAnno,
|
||||
isColorAccessor,
|
||||
isExpanded,
|
||||
selectionState,
|
||||
onColorChangeClick,
|
||||
onCategoryMenuClick,
|
||||
onCategoryMenuKeyPress,
|
||||
onCategoryToggleAllClick,
|
||||
}) => {
|
||||
/*
|
||||
Render category name and controls (eg, color-by button).
|
||||
*/
|
||||
const checkboxRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
checkboxRef.current.indeterminate = selectionState === "some";
|
||||
}, [checkboxRef.current, selectionState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||
htmlFor={checkboxID}
|
||||
>
|
||||
<input
|
||||
id={checkboxID}
|
||||
data-testclass="category-select"
|
||||
data-testid={`${metadataField}:category-select`}
|
||||
onChange={onCategoryToggleAllClick}
|
||||
ref={checkboxRef}
|
||||
checked={selectionState === "all"}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span className={Classes.CONTROL_INDICATOR} />
|
||||
</label>
|
||||
<span
|
||||
role="menuitem"
|
||||
tabIndex="0"
|
||||
data-testclass="category-expand"
|
||||
data-testid={`${metadataField}:category-expand`}
|
||||
onKeyPress={onCategoryMenuKeyPress}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={onCategoryMenuClick}
|
||||
>
|
||||
<Truncate>
|
||||
<span
|
||||
style={{
|
||||
maxWidth: isUserAnno ? LABEL_WIDTH_ANNO : LABEL_WIDTH,
|
||||
}}
|
||||
data-testid={`${metadataField}:category-label`}
|
||||
tabIndex="-1"
|
||||
>
|
||||
{metadataField}
|
||||
</span>
|
||||
</Truncate>
|
||||
{isExpanded ? (
|
||||
<FaChevronDown
|
||||
data-testclass="category-expand-is-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<FaChevronRight
|
||||
data-testclass="category-expand-is-not-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{<AnnoDialogEditCategoryName metadataField={metadataField} />}
|
||||
{<AnnoDialogAddLabel metadataField={metadataField} />}
|
||||
<div>
|
||||
<AnnoMenu
|
||||
metadataField={metadataField}
|
||||
isUserAnno={isUserAnno}
|
||||
createText="Add a new label to this category"
|
||||
editText="Edit this category's name"
|
||||
deleteText="Delete this category, all associated labels, and remove all cell assignments"
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
content="Use as color scale"
|
||||
position={Position.LEFT}
|
||||
usePortal
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||
modifiers={{
|
||||
preventOverflow: { enabled: false },
|
||||
hide: { enabled: false },
|
||||
}}
|
||||
>
|
||||
<AnchorButton
|
||||
data-testclass="colorby"
|
||||
data-testid={`colorby-${metadataField}`}
|
||||
onClick={onColorChangeClick}
|
||||
active={isColorAccessor}
|
||||
intent={isColorAccessor ? "primary" : "none"}
|
||||
icon="tint"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CategoryRender = React.memo(
|
||||
({
|
||||
metadataField,
|
||||
checkboxID,
|
||||
isUserAnno,
|
||||
isColorAccessor,
|
||||
isExpanded,
|
||||
selectionState,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
colorTable,
|
||||
onColorChangeClick,
|
||||
onCategoryMenuClick,
|
||||
onCategoryMenuKeyPress,
|
||||
onCategoryToggleAllClick,
|
||||
}) => {
|
||||
/*
|
||||
Render the core of the category, including checkboxes, controls, etc.
|
||||
*/
|
||||
const { numCategoryValues } = categorySummary;
|
||||
const isSingularValue = !isUserAnno && numCategoryValues === 1;
|
||||
|
||||
if (isSingularValue) {
|
||||
/*
|
||||
Entire category has a single value, special case.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
Otherwise, our normal multi-layout layout
|
||||
*/
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: globals.maxControlsWidth,
|
||||
}}
|
||||
data-testclass="category"
|
||||
data-testid={`category-${metadataField}`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<CategoryHeader
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={selectionState}
|
||||
onColorChangeClick={onColorChangeClick}
|
||||
onCategoryToggleAllClick={onCategoryToggleAllClick}
|
||||
onCategoryMenuClick={onCategoryMenuClick}
|
||||
onCategoryMenuKeyPress={onCategoryMenuKeyPress}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginLeft: 26 }}>
|
||||
{
|
||||
/* values*/
|
||||
isExpanded ? (
|
||||
<CategoryValueList
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CategoryValueList = React.memo(
|
||||
({
|
||||
isUserAnno,
|
||||
metadataField,
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
colorTable,
|
||||
}) => {
|
||||
const tuples = [...categorySummary.categoryValueIndices];
|
||||
|
||||
/*
|
||||
Render the value list. If this is a user annotation, we use a flipper
|
||||
animation, if read-only, we don't bother and save a few bits of perf.
|
||||
*/
|
||||
if (!isUserAnno) {
|
||||
return (
|
||||
<>
|
||||
{tuples.map(([value, index]) => (
|
||||
<Value
|
||||
key={value}
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={index}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* User annotation */
|
||||
const flipKey = tuples.map((t) => t[0]).join("");
|
||||
return (
|
||||
<Flipper flipKey={flipKey}>
|
||||
{tuples.map(([value, index]) => (
|
||||
<Flipped key={value} flipId={value}>
|
||||
<Value
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={index}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
</Flipped>
|
||||
))}
|
||||
</Flipper>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,724 +0,0 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||
import {
|
||||
AnchorButton,
|
||||
Button,
|
||||
Classes,
|
||||
Position,
|
||||
Tooltip,
|
||||
} from "@blueprintjs/core";
|
||||
import { Flipper, Flipped } from "react-flip-toolkit";
|
||||
import Async from "react-async";
|
||||
import memoize from "memoize-one";
|
||||
|
||||
import Value from "../value";
|
||||
import AnnoMenu from "./annoMenuCategory";
|
||||
import AnnoDialogEditCategoryName from "./annoDialogEditCategoryName";
|
||||
import AnnoDialogAddLabel from "./annoDialogAddLabel";
|
||||
import Truncate from "../../util/truncate";
|
||||
import { CategoryCrossfilterContext } from "../categoryContext";
|
||||
|
||||
import * as globals from "../../../globals";
|
||||
import { createCategorySummaryFromDfCol } from "../../../util/stateManager/controlsHelpers";
|
||||
import {
|
||||
createColorTable,
|
||||
createColorQuery,
|
||||
} from "../../../util/stateManager/colorHelpers";
|
||||
import actions from "../../../actions";
|
||||
import { Dataframe } from "../../../util/dataframe";
|
||||
|
||||
const LABEL_WIDTH = globals.leftSidebarWidth - 100;
|
||||
const ANNO_BUTTON_WIDTH = 50;
|
||||
const LABEL_WIDTH_ANNO = LABEL_WIDTH - ANNO_BUTTON_WIDTH;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state, ownProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const schema = (state as any).annoMatrix?.schema;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type '{... Remove this comment to see the full error message
|
||||
const { metadataField } = ownProps;
|
||||
const isUserAnno = schema?.annotations?.obsByName[metadataField]?.writable;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const categoricalSelection = (state as any).categoricalSelection?.[
|
||||
metadataField
|
||||
];
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colors: (state as any).colors,
|
||||
categoricalSelection,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
crossfilter: (state as any).obsCrossfilter,
|
||||
isUserAnno,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
};
|
||||
})
|
||||
class Category extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
static getSelectionState(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoricalSelection: any,
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'metadataField' is declared but its value is never... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categorySummary: any
|
||||
) {
|
||||
// total number of categories in this dimension
|
||||
const totalCatCount = categorySummary.numCategoryValues;
|
||||
// number of selected options in this category
|
||||
const selectedCatCount = categorySummary.categoryValues.reduce(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(res: any, label: any) =>
|
||||
categoricalSelection.get(label) ?? true ? res + 1 : res,
|
||||
0
|
||||
);
|
||||
return selectedCatCount === totalCatCount
|
||||
? "all"
|
||||
: selectedCatCount === 0
|
||||
? "none"
|
||||
: "some";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static watchAsync(props: any, prevProps: any) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
createCategorySummaryFromDfCol = memoize(createCategorySummaryFromDfCol);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
getSelectionState(categorySummary: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoricalSelection' does not exist on ... Remove this comment to see the full error message
|
||||
const { categoricalSelection, metadataField } = this.props;
|
||||
return Category.getSelectionState(
|
||||
categoricalSelection,
|
||||
metadataField,
|
||||
categorySummary
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleColorChange = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch({
|
||||
type: "color by categorical metadata",
|
||||
colorAccessor: metadataField,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleCategoryClick = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
const { annotations, metadataField, onExpansionChange } = this.props;
|
||||
const editingCategory =
|
||||
annotations.isEditingCategoryName &&
|
||||
annotations.categoryBeingEdited === metadataField;
|
||||
if (!editingCategory) {
|
||||
onExpansionChange(metadataField);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleCategoryKeyPress = (e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
this.handleCategoryClick();
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleToggleAllClick = (categorySummary: any) => {
|
||||
const isChecked = this.getSelectionState(categorySummary);
|
||||
if (isChecked === "all") {
|
||||
this.toggleNone(categorySummary);
|
||||
} else {
|
||||
this.toggleAll(categorySummary);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
fetchAsyncProps = async (props: any) => {
|
||||
const { annoMatrix, metadataField, colors } = props.watchProps;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'crossfilter' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
const { crossfilter } = this.props;
|
||||
|
||||
const [categoryData, categorySummary, colorData] = await this.fetchData(
|
||||
annoMatrix,
|
||||
metadataField,
|
||||
colors
|
||||
);
|
||||
|
||||
return {
|
||||
categoryData,
|
||||
categorySummary,
|
||||
colorData,
|
||||
crossfilter,
|
||||
...this.updateColorTable(colorData),
|
||||
handleCategoryToggleAllClick: () =>
|
||||
this.handleToggleAllClick(categorySummary),
|
||||
};
|
||||
};
|
||||
|
||||
async fetchData(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colors: any
|
||||
): Promise<
|
||||
[
|
||||
Dataframe,
|
||||
ReturnType<typeof createCategorySummaryFromDfCol>,
|
||||
Dataframe | null
|
||||
]
|
||||
> {
|
||||
/*
|
||||
fetch our data and the color-by data if appropriate, and then build a summary
|
||||
of our category and a color table for the color-by annotation.
|
||||
*/
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor, colorMode } = colors;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { genesets } = this.props;
|
||||
let colorDataPromise: Promise<Dataframe | null> = Promise.resolve(null);
|
||||
if (colorAccessor) {
|
||||
const query = createColorQuery(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
schema,
|
||||
genesets
|
||||
);
|
||||
if (query) colorDataPromise = annoMatrix.fetch(...query);
|
||||
}
|
||||
const [categoryData, colorData] = await Promise.all<
|
||||
Dataframe,
|
||||
Dataframe | null
|
||||
>([annoMatrix.fetch("obs", metadataField), colorDataPromise]);
|
||||
|
||||
// our data
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[metadataField];
|
||||
const categorySummary = this.createCategorySummaryFromDfCol(
|
||||
column,
|
||||
colSchema
|
||||
);
|
||||
return [categoryData, categorySummary, colorData];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -- - FIXME: disabled temporarily on migrate to TS.
|
||||
updateColorTable(colorData: Dataframe|null) {
|
||||
// color table, which may be null
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
const { schema, colors, metadataField } = this.props;
|
||||
const { colorAccessor, userColors, colorMode } = colors;
|
||||
return {
|
||||
isColorAccessor: colorAccessor === metadataField,
|
||||
colorAccessor,
|
||||
colorMode,
|
||||
colorTable: createColorTable(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
schema,
|
||||
userColors
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
toggleNone(categorySummary: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch(
|
||||
actions.selectCategoricalAllMetadataAction(
|
||||
"categorical metadata filter none of these",
|
||||
metadataField,
|
||||
categorySummary.allCategoryValues,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
toggleAll(categorySummary: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField } = this.props;
|
||||
dispatch(
|
||||
actions.selectCategoricalAllMetadataAction(
|
||||
"categorical metadata filter all of these",
|
||||
metadataField,
|
||||
categorySummary.allCategoryValues,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isExpanded' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
isExpanded,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoricalSelection' does not exist on ... Remove this comment to see the full error message
|
||||
categoricalSelection,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'crossfilter' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
crossfilter,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colors' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
colors,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
annoMatrix,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserAnno' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
isUserAnno,
|
||||
} = this.props;
|
||||
|
||||
const checkboxID = `category-select-${metadataField}`;
|
||||
|
||||
return (
|
||||
<CategoryCrossfilterContext.Provider value={crossfilter}>
|
||||
<Async
|
||||
watchFn={Category.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{
|
||||
metadataField,
|
||||
annoMatrix,
|
||||
categoricalSelection,
|
||||
colors,
|
||||
}}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
<StillLoading
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
/>
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
<ErrorLoading metadataField={metadataField} error={error} />
|
||||
)}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled persist>
|
||||
{(asyncProps) => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'u... Remove this comment to see the full error message
|
||||
colorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTable' does not exist on type 'unkn... Remove this comment to see the full error message
|
||||
colorTable,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorData' does not exist on type 'unkno... Remove this comment to see the full error message
|
||||
colorData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryData' does not exist on type 'un... Remove this comment to see the full error message
|
||||
categoryData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categorySummary' does not exist on type ... Remove this comment to see the full error message
|
||||
categorySummary,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleCategoryToggleAllClick' does not e... Remove this comment to see the full error message
|
||||
handleCategoryToggleAllClick,
|
||||
} = asyncProps;
|
||||
const selectionState = this.getSelectionState(categorySummary);
|
||||
return (
|
||||
<CategoryRender
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ metadataField: any; checkboxID: string; is... Remove this comment to see the full error message
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={selectionState}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
onColorChangeClick={this.handleColorChange}
|
||||
onCategoryToggleAllClick={handleCategoryToggleAllClick}
|
||||
onCategoryMenuClick={this.handleCategoryClick}
|
||||
onCategoryMenuKeyPress={this.handleCategoryKeyPress}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
</CategoryCrossfilterContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Category;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const StillLoading = ({ metadataField, checkboxID }: any) => (
|
||||
/*
|
||||
We are still loading this category, so render a "busy" signal.
|
||||
*/
|
||||
<div
|
||||
style={{
|
||||
maxWidth: globals.maxControlsWidth,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
htmlFor={checkboxID}
|
||||
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||
>
|
||||
<input disabled id={checkboxID} checked type="checkbox" />
|
||||
<span className={Classes.CONTROL_INDICATOR} />
|
||||
</label>
|
||||
<Truncate>
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
width: LABEL_WIDTH,
|
||||
}}
|
||||
>
|
||||
{metadataField}
|
||||
</span>
|
||||
</Truncate>
|
||||
</div>
|
||||
<div>
|
||||
<Button minimal loading intent="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const ErrorLoading = ({ metadataField, error }: any) => {
|
||||
console.error(error); // log error to console as it is unexpected.
|
||||
return (
|
||||
<div style={{ marginBottom: 10, marginTop: 4 }}>
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
width: LABEL_WIDTH,
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
{`Failure loading ${metadataField}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CategoryHeader = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type '{... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'checkboxID' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
checkboxID,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserAnno' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
isUserAnno,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isExpanded' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
isExpanded,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionState' does not exist on type '... Remove this comment to see the full error message
|
||||
selectionState,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onColorChangeClick' does not exist on ty... Remove this comment to see the full error message
|
||||
onColorChangeClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onCategoryMenuClick' does not exist on t... Remove this comment to see the full error message
|
||||
onCategoryMenuClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onCategoryMenuKeyPress' does not exist o... Remove this comment to see the full error message
|
||||
onCategoryMenuKeyPress,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onCategoryToggleAllClick' does not exist... Remove this comment to see the full error message
|
||||
onCategoryToggleAllClick,
|
||||
}) => {
|
||||
/*
|
||||
Render category name and controls (eg, color-by button).
|
||||
*/
|
||||
const checkboxRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
checkboxRef.current.indeterminate = selectionState === "some";
|
||||
}, [checkboxRef.current, selectionState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||
htmlFor={checkboxID}
|
||||
>
|
||||
<input
|
||||
id={checkboxID}
|
||||
data-testclass="category-select"
|
||||
data-testid={`${metadataField}:category-select`}
|
||||
onChange={onCategoryToggleAllClick}
|
||||
ref={checkboxRef}
|
||||
checked={selectionState === "all"}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span className={Classes.CONTROL_INDICATOR} />
|
||||
</label>
|
||||
<span
|
||||
role="menuitem"
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="0"
|
||||
data-testclass="category-expand"
|
||||
data-testid={`${metadataField}:category-expand`}
|
||||
onKeyPress={onCategoryMenuKeyPress}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={onCategoryMenuClick}
|
||||
>
|
||||
<Truncate>
|
||||
<span
|
||||
style={{
|
||||
maxWidth: isUserAnno ? LABEL_WIDTH_ANNO : LABEL_WIDTH,
|
||||
}}
|
||||
data-testid={`${metadataField}:category-label`}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="-1"
|
||||
>
|
||||
{metadataField}
|
||||
</span>
|
||||
</Truncate>
|
||||
{isExpanded ? (
|
||||
<FaChevronDown
|
||||
data-testclass="category-expand-is-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<FaChevronRight
|
||||
data-testclass="category-expand-is-not-expanded"
|
||||
style={{ fontSize: 10, marginLeft: 5 }}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ metadataField: any; }' is not assignable t... Remove this comment to see the full error message */}
|
||||
<AnnoDialogEditCategoryName metadataField={metadataField} />
|
||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ metadataField: any; }' is not assignable t... Remove this comment to see the full error message */}
|
||||
<AnnoDialogAddLabel metadataField={metadataField} />
|
||||
<div>
|
||||
<AnnoMenu
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ metadataField: any; isUserAnno: any; creat... Remove this comment to see the full error message
|
||||
metadataField={metadataField}
|
||||
isUserAnno={isUserAnno}
|
||||
createText="Add a new label to this category"
|
||||
editText="Edit this category's name"
|
||||
deleteText="Delete this category, all associated labels, and remove all cell assignments"
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
content="Use as color scale"
|
||||
position={Position.LEFT}
|
||||
usePortal
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||
modifiers={{
|
||||
preventOverflow: { enabled: false },
|
||||
hide: { enabled: false },
|
||||
}}
|
||||
>
|
||||
<AnchorButton
|
||||
data-testclass="colorby"
|
||||
data-testid={`colorby-${metadataField}`}
|
||||
onClick={onColorChangeClick}
|
||||
active={isColorAccessor}
|
||||
intent={isColorAccessor ? "primary" : "none"}
|
||||
icon="tint"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CategoryRender = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type '{... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'checkboxID' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
checkboxID,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserAnno' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
isUserAnno,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isExpanded' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
isExpanded,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionState' does not exist on type '... Remove this comment to see the full error message
|
||||
selectionState,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryData' does not exist on type '{ ... Remove this comment to see the full error message
|
||||
categoryData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categorySummary' does not exist on type ... Remove this comment to see the full error message
|
||||
categorySummary,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type '{... Remove this comment to see the full error message
|
||||
colorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorData' does not exist on type '{ chi... Remove this comment to see the full error message
|
||||
colorData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTable' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
colorTable,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onColorChangeClick' does not exist on ty... Remove this comment to see the full error message
|
||||
onColorChangeClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onCategoryMenuClick' does not exist on t... Remove this comment to see the full error message
|
||||
onCategoryMenuClick,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onCategoryMenuKeyPress' does not exist o... Remove this comment to see the full error message
|
||||
onCategoryMenuKeyPress,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onCategoryToggleAllClick' does not exist... Remove this comment to see the full error message
|
||||
onCategoryToggleAllClick,
|
||||
}) => {
|
||||
/*
|
||||
Render the core of the category, including checkboxes, controls, etc.
|
||||
*/
|
||||
const { numCategoryValues } = categorySummary;
|
||||
const isSingularValue = !isUserAnno && numCategoryValues === 1;
|
||||
|
||||
if (isSingularValue) {
|
||||
/*
|
||||
Entire category has a single value, special case.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
Otherwise, our normal multi-layout layout
|
||||
*/
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: globals.maxControlsWidth,
|
||||
}}
|
||||
data-testclass="category"
|
||||
data-testid={`category-${metadataField}`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<CategoryHeader
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ metadataField: any; checkboxID: any; isUse... Remove this comment to see the full error message
|
||||
metadataField={metadataField}
|
||||
checkboxID={checkboxID}
|
||||
isUserAnno={isUserAnno}
|
||||
isExpanded={isExpanded}
|
||||
isColorAccessor={isColorAccessor}
|
||||
selectionState={selectionState}
|
||||
onColorChangeClick={onColorChangeClick}
|
||||
onCategoryToggleAllClick={onCategoryToggleAllClick}
|
||||
onCategoryMenuClick={onCategoryMenuClick}
|
||||
onCategoryMenuKeyPress={onCategoryMenuKeyPress}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginLeft: 26 }}>
|
||||
{
|
||||
/* values*/
|
||||
isExpanded ? (
|
||||
<CategoryValueList
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isUserAnno: any; metadataField: any; categ... Remove this comment to see the full error message
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CategoryValueList = React.memo(
|
||||
({
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserAnno' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
isUserAnno,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type '{... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryData' does not exist on type '{ ... Remove this comment to see the full error message
|
||||
categoryData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categorySummary' does not exist on type ... Remove this comment to see the full error message
|
||||
categorySummary,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type '{... Remove this comment to see the full error message
|
||||
colorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorData' does not exist on type '{ chi... Remove this comment to see the full error message
|
||||
colorData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTable' does not exist on type '{ ch... Remove this comment to see the full error message
|
||||
colorTable,
|
||||
}) => {
|
||||
const tuples = [...categorySummary.categoryValueIndices];
|
||||
|
||||
/*
|
||||
Render the value list. If this is a user annotation, we use a flipper
|
||||
animation, if read-only, we don't bother and save a few bits of perf.
|
||||
*/
|
||||
if (!isUserAnno) {
|
||||
return (
|
||||
<>
|
||||
{tuples.map(([value, index]) => (
|
||||
<Value
|
||||
key={value}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: any; isUserAnno: any; metadataField: ... Remove this comment to see the full error message
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={index}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* User annotation */
|
||||
const flipKey = tuples.map((t) => t[0]).join("");
|
||||
return (
|
||||
<Flipper flipKey={flipKey}>
|
||||
{tuples.map(([value, index]) => (
|
||||
<Flipped key={value} flipId={value}>
|
||||
<Value
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isUserAnno: any; metadataField: any; categ... Remove this comment to see the full error message
|
||||
isUserAnno={isUserAnno}
|
||||
metadataField={metadataField}
|
||||
categoryIndex={index}
|
||||
categoryData={categoryData}
|
||||
categorySummary={categorySummary}
|
||||
colorAccessor={colorAccessor}
|
||||
colorData={colorData}
|
||||
colorTable={colorTable}
|
||||
/>
|
||||
</Flipped>
|
||||
))}
|
||||
</Flipper>
|
||||
);
|
||||
}
|
||||
);
|
||||
+35
-59
@@ -10,23 +10,13 @@ import LabelInput from "../labelInput";
|
||||
import { labelPrompt } from "./labelUtil";
|
||||
import actions from "../../actions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
writableCategoriesEnabled:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).config?.parameters?.annotations ?? false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userInfo: (state as any).userInfo,
|
||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||
schema: state.annoMatrix?.schema,
|
||||
userInfo: state.userInfo,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class Categories extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class Categories extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
createAnnoModeActive: false,
|
||||
@@ -36,9 +26,7 @@ class Categories extends React.Component<{}, State> {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleCreateUserAnno = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleCreateUserAnno = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
const { newCategoryText, categoryToDuplicate } = this.state;
|
||||
dispatch(
|
||||
@@ -55,12 +43,10 @@ class Categories extends React.Component<{}, State> {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleEnableAnnoMode = () => {
|
||||
this.setState({ createAnnoModeActive: true });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleDisableAnnoMode = () => {
|
||||
this.setState({
|
||||
createAnnoModeActive: false,
|
||||
@@ -69,58 +55,56 @@ class Categories extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleModalDuplicateCategorySelection = (d: any) => {
|
||||
handleModalDuplicateCategorySelection = (d) => {
|
||||
this.setState({ categoryToDuplicate: d });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryNameError = (name: any) => {
|
||||
categoryNameError = (name) => {
|
||||
/*
|
||||
return false if this is a LEGAL/acceptable category name or NULL/empty string,
|
||||
or return an error type.
|
||||
*/
|
||||
return false if this is a LEGAL/acceptable category name or NULL/empty string,
|
||||
or return an error type.
|
||||
*/
|
||||
|
||||
/* allow empty string */
|
||||
if (name === "") return false;
|
||||
|
||||
/*
|
||||
test for uniqueness against *all* annotation names, not just the subset
|
||||
we render as categorical.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
test for uniqueness against *all* annotation names, not just the subset
|
||||
we render as categorical.
|
||||
*/
|
||||
const { schema } = this.props;
|
||||
const allCategoryNames = schema.annotations.obs.columns.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(c: any) => c.name
|
||||
);
|
||||
const allCategoryNames = schema.annotations.obs.columns.map((c) => c.name);
|
||||
|
||||
/* check category name syntax */
|
||||
const error = AnnotationsHelpers.annotationNameIsErroneous(name);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
/* disallow duplicates */
|
||||
if (allCategoryNames.indexOf(name) !== -1) {
|
||||
return "duplicate";
|
||||
}
|
||||
|
||||
/* otherwise, no error */
|
||||
return false;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChange = (name: any) => {
|
||||
handleChange = (name) => {
|
||||
this.setState({ newCategoryText: name });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleSelect = (name: any) => {
|
||||
handleSelect = (name) => {
|
||||
this.setState({ newCategoryText: name });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
instruction = (name: any) =>
|
||||
labelPrompt(this.categoryNameError(name), "New, unique category name", ":");
|
||||
instruction = (name) => labelPrompt(
|
||||
this.categoryNameError(name),
|
||||
"New, unique category name",
|
||||
":"
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
onExpansionChange = (catName: any) => {
|
||||
onExpansionChange = (catName) => {
|
||||
const { expandedCats } = this.state;
|
||||
if (expandedCats.has(catName)) {
|
||||
const _expandedCats = new Set(expandedCats);
|
||||
@@ -133,7 +117,6 @@ class Categories extends React.Component<{}, State> {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
createAnnoModeActive,
|
||||
@@ -141,13 +124,11 @@ class Categories extends React.Component<{}, State> {
|
||||
newCategoryText,
|
||||
expandedCats,
|
||||
} = this.state;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
|
||||
const { writableCategoriesEnabled, schema, userInfo } = this.props;
|
||||
/* all names, sorted in display order. Will be rendered in this order */
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||
const allCategoryNames = ControlsHelpers.selectableCategoryNames(
|
||||
schema
|
||||
).sort();
|
||||
const allCategoryNames =
|
||||
ControlsHelpers.selectableCategoryNames(schema).sort();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -155,7 +136,6 @@ class Categories extends React.Component<{}, State> {
|
||||
}}
|
||||
>
|
||||
<AnnoDialog
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isActive: any; title: string; instruction:... Remove this comment to see the full error message
|
||||
isActive={createAnnoModeActive}
|
||||
title="Create new category"
|
||||
instruction={this.instruction(newCategoryText)}
|
||||
@@ -168,7 +148,6 @@ class Categories extends React.Component<{}, State> {
|
||||
handleCancel={this.handleDisableAnnoMode}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ labelSuggestions: null; onChange: (name: a... Remove this comment to see the full error message
|
||||
labelSuggestions={null}
|
||||
onChange={this.handleChange}
|
||||
onSelect={this.handleSelect}
|
||||
@@ -183,7 +162,6 @@ class Categories extends React.Component<{}, State> {
|
||||
}
|
||||
annoSelect={
|
||||
<AnnoSelect
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ handleModalDuplicateCategorySelection: (d:... Remove this comment to see the full error message
|
||||
handleModalDuplicateCategorySelection={
|
||||
this.handleModalDuplicateCategorySelection
|
||||
}
|
||||
@@ -192,6 +170,7 @@ class Categories extends React.Component<{}, State> {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{writableCategoriesEnabled ? (
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<Tooltip
|
||||
@@ -220,16 +199,15 @@ class Categories extends React.Component<{}, State> {
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* READ ONLY CATEGORICAL FIELDS */}
|
||||
{/* this is duplicative but flat, could be abstracted */}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS */}
|
||||
{allCategoryNames.map((catName: any) =>
|
||||
{allCategoryNames.map((catName) =>
|
||||
!schema.annotations.obsByName[catName].writable &&
|
||||
(schema.annotations.obsByName[catName].categories?.length > 1 ||
|
||||
!schema.annotations.obsByName[catName].categories) ? (
|
||||
<Category
|
||||
key={catName}
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
metadataField={catName}
|
||||
onExpansionChange={this.onExpansionChange}
|
||||
isExpanded={expandedCats.has(catName)}
|
||||
@@ -238,12 +216,10 @@ class Categories extends React.Component<{}, State> {
|
||||
) : null
|
||||
)}
|
||||
{/* WRITEABLE FIELDS */}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS */}
|
||||
{allCategoryNames.map((catName: any) =>
|
||||
{allCategoryNames.map((catName) =>
|
||||
schema.annotations.obsByName[catName].writable ? (
|
||||
<Category
|
||||
key={catName}
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
metadataField={catName}
|
||||
onExpansionChange={this.onExpansionChange}
|
||||
isExpanded={expandedCats.has(catName)}
|
||||
+2
-5
@@ -3,8 +3,7 @@ import { Colors } from "@blueprintjs/core";
|
||||
|
||||
import { AnnotationsHelpers } from "../../util/stateManager";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function isLabelErroneous(label: any, metadataField: any, schema: any) {
|
||||
export function isLabelErroneous(label, metadataField, schema) {
|
||||
/*
|
||||
return false if this is a LEGAL/acceptable category name or NULL/empty string,
|
||||
or return an error type.
|
||||
@@ -36,11 +35,9 @@ const errorMessageMap = {
|
||||
"multi-space-run": "Multiple consecutive spaces not allowed",
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function labelPrompt(err: any, prolog: any, epilog: any) {
|
||||
export function labelPrompt(err, prolog, epilog) {
|
||||
let errPrompt = null;
|
||||
if (err) {
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
let errMsg = errorMessageMap[err] ?? "error";
|
||||
errMsg = errMsg[0].toLowerCase() + errMsg.slice(1);
|
||||
errPrompt = (
|
||||
+46
-163
@@ -13,7 +13,6 @@ import {
|
||||
Position,
|
||||
} from "@blueprintjs/core";
|
||||
import * as globals from "../../../globals";
|
||||
// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module '../categorical.css' or its cor... Remove this comment to see the full error message
|
||||
import styles from "../categorical.css";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
@@ -25,28 +24,20 @@ import actions from "../../../actions";
|
||||
import MiniHistogram from "../../miniHistogram";
|
||||
import MiniStackedBar from "../../miniStackedBar";
|
||||
import { CategoryCrossfilterContext } from "../categoryContext";
|
||||
import { Dataframe, ContinuousHistogram } from "../../../util/dataframe";
|
||||
|
||||
const STACKED_BAR_HEIGHT = 11;
|
||||
const STACKED_BAR_WIDTH = 100;
|
||||
|
||||
/* this is defined outside of the class so we can use it in connect() */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function _currentLabelAsString(ownProps: any) {
|
||||
function _currentLabelAsString(ownProps) {
|
||||
const { label } = ownProps;
|
||||
// when called as a function, the String() constructor performs type conversion,
|
||||
// and returns a primitive string.
|
||||
return String(label);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state, ownProps) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'pointDilation' does not exist on type 'D... Remove this comment to see the full error message
|
||||
const { pointDilation, categoricalSelection } = state;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type '{... Remove this comment to see the full error message
|
||||
const { metadataField, categorySummary, categoryIndex } = ownProps;
|
||||
const isDilated =
|
||||
pointDilation.metadataField === metadataField &&
|
||||
@@ -57,35 +48,27 @@ type State = any;
|
||||
const isSelected = category.get(label) ?? true;
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
annotations: state.annotations,
|
||||
schema: state.annoMatrix?.schema,
|
||||
isDilated,
|
||||
isSelected,
|
||||
label,
|
||||
};
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class CategoryValue extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class CategoryValue extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
editedLabelText: this.currentLabelAsString(),
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidUpdate(prevProps: {}) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
componentDidUpdate(prevProps) {
|
||||
const { metadataField, categoryIndex, categorySummary } = this.props;
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(prevProps as any).metadataField !== metadataField ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(prevProps as any).categoryIndex !== categoryIndex || // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(prevProps as any).categorySummary !== categorySummary
|
||||
prevProps.metadataField !== metadataField ||
|
||||
prevProps.categoryIndex !== categoryIndex ||
|
||||
prevProps.categorySummary !== categorySummary
|
||||
) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state --- adequately checked to prevent looping
|
||||
this.setState({
|
||||
@@ -95,31 +78,23 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
}
|
||||
|
||||
// If coloring by and this isn't the colorAccessor and it isn't being edited
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
get shouldRenderStackedBarOrHistogram() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { colorAccessor, isColorBy, annotations } = this.props;
|
||||
|
||||
return !!colorAccessor && !isColorBy && !annotations.isEditingLabelName;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleDeleteValue = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
dispatch(actions.annotationDeleteLabelFromCategory(metadataField, label));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleAddCurrentSelectionToThisLabel = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
dispatch(actions.annotationLabelCurrentSelection(metadataField, label));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleEditValue = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleEditValue = (e) => {
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
const { editedLabelText } = this.state;
|
||||
this.cancelEditMode();
|
||||
@@ -133,9 +108,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleCreateArbitraryLabel = (txt: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleCreateArbitraryLabel = (txt) => {
|
||||
const { dispatch, metadataField, label } = this.props;
|
||||
this.cancelEditMode();
|
||||
dispatch(
|
||||
@@ -143,21 +116,15 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
labelNameError = (name: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
labelNameError = (name) => {
|
||||
const { metadataField, schema } = this.props;
|
||||
if (name === this.currentLabelAsString()) return false;
|
||||
return isLabelErroneous(name, metadataField, schema);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
instruction = (label: any) =>
|
||||
labelPrompt(this.labelNameError(label), "New, unique label", ":");
|
||||
instruction = (label) => labelPrompt(this.labelNameError(label), "New, unique label", ":");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
activateEditLabelMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
dispatch({
|
||||
type: "annotation: activate edit label mode",
|
||||
@@ -167,9 +134,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
cancelEditMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
this.setState({
|
||||
editedLabelText: this.currentLabelAsString(),
|
||||
@@ -182,18 +147,9 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
toggleOff = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryIndex' does not exist on type 'R... Remove this comment to see the full error message
|
||||
categoryIndex,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categorySummary' does not exist on type ... Remove this comment to see the full error message
|
||||
categorySummary,
|
||||
} = this.props;
|
||||
const { dispatch, metadataField, categoryIndex, categorySummary } =
|
||||
this.props;
|
||||
const label = categorySummary.categoryValues[categoryIndex];
|
||||
dispatch(
|
||||
actions.selectCategoricalMetadataAction(
|
||||
@@ -206,8 +162,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
shouldComponentUpdate = (nextProps: any, nextState: any) => {
|
||||
shouldComponentUpdate = (nextProps, nextState) => {
|
||||
/*
|
||||
Checks to see if at least one of the following changed:
|
||||
* world state
|
||||
@@ -218,7 +173,6 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
If and only if true, update the component
|
||||
*/
|
||||
const { props, state } = this;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryIndex' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { categoryIndex, categorySummary, isSelected } = props;
|
||||
const {
|
||||
categoryIndex: newCategoryIndex,
|
||||
@@ -231,15 +185,10 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
const labelChanged = label !== newLabel;
|
||||
const valueSelectionChange = isSelected !== newIsSelected;
|
||||
|
||||
const colorAccessorChange =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(props as any).colorAccessor !== nextProps.colorAccessor;
|
||||
const annotationsChange =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(props as any).annotations !== nextProps.annotations;
|
||||
const colorAccessorChange = props.colorAccessor !== nextProps.colorAccessor;
|
||||
const annotationsChange = props.annotations !== nextProps.annotations;
|
||||
const editingLabel = state.editedLabelText !== nextState.editedLabelText;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const dilationChange = (props as any).isDilated !== nextProps.isDilated;
|
||||
const dilationChange = props.isDilated !== nextProps.isDilated;
|
||||
|
||||
const count = categorySummary.categoryValueCounts[categoryIndex];
|
||||
const newCount = newCategorySummary.categoryValueCounts[newCategoryIndex];
|
||||
@@ -250,8 +199,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
// if any one changes, but only for the currently colored-by category.
|
||||
const colorMightHaveChanged =
|
||||
nextProps.colorAccessor === nextProps.metadataField &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(props as any).categorySummary !== nextProps.categorySummary;
|
||||
props.categorySummary !== nextProps.categorySummary;
|
||||
|
||||
return (
|
||||
labelChanged ||
|
||||
@@ -265,18 +213,9 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
toggleOn = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryIndex' does not exist on type 'R... Remove this comment to see the full error message
|
||||
categoryIndex,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categorySummary' does not exist on type ... Remove this comment to see the full error message
|
||||
categorySummary,
|
||||
} = this.props;
|
||||
const { dispatch, metadataField, categoryIndex, categorySummary } =
|
||||
this.props;
|
||||
const label = categorySummary.categoryValues[categoryIndex];
|
||||
dispatch(
|
||||
actions.selectCategoricalMetadataAction(
|
||||
@@ -289,9 +228,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleMouseEnter = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover start",
|
||||
@@ -301,9 +238,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleMouseExit = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, metadataField, categoryIndex, label } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover end",
|
||||
@@ -313,32 +248,23 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleTextChange = (text: any) => {
|
||||
handleTextChange = (text) => {
|
||||
this.setState({ editedLabelText: text });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChoice = (e: any) => {
|
||||
handleChoice = (e) => {
|
||||
/* Blueprint Suggest format */
|
||||
this.setState({ editedLabelText: e.target });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
createHistogramBins = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any,
|
||||
categoryData: Dataframe,
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'colorAccessor' is declared but its value is never... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colorAccessor: any,
|
||||
colorData: Dataframe,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryValue: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
width: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
height: any
|
||||
metadataField,
|
||||
categoryData,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
categoryValue,
|
||||
width,
|
||||
height
|
||||
) => {
|
||||
/*
|
||||
Knowing that colorScale is based off continuous data,
|
||||
@@ -347,17 +273,17 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
*/
|
||||
const groupBy = categoryData.col(metadataField);
|
||||
const col = colorData.icol(0);
|
||||
const range = col.summarizeContinuous();
|
||||
const range = col.summarize();
|
||||
|
||||
const histogramMap = col.histogramContinuousBy(
|
||||
const histogramMap = col.histogram(
|
||||
50,
|
||||
[range.min, range.max],
|
||||
groupBy
|
||||
);
|
||||
); /* Because the signature changes we really need different names for histogram to differentiate signatures */
|
||||
|
||||
const bins = histogramMap.has(categoryValue)
|
||||
? histogramMap.get(categoryValue) as ContinuousHistogram
|
||||
: new Array<number>(50).fill(0);
|
||||
? histogramMap.get(categoryValue)
|
||||
: new Array(50).fill(0);
|
||||
|
||||
const xScale = d3.scaleLinear().domain([0, bins.length]).range([0, width]);
|
||||
|
||||
@@ -372,23 +298,15 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
createStackedGraphBins = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any,
|
||||
categoryData: Dataframe,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colorAccessor: any,
|
||||
colorData: Dataframe,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
categoryValue: any,
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'colorTable' is declared but its value is never re... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colorTable: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
schema: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
width: any
|
||||
metadataField,
|
||||
categoryData,
|
||||
colorAccessor,
|
||||
colorData,
|
||||
categoryValue,
|
||||
colorTable,
|
||||
schema,
|
||||
width
|
||||
) => {
|
||||
/*
|
||||
Knowing that the color scale is based off of categorical data,
|
||||
@@ -398,7 +316,7 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
const groupBy = categoryData.col(metadataField);
|
||||
const occupancyMap = colorData
|
||||
.col(colorAccessor)
|
||||
.histogramCategoricalBy(groupBy);
|
||||
.histogramCategorical(groupBy);
|
||||
|
||||
const occupancy = occupancyMap.get(categoryValue);
|
||||
|
||||
@@ -425,19 +343,16 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
return null;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
currentLabelAsString() {
|
||||
return _currentLabelAsString(this.props);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
isAddCurrentSelectionDisabled(crossfilter: any, category: any, value: any) {
|
||||
isAddCurrentSelectionDisabled(crossfilter, category, value) {
|
||||
/*
|
||||
disable "add current selection to label", if one of the following is true:
|
||||
1. no cells are selected
|
||||
2. all currently selected cells already have this label, on this category
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryData' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
const { categoryData } = this.props;
|
||||
|
||||
// 1. no cells selected?
|
||||
@@ -455,22 +370,14 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
renderMiniStackedBar = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'R... Remove this comment to see the full error message
|
||||
colorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryData' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
categoryData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorData' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
colorData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTable' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
colorTable,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
schema,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'label' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
label,
|
||||
} = this.props;
|
||||
const isColorBy = metadataField === colorAccessor;
|
||||
@@ -508,29 +415,20 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
domain,
|
||||
occupancy,
|
||||
}}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ height: number; width: number; colorTable:... Remove this comment to see the full error message
|
||||
height={STACKED_BAR_HEIGHT}
|
||||
width={STACKED_BAR_WIDTH}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
renderMiniHistogram = () => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'R... Remove this comment to see the full error message
|
||||
colorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorData' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
colorData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryData' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
categoryData,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTable' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
colorTable,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
schema,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'label' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
label,
|
||||
} = this.props;
|
||||
const colorScale = colorTable?.scale;
|
||||
@@ -565,7 +463,6 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
yScale,
|
||||
bins,
|
||||
}}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ obsOrVarContinuousFieldDisplayName: any; d... Remove this comment to see the full error message
|
||||
obsOrVarContinuousFieldDisplayName={colorAccessor}
|
||||
domainLabel={label}
|
||||
height={STACKED_BAR_HEIGHT}
|
||||
@@ -574,28 +471,17 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryIndex' does not exist on type 'R... Remove this comment to see the full error message
|
||||
categoryIndex,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'R... Remove this comment to see the full error message
|
||||
colorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorTable' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
colorTable,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserAnno' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
isUserAnno,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
annotations,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isDilated' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
isDilated,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isSelected' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
isSelected,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categorySummary' does not exist on type ... Remove this comment to see the full error message
|
||||
categorySummary,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'label' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
label,
|
||||
} = this.props;
|
||||
const colorScale = colorTable?.scale;
|
||||
@@ -691,7 +577,6 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
<span
|
||||
data-testid={`categorical-value-${metadataField}-${displayString}`}
|
||||
data-testclass="categorical-value"
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="-1"
|
||||
style={{
|
||||
width: labelWidth,
|
||||
@@ -717,7 +602,6 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
{editModeActive ? (
|
||||
<div>
|
||||
<AnnoDialog
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isActive: any; inputProps: { "data-testid"... Remove this comment to see the full error message
|
||||
isActive={editModeActive}
|
||||
inputProps={{
|
||||
"data-testid": `${metadataField}:edit-label-name-dialog`,
|
||||
@@ -736,7 +620,6 @@ class CategoryValue extends React.Component<{}, State> {
|
||||
handleCancel={this.cancelEditMode}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ label: any; labelSuggestions: null; onChan... Remove this comment to see the full error message
|
||||
label={editedLabelText}
|
||||
labelSuggestions={null}
|
||||
onChange={this.handleTextChange}
|
||||
+41
-49
@@ -8,62 +8,60 @@ import {
|
||||
Position,
|
||||
} from "@blueprintjs/core";
|
||||
|
||||
import { Dataframe } from "../../../util/dataframe";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
schema: state.annoMatrix?.schema,
|
||||
}))
|
||||
class Occupancy extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
canvas: any;
|
||||
|
||||
_WIDTH = 100;
|
||||
|
||||
_HEIGHT = 11;
|
||||
|
||||
createHistogram = (): void => {
|
||||
createHistogram = () => {
|
||||
/*
|
||||
Knowing that colorScale is based off continuous data,
|
||||
createHistogram fetches the continuous data in relation to the cells relevant to the category value.
|
||||
It then separates that data into 50 bins for drawing the mini-histogram
|
||||
*/
|
||||
const { metadataField, categoryData, colorData, categoryValue } = this
|
||||
.props as {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'metadataField' does not exist on type 'R... Remove this comment to see the full error message
|
||||
metadataField;
|
||||
categoryData: Dataframe;
|
||||
colorData: Dataframe;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryValue' does not exist on type 'R... Remove this comment to see the full error message
|
||||
categoryValue;
|
||||
};
|
||||
Knowing that colorScale is based off continous data,
|
||||
createHistogram fetches the continous data in relation to the cells releveant to the catagory value.
|
||||
It then seperates that data into 50 bins for drawing the mini-histogram
|
||||
*/
|
||||
const { metadataField, categoryData, colorData, categoryValue } =
|
||||
this.props;
|
||||
|
||||
if (!this.canvas) return;
|
||||
|
||||
const groupBy = categoryData.col(metadataField);
|
||||
const col = colorData.icol(0);
|
||||
const range = col.summarizeContinuous();
|
||||
const histogramMap = col.histogramContinuousBy(
|
||||
const range = col.summarize();
|
||||
|
||||
const histogramMap = col.histogram(
|
||||
50,
|
||||
[range.min, range.max],
|
||||
groupBy
|
||||
);
|
||||
); /* Because the signature changes we really need different names for histogram to differentiate signatures */
|
||||
|
||||
const bins = histogramMap.has(categoryValue)
|
||||
? (histogramMap.get(categoryValue) as number[])
|
||||
? histogramMap.get(categoryValue)
|
||||
: new Array(50).fill(0);
|
||||
|
||||
const xScale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, bins.length])
|
||||
.range([0, this._WIDTH]);
|
||||
|
||||
const largestBin = Math.max(...bins);
|
||||
|
||||
const yScale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, largestBin])
|
||||
.range([0, this._HEIGHT]);
|
||||
|
||||
const ctx = this.canvas.getContext("2d");
|
||||
|
||||
ctx.fillStyle = "#000";
|
||||
|
||||
let x;
|
||||
let y;
|
||||
|
||||
const rectWidth = this._WIDTH / bins.length;
|
||||
|
||||
for (let i = 0, { length } = bins; i < length; i += 1) {
|
||||
x = xScale(i);
|
||||
y = yScale(bins[i]);
|
||||
@@ -71,12 +69,12 @@ class Occupancy extends React.PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
createOccupancyStack = (): void => {
|
||||
createOccupancyStack = () => {
|
||||
/*
|
||||
Knowing that the color scale is based off of catagorical data,
|
||||
createOccupancyStack obtains a map showing the number if cells per colored value
|
||||
Using the colorScale a stack of colored bars is drawn representing the map
|
||||
*/
|
||||
Knowing that the color scale is based off of catagorical data,
|
||||
createOccupancyStack obtains a map showing the number if cells per colored value
|
||||
Using the colorScale a stack of colored bars is drawn representing the map
|
||||
*/
|
||||
const {
|
||||
metadataField,
|
||||
categoryData,
|
||||
@@ -85,28 +83,20 @@ class Occupancy extends React.PureComponent {
|
||||
colorTable,
|
||||
schema,
|
||||
colorData,
|
||||
} = this.props as {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
metadataField: any;
|
||||
categoryData: Dataframe;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colorAccessor: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
categoryValue: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colorTable: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: any;
|
||||
colorData: Dataframe;
|
||||
};
|
||||
} = this.props;
|
||||
const { scale: colorScale } = colorTable;
|
||||
|
||||
const ctx = this.canvas?.getContext("2d");
|
||||
|
||||
if (!ctx) return;
|
||||
|
||||
const groupBy = categoryData.col(metadataField);
|
||||
const occupancyMap = colorData
|
||||
.col(colorAccessor)
|
||||
.histogramCategoricalBy(groupBy);
|
||||
.histogramCategorical(groupBy);
|
||||
|
||||
const occupancy = occupancyMap.get(categoryValue);
|
||||
|
||||
if (occupancy && occupancy.size > 0) {
|
||||
// not all categories have occupancy, so occupancy may be undefined.
|
||||
const x = d3
|
||||
@@ -116,15 +106,18 @@ class Occupancy extends React.PureComponent {
|
||||
.range([0, this._WIDTH]);
|
||||
const categories =
|
||||
schema.annotations.obsByName[colorAccessor]?.categories;
|
||||
|
||||
let currentOffset = 0;
|
||||
const dfColumn = colorData.col(colorAccessor);
|
||||
const categoryValues = dfColumn.summarizeCategorical().categories;
|
||||
|
||||
let o;
|
||||
let scaledValue;
|
||||
let value;
|
||||
|
||||
for (let i = 0, { length } = categoryValues; i < length; i += 1) {
|
||||
value = categoryValues[i];
|
||||
o = occupancy.get(value) as number;
|
||||
o = occupancy.get(value);
|
||||
scaledValue = x(o);
|
||||
ctx.fillStyle = o
|
||||
? colorScale(categories.indexOf(value))
|
||||
@@ -135,13 +128,12 @@ class Occupancy extends React.PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { colorAccessor, categoryValue, colorByIsCategorical } = this.props;
|
||||
const { canvas } = this;
|
||||
if (canvas)
|
||||
canvas.getContext("2d").clearRect(0, 0, this._WIDTH, this._HEIGHT);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
interactionKind={PopoverInteractionKind.HOVER_TARGET_ONLY}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* rc slider https://www.npmjs.com/package/rc-slider */
|
||||
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import HistogramBrush from "../brushableHistogram";
|
||||
|
||||
@connect((state) => ({
|
||||
schema: state.annoMatrix?.schema,
|
||||
}))
|
||||
class Continuous extends React.PureComponent {
|
||||
render() {
|
||||
/* initial value for iterator to simulate index, ranges is an object */
|
||||
const { schema } = this.props;
|
||||
if (!schema) return null;
|
||||
const obsIndex = schema.annotations.obs.index;
|
||||
const allContinuousNames = schema.annotations.obs.columns
|
||||
.filter((col) => col.type === "int32" || col.type === "float32")
|
||||
.filter((col) => col.name !== obsIndex)
|
||||
.filter((col) => !col.writable) // skip user annotations - they will be treated as categorical
|
||||
.map((col) => col.name);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{allContinuousNames.map((key, zebra) => (
|
||||
<HistogramBrush key={key} field={key} isObs zebra={zebra % 2 === 0} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Continuous;
|
||||
@@ -1,41 +0,0 @@
|
||||
/* rc slider https://www.npmjs.com/package/rc-slider */
|
||||
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import HistogramBrush from "../brushableHistogram";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
}))
|
||||
class Continuous extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
/* initial value for iterator to simulate index, ranges is an object */
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
const { schema } = this.props;
|
||||
if (!schema) return null;
|
||||
const obsIndex = schema.annotations.obs.index;
|
||||
const allContinuousNames = schema.annotations.obs.columns
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.filter((col: any) => col.type === "int32" || col.type === "float32")
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.filter((col: any) => col.name !== obsIndex)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.filter((col: any) => !col.writable) // skip user annotations - they will be treated as categorical
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.map((col: any) => col.name);
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{allContinuousNames.map((key: any, zebra: any) => (
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
<HistogramBrush key={key} field={key} isObs zebra={zebra % 2 === 0} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Continuous;
|
||||
+1
-7
@@ -5,8 +5,7 @@
|
||||
******************************************/
|
||||
import * as d3 from "d3";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const setupParallelCoordinates = (width: any, height: any, margin: any) => {
|
||||
const setupParallelCoordinates = (width, height, margin) => {
|
||||
const container = d3.select("#parcoords");
|
||||
|
||||
const svg = container
|
||||
@@ -25,15 +24,10 @@ const setupParallelCoordinates = (width: any, height: any, margin: any) => {
|
||||
.style("margin-top", `${margin.top}px`)
|
||||
.style("margin-left", `${margin.left}px`);
|
||||
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
const ctx = canvas.node().getContext("2d");
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
ctx.globalCompositeOperation = "darken";
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
ctx.globalAlpha = 0.15;
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
ctx.lineWidth = 1.5;
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
ctx.scale(devicePixelRatio, devicePixelRatio);
|
||||
|
||||
return {
|
||||
@@ -0,0 +1,49 @@
|
||||
import each from "lodash.foreach";
|
||||
import * as d3 from "d3";
|
||||
|
||||
const paddingRight = 120;
|
||||
const continuousChartWidth = 1200;
|
||||
|
||||
export const margin = { top: 66, right: 110, bottom: 20, left: 60 };
|
||||
export const width =
|
||||
continuousChartWidth - margin.left - margin.right - paddingRight;
|
||||
export const height = 340 - margin.top - margin.bottom;
|
||||
export const innerHeight = height - 2;
|
||||
|
||||
export const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
|
||||
export const createDimensions = (data) => {
|
||||
const newArr = [];
|
||||
each(data, (value, key) => {
|
||||
if (value.range) {
|
||||
newArr.push({
|
||||
key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */,
|
||||
type: {
|
||||
within: (d, extent, dim) =>
|
||||
extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1],
|
||||
},
|
||||
scale: d3
|
||||
.scaleSqrt()
|
||||
.range([innerHeight, 0])
|
||||
.domain([0, value.range.max]),
|
||||
});
|
||||
}
|
||||
});
|
||||
return newArr;
|
||||
};
|
||||
|
||||
export const yAxis = d3.axisLeft();
|
||||
|
||||
export const brushstart = () => {
|
||||
d3.event.sourceEvent.stopPropagation();
|
||||
};
|
||||
|
||||
// Unused.
|
||||
// export const d3_functor = v => (typeof v === "function" ? v : () => v);
|
||||
export const project = (d, dimensions, xscale) =>
|
||||
dimensions.map((p, i) => {
|
||||
// check if data element has property and contains a value
|
||||
if (!(p.key in d) || d[p.key] === null) return null;
|
||||
|
||||
return [xscale(i), p.scale(d[p.key])];
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import each from "lodash.foreach";
|
||||
import * as d3 from "d3";
|
||||
|
||||
const paddingRight = 120;
|
||||
const continuousChartWidth = 1200;
|
||||
|
||||
export const margin = { top: 66, right: 110, bottom: 20, left: 60 };
|
||||
export const width =
|
||||
continuousChartWidth - margin.left - margin.right - paddingRight;
|
||||
export const height = 340 - margin.top - margin.bottom;
|
||||
export const innerHeight = height - 2;
|
||||
|
||||
export const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const createDimensions = (data: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const newArr: any = [];
|
||||
each(data, (value, key) => {
|
||||
if (value.range) {
|
||||
newArr.push({
|
||||
key /* room for confusion: lodash calls this key, it's also the name of the property parallel coords code is looking for */,
|
||||
type: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
within: (d: any, extent: any, dim: any) =>
|
||||
extent[0] <= dim.scale(d) && dim.scale(d) <= extent[1],
|
||||
},
|
||||
scale: d3
|
||||
.scaleSqrt()
|
||||
.range([innerHeight, 0])
|
||||
.domain([0, value.range.max]),
|
||||
});
|
||||
}
|
||||
});
|
||||
return newArr;
|
||||
};
|
||||
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
export const yAxis = d3.axisLeft();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export const brushstart = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(d3 as any).event.sourceEvent.stopPropagation();
|
||||
};
|
||||
|
||||
// Unused.
|
||||
// export const d3_functor = v => (typeof v === "function" ? v : () => v);
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const project = (d: any, dimensions: any, xscale: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dimensions.map((p: any, i: any) => {
|
||||
// check if data element has property and contains a value
|
||||
if (!(p.key in d) || d[p.key] === null) return null;
|
||||
|
||||
return [xscale(i), p.scale(d[p.key])];
|
||||
});
|
||||
+11
-19
@@ -9,8 +9,7 @@ import {
|
||||
} from "../../util/stateManager/colorHelpers";
|
||||
|
||||
// create continuous color legend
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => {
|
||||
const continuous = (selectorId, colorScale, colorAccessor) => {
|
||||
const legendHeight = 200;
|
||||
const legendWidth = 80;
|
||||
const margin = { top: 10, right: 60, bottom: 10, left: 2 };
|
||||
@@ -34,7 +33,6 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => {
|
||||
we flip the color scale as well [1, 0] instead of [0, 1] */
|
||||
.node();
|
||||
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
const legendScale = d3
|
||||
@@ -46,7 +44,6 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => {
|
||||
]); /* 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
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
const image = ctx.createImageData(1, legendHeight);
|
||||
d3.range(legendHeight).forEach((i) => {
|
||||
const c = d3.rgb(colorScale(legendScale.invert(i)));
|
||||
@@ -55,7 +52,6 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => {
|
||||
image.data[4 * i + 2] = c.b;
|
||||
image.data[4 * i + 3] = 255;
|
||||
});
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
ctx.putImageData(image, 0, 0);
|
||||
|
||||
// A simpler way to do the above, but possibly slower. keep in mind the legend
|
||||
@@ -109,30 +105,27 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => {
|
||||
.text(colorAccessor);
|
||||
};
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colors: (state as any).colors,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
annoMatrix: state.annoMatrix,
|
||||
colors: state.colors,
|
||||
genesets: state.genesets.genesets,
|
||||
}))
|
||||
class ContinuousLegend extends React.Component {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
async componentDidUpdate(prevProps: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
async componentDidUpdate(prevProps) {
|
||||
const { annoMatrix, colors, genesets } = this.props;
|
||||
if (!colors || !annoMatrix) return;
|
||||
|
||||
if (colors !== prevProps?.colors || annoMatrix !== prevProps?.annoMatrix) {
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode, colorAccessor, userColors } = colors;
|
||||
|
||||
const colorQuery = createColorQuery(
|
||||
colorMode,
|
||||
colorAccessor,
|
||||
schema,
|
||||
genesets
|
||||
);
|
||||
|
||||
const colorDf = colorQuery ? await annoMatrix.fetch(...colorQuery) : null;
|
||||
const colorTable = createColorTable(
|
||||
colorMode,
|
||||
@@ -141,19 +134,19 @@ class ContinuousLegend extends React.Component {
|
||||
schema,
|
||||
userColors
|
||||
);
|
||||
|
||||
const colorScale = colorTable.scale;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'range' does not exist on type '((idx: an... Remove this comment to see the full error message
|
||||
const range = colorScale?.range;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'domain' does not exist on type '((idx: a... Remove this comment to see the full error message
|
||||
const [domainMin, domainMax] = colorScale?.domain?.() ?? [0, 0];
|
||||
|
||||
/* always remove it, if it's not continuous we don't put it back. */
|
||||
d3.select("#continuous_legend").selectAll("*").remove();
|
||||
|
||||
if (colorAccessor && colorScale && range && domainMin < domainMax) {
|
||||
/* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */
|
||||
if (range()[0][0] !== "#") {
|
||||
continuous(
|
||||
"#continuous_legend",
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'domain' does not exist on type '((idx: a... Remove this comment to see the full error message
|
||||
d3.scaleSequential(interpolateCool).domain(colorScale.domain()),
|
||||
colorAccessor
|
||||
);
|
||||
@@ -162,7 +155,6 @@ class ContinuousLegend extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
+12
-32
@@ -15,36 +15,23 @@ import * as globals from "../../globals";
|
||||
import actions from "../../actions";
|
||||
import { getDiscreteCellEmbeddingRowIndex } from "../../util/stateManager/viewStackHelpers";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type EmbeddingState = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: (state as any).layoutChoice,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
crossfilter: (state as any).obsCrossfilter,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class Embedding extends React.PureComponent<{}, EmbeddingState> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
layoutChoice: state.layoutChoice, // TODO: really should clean up naming, s/layout/embedding/g
|
||||
schema: state.annoMatrix?.schema,
|
||||
crossfilter: state.obsCrossfilter,
|
||||
}))
|
||||
class Embedding extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleLayoutChoiceChange = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleLayoutChoiceChange = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(actions.layoutChoiceAction(e.currentTarget.value));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'layoutChoice' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
const { layoutChoice, schema, crossfilter } = this.props;
|
||||
const { annoMatrix } = crossfilter;
|
||||
return (
|
||||
@@ -111,23 +98,18 @@ class Embedding extends React.PureComponent<{}, EmbeddingState> {
|
||||
|
||||
export default Embedding;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const loadAllEmbeddingCounts = async ({ annoMatrix, available }: any) => {
|
||||
const loadAllEmbeddingCounts = async ({ annoMatrix, available }) => {
|
||||
const embeddings = await Promise.all(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
available.map((name: any) => annoMatrix.base().fetch("emb", name))
|
||||
available.map((name) => annoMatrix.base().fetch("emb", name))
|
||||
);
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'name' implicitly has an 'any' type.
|
||||
return available.map((name, idx) => ({
|
||||
embeddingName: name,
|
||||
embedding: embeddings[idx],
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable...
|
||||
discreteCellIndex: getDiscreteCellEmbeddingRowIndex(embeddings[idx]),
|
||||
}));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const EmbeddingChoices = ({ onChange, annoMatrix, layoutChoice }: any) => {
|
||||
const EmbeddingChoices = ({ onChange, annoMatrix, layoutChoice }) => {
|
||||
const { available } = layoutChoice;
|
||||
const { data, error, isPending } = useAsync({
|
||||
promiseFn: loadAllEmbeddingCounts,
|
||||
@@ -143,8 +125,7 @@ const EmbeddingChoices = ({ onChange, annoMatrix, layoutChoice }: any) => {
|
||||
/* still loading, or errored out - just omit counts (TODO: spinner?) */
|
||||
return (
|
||||
<RadioGroup onChange={onChange} selectedValue={layoutChoice.current}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{layoutChoice.available.map((name: any) => (
|
||||
{layoutChoice.available.map((name) => (
|
||||
<Radio label={`${name}`} value={name} key={name} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
@@ -153,8 +134,7 @@ const EmbeddingChoices = ({ onChange, annoMatrix, layoutChoice }: any) => {
|
||||
if (data) {
|
||||
return (
|
||||
<RadioGroup onChange={onChange} selectedValue={layoutChoice.current}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{data.map((summary: any) => {
|
||||
{data.map((summary) => {
|
||||
const { discreteCellIndex, embeddingName } = summary;
|
||||
const sizeHint = `${discreteCellIndex.size()} cells`;
|
||||
return (
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
function Container(props: any) {
|
||||
function Container(props) {
|
||||
const { children } = props;
|
||||
return (
|
||||
<div
|
||||
@@ -2,8 +2,6 @@ import React from "react";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
class Layout extends React.Component {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
viewportRef: any;
|
||||
/*
|
||||
Layout - this react component contains all the layout style and logic for the application once it has loaded.
|
||||
|
||||
@@ -15,7 +13,6 @@ class Layout extends React.Component {
|
||||
should be.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidMount() {
|
||||
/*
|
||||
This is a bit of a hack. In order for the graph to size correctly, it needs to know the size of the parent
|
||||
@@ -24,10 +21,8 @@ class Layout extends React.Component {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
// @ts-expect-error ts-migrate(2488) FIXME: Type 'ReactNode' must have a '[Symbol.iterator]()'... Remove this comment to see the full error message
|
||||
const [leftSidebar, renderGraph, rightSidebar] = children;
|
||||
return (
|
||||
<div
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from "react";
|
||||
import * as globals from "../../globals";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
const Logo = (props: any) => {
|
||||
const Logo = (props) => {
|
||||
const { size } = props;
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 48 48" fill="none">
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Position, Toaster, Intent } from "@blueprintjs/core";
|
||||
|
||||
/** Singleton toaster instance. Create separate instances for different options. */
|
||||
|
||||
const ToastTopCenter = Toaster.create({
|
||||
className: "recipe-toaster",
|
||||
position: Position.TOP,
|
||||
maxToasts: 4,
|
||||
});
|
||||
|
||||
/*
|
||||
A "user" error - eg, bad input
|
||||
*/
|
||||
export const postUserErrorToast = (message) =>
|
||||
ToastTopCenter.show({ message, intent: Intent.WARNING });
|
||||
|
||||
/*
|
||||
A toast the user must dismiss manually, because they need to act on its information,
|
||||
ie., 8 bulk add genes out of 40 were bad. Manually see which ones and fix.
|
||||
*/
|
||||
export const keepAroundErrorToast = (message) =>
|
||||
ToastTopCenter.show({ message, timeout: 0, intent: Intent.WARNING });
|
||||
|
||||
/*
|
||||
a hard network error
|
||||
*/
|
||||
export const postNetworkErrorToast = (message, key = undefined) =>
|
||||
ToastTopCenter.show(
|
||||
{
|
||||
message,
|
||||
timeout: 30000,
|
||||
intent: Intent.DANGER,
|
||||
},
|
||||
key
|
||||
);
|
||||
|
||||
/*
|
||||
Async message to user
|
||||
*/
|
||||
export const postAsyncSuccessToast = (message) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 10000,
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
|
||||
export const postAsyncFailureToast = (message) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 10000,
|
||||
intent: Intent.WARNING,
|
||||
});
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Position, Toaster, Intent } from "@blueprintjs/core";
|
||||
|
||||
/** Singleton toaster instance. Create separate instances for different options. */
|
||||
|
||||
const ToastTopCenter = Toaster.create({
|
||||
className: "recipe-toaster",
|
||||
position: Position.TOP,
|
||||
maxToasts: 4,
|
||||
});
|
||||
|
||||
/*
|
||||
A "user" error - eg, bad input
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const postUserErrorToast = (message: any) =>
|
||||
ToastTopCenter.show({ message, intent: Intent.WARNING });
|
||||
|
||||
/*
|
||||
A toast the user must dismiss manually, because they need to act on its information,
|
||||
ie., 8 bulk add genes out of 40 were bad. Manually see which ones and fix.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const keepAroundErrorToast = (message: any) =>
|
||||
ToastTopCenter.show({ message, timeout: 0, intent: Intent.WARNING });
|
||||
|
||||
/*
|
||||
a hard network error
|
||||
*/
|
||||
export const postNetworkErrorToast = (
|
||||
message: string,
|
||||
key: string | undefined = undefined
|
||||
): string =>
|
||||
ToastTopCenter.show(
|
||||
{
|
||||
message,
|
||||
timeout: 30000,
|
||||
intent: Intent.DANGER,
|
||||
},
|
||||
key
|
||||
);
|
||||
|
||||
/*
|
||||
Async message to user
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const postAsyncSuccessToast = (message: any) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 10000,
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export const postAsyncFailureToast = (message: any) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 10000,
|
||||
intent: Intent.WARNING,
|
||||
});
|
||||
+5
-37
@@ -9,51 +9,34 @@ import actions from "../../actions";
|
||||
|
||||
const MINI_HISTOGRAM_WIDTH = 110;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state, ownProps) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'gene' does not exist on type '{}'.
|
||||
const { gene } = ownProps;
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
isColorAccessor: (state as any).colors.colorAccessor === gene,
|
||||
isScatterplotXXaccessor:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).controls.scatterplotXXaccessor === gene,
|
||||
isScatterplotYYaccessor:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).controls.scatterplotYYaccessor === gene,
|
||||
isColorAccessor: state.colors.colorAccessor === gene,
|
||||
isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === gene,
|
||||
isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === gene,
|
||||
};
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class Gene extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class Gene extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
geneIsExpanded: false,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
onColorChangeClick = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, gene } = this.props;
|
||||
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(gene));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleGeneExpandClick = () => {
|
||||
const { geneIsExpanded } = this.state;
|
||||
this.setState({ geneIsExpanded: !geneIsExpanded });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleSetGeneAsScatterplotX = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, gene } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot x",
|
||||
@@ -61,9 +44,7 @@ class Gene extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleSetGeneAsScatterplotY = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, gene } = this.props;
|
||||
dispatch({
|
||||
type: "set scatterplot y",
|
||||
@@ -71,29 +52,19 @@ class Gene extends React.Component<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleDeleteGeneFromSet = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, gene, geneset } = this.props;
|
||||
dispatch(actions.genesetDeleteGenes(geneset, [gene]));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'gene' does not exist on type 'Readonly<{... Remove this comment to see the full error message
|
||||
gene,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'geneDescription' does not exist on type ... Remove this comment to see the full error message
|
||||
geneDescription,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
|
||||
isColorAccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotXXaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
|
||||
isScatterplotYYaccessor,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'quickGene' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
quickGene,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'removeGene' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
removeGene,
|
||||
} = this.props;
|
||||
const { geneIsExpanded } = this.state;
|
||||
@@ -113,7 +84,6 @@ class Gene extends React.Component<{}, State> {
|
||||
>
|
||||
<div
|
||||
role="menuitem"
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="0"
|
||||
data-testclass="gene-expand"
|
||||
data-testid={`${gene}:gene-expand`}
|
||||
@@ -154,7 +124,6 @@ class Gene extends React.Component<{}, State> {
|
||||
</div>
|
||||
{!geneIsExpanded ? (
|
||||
<HistogramBrush
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
isUserDefined
|
||||
field={gene}
|
||||
mini
|
||||
@@ -219,7 +188,6 @@ class Gene extends React.Component<{}, State> {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}
|
||||
{geneIsExpanded && <HistogramBrush isUserDefined field={gene} />}
|
||||
</div>
|
||||
);
|
||||
+2
-17
@@ -9,28 +9,20 @@ import HistogramBrush from "../brushableHistogram";
|
||||
|
||||
import { diffexpPopNamePrefix1, diffexpPopNamePrefix2 } from "../../globals";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class GeneSet extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class GeneSet extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
onGenesetMenuClick = () => {
|
||||
const { isOpen } = this.state;
|
||||
this.setState({ isOpen: !isOpen });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
renderGenes() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'setName' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
const { setName, setGenes } = this.props;
|
||||
const setGenesNames = [...setGenes.keys()];
|
||||
return (
|
||||
@@ -40,7 +32,6 @@ class GeneSet extends React.Component<{}, State> {
|
||||
return (
|
||||
<Gene
|
||||
key={gene}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: any; gene: any; geneDescription: any;... Remove this comment to see the full error message
|
||||
gene={gene}
|
||||
geneDescription={geneDescription}
|
||||
geneset={setName}
|
||||
@@ -51,9 +42,7 @@ class GeneSet extends React.Component<{}, State> {
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'setName' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
const { setName, genesetDescription, setGenes } = this.props;
|
||||
const { isOpen } = this.state;
|
||||
const genesetNameLengthVisible = 150; /* this magic number determines how much of a long geneset name we see */
|
||||
@@ -76,7 +65,6 @@ class GeneSet extends React.Component<{}, State> {
|
||||
>
|
||||
<span
|
||||
role="menuitem"
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="0"
|
||||
data-testclass={testClass}
|
||||
data-testid={`${setName}:geneset-expand`}
|
||||
@@ -117,7 +105,6 @@ class GeneSet extends React.Component<{}, State> {
|
||||
)}
|
||||
</span>
|
||||
<div>
|
||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ isOpen: any; genesetsEditable: true; genes... Remove this comment to see the full error message */}
|
||||
<GenesetMenus isOpen={isOpen} genesetsEditable geneset={setName} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,7 +118,6 @@ class GeneSet extends React.Component<{}, State> {
|
||||
</div>
|
||||
{isOpen && !genesetIsEmpty && (
|
||||
<HistogramBrush
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
isGeneSetSummary
|
||||
field={setName}
|
||||
setGenes={setGenes}
|
||||
@@ -139,7 +125,6 @@ class GeneSet extends React.Component<{}, State> {
|
||||
)}
|
||||
{isOpen && !genesetIsEmpty && this.renderGenes()}
|
||||
<EditGenesetNameDialogue
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ parentGeneset: any; parentGenesetDescripti... Remove this comment to see the full error message
|
||||
parentGeneset={setName}
|
||||
parentGenesetDescription={genesetDescription}
|
||||
/>
|
||||
+6
-26
@@ -7,33 +7,23 @@ import GeneSet from "./geneSet";
|
||||
import QuickGene from "./quickGene";
|
||||
import CreateGenesetDialogue from "./menus/createGenesetDialogue";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class GeneExpression extends React.Component<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
genesets: state.genesets.genesets,
|
||||
}))
|
||||
class GeneExpression extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { geneSetsExpanded: true };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
renderGeneSets = () => {
|
||||
const sets = [];
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { genesets } = this.props;
|
||||
|
||||
for (const [name, geneset] of genesets) {
|
||||
sets.push(
|
||||
<GeneSet
|
||||
key={name}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: any; setGenes: any; setName: any; gen... Remove this comment to see the full error message
|
||||
setGenes={geneset.genes}
|
||||
setName={name}
|
||||
genesetDescription={geneset.genesetDescription}
|
||||
@@ -43,28 +33,19 @@ class GeneExpression extends React.Component<{}, State> {
|
||||
return sets;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleActivateCreateGenesetMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
const { geneSetsExpanded } = this.state;
|
||||
dispatch({ type: "geneset: activate add new geneset mode" });
|
||||
if (!geneSetsExpanded) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
this.setState((state: any) => ({ ...state, geneSetsExpanded: true }));
|
||||
this.setState((state) => ({ ...state, geneSetsExpanded: true }));
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleExpandGeneSets = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
this.setState((state: any) => ({
|
||||
...state,
|
||||
geneSetsExpanded: !state.geneSetsExpanded,
|
||||
}));
|
||||
this.setState((state) => ({ ...state, geneSetsExpanded: !state.geneSetsExpanded }));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { geneSetsExpanded } = this.state;
|
||||
return (
|
||||
@@ -80,7 +61,6 @@ class GeneExpression extends React.Component<{}, State> {
|
||||
>
|
||||
<H4
|
||||
role="menuitem"
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="0"
|
||||
data-testclass="geneset-heading-expand"
|
||||
onKeyPress={this.handleExpandGeneSets}
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
import parseBulkGeneString from "../../../util/parseBulkGeneString";
|
||||
import actions from "../../../actions";
|
||||
|
||||
@connect((state) => ({
|
||||
genesetsUI: state.genesetsUI,
|
||||
}))
|
||||
class AddGeneToGenesetDialogue extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
genesToAdd: "",
|
||||
};
|
||||
}
|
||||
|
||||
disableAddGeneMode = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "geneset: disable add new genes mode",
|
||||
});
|
||||
};
|
||||
|
||||
handleAddGeneToGeneSet = (e) => {
|
||||
const { geneset, dispatch } = this.props;
|
||||
const { genesToAdd } = this.state;
|
||||
|
||||
const genesTmpHardcodedFormat = [];
|
||||
|
||||
const genesArrayFromString = parseBulkGeneString(genesToAdd);
|
||||
|
||||
genesArrayFromString.forEach((_gene) => {
|
||||
genesTmpHardcodedFormat.push({
|
||||
geneSymbol: _gene,
|
||||
});
|
||||
});
|
||||
|
||||
dispatch(actions.genesetAddGenes(geneset, genesTmpHardcodedFormat));
|
||||
dispatch({
|
||||
type: "geneset: disable add new genes mode",
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({ genesToAdd: e });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { geneset, genesetsUI } = this.props;
|
||||
const { genesToAdd } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
isActive={genesetsUI.isAddingGenesToGeneset === geneset}
|
||||
inputProps={{ "data-testid": `${geneset}:create-label-dialog` }}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${geneset}:submit-gene`,
|
||||
}}
|
||||
title="Add genes to gene set"
|
||||
instruction={`Add genes to ${geneset}`}
|
||||
cancelTooltipContent="Close this dialog without adding genes to gene set."
|
||||
primaryButtonText="Add genes"
|
||||
text={genesToAdd}
|
||||
validationError={false}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-genes",
|
||||
leftIcon: "manually-entered-data",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="New category"
|
||||
/>
|
||||
}
|
||||
handleSubmit={this.handleAddGeneToGeneSet}
|
||||
handleCancel={this.disableAddGeneMode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddGeneToGenesetDialogue;
|
||||
@@ -1,101 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
import parseBulkGeneString from "../../../util/parseBulkGeneString";
|
||||
import actions from "../../../actions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetsUI: (state as any).genesetsUI,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class AddGeneToGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
genesToAdd: "",
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
disableAddGeneMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "geneset: disable add new genes mode",
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleAddGeneToGeneSet = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'geneset' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
const { geneset, dispatch } = this.props;
|
||||
const { genesToAdd } = this.state;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const genesTmpHardcodedFormat: any = [];
|
||||
const genesArrayFromString = parseBulkGeneString(genesToAdd);
|
||||
genesArrayFromString.forEach((_gene) => {
|
||||
genesTmpHardcodedFormat.push({
|
||||
geneSymbol: _gene,
|
||||
});
|
||||
});
|
||||
dispatch(actions.genesetAddGenes(geneset, genesTmpHardcodedFormat));
|
||||
dispatch({
|
||||
type: "geneset: disable add new genes mode",
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChange = (e: any) => {
|
||||
this.setState({ genesToAdd: e });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'geneset' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
const { geneset, genesetsUI } = this.props;
|
||||
const { genesToAdd } = this.state;
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isActive: boolean; inputProps: { "data-tes... Remove this comment to see the full error message
|
||||
isActive={genesetsUI.isAddingGenesToGeneset === geneset}
|
||||
inputProps={{ "data-testid": `${geneset}:create-label-dialog` }}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${geneset}:submit-gene`,
|
||||
}}
|
||||
title="Add genes to gene set"
|
||||
instruction={`Add genes to ${geneset}`}
|
||||
cancelTooltipContent="Close this dialog without adding genes to gene set."
|
||||
primaryButtonText="Add genes"
|
||||
text={genesToAdd}
|
||||
validationError={false}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ onChange: (e: any) => void; inputProps: { ... Remove this comment to see the full error message
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-genes",
|
||||
leftIcon: "manually-entered-data",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
newLabelMessage="New category"
|
||||
/>
|
||||
}
|
||||
handleSubmit={this.handleAddGeneToGeneSet}
|
||||
handleCancel={this.disableAddGeneMode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddGeneToGenesetDialogue;
|
||||
+17
-44
@@ -19,10 +19,8 @@ import {
|
||||
|
||||
import { memoize } from "../../../util/dataframe/util";
|
||||
import parseBulkGeneString from "../../../util/parseBulkGeneString";
|
||||
import { Dataframe } from "../../../util/dataframe";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const renderGene = (fuzzySortResult: any, { handleClick, modifiers }: any) => {
|
||||
const renderGene = (fuzzySortResult, { handleClick, modifiers }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
@@ -35,8 +33,8 @@ const renderGene = (fuzzySortResult: any, { handleClick, modifiers }: any) => {
|
||||
disabled={modifiers.disabled}
|
||||
data-testid={`suggest-menu-item-${geneName}`}
|
||||
key={geneName}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
onClick={(g: any /* this fires when user clicks a menu item */) =>
|
||||
onClick={(g) =>
|
||||
/* this fires when user clicks a menu item */
|
||||
handleClick(g)
|
||||
}
|
||||
text={geneName}
|
||||
@@ -44,30 +42,20 @@ const renderGene = (fuzzySortResult: any, { handleClick, modifiers }: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const filterGenes = (query: any, genes: any) =>
|
||||
const filterGenes = (query, genes) =>
|
||||
/* fires on load, once, and then for each character typed into the input */
|
||||
fuzzysort.go(query, genes, {
|
||||
limit: 5,
|
||||
threshold: -10000, // don't return bad results
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type AddGenesState = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userDefinedGenes: (state as any).controls.userDefinedGenes,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userDefinedGenesLoading: (state as any).controls.userDefinedGenesLoading,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
annoMatrix: state.annoMatrix,
|
||||
userDefinedGenes: state.controls.userDefinedGenes,
|
||||
userDefinedGenesLoading: state.controls.userDefinedGenesLoading,
|
||||
}))
|
||||
class AddGenes extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
bulkAdd: "",
|
||||
@@ -78,20 +66,15 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidMount() {
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidUpdate(prevProps: {}) {
|
||||
componentDidUpdate(prevProps) {
|
||||
this.updateState(prevProps);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleClick(g: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleClick(g) {
|
||||
const { dispatch, userDefinedGenes } = this.props;
|
||||
const { geneNames } = this.state;
|
||||
if (!g) return;
|
||||
@@ -111,8 +94,7 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
_genesToUpper = (listGenes: any) => {
|
||||
_genesToUpper = (listGenes) => {
|
||||
// Has to be a Map to preserve index
|
||||
const upperGenes = new Map();
|
||||
for (let i = 0, { length } = listGenes; i < length; i += 1) {
|
||||
@@ -122,12 +104,10 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
return upperGenes;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/sort-comp, @typescript-eslint/no-explicit-any -- memo requires a defined _genesToUpper
|
||||
_memoGenesToUpper = memoize(this._genesToUpper, (arr: any) => arr);
|
||||
// eslint-disable-next-line react/sort-comp -- memo requires a defined _genesToUpper
|
||||
_memoGenesToUpper = memoize(this._genesToUpper, (arr) => arr);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleBulkAddClick = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, userDefinedGenes } = this.props;
|
||||
const { bulkAdd, geneNames } = this.state;
|
||||
|
||||
@@ -177,9 +157,7 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
async updateState(prevProps: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
async updateState(prevProps) {
|
||||
const { annoMatrix } = this.props;
|
||||
if (!annoMatrix) return;
|
||||
if (annoMatrix !== prevProps?.annoMatrix) {
|
||||
@@ -188,7 +166,7 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
|
||||
this.setState({ status: "pending" });
|
||||
try {
|
||||
const df: Dataframe = await annoMatrix.fetch("var", varIndex);
|
||||
const df = await annoMatrix.fetch("var", varIndex);
|
||||
this.setState({
|
||||
status: "success",
|
||||
geneNames: df.col(varIndex).asArray(),
|
||||
@@ -200,7 +178,6 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
placeholderGeneNames() {
|
||||
/*
|
||||
return a string containing gene name suggestions for use as a user hint.
|
||||
@@ -230,9 +207,7 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
return "Apod, Cd74, ...";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'userDefinedGenesLoading' does not exist ... Remove this comment to see the full error message
|
||||
const { userDefinedGenesLoading } = this.props;
|
||||
const { tab, bulkAdd, activeItem, status, geneNames } = this.state;
|
||||
|
||||
@@ -288,10 +263,8 @@ class AddGenes extends React.Component<{}, AddGenesState> {
|
||||
this.handleClick(g);
|
||||
}}
|
||||
initialContent={<MenuItem disabled text="Enter a gene…" />}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ "data-testid": string; }' is not assignabl... Remove this comment to see the full error message
|
||||
inputProps={{ "data-testid": "gene-search" }}
|
||||
inputValueRenderer={() => ""}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '(query: any, genes: any) => Fuzzysort.Result... Remove this comment to see the full error message
|
||||
itemListPredicate={filterGenes}
|
||||
onActiveItemChange={(item) => this.setState({ activeItem: item })}
|
||||
itemRenderer={renderGene}
|
||||
+23
-49
@@ -7,26 +7,15 @@ import { Tooltip2 } from "@blueprintjs/popover2";
|
||||
import LabelInput from "../../labelInput";
|
||||
import actions from "../../../actions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
obsCrossfilter: (state as any).obsCrossfilter,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetsUI: (state as any).genesetsUI,
|
||||
annotations: state.annotations,
|
||||
schema: state.annoMatrix?.schema,
|
||||
obsCrossfilter: state.obsCrossfilter,
|
||||
genesets: state.genesets.genesets,
|
||||
genesetsUI: state.genesetsUI,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
class CreateGenesetDialogue extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
genesetName: "",
|
||||
@@ -35,9 +24,7 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
disableCreateGenesetMode = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
disableCreateGenesetMode = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
this.setState({
|
||||
genesetName: "",
|
||||
@@ -51,32 +38,30 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
createGeneset = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
createGeneset = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
const {
|
||||
genesetName,
|
||||
genesToPopulateGeneset,
|
||||
genesetDescription,
|
||||
} = this.state;
|
||||
const { genesetName, genesToPopulateGeneset, genesetDescription } =
|
||||
this.state;
|
||||
|
||||
dispatch({
|
||||
type: "geneset: create",
|
||||
genesetName: genesetName.trim(),
|
||||
genesetDescription,
|
||||
});
|
||||
if (genesToPopulateGeneset) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const genesTmpHardcodedFormat: any = [];
|
||||
const genesTmpHardcodedFormat = [];
|
||||
|
||||
const genesArrayFromString = pull(
|
||||
uniq(genesToPopulateGeneset.split(/[ ,]+/)),
|
||||
""
|
||||
);
|
||||
|
||||
genesArrayFromString.forEach((_gene) => {
|
||||
genesTmpHardcodedFormat.push({
|
||||
geneSymbol: _gene,
|
||||
});
|
||||
});
|
||||
|
||||
dispatch(actions.genesetAddGenes(genesetName, genesTmpHardcodedFormat));
|
||||
}
|
||||
dispatch({
|
||||
@@ -89,41 +74,34 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetNameError = () => false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChange = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleChange = (e) => {
|
||||
const { genesets } = this.props;
|
||||
this.setState({ genesetName: e });
|
||||
this.validate(e, genesets);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleGenesetInputChange = (e: any) => {
|
||||
handleGenesetInputChange = (e) => {
|
||||
this.setState({ genesToPopulateGeneset: e });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleDescriptionInputChange = (e: any) => {
|
||||
handleDescriptionInputChange = (e) => {
|
||||
this.setState({ genesetDescription: e });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
instruction = (genesetName: any, genesets: any) =>
|
||||
genesets.has(genesetName)
|
||||
instruction = (genesetName, genesets) => genesets.has(genesetName)
|
||||
? "Gene set name must be unique."
|
||||
: "New, unique gene set name";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
validate = (genesetName: any, genesets: any) => {
|
||||
validate = (genesetName, genesets) => {
|
||||
if (genesets.has(genesetName)) {
|
||||
this.setState({
|
||||
nameErrorMessage: "There is already a geneset with that name",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
genesetName.length > 1 &&
|
||||
// eslint-disable-next-line no-control-regex -- unicode 0-31 127-65535
|
||||
@@ -141,11 +119,10 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
return true;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { genesetName, nameErrorMessage } = this.state;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesetsUI' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { genesetsUI, genesets } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
@@ -163,7 +140,6 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<p>{this.instruction(genesetName, genesets)}</p>
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ onChange: (e: any) => void; inputProps: { ... Remove this comment to see the full error message
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "create-geneset-input",
|
||||
@@ -188,7 +164,6 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
gene set
|
||||
</p>
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ onChange: (e: any) => void; inputProps: { ... Remove this comment to see the full error message
|
||||
onChange={this.handleDescriptionInputChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-geneset-description",
|
||||
@@ -204,7 +179,6 @@ class CreateGenesetDialogue extends React.PureComponent<{}, State> {
|
||||
gene set
|
||||
</p>
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ onChange: (e: any) => void; inputProps: { ... Remove this comment to see the full error message
|
||||
onChange={this.handleGenesetInputChange}
|
||||
inputProps={{
|
||||
"data-testid": "add-genes",
|
||||
@@ -0,0 +1,124 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
|
||||
@connect((state) => ({
|
||||
annotations: state.annotations,
|
||||
schema: state.annoMatrix?.schema,
|
||||
obsCrossfilter: state.obsCrossfilter,
|
||||
genesetsUI: state.genesetsUI,
|
||||
genesets: state.genesets.genesets,
|
||||
}))
|
||||
class RenameGeneset extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
newGenesetName: props.parentGeneset,
|
||||
newGenesetDescription: props.parentGenesetDescription,
|
||||
};
|
||||
}
|
||||
|
||||
disableEditGenesetNameMode = (e) => {
|
||||
const { dispatch } = this.props;
|
||||
this.setState({
|
||||
newGenesetName: "",
|
||||
newGenesetDescription: "",
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable rename geneset mode",
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
renameGeneset = (e) => {
|
||||
const { dispatch, genesetsUI } = this.props;
|
||||
const { newGenesetName, newGenesetDescription } = this.state;
|
||||
|
||||
dispatch({
|
||||
type: "geneset: update",
|
||||
genesetName: genesetsUI.isEditingGenesetName,
|
||||
update: {
|
||||
genesetName: newGenesetName,
|
||||
genesetDescription: newGenesetDescription,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable rename geneset mode",
|
||||
});
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
genesetNameError = () => false;
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({ newGenesetName: e });
|
||||
};
|
||||
|
||||
handleChangeDescription = (e) => {
|
||||
this.setState({ newGenesetDescription: e });
|
||||
};
|
||||
|
||||
validate = (genesetName, genesets) => (
|
||||
!genesets.has(genesetName) &&
|
||||
// eslint-disable-next-line no-control-regex -- unicode 0-31 127-65535
|
||||
genesetName.match(/^\s|[\u0000-\u001F\u007F-\uFFFF]|[ ]{2,}|^$|\s$/g)
|
||||
?.length
|
||||
);
|
||||
|
||||
render() {
|
||||
const { newGenesetName, newGenesetDescription } = this.state;
|
||||
const { genesetsUI, parentGeneset, parentGenesetDescription, genesets } =
|
||||
this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
isActive={genesetsUI.isEditingGenesetName === parentGeneset}
|
||||
inputProps={{
|
||||
"data-testid": `${genesetsUI.isEditingGenesetName}:rename-geneset-dialog`,
|
||||
}}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${genesetsUI.isEditingGenesetName}:submit-geneset`,
|
||||
}}
|
||||
title="Edit gene set name and description"
|
||||
instruction={`Rename ${genesetsUI.isEditingGenesetName}`}
|
||||
cancelTooltipContent="Close this dialog without renaming the gene set."
|
||||
primaryButtonText="Edit gene set name and description"
|
||||
text={newGenesetName}
|
||||
secondaryText={newGenesetDescription}
|
||||
validationError={
|
||||
this.validate(newGenesetName, genesets) &&
|
||||
parentGenesetDescription === newGenesetDescription
|
||||
}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
label={newGenesetName}
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "rename-geneset-modal",
|
||||
leftIcon: "manually-entered-data",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
secondaryInstructions="Edit description"
|
||||
secondaryInput={
|
||||
<LabelInput
|
||||
label={newGenesetDescription}
|
||||
onChange={this.handleChangeDescription}
|
||||
inputProps={{ "data-testid": "change geneset description" }}
|
||||
intent="none"
|
||||
autoFocus={false}
|
||||
/>
|
||||
}
|
||||
handleSubmit={this.renameGeneset}
|
||||
handleCancel={this.disableEditGenesetNameMode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RenameGeneset;
|
||||
@@ -1,154 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import AnnoDialog from "../../annoDialog";
|
||||
import LabelInput from "../../labelInput";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annotations: (state as any).annotations,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix?.schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
obsCrossfilter: (state as any).obsCrossfilter,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetsUI: (state as any).genesetsUI,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class RenameGeneset extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'parentGeneset' does not exist on type '{... Remove this comment to see the full error message
|
||||
newGenesetName: props.parentGeneset,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'parentGenesetDescription' does not exist... Remove this comment to see the full error message
|
||||
newGenesetDescription: props.parentGenesetDescription,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
disableEditGenesetNameMode = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
this.setState({
|
||||
newGenesetName: "",
|
||||
newGenesetDescription: "",
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable rename geneset mode",
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
renameGeneset = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, genesetsUI } = this.props;
|
||||
const { newGenesetName, newGenesetDescription } = this.state;
|
||||
dispatch({
|
||||
type: "geneset: update",
|
||||
genesetName: genesetsUI.isEditingGenesetName,
|
||||
update: {
|
||||
genesetName: newGenesetName,
|
||||
genesetDescription: newGenesetDescription,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: "geneset: disable rename geneset mode",
|
||||
});
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetNameError = () => false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChange = (e: any) => {
|
||||
this.setState({ newGenesetName: e });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChangeDescription = (e: any) => {
|
||||
this.setState({ newGenesetDescription: e });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
validate = (genesetName: any, genesets: any) =>
|
||||
!genesets.has(genesetName) &&
|
||||
// eslint-disable-next-line no-control-regex -- unicode 0-31 127-65535
|
||||
genesetName.match(/^\s|[\u0000-\u001F\u007F-\uFFFF]|[ ]{2,}|^$|\s$/g)
|
||||
?.length;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { newGenesetName, newGenesetDescription } = this.state;
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesetsUI' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
genesetsUI,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'parentGeneset' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
parentGeneset,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'parentGenesetDescription' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
parentGenesetDescription,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
genesets,
|
||||
} = this.props;
|
||||
return (
|
||||
<>
|
||||
<AnnoDialog
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isActive: boolean; inputProps: { "data-tes... Remove this comment to see the full error message
|
||||
isActive={genesetsUI.isEditingGenesetName === parentGeneset}
|
||||
inputProps={{
|
||||
"data-testid": `${genesetsUI.isEditingGenesetName}:rename-geneset-dialog`,
|
||||
}}
|
||||
primaryButtonProps={{
|
||||
"data-testid": `${genesetsUI.isEditingGenesetName}:submit-geneset`,
|
||||
}}
|
||||
title="Edit gene set name and description"
|
||||
instruction={`Rename ${genesetsUI.isEditingGenesetName}`}
|
||||
cancelTooltipContent="Close this dialog without renaming the gene set."
|
||||
primaryButtonText="Edit gene set name and description"
|
||||
text={newGenesetName}
|
||||
secondaryText={newGenesetDescription}
|
||||
validationError={
|
||||
this.validate(newGenesetName, genesets) &&
|
||||
parentGenesetDescription === newGenesetDescription
|
||||
}
|
||||
annoInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ label: any; onChange: (e: any) => void; in... Remove this comment to see the full error message
|
||||
label={newGenesetName}
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
"data-testid": "rename-geneset-modal",
|
||||
leftIcon: "manually-entered-data",
|
||||
intent: "none",
|
||||
autoFocus: true,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
secondaryInstructions="Edit description"
|
||||
secondaryInput={
|
||||
<LabelInput
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ label: any; onChange: (e: any) => void; in... Remove this comment to see the full error message
|
||||
label={newGenesetDescription}
|
||||
onChange={this.handleChangeDescription}
|
||||
inputProps={{ "data-testid": "change geneset description" }}
|
||||
intent="none"
|
||||
autoFocus={false}
|
||||
/>
|
||||
}
|
||||
handleSubmit={this.renameGeneset}
|
||||
handleCancel={this.disableEditGenesetNameMode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RenameGeneset;
|
||||
+5
-24
@@ -17,27 +17,17 @@ import * as globals from "../../../globals";
|
||||
import actions from "../../../actions";
|
||||
import AddGeneToGenesetDialogue from "./addGeneToGenesetDialogue";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesetsUI: (state as any).genesetsUI,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colorAccessor: (state as any).colors.colorAccessor,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class GenesetMenus extends React.PureComponent<{}, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
genesetsUI: state.genesetsUI,
|
||||
colorAccessor: state.colors.colorAccessor,
|
||||
}))
|
||||
class GenesetMenus extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
activateAddGeneToGenesetMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, geneset } = this.props;
|
||||
dispatch({
|
||||
type: "geneset: activate add new genes mode",
|
||||
@@ -45,9 +35,7 @@ class GenesetMenus extends React.PureComponent<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
activateEditGenesetNameMode = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, geneset } = this.props;
|
||||
|
||||
dispatch({
|
||||
@@ -56,9 +44,7 @@ class GenesetMenus extends React.PureComponent<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleColorByEntireGeneset = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, geneset } = this.props;
|
||||
|
||||
dispatch({
|
||||
@@ -67,16 +53,12 @@ class GenesetMenus extends React.PureComponent<{}, State> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleDeleteGeneset = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, geneset } = this.props;
|
||||
dispatch(actions.genesetDelete(geneset));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'geneset' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
const { geneset, genesetsEditable, createText, colorAccessor } = this.props;
|
||||
|
||||
const isColorBy = geneset === colorAccessor;
|
||||
@@ -100,7 +82,6 @@ class GenesetMenus extends React.PureComponent<{}, State> {
|
||||
minimal
|
||||
/>
|
||||
</Tooltip2>
|
||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ geneset: any; }' is not assignable to type... Remove this comment to see the full error message */}
|
||||
<AddGeneToGenesetDialogue geneset={geneset} />
|
||||
<Popover
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
+17
-42
@@ -9,10 +9,8 @@ import Gene from "./gene";
|
||||
|
||||
import { postUserErrorToast } from "../framework/toasters";
|
||||
import actions from "../../actions";
|
||||
import { Dataframe, DataframeValue } from "../../util/dataframe";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const usePrevious = (value: any) => {
|
||||
const usePrevious = (value) => {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
@@ -20,40 +18,34 @@ const usePrevious = (value: any) => {
|
||||
return ref.current;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
function QuickGene() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const [geneNames, setGeneNames] = useState([] as DataframeValue[]);
|
||||
const [geneNames, setGeneNames] = useState([]);
|
||||
const [, setStatus] = useState("pending");
|
||||
|
||||
const { annoMatrix, userDefinedGenes, userDefinedGenesLoading } = useSelector(
|
||||
(state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userDefinedGenes: (state as any).controls.userDefinedGenes,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userDefinedGenesLoading: (state as any).controls.userDefinedGenesLoading,
|
||||
})
|
||||
annoMatrix: state.annoMatrix,
|
||||
userDefinedGenes: state.controls.userDefinedGenes,
|
||||
userDefinedGenesLoading: state.controls.userDefinedGenesLoading,
|
||||
})
|
||||
);
|
||||
|
||||
const prevProps = usePrevious({ annoMatrix });
|
||||
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '() => Promise<void>' is not assi... Remove this comment to see the full error message
|
||||
useEffect(async () => {
|
||||
if (!annoMatrix) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if (annoMatrix !== (prevProps as any)?.annoMatrix) {
|
||||
if (annoMatrix !== prevProps?.annoMatrix) {
|
||||
const { schema } = annoMatrix;
|
||||
const varIndex = schema.annotations.var.index;
|
||||
|
||||
setStatus("pending");
|
||||
try {
|
||||
const df: Dataframe = await annoMatrix.fetch("var", varIndex);
|
||||
const df = await annoMatrix.fetch("var", varIndex);
|
||||
setStatus("success");
|
||||
setGeneNames(df.col(varIndex).asArray() as DataframeValue[]);
|
||||
setGeneNames(df.col(varIndex).asArray());
|
||||
} catch (error) {
|
||||
setStatus("error");
|
||||
throw error;
|
||||
@@ -63,12 +55,7 @@ function QuickGene() {
|
||||
|
||||
const handleExpand = () => setIsExpanded(!isExpanded);
|
||||
|
||||
const renderGene = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
fuzzySortResult: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
{ handleClick, modifiers }: any
|
||||
) => {
|
||||
const renderGene = (fuzzySortResult, { handleClick, modifiers }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
@@ -81,8 +68,8 @@ function QuickGene() {
|
||||
disabled={modifiers.disabled}
|
||||
data-testid={`suggest-menu-item-${geneName}`}
|
||||
key={geneName}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
onClick={(g: any /* this fires when user clicks a menu item */) =>
|
||||
onClick={(g) =>
|
||||
/* this fires when user clicks a menu item */
|
||||
handleClick(g)
|
||||
}
|
||||
text={geneName}
|
||||
@@ -90,8 +77,7 @@ function QuickGene() {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const handleClick = (g: any) => {
|
||||
const handleClick = (g) => {
|
||||
if (!g) return;
|
||||
const gene = g.target;
|
||||
if (userDefinedGenes.indexOf(gene) !== -1) {
|
||||
@@ -105,39 +91,30 @@ function QuickGene() {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const filterGenes = (query: any, genes: any) =>
|
||||
const filterGenes = (query, genes) =>
|
||||
/* fires on load, once, and then for each character typed into the input */
|
||||
fuzzysort.go(query, genes, {
|
||||
limit: 5,
|
||||
threshold: -10000, // don't return bad results
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const removeGene = (gene: any) => () => {
|
||||
const removeGene = (gene) => () => {
|
||||
dispatch({ type: "clear user defined gene", data: gene });
|
||||
};
|
||||
|
||||
const QuickGenes = useMemo(
|
||||
() =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userDefinedGenes.map((gene: any) => (
|
||||
const QuickGenes = useMemo(() => userDefinedGenes.map((gene) => (
|
||||
<Gene
|
||||
key={`quick=${gene}`}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: string; gene: any; removeGene: (gene:... Remove this comment to see the full error message
|
||||
gene={gene}
|
||||
removeGene={removeGene}
|
||||
quickGene
|
||||
/>
|
||||
)),
|
||||
[userDefinedGenes]
|
||||
);
|
||||
)), [userDefinedGenes]);
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", marginBottom: "16px" }}>
|
||||
<H4
|
||||
role="menuitem"
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'number | ... Remove this comment to see the full error message
|
||||
tabIndex="0"
|
||||
data-testclass="quickgene-heading-expand"
|
||||
onKeyPress={handleExpand}
|
||||
@@ -168,14 +145,12 @@ function QuickGene() {
|
||||
}}
|
||||
initialContent={<MenuItem disabled text="Enter a gene…" />}
|
||||
inputProps={{
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ "data-testid": string; placeholder: string... Remove this comment to see the full error message
|
||||
"data-testid": "gene-search",
|
||||
placeholder: "Quick Gene Search",
|
||||
leftIcon: IconNames.SEARCH,
|
||||
fill: true,
|
||||
}}
|
||||
inputValueRenderer={() => ""}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '(query: any, genes: any) => Fuzzysort.Result... Remove this comment to see the full error message
|
||||
itemListPredicate={filterGenes}
|
||||
itemRenderer={renderGene}
|
||||
items={geneNames || ["No genes"]}
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
import { glPointFlags, glPointSize } from "../../util/glHelpers";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export default function drawPointsRegl(regl: any) {
|
||||
export default function drawPointsRegl(regl) {
|
||||
return regl({
|
||||
vert: `
|
||||
precision mediump float;
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
flagSelected,
|
||||
flagHighlight,
|
||||
} from "../../util/glHelpers";
|
||||
import { Dataframe } from "../../util/dataframe";
|
||||
|
||||
/*
|
||||
Simple 2D transforms control all point painting. There are three:
|
||||
@@ -35,8 +34,7 @@ Simple 2D transforms control all point painting. There are three:
|
||||
* camera - apply a 2D camera transformation (pan, zoom)
|
||||
* projection - apply any transformation required for screen size and layout
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function createProjectionTF(viewportWidth: any, viewportHeight: any) {
|
||||
function createProjectionTF(viewportWidth, viewportHeight) {
|
||||
/*
|
||||
the projection transform accounts for the screen size & other layout
|
||||
*/
|
||||
@@ -55,7 +53,6 @@ function createProjectionTF(viewportWidth: any, viewportHeight: any) {
|
||||
0,
|
||||
(bottomGutterSizePx - topGutterSizePx) / viewportHeight / aspectScale[1],
|
||||
]);
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message
|
||||
mat3.scale(m, m, aspectScale);
|
||||
return m;
|
||||
}
|
||||
@@ -70,48 +67,32 @@ function createModelTF() {
|
||||
return m;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type GraphState = any;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
crossfilter: (state as any).obsCrossfilter,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
selectionTool: (state as any).graphSelection.tool,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
currentSelection: (state as any).graphSelection.selection,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: (state as any).layoutChoice,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
graphInteractionMode: (state as any).controls.graphInteractionMode,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colors: (state as any).colors,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
pointDilation: (state as any).pointDilation,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
annoMatrix: state.annoMatrix,
|
||||
crossfilter: state.obsCrossfilter,
|
||||
selectionTool: state.graphSelection.tool,
|
||||
currentSelection: state.graphSelection.selection,
|
||||
layoutChoice: state.layoutChoice,
|
||||
graphInteractionMode: state.controls.graphInteractionMode,
|
||||
colors: state.colors,
|
||||
pointDilation: state.pointDilation,
|
||||
genesets: state.genesets.genesets,
|
||||
}))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
class Graph extends React.Component<{}, GraphState> {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static createReglState(canvas: any) {
|
||||
class Graph extends React.Component {
|
||||
static createReglState(canvas) {
|
||||
/*
|
||||
Must be created for each canvas
|
||||
*/
|
||||
Must be created for each canvas
|
||||
*/
|
||||
// setup canvas, webgl draw function and camera
|
||||
const camera = _camera(canvas);
|
||||
const regl = _regl(canvas);
|
||||
const drawPoints = _drawPoints(regl);
|
||||
|
||||
// preallocate webgl buffers
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
const pointBuffer = regl.buffer();
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
const colorBuffer = regl.buffer();
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||
const flagBuffer = regl.buffer();
|
||||
|
||||
return {
|
||||
camera,
|
||||
regl,
|
||||
@@ -122,21 +103,14 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static watchAsync(props: any, prevProps: any) {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
cachedAsyncProps: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
reglCanvas: any;
|
||||
|
||||
computePointPositions = memoize((X, Y, modelTF) => {
|
||||
/*
|
||||
compute the model coordinate for each point
|
||||
*/
|
||||
compute the model coordinate for each point
|
||||
*/
|
||||
const positions = new Float32Array(2 * X.length);
|
||||
for (let i = 0, len = X.length; i < len; i += 1) {
|
||||
const p = vec2.fromValues(X[i], Y[i]);
|
||||
@@ -149,8 +123,8 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
|
||||
computePointColors = memoize((rgb) => {
|
||||
/*
|
||||
compute webgl colors for each point
|
||||
*/
|
||||
compute webgl colors for each point
|
||||
*/
|
||||
const colors = new Float32Array(3 * rgb.length);
|
||||
for (let i = 0, len = rgb.length; i < len; i += 1) {
|
||||
colors.set(rgb[i], 3 * i);
|
||||
@@ -199,20 +173,21 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
computePointFlags = memoize(
|
||||
(crossfilter, colorByData, pointDilationData, pointDilationLabel) => {
|
||||
/*
|
||||
We communicate with the shader using three flags:
|
||||
- isNaN -- the value is a NaN. Only makes sense when we have a colorAccessor
|
||||
- isSelected -- the value is selected
|
||||
- isHightlighted -- the value is highlighted in the UI (orthogonal from selection highlighting)
|
||||
|
||||
Due to constraints in webgl vertex shader attributes, these are encoded in a float, "kinda"
|
||||
like bitmasks.
|
||||
|
||||
We also have separate code paths for generating flags for categorical and
|
||||
continuous metadata, as they rely on different tests, and some of the flags
|
||||
(eg, isNaN) are meaningless in the face of categorical metadata.
|
||||
*/
|
||||
We communicate with the shader using three flags:
|
||||
- isNaN -- the value is a NaN. Only makes sense when we have a colorAccessor
|
||||
- isSelected -- the value is selected
|
||||
- isHightlighted -- the value is highlighted in the UI (orthogonal from selection highlighting)
|
||||
|
||||
Due to constraints in webgl vertex shader attributes, these are encoded in a float, "kinda"
|
||||
like bitmasks.
|
||||
|
||||
We also have separate code paths for generating flags for categorical and
|
||||
continuous metadata, as they rely on different tests, and some of the flags
|
||||
(eg, isNaN) are meaningless in the face of categorical metadata.
|
||||
*/
|
||||
const nObs = crossfilter.size();
|
||||
const flags = new Float32Array(nObs);
|
||||
|
||||
const selectedFlags = this.computeSelectedFlags(
|
||||
crossfilter,
|
||||
flagSelected,
|
||||
@@ -224,15 +199,16 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
pointDilationLabel
|
||||
);
|
||||
const colorByFlags = this.computeColorByFlags(nObs, colorByData);
|
||||
|
||||
for (let i = 0; i < nObs; i += 1) {
|
||||
flags[i] = selectedFlags[i] + highlightFlags[i] + colorByFlags[i];
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const viewport = this.getViewportDimensions();
|
||||
this.reglCanvas = null;
|
||||
@@ -243,18 +219,20 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
tool: null,
|
||||
container: null,
|
||||
viewport,
|
||||
|
||||
// projection
|
||||
camera: null,
|
||||
modelTF,
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '[]' is not assignable to paramet... Remove this comment to see the full error message
|
||||
modelInvTF: mat3.invert([], modelTF),
|
||||
projectionTF: createProjectionTF(viewport.width, viewport.height),
|
||||
|
||||
// regl state
|
||||
regl: null,
|
||||
drawPoints: null,
|
||||
pointBuffer: null,
|
||||
colorBuffer: null,
|
||||
flagBuffer: null,
|
||||
|
||||
// component rendering derived state - these must stay synchronized
|
||||
// with the reducer state they were generated from.
|
||||
layoutState: {
|
||||
@@ -273,32 +251,23 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidMount() {
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentDidUpdate(prevProps: {}, prevState: GraphState) {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionTool' does not exist on type 'R... Remove this comment to see the full error message
|
||||
selectionTool,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'currentSelection' does not exist on type 'R... Remove this comment to see the full error message
|
||||
currentSelection,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'graphInteractionMode' does not exist on type 'R... Remove this comment to see the full error message
|
||||
graphInteractionMode,
|
||||
} = this.props;
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { selectionTool, currentSelection, graphInteractionMode } =
|
||||
this.props;
|
||||
const { toolSVG, viewport } = this.state;
|
||||
const hasResized =
|
||||
prevState.viewport.height !== viewport.height ||
|
||||
prevState.viewport.width !== viewport.width;
|
||||
let stateChanges = {};
|
||||
|
||||
if (
|
||||
(viewport.height && viewport.width && !toolSVG) || // first time init
|
||||
hasResized || // window size has changed we want to recreate all SVGs
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionTool' does not exist on type '{... Remove this comment to see the full error message
|
||||
selectionTool !== prevProps.selectionTool || // change of selection tool
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'graphInteractionMode' does not exist on ... Remove this comment to see the full error message
|
||||
prevProps.graphInteractionMode !== graphInteractionMode // lasso/zoom mode is switched
|
||||
) {
|
||||
stateChanges = {
|
||||
@@ -306,23 +275,19 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
...this.createToolSVG(),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
if the selection tool or state has changed, ensure that the selection
|
||||
tool correctly reflects the underlying selection.
|
||||
*/
|
||||
if the selection tool or state has changed, ensure that the selection
|
||||
tool correctly reflects the underlying selection.
|
||||
*/
|
||||
if (
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'currentSelection' does not exist on type... Remove this comment to see the full error message
|
||||
currentSelection !== prevProps.currentSelection ||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'graphInteractionMode' does not exist on ... Remove this comment to see the full error message
|
||||
graphInteractionMode !== prevProps.graphInteractionMode ||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'toolSVG' does not exist on type '{}'.
|
||||
stateChanges.toolSVG
|
||||
) {
|
||||
const { tool, container } = this.state;
|
||||
this.selectionToolUpdate(
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'tool' does not exist on type '{}'.
|
||||
stateChanges.tool ? stateChanges.tool : tool,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'container' does not exist on type '{}'.
|
||||
stateChanges.container ? stateChanges.container : container
|
||||
);
|
||||
}
|
||||
@@ -332,12 +297,10 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleResize = () => {
|
||||
const { state } = this.state;
|
||||
const viewport = this.getViewportDimensions();
|
||||
@@ -349,35 +312,27 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleCanvasEvent = (e: any) => {
|
||||
handleCanvasEvent = (e) => {
|
||||
const { camera, projectionTF } = this.state;
|
||||
if (e.type !== "wheel") e.preventDefault();
|
||||
if (camera.handleEvent(e, projectionTF)) {
|
||||
this.renderCanvas();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
this.setState((state: any) => ({
|
||||
...state,
|
||||
updateOverlay: !state.updateOverlay,
|
||||
}));
|
||||
this.setState((state) => ({ ...state, updateOverlay: !state.updateOverlay }));
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleBrushDragAction() {
|
||||
/*
|
||||
event describing brush position:
|
||||
@-------|
|
||||
| |
|
||||
| |
|
||||
|-------@
|
||||
*/
|
||||
event describing brush position:
|
||||
@-------|
|
||||
| |
|
||||
| |
|
||||
|-------@
|
||||
*/
|
||||
// ignore programatically generated events
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'event' does not exist on type 'typeof im... Remove this comment to see the full error message
|
||||
if (d3.event.sourceEvent === null || !d3.event.selection) return;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'event' does not exist on type 'typeof im... Remove this comment to see the full error message
|
||||
const s = d3.event.selection;
|
||||
const northwest = this.mapScreenToPoint(s[0]);
|
||||
const southeast = this.mapScreenToPoint(s[1]);
|
||||
@@ -395,28 +350,23 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleBrushStartAction() {
|
||||
// Ignore programatically generated events.
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'event' does not exist on type 'typeof im... Remove this comment to see the full error message
|
||||
if (!d3.event.sourceEvent) return;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
|
||||
const { dispatch } = this.props;
|
||||
dispatch(actions.graphBrushStartAction());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleBrushEndAction() {
|
||||
// Ignore programatically generated events.
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'event' does not exist on type 'typeof im... Remove this comment to see the full error message
|
||||
if (!d3.event.sourceEvent) return;
|
||||
|
||||
/*
|
||||
coordinates will be included if selection made, null
|
||||
if selection cleared.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
coordinates will be included if selection made, null
|
||||
if selection cleared.
|
||||
*/
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'event' does not exist on type 'typeof im... Remove this comment to see the full error message
|
||||
const s = d3.event.selection;
|
||||
if (s) {
|
||||
const northwest = this.mapScreenToPoint(s[0]);
|
||||
@@ -438,27 +388,21 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleBrushDeselectAction() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
dispatch(actions.graphBrushDeselectAction(layoutChoice.current));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleLassoStart() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||
dispatch(actions.graphLassoStartAction(layoutChoice.current));
|
||||
}
|
||||
|
||||
// when a lasso is completed, filter to the points within the lasso polygon
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleLassoEnd(polygon: any) {
|
||||
handleLassoEnd(polygon) {
|
||||
const minimumPolygonArea = 10;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
|
||||
if (
|
||||
polygon.length < 3 ||
|
||||
Math.abs(d3.polygonArea(polygon)) < minimumPolygonArea
|
||||
@@ -469,38 +413,29 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
dispatch(
|
||||
actions.graphLassoEndAction(
|
||||
layoutChoice.current,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
polygon.map((xy: any) => this.mapScreenToPoint(xy))
|
||||
polygon.map((xy) => this.mapScreenToPoint(xy))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleLassoCancel() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
dispatch(actions.graphLassoCancelAction(layoutChoice.current));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleLassoDeselectAction() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, layoutChoice } = this.props;
|
||||
dispatch(actions.graphLassoDeselectAction(layoutChoice.current));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleDeselectAction() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionTool' does not exist on type 'R... Remove this comment to see the full error message
|
||||
const { selectionTool } = this.props;
|
||||
if (selectionTool === "brush") this.handleBrushDeselectAction();
|
||||
if (selectionTool === "lasso") this.handleLassoDeselectAction();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleOpacityRangeChange(e: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleOpacityRangeChange(e) {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "change opacity deselected cells in 2d graph background",
|
||||
@@ -508,17 +443,14 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
setReglCanvas = (canvas: any) => {
|
||||
setReglCanvas = (canvas) => {
|
||||
this.reglCanvas = canvas;
|
||||
this.setState({
|
||||
...Graph.createReglState(canvas),
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
getViewportDimensions = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'viewportRef' does not exist on type 'Rea... Remove this comment to see the full error message
|
||||
const { viewportRef } = this.props;
|
||||
return {
|
||||
height: viewportRef.clientHeight,
|
||||
@@ -526,19 +458,19 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
createToolSVG = () => {
|
||||
/*
|
||||
Called from componentDidUpdate. Create the tool SVG, and return any
|
||||
state changes that should be passed to setState().
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionTool' does not exist on type 'R... Remove this comment to see the full error message
|
||||
Called from componentDidUpdate. Create the tool SVG, and return any
|
||||
state changes that should be passed to setState().
|
||||
*/
|
||||
const { selectionTool, graphInteractionMode } = this.props;
|
||||
const { viewport } = this.state;
|
||||
|
||||
/* clear out whatever was on the div, even if nothing, but usually the brushes etc */
|
||||
const lasso = d3.select("#lasso-layer");
|
||||
if (lasso.empty()) return {}; // still initializing
|
||||
lasso.selectAll(".lasso-group").remove();
|
||||
|
||||
// Don't render or recreate toolSVG if currently in zoom mode
|
||||
if (graphInteractionMode !== "select") {
|
||||
// don't return "change" of state unless we are really changing it!
|
||||
@@ -546,6 +478,7 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
if (toolSVG === undefined) return {};
|
||||
return { toolSVG: undefined };
|
||||
}
|
||||
|
||||
let handleStart;
|
||||
let handleDrag;
|
||||
let handleEnd;
|
||||
@@ -559,6 +492,7 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
handleEnd = this.handleLassoEnd.bind(this);
|
||||
handleCancel = this.handleLassoCancel.bind(this);
|
||||
}
|
||||
|
||||
const {
|
||||
svg: newToolSVG,
|
||||
tool,
|
||||
@@ -571,11 +505,11 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
handleCancel,
|
||||
viewport
|
||||
);
|
||||
|
||||
return { toolSVG: newToolSVG, tool, container };
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
fetchAsyncProps = async (props: any) => {
|
||||
fetchAsyncProps = async (props) => {
|
||||
const {
|
||||
annoMatrix,
|
||||
colors: colorsProp,
|
||||
@@ -585,19 +519,24 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
viewport,
|
||||
} = props.watchProps;
|
||||
const { modelTF } = this.state;
|
||||
|
||||
const [layoutDf, colorDf, pointDilationDf] = await this.fetchData(
|
||||
annoMatrix,
|
||||
layoutChoice,
|
||||
colorsProp,
|
||||
pointDilation
|
||||
);
|
||||
|
||||
const { currentDimNames } = layoutChoice;
|
||||
const X = layoutDf.col(currentDimNames[0]).asArray();
|
||||
const Y = layoutDf.col(currentDimNames[1]).asArray();
|
||||
const positions = this.computePointPositions(X, Y, modelTF);
|
||||
|
||||
const colorTable = this.updateColorTable(colorsProp, colorDf);
|
||||
const colors = this.computePointColors(colorTable.rgb);
|
||||
const colorByData = colorDf?.icol(0)?.asArray();
|
||||
|
||||
const { colorAccessor } = colorsProp;
|
||||
const colorByData = colorDf?.col(colorAccessor)?.asArray();
|
||||
const {
|
||||
metadataField: pointDilationCategory,
|
||||
categoryField: pointDilationLabel,
|
||||
@@ -611,6 +550,7 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
pointDilationData,
|
||||
pointDilationLabel
|
||||
);
|
||||
|
||||
const { width, height } = viewport;
|
||||
return {
|
||||
positions,
|
||||
@@ -621,53 +561,50 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
async fetchData(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colors: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
pointDilation: any
|
||||
): Promise<[Dataframe, Dataframe | null, Dataframe | null]> {
|
||||
async fetchData(annoMatrix, layoutChoice, colors, pointDilation) {
|
||||
/*
|
||||
fetch all data needed. Includes:
|
||||
- the color by dataframe
|
||||
- the layout dataframe
|
||||
- the point dilation dataframe
|
||||
*/
|
||||
fetch all data needed. Includes:
|
||||
- the color by dataframe
|
||||
- the layout dataframe
|
||||
- the point dilation dataframe
|
||||
*/
|
||||
const { metadataField: pointDilationAccessor } = pointDilation;
|
||||
|
||||
const promises = [];
|
||||
// layout
|
||||
promises.push(annoMatrix.fetch("emb", layoutChoice.current));
|
||||
|
||||
// color
|
||||
const query = this.createColorByQuery(colors);
|
||||
const promises: [
|
||||
Promise<Dataframe>,
|
||||
Promise<Dataframe | null>,
|
||||
Promise<Dataframe | null>
|
||||
] = [
|
||||
annoMatrix.fetch("emb", layoutChoice.current),
|
||||
query ? annoMatrix.fetch(...query) : Promise.resolve(null),
|
||||
pointDilationAccessor
|
||||
? annoMatrix.fetch("obs", pointDilationAccessor)
|
||||
: Promise.resolve(null),
|
||||
];
|
||||
if (query) {
|
||||
promises.push(annoMatrix.fetch(...query));
|
||||
} else {
|
||||
promises.push(Promise.resolve(null));
|
||||
}
|
||||
|
||||
// point highlighting
|
||||
if (pointDilationAccessor) {
|
||||
promises.push(annoMatrix.fetch("obs", pointDilationAccessor));
|
||||
} else {
|
||||
promises.push(Promise.resolve(null));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
brushToolUpdate(tool: any, container: any) {
|
||||
brushToolUpdate(tool, container) {
|
||||
/*
|
||||
this is called from componentDidUpdate(), so be very careful using
|
||||
anything from this.state, which may be updated asynchronously.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'currentSelection' does not exist on type... Remove this comment to see the full error message
|
||||
this is called from componentDidUpdate(), so be very careful using
|
||||
anything from this.state, which may be updated asynchronously.
|
||||
*/
|
||||
const { currentSelection } = this.props;
|
||||
if (container) {
|
||||
const toolCurrentSelection = d3.brushSelection(container.node());
|
||||
|
||||
if (currentSelection.mode === "within-rect") {
|
||||
/*
|
||||
if there is a selection, make sure the brush tool matches
|
||||
*/
|
||||
if there is a selection, make sure the brush tool matches
|
||||
*/
|
||||
const screenCoords = [
|
||||
this.mapPointToScreen(currentSelection.brushCoords.northwest),
|
||||
this.mapPointToScreen(currentSelection.brushCoords.southeast),
|
||||
@@ -682,7 +619,6 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
for (let x = 0; x < 2; x += 1) {
|
||||
for (let y = 0; y < 2; y += 1) {
|
||||
delta += Math.abs(
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
screenCoords[x][y] - toolCurrentSelection[x][y]
|
||||
);
|
||||
}
|
||||
@@ -698,20 +634,17 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
lassoToolUpdate(tool: any) {
|
||||
lassoToolUpdate(tool) {
|
||||
/*
|
||||
this is called from componentDidUpdate(), so be very careful using
|
||||
anything from this.state, which may be updated asynchronously.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'currentSelection' does not exist on type... Remove this comment to see the full error message
|
||||
this is called from componentDidUpdate(), so be very careful using
|
||||
anything from this.state, which may be updated asynchronously.
|
||||
*/
|
||||
const { currentSelection } = this.props;
|
||||
if (currentSelection.mode === "within-polygon") {
|
||||
/*
|
||||
if there is a current selection, make sure the lasso tool matches
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const polygon = currentSelection.polygon.map((p: any) =>
|
||||
if there is a current selection, make sure the lasso tool matches
|
||||
*/
|
||||
const polygon = currentSelection.polygon.map((p) =>
|
||||
this.mapPointToScreen(p)
|
||||
);
|
||||
tool.move(polygon);
|
||||
@@ -720,20 +653,17 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectionToolUpdate(tool: any, container: any) {
|
||||
selectionToolUpdate(tool, container) {
|
||||
/*
|
||||
this is called from componentDidUpdate(), so be very careful using
|
||||
anything from this.state, which may be updated asynchronously.
|
||||
*/
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'selectionTool' does not exist on type 'R... Remove this comment to see the full error message
|
||||
this is called from componentDidUpdate(), so be very careful using
|
||||
anything from this.state, which may be updated asynchronously.
|
||||
*/
|
||||
const { selectionTool } = this.props;
|
||||
switch (selectionTool) {
|
||||
case "brush":
|
||||
this.brushToolUpdate(tool, container);
|
||||
break;
|
||||
case "lasso":
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.
|
||||
this.lassoToolUpdate(tool, container);
|
||||
break;
|
||||
default:
|
||||
@@ -742,17 +672,19 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
mapScreenToPoint(pin: any) {
|
||||
mapScreenToPoint(pin) {
|
||||
/*
|
||||
Map an XY coordinates from screen domain to cell/point range,
|
||||
accounting for current pan/zoom camera.
|
||||
*/
|
||||
Map an XY coordinates from screen domain to cell/point range,
|
||||
accounting for current pan/zoom camera.
|
||||
*/
|
||||
|
||||
const { camera, projectionTF, modelInvTF, viewport } = this.state;
|
||||
const cameraInvTF = camera.invView();
|
||||
|
||||
/* screen -> gl */
|
||||
const x = (2 * pin[0]) / viewport.width - 1;
|
||||
const y = 2 * (1 - pin[1] / viewport.height) - 1;
|
||||
|
||||
const xy = vec2.fromValues(x, y);
|
||||
const projectionInvTF = mat3.invert(mat3.create(), projectionTF);
|
||||
vec2.transformMat3(xy, xy, projectionInvTF);
|
||||
@@ -761,17 +693,19 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
return xy;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
mapPointToScreen(xyCell: any) {
|
||||
mapPointToScreen(xyCell) {
|
||||
/*
|
||||
Map an XY coordinate from cell/point domain to screen range. Inverse
|
||||
of mapScreenToPoint()
|
||||
*/
|
||||
Map an XY coordinate from cell/point domain to screen range. Inverse
|
||||
of mapScreenToPoint()
|
||||
*/
|
||||
|
||||
const { camera, projectionTF, modelTF, viewport } = this.state;
|
||||
const cameraTF = camera.view();
|
||||
|
||||
const xy = vec2.transformMat3(vec2.create(), xyCell, modelTF);
|
||||
vec2.transformMat3(xy, xy, cameraTF);
|
||||
vec2.transformMat3(xy, xy, projectionTF);
|
||||
|
||||
return [
|
||||
Math.round(((xy[0] + 1) * viewport.width) / 2),
|
||||
Math.round(-((xy[1] + 1) / 2 - 1) * viewport.height),
|
||||
@@ -799,12 +733,12 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
updateReglAndRender(asyncProps: any, prevAsyncProps: any) {
|
||||
updateReglAndRender(asyncProps, prevAsyncProps) {
|
||||
const { positions, colors, flags, height, width } = asyncProps;
|
||||
this.cachedAsyncProps = asyncProps;
|
||||
const { pointBuffer, colorBuffer, flagBuffer } = this.state;
|
||||
let needToRenderCanvas = false;
|
||||
|
||||
if (height !== prevAsyncProps?.height || width !== prevAsyncProps?.width) {
|
||||
needToRenderCanvas = true;
|
||||
}
|
||||
@@ -823,11 +757,10 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
if (needToRenderCanvas) this.renderCanvas();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
updateColorTable(colors: any, colorDf: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
updateColorTable(colors, colorDf) {
|
||||
const { annoMatrix } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
|
||||
/* update color table state */
|
||||
if (!colors || !colorDf) {
|
||||
return createColorTable(
|
||||
@@ -838,6 +771,7 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
const { colorAccessor, userColors, colorMode } = colors;
|
||||
return createColorTable(
|
||||
colorMode,
|
||||
@@ -848,35 +782,26 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
createColorByQuery(colors: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
createColorByQuery(colors) {
|
||||
const { annoMatrix, genesets } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode, colorAccessor } = colors;
|
||||
|
||||
return createColorQuery(colorMode, colorAccessor, schema, genesets);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
renderPoints(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
regl: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
drawPoints: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colorBuffer: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
pointBuffer: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
flagBuffer: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
camera: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
projectionTF: any
|
||||
regl,
|
||||
drawPoints,
|
||||
colorBuffer,
|
||||
pointBuffer,
|
||||
flagBuffer,
|
||||
camera,
|
||||
projectionTF
|
||||
) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { annoMatrix } = this.props;
|
||||
if (!this.reglCanvas || !annoMatrix) return;
|
||||
|
||||
const { schema } = annoMatrix;
|
||||
const cameraTF = camera.view();
|
||||
const projView = mat3.multiply(mat3.create(), projectionTF, cameraTF);
|
||||
@@ -899,24 +824,18 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
regl._gl.flush();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'graphInteractionMode' does not exist on ... Remove this comment to see the full error message
|
||||
graphInteractionMode,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on ... Remove this comment to see the full error message
|
||||
annoMatrix,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colors' does not exist on ... Remove this comment to see the full error message
|
||||
colors,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'layoutChoice' does not exist on ... Remove this comment to see the full error message
|
||||
layoutChoice,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'pointDilation' does not exist on ... Remove this comment to see the full error message
|
||||
pointDilation,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'crossfilter' does not exist on ... Remove this comment to see the full error message
|
||||
crossfilter,
|
||||
} = this.props;
|
||||
const { modelTF, projectionTF, camera, viewport, regl } = this.state;
|
||||
const cameraTF = camera?.view()?.slice();
|
||||
|
||||
return (
|
||||
<div
|
||||
id="graph-wrapper"
|
||||
@@ -927,7 +846,6 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}}
|
||||
>
|
||||
<GraphOverlayLayer
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element; width: any; height: any... Remove this comment to see the full error message
|
||||
width={viewport.width}
|
||||
height={viewport.height}
|
||||
cameraTF={cameraTF}
|
||||
@@ -987,7 +905,6 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}}
|
||||
>
|
||||
<Async.Pending initial>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-use-before-define --- StillLoading used before defined */}
|
||||
<StillLoading
|
||||
displayName={layoutChoice.current}
|
||||
width={viewport.width}
|
||||
@@ -996,7 +913,6 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => (
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define --- ErrorLoading used before defined
|
||||
<ErrorLoading
|
||||
displayName={layoutChoice.current}
|
||||
error={error}
|
||||
@@ -1019,8 +935,7 @@ class Graph extends React.Component<{}, GraphState> {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const ErrorLoading = ({ displayName, error, width, height }: any) => {
|
||||
const ErrorLoading = ({ displayName, error, width, height }) => {
|
||||
console.log(error); // log to console as this is an unepected error
|
||||
return (
|
||||
<div
|
||||
@@ -1036,30 +951,32 @@ const ErrorLoading = ({ displayName, error, width, height }: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const StillLoading = ({ displayName, width, height }: any) => (
|
||||
const StillLoading = ({ displayName, width, height }) =>
|
||||
/*
|
||||
Render a busy/loading indicator
|
||||
*/
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
fontWeight: 500,
|
||||
top: height / 2,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
position: "fixed",
|
||||
fontWeight: 500,
|
||||
top: height / 2,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Button minimal loading intent="primary" />
|
||||
<span style={{ fontStyle: "italic" }}>Loading {displayName}</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
justifyItems: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Button minimal loading intent="primary" />
|
||||
<span style={{ fontStyle: "italic" }}>Loading {displayName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
;
|
||||
|
||||
export default Graph;
|
||||
@@ -0,0 +1,218 @@
|
||||
import React, { PureComponent } from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import Async from "react-async";
|
||||
|
||||
import { categoryLabelDisplayStringLongLength } from "../../../globals";
|
||||
import calcCentroid from "../../../util/centroid";
|
||||
import { createColorQuery } from "../../../util/stateManager/colorHelpers";
|
||||
|
||||
export default
|
||||
@connect((state) => ({
|
||||
annoMatrix: state.annoMatrix,
|
||||
colors: state.colors,
|
||||
layoutChoice: state.layoutChoice,
|
||||
dilatedValue: state.pointDilation.categoryField,
|
||||
categoricalSelection: state.categoricalSelection,
|
||||
showLabels: state.centroidLabels?.showLabels,
|
||||
genesets: state.genesets.genesets,
|
||||
}))
|
||||
class CentroidLabels extends PureComponent {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
fetchAsyncProps = async (props) => {
|
||||
const {
|
||||
annoMatrix,
|
||||
colors,
|
||||
layoutChoice,
|
||||
categoricalSelection,
|
||||
showLabels,
|
||||
} = props.watchProps;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor } = colors;
|
||||
|
||||
const [layoutDf, colorDf] = await this.fetchData();
|
||||
let labels;
|
||||
if (colorDf) {
|
||||
labels = calcCentroid(
|
||||
schema,
|
||||
colorAccessor,
|
||||
colorDf,
|
||||
layoutChoice,
|
||||
layoutDf
|
||||
);
|
||||
} else {
|
||||
labels = new Map();
|
||||
}
|
||||
|
||||
const { overlaySetShowing } = this.props;
|
||||
overlaySetShowing("centroidLabels", showLabels && labels.size > 0);
|
||||
|
||||
return {
|
||||
labels,
|
||||
colorAccessor,
|
||||
category: categoricalSelection[colorAccessor],
|
||||
};
|
||||
};
|
||||
|
||||
handleMouseEnter = (e, colorAccessor, label) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover start",
|
||||
metadataField: colorAccessor,
|
||||
categoryField: label,
|
||||
});
|
||||
};
|
||||
|
||||
handleMouseOut = (e, colorAccessor, label) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover end",
|
||||
metadataField: colorAccessor,
|
||||
categoryField: label,
|
||||
});
|
||||
};
|
||||
|
||||
colorByQuery() {
|
||||
const { annoMatrix, colors, genesets } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode, colorAccessor } = colors;
|
||||
return createColorQuery(colorMode, colorAccessor, schema, genesets);
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
const { annoMatrix, layoutChoice } = this.props;
|
||||
// fetch all data we need: layout, category
|
||||
const promises = [];
|
||||
// layout
|
||||
promises.push(annoMatrix.fetch("emb", layoutChoice.current));
|
||||
// category to label - we ONLY label on obs, never on X, etc.
|
||||
const query = this.colorByQuery();
|
||||
if (query && query[0] === "obs") {
|
||||
promises.push(annoMatrix.fetch(...query));
|
||||
} else {
|
||||
promises.push(Promise.resolve(null));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
inverseTransform,
|
||||
dilatedValue,
|
||||
categoricalSelection,
|
||||
showLabels,
|
||||
colors,
|
||||
annoMatrix,
|
||||
layoutChoice,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Async
|
||||
watchFn={CentroidLabels.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{
|
||||
annoMatrix,
|
||||
colors,
|
||||
layoutChoice,
|
||||
categoricalSelection,
|
||||
dilatedValue,
|
||||
showLabels,
|
||||
}}
|
||||
>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) => {
|
||||
if (!showLabels) return null;
|
||||
|
||||
const labelSVGS = [];
|
||||
const deselectOpacity = 0.375;
|
||||
const { category, colorAccessor, labels } = asyncProps;
|
||||
|
||||
labels.forEach((coords, label) => {
|
||||
const selected = category.get(label) ?? true;
|
||||
|
||||
// Mirror LSB middle truncation
|
||||
let displayLabel = label;
|
||||
if (displayLabel.length > categoryLabelDisplayStringLongLength) {
|
||||
displayLabel = `${label.slice(
|
||||
0,
|
||||
categoryLabelDisplayStringLongLength / 2
|
||||
)}…${label.slice(-categoryLabelDisplayStringLongLength / 2)}`;
|
||||
}
|
||||
|
||||
labelSVGS.push(
|
||||
// eslint-disable-next-line jsx-a11y/mouse-events-have-key-events -- the mouse actions for centroid labels do not have a screen reader alternative
|
||||
<Label
|
||||
key={label} // eslint-disable-line react/no-array-index-key --- label is not an index, eslint is confused
|
||||
label={label}
|
||||
dilatedValue={dilatedValue}
|
||||
coords={coords}
|
||||
inverseTransform={inverseTransform}
|
||||
opactity={selected ? 1 : deselectOpacity}
|
||||
colorAccessor={colorAccessor}
|
||||
displayLabel={displayLabel}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return <>{labelSVGS}</>;
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Label = ({
|
||||
label,
|
||||
dilatedValue,
|
||||
coords,
|
||||
inverseTransform,
|
||||
opacity,
|
||||
colorAccessor,
|
||||
displayLabel,
|
||||
onMouseEnter,
|
||||
onMouseOut,
|
||||
}) => {
|
||||
/*
|
||||
Render a label at a given coordinate.
|
||||
*/
|
||||
let fontSize = "15px";
|
||||
let fontWeight = null;
|
||||
if (label === dilatedValue) {
|
||||
fontSize = "18px";
|
||||
fontWeight = "800";
|
||||
}
|
||||
|
||||
return (
|
||||
<g
|
||||
key={label}
|
||||
className="centroid-label"
|
||||
transform={`translate(${coords[0]}, ${coords[1]})`}
|
||||
data-testclass="centroid-label"
|
||||
data-testid={`${label}-centroid-label`}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events --- the mouse actions for centroid labels do not have a screen reader alternative*/}
|
||||
<text
|
||||
transform={inverseTransform}
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fill: "black",
|
||||
userSelect: "none",
|
||||
opacity: { opacity },
|
||||
}}
|
||||
onMouseEnter={(e) => onMouseEnter(e, colorAccessor, label)}
|
||||
onMouseOut={(e) => onMouseOut(e, colorAccessor, label)}
|
||||
pointerEvents="visiblePainted"
|
||||
>
|
||||
{displayLabel}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
@@ -1,241 +0,0 @@
|
||||
import React, { PureComponent } from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import Async from "react-async";
|
||||
|
||||
import { categoryLabelDisplayStringLongLength } from "../../../globals";
|
||||
import calcCentroid from "../../../util/centroid";
|
||||
import { createColorQuery } from "../../../util/stateManager/colorHelpers";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
annoMatrix: (state as any).annoMatrix,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
colors: (state as any).colors,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: (state as any).layoutChoice,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dilatedValue: (state as any).pointDilation.categoryField,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
categoricalSelection: (state as any).categoricalSelection,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
showLabels: (state as any).centroidLabels?.showLabels,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
genesets: (state as any).genesets.genesets,
|
||||
}))
|
||||
export default class CentroidLabels extends PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static watchAsync(props: any, prevProps: any) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
fetchAsyncProps = async (props: any) => {
|
||||
const {
|
||||
annoMatrix,
|
||||
colors,
|
||||
layoutChoice,
|
||||
categoricalSelection,
|
||||
showLabels,
|
||||
} = props.watchProps;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorAccessor } = colors;
|
||||
const [layoutDf, colorDf] = await this.fetchData();
|
||||
let labels;
|
||||
if (colorDf) {
|
||||
labels = calcCentroid(
|
||||
schema,
|
||||
colorAccessor,
|
||||
colorDf,
|
||||
layoutChoice,
|
||||
layoutDf
|
||||
);
|
||||
} else {
|
||||
labels = new Map();
|
||||
}
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'overlaySetShowing' does not exist on typ... Remove this comment to see the full error message
|
||||
const { overlaySetShowing } = this.props;
|
||||
overlaySetShowing("centroidLabels", showLabels && labels.size > 0);
|
||||
return {
|
||||
labels,
|
||||
colorAccessor,
|
||||
category: categoricalSelection[colorAccessor],
|
||||
};
|
||||
};
|
||||
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'e' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleMouseEnter = (e: any, colorAccessor: any, label: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover start",
|
||||
metadataField: colorAccessor,
|
||||
categoryField: label,
|
||||
});
|
||||
};
|
||||
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'e' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleMouseOut = (e: any, colorAccessor: any, label: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: "category value mouse hover end",
|
||||
metadataField: colorAccessor,
|
||||
categoryField: label,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
colorByQuery() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { annoMatrix, colors, genesets } = this.props;
|
||||
const { schema } = annoMatrix;
|
||||
const { colorMode, colorAccessor } = colors;
|
||||
return createColorQuery(colorMode, colorAccessor, schema, genesets);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
async fetchData() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
const { annoMatrix, layoutChoice } = this.props;
|
||||
// fetch all data we need: layout, category
|
||||
const promises = [];
|
||||
// layout
|
||||
promises.push(annoMatrix.fetch("emb", layoutChoice.current));
|
||||
// category to label - we ONLY label on obs, never on X, etc.
|
||||
const query = this.colorByQuery();
|
||||
if (query && query[0] === "obs") {
|
||||
promises.push(annoMatrix.fetch(...query));
|
||||
} else {
|
||||
promises.push(Promise.resolve(null));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'inverseTransform' does not exist on type... Remove this comment to see the full error message
|
||||
inverseTransform,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dilatedValue' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
dilatedValue,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'categoricalSelection' does not exist on ... Remove this comment to see the full error message
|
||||
categoricalSelection,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'showLabels' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
showLabels,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'colors' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
colors,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
annoMatrix,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'layoutChoice' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
layoutChoice,
|
||||
} = this.props;
|
||||
return (
|
||||
<Async
|
||||
watchFn={CentroidLabels.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{
|
||||
annoMatrix,
|
||||
colors,
|
||||
layoutChoice,
|
||||
categoricalSelection,
|
||||
dilatedValue,
|
||||
showLabels,
|
||||
}}
|
||||
>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) => {
|
||||
if (!showLabels) return null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const labelSVGS: any = [];
|
||||
const deselectOpacity = 0.375;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'category' does not exist on type 'unknow... Remove this comment to see the full error message
|
||||
const { category, colorAccessor, labels } = asyncProps;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
labels.forEach((coords: any, label: any) => {
|
||||
const selected = category.get(label) ?? true;
|
||||
// Mirror LSB middle truncation
|
||||
let displayLabel = label;
|
||||
if (displayLabel.length > categoryLabelDisplayStringLongLength) {
|
||||
displayLabel = `${label.slice(
|
||||
0,
|
||||
categoryLabelDisplayStringLongLength / 2
|
||||
)}…${label.slice(-categoryLabelDisplayStringLongLength / 2)}`;
|
||||
}
|
||||
labelSVGS.push(
|
||||
// eslint-disable-next-line jsx-a11y/mouse-events-have-key-events -- the mouse actions for centroid labels do not have a screen reader alternative
|
||||
<Label
|
||||
key={label} // eslint-disable-line react/no-array-index-key --- label is not an index, eslint is confused
|
||||
label={label}
|
||||
dilatedValue={dilatedValue}
|
||||
coords={coords}
|
||||
inverseTransform={inverseTransform}
|
||||
opactity={selected ? 1 : deselectOpacity}
|
||||
colorAccessor={colorAccessor}
|
||||
displayLabel={displayLabel}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <>{labelSVGS}</>;
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Label = ({
|
||||
label,
|
||||
dilatedValue,
|
||||
coords,
|
||||
inverseTransform,
|
||||
opacity,
|
||||
colorAccessor,
|
||||
displayLabel,
|
||||
onMouseEnter,
|
||||
onMouseOut, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
}: any) => {
|
||||
/*
|
||||
Render a label at a given coordinate.
|
||||
*/
|
||||
let fontSize = "15px";
|
||||
let fontWeight = null;
|
||||
if (label === dilatedValue) {
|
||||
fontSize = "18px";
|
||||
fontWeight = "800";
|
||||
}
|
||||
|
||||
return (
|
||||
<g
|
||||
key={label}
|
||||
className="centroid-label"
|
||||
transform={`translate(${coords[0]}, ${coords[1]})`}
|
||||
data-testclass="centroid-label"
|
||||
data-testid={`${label}-centroid-label`}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events --- the mouse actions for centroid labels do not have a screen reader alternative*/}
|
||||
<text
|
||||
transform={inverseTransform}
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
fontSize,
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string | null' is not assignable to type 'Fo... Remove this comment to see the full error message
|
||||
fontWeight,
|
||||
fill: "black",
|
||||
userSelect: "none",
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ opacity: any; }' is not assignable to type... Remove this comment to see the full error message
|
||||
opacity: { opacity },
|
||||
}}
|
||||
onMouseEnter={(e) => onMouseEnter(e, colorAccessor, label)}
|
||||
onMouseOut={(e) => onMouseOut(e, colorAccessor, label)}
|
||||
pointerEvents="visiblePainted"
|
||||
>
|
||||
{displayLabel}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
+8
-29
@@ -1,29 +1,22 @@
|
||||
import React, { PureComponent, cloneElement } from "react";
|
||||
|
||||
// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module '../graph.css' or its correspon... Remove this comment to see the full error message
|
||||
import styles from "../graph.css";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export default class GraphOverlayLayer extends PureComponent<{}, State> {
|
||||
export default class GraphOverlayLayer extends PureComponent {
|
||||
/*
|
||||
This component takes its children (assumed in the data coordinate space ([0, 1] range, origin in bottom left corner))
|
||||
and transforms itself multiple times resulting in screen space ([0, screenWidth/Height] range, origin in top left corner)
|
||||
|
||||
Children are assigned in the graph component and must implement onDisplayChange()
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
display: {},
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
matrixToTransformString = (m: any) =>
|
||||
matrixToTransformString = (m) =>
|
||||
/*
|
||||
Translates the gl-matrix mat3 to SVG matrix transform style
|
||||
|
||||
@@ -32,37 +25,24 @@ export default class GraphOverlayLayer extends PureComponent<{}, State> {
|
||||
b d f / [a, b, 0, c, d, 0, e, f, 1] => matrix(a, b, c, d, e, f) / matrix(sx, 0, 0, sy, tx, ty) / matrix(m[0] m[3] m[1] m[4] m[6] m[7])
|
||||
0 0 1
|
||||
*/
|
||||
`matrix(${m[0]} ${m[1]} ${m[3]} ${m[4]} ${m[6]} ${m[7]})`;
|
||||
`matrix(${m[0]} ${m[1]} ${m[3]} ${m[4]} ${m[6]} ${m[7]})`
|
||||
;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
reverseMatrixScaleTransformString = (m: any) =>
|
||||
`matrix(${1 / m[0]} 0 0 ${1 / m[4]} 0 0)`;
|
||||
reverseMatrixScaleTransformString = (m) => `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
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
overlaySetShowing = (overlay: any, displaying: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
this.setState((state: any) => ({
|
||||
...state,
|
||||
display: { ...state.display, [overlay]: displaying },
|
||||
}));
|
||||
overlaySetShowing = (overlay, displaying) => {
|
||||
this.setState((state) => ({ ...state, display: { ...state.display, [overlay]: displaying } }));
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'cameraTF' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
cameraTF,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'modelTF' does not exist on type 'Readonl... Remove this comment to see the full error message
|
||||
modelTF,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'projectionTF' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
projectionTF,
|
||||
children,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleCanvasEvent' does not exist on typ... Remove this comment to see the full error message
|
||||
handleCanvasEvent,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'width' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
width,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'height' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
height,
|
||||
} = this.props;
|
||||
const { display } = this.state;
|
||||
@@ -81,7 +61,6 @@ export default class GraphOverlayLayer extends PureComponent<{}, State> {
|
||||
|
||||
// Copy the children passed with the overlay and add the inverse transform and onDisplayChange props
|
||||
const newChildren = React.Children.map(children, (child) =>
|
||||
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||
cloneElement(child, {
|
||||
inverseTransform,
|
||||
overlaySetShowing: this.overlaySetShowing,
|
||||
+16
-31
@@ -1,28 +1,19 @@
|
||||
import * as d3 from "d3";
|
||||
import { Colors } from "@blueprintjs/core";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
const Lasso = () => {
|
||||
const dispatch = d3.dispatch("start", "end", "cancel");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const lasso = (svg: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
let lassoPolygon: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
let lassoPath: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
let closePath: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
let lassoInProgress: any;
|
||||
const lasso = (svg) => {
|
||||
let lassoPolygon;
|
||||
let lassoPath;
|
||||
let closePath;
|
||||
let lassoInProgress;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const polygonToPath = (polygon: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
`M${polygon.map((d: any) => d.join(",")).join("L")}`;
|
||||
const polygonToPath = (polygon) =>
|
||||
`M${polygon.map((d) => d.join(",")).join("L")}`;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const distance = (pt1: any, pt2: any) =>
|
||||
const distance = (pt1, pt2) =>
|
||||
Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2);
|
||||
|
||||
// distance last point has to be to first point before it auto closes when mouse is released
|
||||
@@ -30,14 +21,12 @@ const Lasso = () => {
|
||||
const lassoPathColor = Colors.BLUE5;
|
||||
|
||||
const handleDragStart = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
lassoPolygon = [(d3 as any).mouse(svg.node())]; // current x y of mouse within element
|
||||
lassoPolygon = [d3.mouse(svg.node())]; // current x y of mouse within element
|
||||
|
||||
if (lassoPath) {
|
||||
// If the existing path is in progress
|
||||
if (lassoInProgress) {
|
||||
// cancel the existing lasso
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define --- handleCancel used before defined
|
||||
handleCancel();
|
||||
// Don't continue with current drag start
|
||||
return;
|
||||
@@ -48,14 +37,12 @@ const Lasso = () => {
|
||||
// We're starting a new drag
|
||||
lassoInProgress = true;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define --- g used before defined
|
||||
lassoPath = g
|
||||
.append("path")
|
||||
.attr("data-testid", "lasso-element")
|
||||
.attr("fill-opacity", 0.1)
|
||||
.attr("stroke-dasharray", "3, 3");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define --- g used before defined
|
||||
closePath = g
|
||||
.append("line")
|
||||
.attr("x2", lassoPolygon[0][0])
|
||||
@@ -66,8 +53,7 @@ const Lasso = () => {
|
||||
};
|
||||
|
||||
const handleDrag = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const point = (d3 as any).mouse(svg.node());
|
||||
const point = d3.mouse(svg.node());
|
||||
lassoPolygon.push(point);
|
||||
lassoPath.attr("d", polygonToPath(lassoPolygon));
|
||||
|
||||
@@ -137,12 +123,12 @@ const Lasso = () => {
|
||||
|
||||
area.call(drag);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(lasso as any).reset = () => {
|
||||
lasso.reset = () => {
|
||||
if (lassoPath) {
|
||||
lassoPath.remove();
|
||||
lassoPath = null;
|
||||
}
|
||||
|
||||
lassoPolygon = null;
|
||||
if (closePath) {
|
||||
closePath.remove();
|
||||
@@ -150,11 +136,10 @@ const Lasso = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(lasso as any).move = (polygon: any) => {
|
||||
lasso.move = (polygon) => {
|
||||
if (polygon !== lassoPolygon || polygon.length !== lassoPolygon.length) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'reset' does not exist on type '(svg: any... Remove this comment to see the full error message
|
||||
lasso.reset();
|
||||
|
||||
lassoPolygon = polygon;
|
||||
lassoPath = g
|
||||
.append("path")
|
||||
@@ -163,13 +148,13 @@ const Lasso = () => {
|
||||
.attr("fill-opacity", 0.1)
|
||||
.attr("stroke", lassoPathColor)
|
||||
.attr("stroke-dasharray", "3, 3");
|
||||
|
||||
lassoPath.attr("d", `${polygonToPath(lassoPolygon)}Z`);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
lasso.on = (type: any, callback: any) => {
|
||||
lasso.on = (type, callback) => {
|
||||
dispatch.on(type, callback);
|
||||
return lasso;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import * as d3 from "d3";
|
||||
import Lasso from "./setupLasso";
|
||||
|
||||
/******************************************
|
||||
*******************************************
|
||||
put svg & brush in DOM
|
||||
*******************************************
|
||||
******************************************/
|
||||
|
||||
export default (
|
||||
selectionToolType,
|
||||
handleStartAction,
|
||||
handleDragAction,
|
||||
handleEndAction,
|
||||
handleCancelAction,
|
||||
viewport
|
||||
) => {
|
||||
const svg = d3.select("#graph-wrapper").select("#lasso-layer");
|
||||
if (svg.empty()) return {};
|
||||
|
||||
if (selectionToolType === "brush") {
|
||||
const brush = d3
|
||||
.brush()
|
||||
.extent([
|
||||
[0, 0],
|
||||
[viewport.width, viewport.height],
|
||||
])
|
||||
.on("start", handleStartAction)
|
||||
.on("brush", handleDragAction)
|
||||
// FYI, brush doesn't generate cancel
|
||||
.on("end", handleEndAction);
|
||||
|
||||
const brushContainer = svg
|
||||
.append("g")
|
||||
.attr("class", "graph_brush")
|
||||
.call(brush);
|
||||
|
||||
return { svg, container: brushContainer, tool: brush };
|
||||
}
|
||||
|
||||
if (selectionToolType === "lasso") {
|
||||
const lasso = Lasso()
|
||||
.on("end", handleEndAction)
|
||||
// FYI, Lasso doesn't generate drag
|
||||
.on("start", handleStartAction)
|
||||
.on("cancel", handleCancelAction);
|
||||
|
||||
const lassoContainer = svg.call(lasso);
|
||||
|
||||
return { svg, container: lassoContainer, tool: lasso };
|
||||
}
|
||||
|
||||
throw new Error("unknown graph selection tool");
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
import * as d3 from "d3";
|
||||
import Lasso from "./setupLasso";
|
||||
|
||||
/******************************************
|
||||
*******************************************
|
||||
put svg & brush in DOM
|
||||
*******************************************
|
||||
******************************************/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export default (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectionToolType: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleStartAction: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleDragAction: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleEndAction: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleCancelAction: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
viewport: any
|
||||
) => {
|
||||
const svg = d3.select("#graph-wrapper").select("#lasso-layer");
|
||||
if (svg.empty()) return {};
|
||||
|
||||
if (selectionToolType === "brush") {
|
||||
const brush = d3
|
||||
.brush()
|
||||
.extent([
|
||||
[0, 0],
|
||||
[viewport.width, viewport.height],
|
||||
])
|
||||
.on("start", handleStartAction)
|
||||
.on("brush", handleDragAction)
|
||||
// FYI, brush doesn't generate cancel
|
||||
.on("end", handleEndAction);
|
||||
|
||||
const brushContainer = svg
|
||||
.append("g")
|
||||
.attr("class", "graph_brush")
|
||||
.call(brush);
|
||||
|
||||
return { svg, container: brushContainer, tool: brush };
|
||||
}
|
||||
|
||||
if (selectionToolType === "lasso") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const lasso = (Lasso() as any)
|
||||
.on("end", handleEndAction)
|
||||
// FYI, Lasso doesn't generate drag
|
||||
.on("start", handleStartAction)
|
||||
.on("cancel", handleCancelAction);
|
||||
|
||||
const lassoContainer = svg.call(lasso);
|
||||
|
||||
return { svg, container: lassoContainer, tool: lasso };
|
||||
}
|
||||
|
||||
throw new Error("unknown graph selection tool");
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import React, { PureComponent } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Drawer } from "@blueprintjs/core";
|
||||
|
||||
import InfoFormat from "./infoFormat";
|
||||
import { selectableCategoryNames } from "../../util/stateManager/controlsHelpers";
|
||||
|
||||
@connect((state) => ({
|
||||
schema: state.annoMatrix.schema,
|
||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||
aboutURL: state.config?.links?.["about-dataset"],
|
||||
isOpen: state.controls.datasetDrawer,
|
||||
dataPortalProps: state.config?.corpora_props,
|
||||
}))
|
||||
class InfoDrawer extends PureComponent {
|
||||
handleClose = () => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch({ type: "toggle dataset drawer" });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
position,
|
||||
aboutURL,
|
||||
datasetTitle,
|
||||
schema,
|
||||
isOpen,
|
||||
dataPortalProps,
|
||||
} = this.props;
|
||||
|
||||
const allCategoryNames = selectableCategoryNames(schema).sort();
|
||||
const singleValueCategories = new Map();
|
||||
|
||||
allCategoryNames.forEach((catName) => {
|
||||
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
|
||||
const colSchema = schema.annotations.obsByName[catName];
|
||||
if (!isUserAnno && colSchema.categories?.length === 1) {
|
||||
singleValueCategories.set(catName, colSchema.categories[0]);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title="Dataset Overview"
|
||||
onClose={this.handleClose}
|
||||
{...{ isOpen, position }}
|
||||
>
|
||||
<InfoFormat
|
||||
{...{
|
||||
datasetTitle,
|
||||
aboutURL,
|
||||
singleValueCategories,
|
||||
dataPortalProps: dataPortalProps ?? {},
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default InfoDrawer;
|
||||
@@ -1,78 +0,0 @@
|
||||
import React, { PureComponent } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Drawer } from "@blueprintjs/core";
|
||||
|
||||
import InfoFormat from "./infoFormat";
|
||||
import { selectableCategoryNames } from "../../util/stateManager/controlsHelpers";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: (state as any).annoMatrix.schema,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
datasetTitle: (state as any).config?.displayNames?.dataset ?? "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
aboutURL: (state as any).config?.links?.["about-dataset"],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
isOpen: (state as any).controls.datasetDrawer,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dataPortalProps: (state as any).config?.corpora_props,
|
||||
}))
|
||||
class InfoDrawer extends PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleClose = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch({ type: "toggle dataset drawer" });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'position' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
position,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'aboutURL' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
aboutURL,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'datasetTitle' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
datasetTitle,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'schema' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
schema,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isOpen' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
isOpen,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dataPortalProps' does not exist on type ... Remove this comment to see the full error message
|
||||
dataPortalProps,
|
||||
} = this.props;
|
||||
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||
const allCategoryNames = selectableCategoryNames(schema).sort();
|
||||
const singleValueCategories = new Map();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
allCategoryNames.forEach((catName: any) => {
|
||||
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
|
||||
const colSchema = schema.annotations.obsByName[catName];
|
||||
if (!isUserAnno && colSchema.categories?.length === 1) {
|
||||
singleValueCategories.set(catName, colSchema.categories[0]);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title="Dataset Overview"
|
||||
onClose={this.handleClose}
|
||||
{...{ isOpen, position }}
|
||||
>
|
||||
<InfoFormat
|
||||
{...{
|
||||
datasetTitle,
|
||||
aboutURL,
|
||||
singleValueCategories,
|
||||
dataPortalProps: dataPortalProps ?? {},
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default InfoDrawer;
|
||||
+18
-37
@@ -1,16 +1,14 @@
|
||||
import { H3, H1, UL, HTMLTable, Classes } from "@blueprintjs/core";
|
||||
import React from "react";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const renderContributors = (contributors: any, affiliations: any) => {
|
||||
const renderContributors = (contributors, affiliations) => {
|
||||
// eslint-disable-next-line no-constant-condition -- Temp removed contributor section to avoid publishing PII
|
||||
if (!contributors || contributors.length === 0 || true) return null;
|
||||
return (
|
||||
<>
|
||||
<H3>Contributors</H3>
|
||||
<p>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{contributors.map((contributor: any) => {
|
||||
{contributors.map((contributor) => {
|
||||
const { email, name, institution } = contributor;
|
||||
|
||||
return (
|
||||
@@ -29,8 +27,7 @@ const renderContributors = (contributors: any, affiliations: any) => {
|
||||
|
||||
// generates a list of unique institutions by order of appearance in contributors
|
||||
const buildAffiliations = (contributors = []) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const affiliations: any = [];
|
||||
const affiliations = [];
|
||||
contributors.forEach((contributor) => {
|
||||
const { institution } = contributor;
|
||||
if (affiliations.indexOf(institution) === -1) {
|
||||
@@ -40,15 +37,13 @@ const buildAffiliations = (contributors = []) => {
|
||||
return affiliations;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const renderAffiliations = (affiliations: any) => {
|
||||
const renderAffiliations = (affiliations) => {
|
||||
if (affiliations.length === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<H3>Affiliations</H3>
|
||||
<UL>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{affiliations.map((item: any, index: any) => (
|
||||
{affiliations.map((item, index) => (
|
||||
<div key={item}>
|
||||
<sup>{index + 1}</sup>
|
||||
{" "}
|
||||
@@ -60,8 +55,7 @@ const renderAffiliations = (affiliations: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const renderDOILink = (type: any, doi: any) => {
|
||||
const renderDOILink = (type, doi) => {
|
||||
if (!doi) return null;
|
||||
return (
|
||||
<>
|
||||
@@ -77,12 +71,7 @@ const renderDOILink = (type: any, doi: any) => {
|
||||
|
||||
const ONTOLOGY_KEY = "ontology_term_id";
|
||||
// Render list of metadata attributes found in categorical field
|
||||
const renderDatasetMetadata = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
singleValueCategories: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
corporaMetadata: any
|
||||
) => {
|
||||
const renderDatasetMetadata = (singleValueCategories, corporaMetadata) => {
|
||||
if (singleValueCategories.size === 0) return null;
|
||||
return (
|
||||
<>
|
||||
@@ -101,34 +90,29 @@ const renderDatasetMetadata = (
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(corporaMetadata).map(([key, value]) => (
|
||||
<tr {...{ key }}>
|
||||
<td>{`${key}:`}</td>
|
||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'ReactNod... Remove this comment to see the full error message */}
|
||||
<td>{value}</td>
|
||||
<td />
|
||||
</tr>
|
||||
))}
|
||||
<tr {...{ key }}>
|
||||
<td>{`${key}:`}</td>
|
||||
<td>{value}</td>
|
||||
<td />
|
||||
</tr>
|
||||
))}
|
||||
{Array.from(singleValueCategories).reduce((elems, pair) => {
|
||||
// @ts-expect-error ts-migrate(2488) FIXME: Type 'unknown' must have a '[Symbol.iterator]()' m... Remove this comment to see the full error message
|
||||
const [category, value] = pair;
|
||||
// If the value is empty skip it
|
||||
if (!value) return elems;
|
||||
|
||||
// If this category is a ontology term, let's add its value to the previous node
|
||||
if (String(category).includes(ONTOLOGY_KEY)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const prevElem = (elems as any).pop();
|
||||
const prevElem = elems.pop();
|
||||
const newChildren = [...prevElem.props.children];
|
||||
newChildren.splice(2, 1, [<td key="ontology">{value}</td>]);
|
||||
// Props aren't extensible so we must clone and alter the component to append the new child
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(elems as any).push(
|
||||
elems.push(
|
||||
React.cloneElement(prevElem, prevElem.props, newChildren)
|
||||
);
|
||||
} else {
|
||||
// Create the list item
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(elems as any).push(
|
||||
elems.push(
|
||||
<tr key={category}>
|
||||
<td>{`${category}:`}</td>
|
||||
<td>{value}</td>
|
||||
@@ -146,16 +130,14 @@ const renderDatasetMetadata = (
|
||||
|
||||
// Renders any links found in the config where link_type is not "SUMMARY"
|
||||
// If there are no links in the config, render the aboutURL
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const renderLinks = (projectLinks: any, aboutURL: any) => {
|
||||
const renderLinks = (projectLinks, aboutURL) => {
|
||||
if (!projectLinks && !aboutURL) return null;
|
||||
if (projectLinks)
|
||||
return (
|
||||
<>
|
||||
<H3>Project Links</H3>
|
||||
<UL>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
|
||||
{projectLinks.map((link: any) => {
|
||||
{projectLinks.map((link) => {
|
||||
if (link.link_type === "SUMMARY") return null;
|
||||
return (
|
||||
<li key={link.link_name}>
|
||||
@@ -182,7 +164,6 @@ const renderLinks = (projectLinks: any, aboutURL: any) => {
|
||||
};
|
||||
|
||||
const InfoFormat = React.memo(
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'datasetTitle' does not exist on type '{ ... Remove this comment to see the full error message
|
||||
({ datasetTitle, singleValueCategories, aboutURL, dataPortalProps = {} }) => {
|
||||
if (
|
||||
["1.0.0", "1.1.0"].indexOf(
|
||||
@@ -3,11 +3,7 @@ import { InputGroup, MenuItem, Keys } from "@blueprintjs/core";
|
||||
import { Suggest } from "@blueprintjs/select";
|
||||
import fuzzysort from "fuzzysort";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
type State = any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
export default class LabelInput extends React.PureComponent {
|
||||
/*
|
||||
Input widget for text labels, which acts like an InputGroup, but will also
|
||||
accept a suggestion list (of labels), with sublime-like suggest search.
|
||||
@@ -30,11 +26,9 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
/* maxinum number of suggestions */
|
||||
static QueryResultLimit = 100;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(props: {}) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'label' does not exist on type '{}'.
|
||||
const { label } = props;
|
||||
const query = label || "";
|
||||
const queryResults = this.filterLabels(query);
|
||||
@@ -44,8 +38,7 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleQueryChange = (query: any, event: any) => {
|
||||
handleQueryChange = (query, event) => {
|
||||
// https://github.com/palantir/blueprint/issues/2983
|
||||
if (!event) return;
|
||||
|
||||
@@ -55,23 +48,19 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
queryResults,
|
||||
});
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onChange' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { onChange } = this.props;
|
||||
if (onChange) onChange(query, event);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleItemSelect = (item: any, event: any) => {
|
||||
handleItemSelect = (item, event) => {
|
||||
/* only report the select if not already reported via onChange() */
|
||||
const { target } = item;
|
||||
const { query } = this.state;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onSelect' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { onSelect } = this.props;
|
||||
if (target !== query && onSelect) onSelect(target, event);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleKeyDown = (e: any) => {
|
||||
handleKeyDown = (e) => {
|
||||
/*
|
||||
prevent these events from propagating to containing form/dialog
|
||||
and causing further side effects (eg, closing dialog, submitting
|
||||
@@ -86,22 +75,13 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
handleChange = (e: any) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onChange' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
handleChange = (e) => {
|
||||
const { onChange } = this.props;
|
||||
if (onChange) onChange(e.target.value);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
renderLabelSuggestion = (
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
queryResult: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
{ handleClick, modifiers }: any
|
||||
) => {
|
||||
renderLabelSuggestion = (queryResult, { handleClick, modifiers }) => {
|
||||
if (queryResult.newLabel) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'newLabelMessage' does not exist on type ... Remove this comment to see the full error message
|
||||
const { newLabelMessage } = this.props;
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -126,23 +106,18 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
filterLabels(query: any) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'labelSuggestions' does not exist on type... Remove this comment to see the full error message
|
||||
filterLabels(query) {
|
||||
const { labelSuggestions } = this.props;
|
||||
if (!labelSuggestions) return [];
|
||||
|
||||
/* empty query is wildcard */
|
||||
if (query === "") {
|
||||
return (
|
||||
labelSuggestions
|
||||
.slice(0, LabelInput.QueryResultLimit)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
.map((l: any) => ({
|
||||
target: l,
|
||||
score: -10000,
|
||||
}))
|
||||
);
|
||||
return labelSuggestions
|
||||
.slice(0, LabelInput.QueryResultLimit)
|
||||
.map((l) => ({
|
||||
target: l,
|
||||
score: -10000,
|
||||
}));
|
||||
}
|
||||
|
||||
/* else, do a fuzzy query */
|
||||
@@ -153,16 +128,13 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
let queryResults = fuzzysort.go(query, labelSuggestions, options);
|
||||
/* exact match will always be first in list */
|
||||
if (query !== "" && queryResults[0]?.target !== query)
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ target: any; newLabel: true; }' is not ass... Remove this comment to see the full error message
|
||||
queryResults = [{ target: query, newLabel: true }, ...queryResults];
|
||||
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const { props } = this;
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'labelSuggestions' does not exist on type... Remove this comment to see the full error message
|
||||
const { labelSuggestions, label, autoFocus = true } = props;
|
||||
const suggestEnabled = !!labelSuggestions && labelSuggestions.length > 0;
|
||||
|
||||
@@ -170,8 +142,7 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
return (
|
||||
<InputGroup
|
||||
autoFocus={autoFocus}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
{...(props as any).inputProps} // eslint-disable-line react/jsx-props-no-spreading --- Allows for modularity
|
||||
{...props.inputProps} // eslint-disable-line react/jsx-props-no-spreading --- Allows for modularity
|
||||
value={label}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
@@ -180,12 +151,10 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
|
||||
const popoverProps = {
|
||||
minimal: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
...(props as any).popoverProps,
|
||||
...props.popoverProps,
|
||||
};
|
||||
const inputProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
...(props as any).inputProps,
|
||||
...props.inputProps,
|
||||
autoFocus: false,
|
||||
};
|
||||
const { queryResults } = this.state;
|
||||
@@ -201,7 +170,6 @@ export default class LabelInput extends React.PureComponent<{}, State> {
|
||||
onQueryChange={this.handleQueryChange}
|
||||
popoverProps={popoverProps}
|
||||
inputProps={inputProps}
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ fill: true; inputValueRenderer: (i: any) =... Remove this comment to see the full error message
|
||||
onKeyDown={this.handleKeyDown}
|
||||
/>
|
||||
</>
|
||||
+2
-7
@@ -6,17 +6,12 @@ import DynamicScatterplot from "../scatterplot/scatterplot";
|
||||
import TopLeftLogoAndTitle from "./topLeftLogoAndTitle";
|
||||
import Continuous from "../continuous/continuous";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
scatterplotXXaccessor: (state as any).controls.scatterplotXXaccessor,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
scatterplotYYaccessor: (state as any).controls.scatterplotYYaccessor,
|
||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||
}))
|
||||
class LeftSideBar extends React.Component {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'scatterplotXXaccessor' does not exist on... Remove this comment to see the full error message
|
||||
const { scatterplotXXaccessor, scatterplotYYaccessor } = this.props;
|
||||
return (
|
||||
<div
|
||||
-1
@@ -3,7 +3,6 @@ import { Button, Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
||||
import { IconNames } from "@blueprintjs/icons";
|
||||
|
||||
const InformationMenu = React.memo((props) => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'libraryVersions' does not exist on type ... Remove this comment to see the full error message
|
||||
const { libraryVersions, tosURL, privacyURL } = props;
|
||||
return (
|
||||
<Popover
|
||||
@@ -0,0 +1,116 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
import Logo from "../framework/logo";
|
||||
import Truncate from "../util/truncate";
|
||||
import InfoDrawer from "../infoDrawer/infoDrawer";
|
||||
import InformationMenu from "./infoMenu";
|
||||
|
||||
const DATASET_TITLE_FONT_SIZE = 14;
|
||||
|
||||
@connect((state) => {
|
||||
const { corpora_props: corporaProps } = state.config;
|
||||
const correctVersion =
|
||||
["1.0.0", "1.1.0"].indexOf(corporaProps?.version?.corpora_schema_version) >
|
||||
-1;
|
||||
return {
|
||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||
libraryVersions: state.config?.library_versions,
|
||||
aboutLink: state.config?.links?.["about-dataset"],
|
||||
tosURL: state.config?.parameters?.about_legal_tos,
|
||||
privacyURL: state.config?.parameters?.about_legal_privacy,
|
||||
title: correctVersion ? corporaProps?.title : undefined,
|
||||
};
|
||||
})
|
||||
class LeftSideBar extends React.Component {
|
||||
handleClick = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({ type: "toggle dataset drawer" });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
datasetTitle,
|
||||
libraryVersions,
|
||||
aboutLink,
|
||||
privacyURL,
|
||||
tosURL,
|
||||
dispatch,
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 8,
|
||||
paddingTop: 8,
|
||||
width: globals.leftSidebarWidth,
|
||||
zIndex: 1,
|
||||
borderBottom: `1px solid ${globals.lighterGrey}`,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Logo size={28} />
|
||||
<span
|
||||
style={{
|
||||
fontSize: 24,
|
||||
position: "relative",
|
||||
top: -6,
|
||||
fontWeight: "bold",
|
||||
marginLeft: 5,
|
||||
color: globals.logoColor,
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
cell
|
||||
<span
|
||||
style={{
|
||||
position: "relative",
|
||||
top: 1,
|
||||
fontWeight: 300,
|
||||
fontSize: 24,
|
||||
}}
|
||||
>
|
||||
×
|
||||
</span>
|
||||
gene
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginRight: 5, height: "100%" }}>
|
||||
<Button
|
||||
minimal
|
||||
style={{
|
||||
fontSize: DATASET_TITLE_FONT_SIZE,
|
||||
position: "relative",
|
||||
top: -1,
|
||||
}}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<Truncate>
|
||||
<span style={{ maxWidth: 155 }} data-testid="header">
|
||||
{title ?? datasetTitle}
|
||||
</span>
|
||||
</Truncate>
|
||||
</Button>
|
||||
<InfoDrawer />
|
||||
<InformationMenu
|
||||
{...{
|
||||
libraryVersions,
|
||||
aboutLink,
|
||||
tosURL,
|
||||
privacyURL,
|
||||
dispatch,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LeftSideBar;
|
||||
@@ -1,133 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Button } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
import Logo from "../framework/logo";
|
||||
import Truncate from "../util/truncate";
|
||||
import InfoDrawer from "../infoDrawer/infoDrawer";
|
||||
import InformationMenu from "./infoMenu";
|
||||
|
||||
const DATASET_TITLE_FONT_SIZE = 14;
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const { corpora_props: corporaProps } = (state as any).config;
|
||||
const correctVersion =
|
||||
["1.0.0", "1.1.0"].indexOf(corporaProps?.version?.corpora_schema_version) >
|
||||
-1;
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
datasetTitle: (state as any).config?.displayNames?.dataset ?? "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
libraryVersions: (state as any).config?.library_versions,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
aboutLink: (state as any).config?.links?.["about-dataset"],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
tosURL: (state as any).config?.parameters?.about_legal_tos,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
privacyURL: (state as any).config?.parameters?.about_legal_privacy,
|
||||
title: correctVersion ? corporaProps?.title : undefined,
|
||||
};
|
||||
})
|
||||
class LeftSideBar extends React.Component {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
handleClick = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch } = this.props;
|
||||
dispatch({ type: "toggle dataset drawer" });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'datasetTitle' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
datasetTitle,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'libraryVersions' does not exist on type ... Remove this comment to see the full error message
|
||||
libraryVersions,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'aboutLink' does not exist on type 'Reado... Remove this comment to see the full error message
|
||||
aboutLink,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'privacyURL' does not exist on type 'Read... Remove this comment to see the full error message
|
||||
privacyURL,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'tosURL' does not exist on type 'Readonly... Remove this comment to see the full error message
|
||||
tosURL,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
dispatch,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type 'Readonly<... Remove this comment to see the full error message
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 8,
|
||||
paddingTop: 8,
|
||||
width: globals.leftSidebarWidth,
|
||||
zIndex: 1,
|
||||
borderBottom: `1px solid ${globals.lighterGrey}`,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Logo size={28} />
|
||||
<span
|
||||
style={{
|
||||
fontSize: 24,
|
||||
position: "relative",
|
||||
top: -6,
|
||||
fontWeight: "bold",
|
||||
marginLeft: 5,
|
||||
color: globals.logoColor,
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
cell
|
||||
<span
|
||||
style={{
|
||||
position: "relative",
|
||||
top: 1,
|
||||
fontWeight: 300,
|
||||
fontSize: 24,
|
||||
}}
|
||||
>
|
||||
×
|
||||
</span>
|
||||
gene
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginRight: 5, height: "100%" }}>
|
||||
<Button
|
||||
minimal
|
||||
style={{
|
||||
fontSize: DATASET_TITLE_FONT_SIZE,
|
||||
position: "relative",
|
||||
top: -1,
|
||||
}}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<Truncate>
|
||||
<span style={{ maxWidth: 155 }} data-testid="header">
|
||||
{title ?? datasetTitle}
|
||||
</span>
|
||||
</Truncate>
|
||||
</Button>
|
||||
<InfoDrawer />
|
||||
<InformationMenu
|
||||
{...{
|
||||
libraryVersions,
|
||||
aboutLink,
|
||||
tosURL,
|
||||
privacyURL,
|
||||
dispatch,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LeftSideBar;
|
||||
+2
-8
@@ -17,7 +17,6 @@ import { IconNames } from "@blueprintjs/icons";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
|
||||
// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module './menubar.css' or its correspo... Remove this comment to see the full error message
|
||||
import styles from "./menubar.css";
|
||||
|
||||
import { storageGet, storageSet, KEYS } from "../util/localStorage";
|
||||
@@ -32,13 +31,11 @@ const LOGIN_PROMPT_OFF = "off";
|
||||
const Auth = React.memo((props) => {
|
||||
const [isPromptOpen, setIsPromptOpen] = useState(shouldShowPrompt());
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'auth' does not exist on type '{ children... Remove this comment to see the full error message
|
||||
const { auth, userInfo } = props;
|
||||
|
||||
const isAuthenticated = userInfo && userInfo.is_authenticated;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(window as any).userInfo = userInfo;
|
||||
window.userInfo = userInfo;
|
||||
|
||||
const randomInt = Math.random() * 15;
|
||||
const sexIndex = Math.floor(randomInt / 5);
|
||||
@@ -78,7 +75,6 @@ const Auth = React.memo((props) => {
|
||||
>
|
||||
{/* eslint-disable-next-line no-constant-condition -- disable profile picture until CSP is tweaked */}
|
||||
{userInfo?.picture && false ? (
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ alt: string; size: string; src: any; }' is... Remove this comment to see the full error message
|
||||
<img alt="profile" size="21px" src={userInfo?.picture} />
|
||||
) : (
|
||||
<span style={{ fontSize: "18px" }}>{scientist}</span>
|
||||
@@ -127,13 +123,11 @@ const Auth = React.memo((props) => {
|
||||
function shouldShowPrompt() {
|
||||
if (storageGet(KEYS.LOGIN_PROMPT) === LOGIN_PROMPT_OFF) return false;
|
||||
|
||||
// @ts-expect-error ts-migrate(2774) FIXME: This condition will always return true since the f... Remove this comment to see the full error message
|
||||
return shouldShowAuth && !isAuthenticated;
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function PromptContent({ setIsPromptOpen }: any) {
|
||||
function PromptContent({ setIsPromptOpen }) {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
|
||||
function handleOKClick() {
|
||||
+2
-7
@@ -4,22 +4,17 @@ import { connect } from "react-redux";
|
||||
import { tooltipHoverOpenDelay } from "../../globals";
|
||||
import actions from "../../actions";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
differential: (state as any).differential,
|
||||
differential: state.differential,
|
||||
}))
|
||||
class CellSetButton extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
set() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, eitherCellSetOneOrTwo } = this.props;
|
||||
|
||||
dispatch(actions.setCellSetFromSelection(eitherCellSetOneOrTwo));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'differential' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
const { differential, eitherCellSetOneOrTwo } = this.props;
|
||||
const cellListName = `celllist${eitherCellSetOneOrTwo}`;
|
||||
const cellsSelected = differential[cellListName]
|
||||
@@ -12,30 +12,19 @@ import {
|
||||
import { IconNames } from "@blueprintjs/icons";
|
||||
|
||||
import { tooltipHoverOpenDelay } from "../../globals";
|
||||
// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module './menubar.css' or its correspo... Remove this comment to see the full error message
|
||||
import styles from "./menubar.css";
|
||||
|
||||
const Clip = React.memo((props) => {
|
||||
const {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'pendingClipPercentiles' does not exist o... Remove this comment to see the full error message
|
||||
pendingClipPercentiles,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'clipPercentileMin' does not exist on typ... Remove this comment to see the full error message
|
||||
clipPercentileMin,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'clipPercentileMax' does not exist on typ... Remove this comment to see the full error message
|
||||
clipPercentileMax,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleClipOpening' does not exist on typ... Remove this comment to see the full error message
|
||||
handleClipOpening,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleClipClosing' does not exist on typ... Remove this comment to see the full error message
|
||||
handleClipClosing,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleClipCommit' does not exist on type... Remove this comment to see the full error message
|
||||
handleClipCommit,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isClipDisabled' does not exist on type '... Remove this comment to see the full error message
|
||||
isClipDisabled,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleClipOnKeyPress' does not exist on ... Remove this comment to see the full error message
|
||||
handleClipOnKeyPress,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleClipPercentileMaxValueChange' does... Remove this comment to see the full error message
|
||||
handleClipPercentileMaxValueChange,
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleClipPercentileMinValueChange' does... Remove this comment to see the full error message
|
||||
handleClipPercentileMinValueChange,
|
||||
} = props;
|
||||
|
||||
@@ -45,8 +34,7 @@ const Clip = React.memo((props) => {
|
||||
pendingClipPercentiles?.clipPercentileMax ?? clipPercentileMax;
|
||||
const intent =
|
||||
clipPercentileMin > 0 || clipPercentileMax < 100
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(Intent as any).INTENT_WARNING
|
||||
? Intent.INTENT_WARNING
|
||||
: Intent.NONE;
|
||||
|
||||
return (
|
||||
+8
-15
@@ -2,25 +2,17 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { ButtonGroup, AnchorButton, Tooltip } from "@blueprintjs/core";
|
||||
import * as globals from "../../globals";
|
||||
// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module './menubar.css' or its correspo... Remove this comment to see the full error message
|
||||
import styles from "./menubar.css";
|
||||
import actions from "../../actions";
|
||||
import CellSetButton from "./cellSetButtons";
|
||||
|
||||
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
|
||||
@connect((state) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
differential: (state as any).differential,
|
||||
diffexpMayBeSlow:
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(state as any).config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
diffexpCellcountMax: (state as any).config?.limits?.diffexp_cellcount_max,
|
||||
differential: state.differential,
|
||||
diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
||||
diffexpCellcountMax: state.config?.limits?.diffexp_cellcount_max,
|
||||
}))
|
||||
class DiffexpButtons extends React.PureComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
computeDiffExp = () => {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
|
||||
const { dispatch, differential } = this.props;
|
||||
if (differential.celllist1 && differential.celllist2) {
|
||||
dispatch(
|
||||
@@ -32,32 +24,33 @@ class DiffexpButtons extends React.PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
render() {
|
||||
/* diffexp-related buttons may be disabled */
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'differential' does not exist on type 'Re... Remove this comment to see the full error message
|
||||
const { differential, diffexpMayBeSlow, diffexpCellcountMax } = this.props;
|
||||
|
||||
const haveBothCellSets =
|
||||
!!differential.celllist1 && !!differential.celllist2;
|
||||
|
||||
const haveEitherCellSet =
|
||||
!!differential.celllist1 || !!differential.celllist2;
|
||||
|
||||
const slowMsg = diffexpMayBeSlow
|
||||
? " (CAUTION: large dataset - may take longer or fail)"
|
||||
: "";
|
||||
const tipMessage = `See top 10 differentially expressed genes${slowMsg}`;
|
||||
const tipMessageWarn = `The total number of cells for differential expression computation
|
||||
may not exceed ${diffexpCellcountMax}. Try reselecting new cell sets.`;
|
||||
|
||||
const warnMaxSizeExceeded =
|
||||
haveEitherCellSet &&
|
||||
!!diffexpCellcountMax &&
|
||||
(differential.celllist1?.length ?? 0) +
|
||||
(differential.celllist2?.length ?? 0) >
|
||||
diffexpCellcountMax;
|
||||
|
||||
return (
|
||||
<ButtonGroup className={styles.menubarButton}>
|
||||
{/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}
|
||||
<CellSetButton eitherCellSetOneOrTwo={1} />
|
||||
{/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}
|
||||
<CellSetButton eitherCellSetOneOrTwo={2} />
|
||||
<Tooltip
|
||||
content={warnMaxSizeExceeded ? tipMessageWarn : tipMessage}
|
||||
@@ -0,0 +1,327 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { ButtonGroup, AnchorButton, Tooltip } from "@blueprintjs/core";
|
||||
|
||||
import * as globals from "../../globals";
|
||||
import styles from "./menubar.css";
|
||||
import actions from "../../actions";
|
||||
import Clip from "./clip";
|
||||
|
||||
import AuthButtons from "./authButtons";
|
||||
import Subset from "./subset";
|
||||
import UndoRedoReset from "./undoRedo";
|
||||
import DiffexpButtons from "./diffexpButtons";
|
||||
import { getEmbSubsetView } from "../../util/stateManager/viewStackHelpers";
|
||||
|
||||
@connect((state) => {
|
||||
const { annoMatrix } = state;
|
||||
const crossfilter = state.obsCrossfilter;
|
||||
const selectedCount = crossfilter.countSelected();
|
||||
|
||||
const subsetPossible =
|
||||
selectedCount !== 0 && selectedCount !== crossfilter.size(); // ie, not all and not none are selected
|
||||
const embSubsetView = getEmbSubsetView(annoMatrix);
|
||||
const subsetResetPossible = !embSubsetView
|
||||
? annoMatrix.nObs !== annoMatrix.schema.dataframe.nObs
|
||||
: annoMatrix.nObs !== embSubsetView.nObs;
|
||||
|
||||
return {
|
||||
subsetPossible,
|
||||
subsetResetPossible,
|
||||
graphInteractionMode: state.controls.graphInteractionMode,
|
||||
clipPercentileMin: Math.round(100 * (annoMatrix?.clipRange?.[0] ?? 0)),
|
||||
clipPercentileMax: Math.round(100 * (annoMatrix?.clipRange?.[1] ?? 1)),
|
||||
userDefinedGenes: state.controls.userDefinedGenes,
|
||||
colorAccessor: state.colors.colorAccessor,
|
||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
||||
libraryVersions: state.config?.library_versions,
|
||||
auth: state.config?.authentication,
|
||||
userInfo: state.userInfo,
|
||||
undoDisabled: state["@@undoable/past"].length === 0,
|
||||
redoDisabled: state["@@undoable/future"].length === 0,
|
||||
aboutLink: state.config?.links?.["about-dataset"],
|
||||
disableDiffexp: state.config?.parameters?.["disable-diffexp"] ?? false,
|
||||
diffexpMayBeSlow:
|
||||
state.config?.parameters?.["diffexp-may-be-slow"] ?? false,
|
||||
showCentroidLabels: state.centroidLabels.showLabels,
|
||||
tosURL: state.config?.parameters?.about_legal_tos,
|
||||
privacyURL: state.config?.parameters?.about_legal_privacy,
|
||||
categoricalSelection: state.categoricalSelection,
|
||||
};
|
||||
})
|
||||
class MenuBar extends React.PureComponent {
|
||||
static isValidDigitKeyEvent(e) {
|
||||
/*
|
||||
Return true if this event is necessary to enter a percent number input.
|
||||
Return false if not.
|
||||
|
||||
Returns true for events with keys: backspace, control, alt, meta, [0-9],
|
||||
or events that don't have a key.
|
||||
*/
|
||||
if (e.key === null) return true;
|
||||
if (e.ctrlKey || e.altKey || e.metaKey) return true;
|
||||
|
||||
// concept borrowed from blueprint's numericInputUtils:
|
||||
// keys that print a single character when pressed have a `key` name of
|
||||
// length 1. every other key has a longer `key` name (e.g. "Backspace",
|
||||
// "ArrowUp", "Shift"). since none of those keys can print a character
|
||||
// to the field--and since they may have important native behaviors
|
||||
// beyond printing a character--we don't want to disable their effects.
|
||||
const isSingleCharKey = e.key.length === 1;
|
||||
if (!isSingleCharKey) return true;
|
||||
|
||||
const key = e.key.charCodeAt(0) - 48; /* "0" */
|
||||
return key >= 0 && key <= 9;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
pendingClipPercentiles: null,
|
||||
};
|
||||
}
|
||||
|
||||
isClipDisabled = () => {
|
||||
/*
|
||||
return true if clip button should be disabled.
|
||||
*/
|
||||
const { pendingClipPercentiles } = this.state;
|
||||
const clipPercentileMin = pendingClipPercentiles?.clipPercentileMin;
|
||||
const clipPercentileMax = pendingClipPercentiles?.clipPercentileMax;
|
||||
const {
|
||||
clipPercentileMin: currentClipMin,
|
||||
clipPercentileMax: currentClipMax,
|
||||
} = this.props;
|
||||
|
||||
// if you change this test, be careful with logic around
|
||||
// comparisons between undefined / NaN handling.
|
||||
const isDisabled =
|
||||
!(clipPercentileMin < clipPercentileMax) ||
|
||||
(clipPercentileMin === currentClipMin &&
|
||||
clipPercentileMax === currentClipMax);
|
||||
|
||||
return isDisabled;
|
||||
};
|
||||
|
||||
handleClipOnKeyPress = (e) => {
|
||||
/*
|
||||
allow only numbers, plus other critical keys which
|
||||
may be required to make a number
|
||||
*/
|
||||
if (!MenuBar.isValidDigitKeyEvent(e)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
handleClipPercentileMinValueChange = (v) => {
|
||||
/*
|
||||
Ignore anything that isn't a legit number
|
||||
*/
|
||||
if (!Number.isFinite(v)) return;
|
||||
|
||||
const { pendingClipPercentiles } = this.state;
|
||||
const clipPercentileMax = pendingClipPercentiles?.clipPercentileMax;
|
||||
|
||||
/*
|
||||
clamp to [0, currentClipPercentileMax]
|
||||
*/
|
||||
if (v <= 0) v = 0;
|
||||
if (v > 100) v = 100;
|
||||
const clipPercentileMin = Math.round(v); // paranoia
|
||||
this.setState({
|
||||
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
|
||||
});
|
||||
};
|
||||
|
||||
handleClipPercentileMaxValueChange = (v) => {
|
||||
/*
|
||||
Ignore anything that isn't a legit number
|
||||
*/
|
||||
if (!Number.isFinite(v)) return;
|
||||
|
||||
const { pendingClipPercentiles } = this.state;
|
||||
const clipPercentileMin = pendingClipPercentiles?.clipPercentileMin;
|
||||
|
||||
/*
|
||||
clamp to [0, 100]
|
||||
*/
|
||||
if (v < 0) v = 0;
|
||||
if (v > 100) v = 100;
|
||||
const clipPercentileMax = Math.round(v); // paranoia
|
||||
|
||||
this.setState({
|
||||
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
|
||||
});
|
||||
};
|
||||
|
||||
handleClipCommit = () => {
|
||||
const { dispatch } = this.props;
|
||||
const { pendingClipPercentiles } = this.state;
|
||||
const { clipPercentileMin, clipPercentileMax } = pendingClipPercentiles;
|
||||
const min = clipPercentileMin / 100;
|
||||
const max = clipPercentileMax / 100;
|
||||
dispatch(actions.clipAction(min, max));
|
||||
};
|
||||
|
||||
handleClipOpening = () => {
|
||||
const { clipPercentileMin, clipPercentileMax } = this.props;
|
||||
this.setState({
|
||||
pendingClipPercentiles: { clipPercentileMin, clipPercentileMax },
|
||||
});
|
||||
};
|
||||
|
||||
handleClipClosing = () => {
|
||||
this.setState({ pendingClipPercentiles: null });
|
||||
};
|
||||
|
||||
handleCentroidChange = () => {
|
||||
const { dispatch, showCentroidLabels } = this.props;
|
||||
|
||||
dispatch({
|
||||
type: "show centroid labels for category",
|
||||
showLabels: !showCentroidLabels,
|
||||
});
|
||||
};
|
||||
|
||||
handleSubset = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(actions.subsetAction());
|
||||
};
|
||||
|
||||
handleSubsetReset = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(actions.resetSubsetAction());
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatch,
|
||||
disableDiffexp,
|
||||
undoDisabled,
|
||||
redoDisabled,
|
||||
selectionTool,
|
||||
clipPercentileMin,
|
||||
clipPercentileMax,
|
||||
graphInteractionMode,
|
||||
showCentroidLabels,
|
||||
categoricalSelection,
|
||||
colorAccessor,
|
||||
subsetPossible,
|
||||
subsetResetPossible,
|
||||
userInfo,
|
||||
auth,
|
||||
} = this.props;
|
||||
const { pendingClipPercentiles } = this.state;
|
||||
|
||||
const isColoredByCategorical = !!categoricalSelection?.[colorAccessor];
|
||||
|
||||
// constants used to create selection tool button
|
||||
const [selectionTooltip, selectionButtonIcon] =
|
||||
selectionTool === "brush"
|
||||
? ["Brush selection", "Lasso selection"]
|
||||
: ["select", "polygon-filter"];
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 0,
|
||||
display: "flex",
|
||||
flexDirection: "row-reverse",
|
||||
alignItems: "flex-start",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "flex-start",
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
<AuthButtons {...{ auth, userInfo }} />
|
||||
<UndoRedoReset
|
||||
dispatch={dispatch}
|
||||
undoDisabled={undoDisabled}
|
||||
redoDisabled={redoDisabled}
|
||||
/>
|
||||
<Clip
|
||||
pendingClipPercentiles={pendingClipPercentiles}
|
||||
clipPercentileMin={clipPercentileMin}
|
||||
clipPercentileMax={clipPercentileMax}
|
||||
handleClipOpening={this.handleClipOpening}
|
||||
handleClipClosing={this.handleClipClosing}
|
||||
handleClipCommit={this.handleClipCommit}
|
||||
isClipDisabled={this.isClipDisabled}
|
||||
handleClipOnKeyPress={this.handleClipOnKeyPress}
|
||||
handleClipPercentileMaxValueChange={
|
||||
this.handleClipPercentileMaxValueChange
|
||||
}
|
||||
handleClipPercentileMinValueChange={
|
||||
this.handleClipPercentileMinValueChange
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
content="When a category is colored by, show labels on the graph"
|
||||
position="bottom"
|
||||
disabled={graphInteractionMode === "zoom"}
|
||||
>
|
||||
<AnchorButton
|
||||
className={styles.menubarButton}
|
||||
type="button"
|
||||
data-testid="centroid-label-toggle"
|
||||
icon="property"
|
||||
onClick={this.handleCentroidChange}
|
||||
active={showCentroidLabels}
|
||||
intent={showCentroidLabels ? "primary" : "none"}
|
||||
disabled={!isColoredByCategorical}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ButtonGroup className={styles.menubarButton}>
|
||||
<Tooltip
|
||||
content={selectionTooltip}
|
||||
position="bottom"
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||
>
|
||||
<AnchorButton
|
||||
type="button"
|
||||
data-testid="mode-lasso"
|
||||
icon={selectionButtonIcon}
|
||||
active={graphInteractionMode === "select"}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: "change graph interaction mode",
|
||||
data: "select",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content="Drag to pan, scroll to zoom"
|
||||
position="bottom"
|
||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||
>
|
||||
<AnchorButton
|
||||
type="button"
|
||||
data-testid="mode-pan-zoom"
|
||||
icon="zoom-in"
|
||||
active={graphInteractionMode === "zoom"}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: "change graph interaction mode",
|
||||
data: "zoom",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<Subset
|
||||
subsetPossible={subsetPossible}
|
||||
subsetResetPossible={subsetResetPossible}
|
||||
handleSubset={this.handleSubset}
|
||||
handleSubsetReset={this.handleSubsetReset}
|
||||
/>
|
||||
{disableDiffexp ? null : <DiffexpButtons />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MenuBar;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user