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:
Bruce Martin
2021-08-23 15:01:36 -07:00
committed by GitHub
co-authored by maniarathi Madison Dunitz Severiano Badajoz
parent 295590a7c6
commit eaae6df5e3
253 changed files with 11175 additions and 22271 deletions
+455
View File
@@ -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,
});
}
};
-530
View File
@@ -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,
});
}
};
+47
View File
@@ -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,
});
};
-60
View File
@@ -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 {
+192
View File
@@ -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,
});
};
-248
View File
@@ -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()];
+25
View File
@@ -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;
}
-28
View File
@@ -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, {});
}
-102
View File
@@ -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);
}
-8
View File
@@ -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;
}
-76
View File
@@ -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",
}
+94
View File
@@ -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;
-117
View File
@@ -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} />
</>
)}
@@ -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"
+122
View File
@@ -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;
-160
View File
@@ -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;
@@ -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,
}) => {
/*
@@ -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;
@@ -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>
);
}
);
@@ -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)}
@@ -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 = (
@@ -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}
@@ -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;
@@ -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 {
+49
View File
@@ -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])];
});
-57
View File
@@ -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])];
});
@@ -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
@@ -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,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,
});
@@ -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>
);
@@ -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}
/>
@@ -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;
@@ -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}
@@ -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;
@@ -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}
@@ -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,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>
);
};
@@ -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,
@@ -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;
@@ -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}
/>
</>
@@ -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
@@ -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;
@@ -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() {
@@ -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 (
@@ -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}
+327
View File
@@ -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