Files
cellxgene/client/src/util/stateManager/viewStackHelpers.js
T
eaae6df5e3 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>
2021-08-23 15:01:36 -07:00

176 lines
5.2 KiB
JavaScript

/*
The annoMatrix view stack has a set of conventions which are assumed elsewhere in the
application. These helper functions make it simple for action creators to manage
the stack.
The annoMatrix module does not care about this order, but we maintain it as
a convention to make it simpler to manipulate the views.
Terminology:
- clip view: AnnoMatrixClipView
- subset view: AnnoMatrixRowSubsetView
- user subset view: create by the user explicitly subsetting by selection
- embedding subset view: implicitly created by switching the current embedding
- loader, or base annoMatrix: the root, which loads data
Rules:
1. there will be zero or one clip view
2. there will be zero or more subset views
3. there will be zero or one embedding view
4. there will be one loader/base, which is always the bottom view
5. the view ordering MUST be (top to bottom):
[clip] -> [user subset] -> [embedding subset] -> loader
There is code elsewhere in the app (eg, menubar/clip.js) which assumes this order.
Views can be interogated for their type with the following:
* is a view: annoMatrix.isView
* is the loader: !anonMatrix.isView (or annoMatrix === annoMatrix.base())
* is a clip view: annoMatrix.isClipped (or annoMatrix.clipRange)
* is a subset view: (annoMatrix.isView && !annoMatrix.isClipped)
* is a user subset view: annoMatrix.userFlags?.isUserSubsetView
* is an embedding subset view: annomatrix.userFlags?.isEmbSubsetView
*/
import { clip, isubsetMask, isubset } from "../../annoMatrix";
import { memoize } from "../dataframe/util";
export function _clipAnnoMatrix(annoMatrix, min, max) {
/*
clip the annoMatrix.
*/
return annoMatrix.isClipped
? clip(annoMatrix.viewOf, min, max)
: clip(annoMatrix, min, max);
}
export function _userSubsetAnnoMatrix(annoMatrix, mask) {
/*
user-requested row subset of annoMatrix, to be added on top of any
other previous row subsets.
*/
const { clipRange } = annoMatrix;
if (clipRange) {
annoMatrix = annoMatrix.viewOf;
}
annoMatrix = isubsetMask(annoMatrix, mask);
annoMatrix.userFlags.isUserSubsetView = true;
if (clipRange) {
annoMatrix = clip(annoMatrix, ...clipRange);
}
return annoMatrix;
}
export function _userResetSubsetAnnoMatrix(annoMatrix) {
/*
Reset/remove all user-requested subsets. Do not remove clip or embedding subset.
*/
/* stash clipping info, if any */
const { clipRange } = annoMatrix;
if (clipRange) {
annoMatrix = annoMatrix.viewOf;
}
/* pop all views except embedding subset and loader */
while (annoMatrix.isView && annoMatrix.userFlags.isUserSubsetView) {
annoMatrix = annoMatrix.viewOf;
}
/* re-apply the clip, if any */
if (clipRange) {
annoMatrix = clip(annoMatrix, ...clipRange);
}
return annoMatrix;
}
export function _setEmbeddingSubset(annoMatrix, embeddingDf) {
/*
Set the embedding subset view. Only create a subset view for the embedding
when it is needed, ie, there are NaN values in the embeddings.
*/
const embRowOffsets = _getEmbeddingRowOffsets(
annoMatrix.rowIndex,
embeddingDf
);
const curEmbSubsetView = getEmbSubsetView(annoMatrix);
/* if no current embedding subset, and no new embedding subset, just noop */
if (!embRowOffsets && !curEmbSubsetView) return annoMatrix;
// ... otherwise, do the work
/* stash clipping info, if any */
const clipRange = annoMatrix.isClipped ? annoMatrix.clipRange : null;
/* pop all subsets, user or embedding */
while (annoMatrix.isView) {
annoMatrix = annoMatrix.viewOf;
}
/* apply new embedding row index, if needed */
if (embRowOffsets) {
annoMatrix = isubset(annoMatrix, embRowOffsets);
annoMatrix.userFlags.isEmbSubsetView = true;
}
/* re-apply clip, if needed */
if (clipRange) {
annoMatrix = clip(annoMatrix, ...clipRange);
}
return annoMatrix;
}
function _getEmbeddingRowOffsets(baseRowIndex, embeddingDf) {
/*
given a dataframe containing an embedding:
- if the embedding contains no NaN coordinates, return null
- if the embedding contains NaN coordinates, return a rowIndex
that contains only the rows with discrete valued coordinates.
Currently assumes that there will be onl two dimensions in the embedding.
*/
const X = embeddingDf.icol(0).asArray();
const Y = embeddingDf.icol(1).asArray();
const offsets = new Int32Array(X.length);
let numOffsets = 0;
for (let i = 0, l = X.length; i < l; i += 1) {
if (!Number.isNaN(X[i]) && !Number.isNaN(Y[i])) {
offsets[numOffsets] = i;
numOffsets += 1;
}
}
if (numOffsets === X.length) return null;
return offsets.subarray(0, numOffsets);
}
export function _getDiscreteCellEmbeddingRowIndex(embeddingDf) {
const idx = _getEmbeddingRowOffsets(embeddingDf.rowIndex, embeddingDf);
if (idx === null) return embeddingDf.rowIndex;
return embeddingDf.rowIndex.isubset(idx);
}
export const getDiscreteCellEmbeddingRowIndex = memoize(
_getDiscreteCellEmbeddingRowIndex,
(df) => df.__id
);
export function getEmbSubsetView(annoMatrix) {
/* if there is an embedding subset in the view stack, return it. Falsish if not. */
while (annoMatrix.isView) {
if (annoMatrix.userFlags.isEmbSubsetView) return annoMatrix;
annoMatrix = annoMatrix.viewOf;
}
return undefined;
}