mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-24 02:08:12 +08:00
TS Revert (1) (#2402)
* revert all commits to before Typescript migration * update compat workflow to match latest deps (#2335) * update compat workflow to match latest deps * attempt to debug * attempt to debug * remove debugging code * typo * update deps to match desktop (#2340) * fix: don't run lint with `--fix` on push tests (#2273) * fix: don't run lint with `--fix` on push tests * npx Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com> * rename X_approx_distribution to X_approximate_distribution (#2337) * Correctly handle non-finite numbers in heuristic determination of X distribution (#2342) * handle non-finites explicitly * improve and test edge case handling for distribution estimation * revert debugging changes * code readability * clean up type inferencing (#2332) * unit tests for 64 bit conversion * clean up type handling * type inference tests * more type inference fixes * use schema to determine user intent for data typing * stop using deprecated API * fbs type encoding test * add missing test * add more tests * correctly infer X type for CXG adaptor * lint * fix typo * ts migration * cleanup from PR review * lint * PR review changes * remove unused packages from client (#2359) * remove unused packages from client * add missing peer dep * fix: disable FE auth testing on compatibility tests (#2377) * update: release process (#2277) Co-authored-by: maniarathi <mani.arathi@gmail.com> * fix: remove spaces in param setup (#2380) * delete deploy workflow (#2396) * undo reformatting which now does not pass lint * fix snapshots which changed due to npm dep changes * add missing quoting to snapshot * another snapshot typo fix * TS Revert (2) - replay PR #2347 and #2354 (#2403) * replay edits from PR 2347 * TS Revert (3) - replay edits in PR #2327 (#2404) * replay edits in PR 2327 * TS Revert (4) - replay PR #2355 (#2405) * replay edits in PR 2355 * add additional babel config * reformat with new prettier config Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com> Co-authored-by: maniarathi <mani.arathi@gmail.com> Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
This commit is contained in:
co-authored by
maniarathi
Madison Dunitz
Severiano Badajoz
parent
295590a7c6
commit
eaae6df5e3
@@ -1,14 +1,13 @@
|
||||
import sortBy from "lodash.sortby";
|
||||
/* XXX: cough, cough, ... */
|
||||
import { postNetworkErrorToast } from "../components/framework/toasters";
|
||||
import type { AppDispatch, RootState } from "../reducers";
|
||||
|
||||
/*
|
||||
dispatch an action error to the user. Currently we use
|
||||
async toasts.
|
||||
*/
|
||||
let networkErrorToastKey: string | null = null;
|
||||
export const dispatchNetworkErrorMessageToUser = (message: string): void => {
|
||||
let networkErrorToastKey = null;
|
||||
export const dispatchNetworkErrorMessageToUser = (message) => {
|
||||
if (!networkErrorToastKey) {
|
||||
networkErrorToastKey = postNetworkErrorToast(message);
|
||||
} else {
|
||||
@@ -19,12 +18,9 @@ export const dispatchNetworkErrorMessageToUser = (message: string): void => {
|
||||
/*
|
||||
Catch unexpected errors and make sure we don't lose them!
|
||||
*/
|
||||
export function catchErrorsWrap(
|
||||
fn: (dispatch: AppDispatch, getState: () => RootState) => Promise<void>,
|
||||
dispatchToUser = false
|
||||
) {
|
||||
return (dispatch: AppDispatch, getState: () => RootState): void => {
|
||||
fn(dispatch, getState).catch((error: Error) => {
|
||||
export function catchErrorsWrap(fn, dispatchToUser = false) {
|
||||
return (dispatch, getState) => {
|
||||
fn(dispatch, getState).catch((error) => {
|
||||
console.error(error);
|
||||
if (dispatchToUser) {
|
||||
dispatchNetworkErrorMessageToUser(error.message);
|
||||
@@ -38,10 +34,7 @@ export function catchErrorsWrap(
|
||||
* Wrapper to perform async fetch with some modest error handling
|
||||
* and decoding. Arguments are identical to standard fetch.
|
||||
*/
|
||||
export const doFetch = async (
|
||||
url: string,
|
||||
init?: RequestInit
|
||||
): Promise<Response> => {
|
||||
export const doFetch = async (url, init = {}) => {
|
||||
try {
|
||||
// add defaults to the fetch init param.
|
||||
init = {
|
||||
@@ -49,11 +42,11 @@ export const doFetch = async (
|
||||
credentials: "include",
|
||||
...init,
|
||||
};
|
||||
const acceptType = (init.headers as Headers)?.get("Accept");
|
||||
const acceptType = init.headers?.get("Accept");
|
||||
const res = await fetch(url, init);
|
||||
if (
|
||||
res.ok &&
|
||||
(!acceptType || res.headers?.get("Content-Type")?.includes(acceptType))
|
||||
(!acceptType || res.headers.get("Content-Type").includes(acceptType))
|
||||
) {
|
||||
return res;
|
||||
}
|
||||
@@ -73,10 +66,7 @@ export const doFetch = async (
|
||||
/*
|
||||
Wrapper to perform an async fetch and JSON decode response.
|
||||
*/
|
||||
export const doJsonRequest = async <T = unknown>(
|
||||
url: string,
|
||||
init?: RequestInit
|
||||
): Promise<T> => {
|
||||
export const doJsonRequest = async (url, init = {}) => {
|
||||
const res = await doFetch(url, {
|
||||
...init,
|
||||
headers: new Headers({ Accept: "application/json" }),
|
||||
@@ -87,10 +77,7 @@ export const doJsonRequest = async <T = unknown>(
|
||||
/*
|
||||
Wrapper to perform an async fetch for binary data.
|
||||
*/
|
||||
export const doBinaryRequest = async (
|
||||
url: string,
|
||||
init?: RequestInit
|
||||
): Promise<ArrayBuffer> => {
|
||||
export const doBinaryRequest = async (url, init = {}) => {
|
||||
const res = await doFetch(url, {
|
||||
...init,
|
||||
headers: new Headers({ Accept: "application/octet-stream" }),
|
||||
@@ -114,10 +101,10 @@ Parameters:
|
||||
So [1, 2, 3, 4, 10, 11, 14] -> [ [1, 4], [10, 11], 14]
|
||||
*/
|
||||
export const rangeEncodeIndices = (
|
||||
indices: Array<number>,
|
||||
indices,
|
||||
minRangeLength = 3,
|
||||
sorted = false
|
||||
): Array<number | [number, number]> => {
|
||||
) => {
|
||||
if (indices.length === 0) {
|
||||
return indices;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { vec2, mat3 } from "gl-matrix";
|
||||
import clamp from "./clamp";
|
||||
|
||||
const EPSILON = 0.000001;
|
||||
|
||||
@@ -12,49 +11,41 @@ const panBound = 0.8;
|
||||
const scratch0 = new Float32Array(16);
|
||||
const scratch1 = new Float32Array(16);
|
||||
|
||||
function clamp(val, rng) {
|
||||
return Math.max(Math.min(val, rng[1]), rng[0]);
|
||||
}
|
||||
|
||||
class Camera {
|
||||
canvas: HTMLCanvasElement;
|
||||
|
||||
prevEvent: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
viewMatrix: mat3;
|
||||
|
||||
viewMatrixInv: mat3;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
constructor(canvas) {
|
||||
this.prevEvent = {
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
type: "",
|
||||
type: 0,
|
||||
};
|
||||
this.canvas = canvas;
|
||||
this.viewMatrix = mat3.create();
|
||||
this.viewMatrixInv = mat3.create();
|
||||
}
|
||||
|
||||
view(): mat3 {
|
||||
view() {
|
||||
return this.viewMatrix;
|
||||
}
|
||||
|
||||
invView(): mat3 {
|
||||
invView() {
|
||||
return this.viewMatrixInv;
|
||||
}
|
||||
|
||||
distance(): number {
|
||||
distance() {
|
||||
return this.viewMatrix[0];
|
||||
}
|
||||
|
||||
pan(dx: number, dy: number): void {
|
||||
pan(dx, dy) {
|
||||
const m = this.viewMatrix;
|
||||
const dyRange: [number, number] = [
|
||||
const dyRange = [
|
||||
-panBound - (m[7] + 1) / m[4],
|
||||
panBound - (m[7] - 1) / m[4],
|
||||
];
|
||||
const dxRange: [number, number] = [
|
||||
const dxRange = [
|
||||
-panBound - (m[6] + 1) / m[0],
|
||||
panBound - (m[6] - 1) / m[0],
|
||||
];
|
||||
@@ -68,12 +59,12 @@ class Camera {
|
||||
mat3.invert(this.viewMatrixInv, m);
|
||||
}
|
||||
|
||||
zoomAt(d: number, x = 0, y = 0): void {
|
||||
zoomAt(d, x = 0, y = 0) {
|
||||
/*
|
||||
Camera zoom at [x,y]
|
||||
*/
|
||||
const m = this.viewMatrix;
|
||||
const bounds: [number, number] = [-panBound, panBound];
|
||||
const bounds = [-panBound, panBound];
|
||||
x = clamp(x, bounds);
|
||||
y = clamp(y, bounds);
|
||||
|
||||
@@ -91,18 +82,13 @@ class Camera {
|
||||
Event handling
|
||||
*/
|
||||
|
||||
flush(e: MouseEvent) {
|
||||
flush(e) {
|
||||
this.prevEvent.type = e.type;
|
||||
this.prevEvent.clientX = e.clientX;
|
||||
this.prevEvent.clientY = e.clientY;
|
||||
}
|
||||
|
||||
localPosition(
|
||||
target: HTMLCanvasElement,
|
||||
canvasX: number,
|
||||
canvasY: number,
|
||||
projectionInvTF: mat3
|
||||
): vec2 {
|
||||
localPosition(target, canvasX, canvasY, projectionInvTF) {
|
||||
/*
|
||||
Convert mouse position to local
|
||||
*/
|
||||
@@ -122,7 +108,7 @@ class Camera {
|
||||
return pos;
|
||||
}
|
||||
|
||||
mousePan(e: MouseEvent, projectionTF: mat3): true {
|
||||
mousePan(e, projectionTF) {
|
||||
const projectionInvTF = mat3.invert(scratch0, projectionTF);
|
||||
const pos = this.localPosition(
|
||||
this.canvas,
|
||||
@@ -142,7 +128,7 @@ class Camera {
|
||||
return true;
|
||||
}
|
||||
|
||||
wheelZoom(e: WheelEvent, projectionTF: mat3): true {
|
||||
wheelZoom(e, projectionTF) {
|
||||
const { height } = this.canvas;
|
||||
const { deltaY, deltaMode, clientX, clientY } = e;
|
||||
const scale = scaleSpeed * (deltaMode === 1 ? 12 : 1) * (deltaY || 0);
|
||||
@@ -158,7 +144,7 @@ class Camera {
|
||||
return true;
|
||||
}
|
||||
|
||||
handleEvent(e: MouseEvent, projectionTF: mat3): boolean {
|
||||
handleEvent(e, projectionTF) {
|
||||
/*
|
||||
process the event, and return true if camera view changed
|
||||
*/
|
||||
@@ -174,7 +160,7 @@ class Camera {
|
||||
}
|
||||
|
||||
case "wheel": {
|
||||
viewChanged = this.wheelZoom(e as WheelEvent, projectionTF);
|
||||
viewChanged = this.wheelZoom(e, projectionTF);
|
||||
this.flush(e);
|
||||
break;
|
||||
}
|
||||
@@ -187,7 +173,7 @@ class Camera {
|
||||
}
|
||||
}
|
||||
|
||||
function attachCamera(canvas: HTMLCanvasElement): Camera {
|
||||
function attachCamera(canvas) {
|
||||
return new Camera(canvas);
|
||||
}
|
||||
|
||||
@@ -11,23 +11,20 @@ TL;DR: sort order is:
|
||||
import isNumber from "is-number";
|
||||
import * as globals from "../globals";
|
||||
|
||||
function caseInsensitiveCompare(a: string, b: string): number {
|
||||
function caseInsensitiveCompare(a, b) {
|
||||
const textA = String(a).toUpperCase();
|
||||
const textB = String(b).toUpperCase();
|
||||
return textA < textB ? -1 : textA > textB ? 1 : 0;
|
||||
}
|
||||
|
||||
const catLabelSort = (
|
||||
isUserAnno: boolean,
|
||||
values: Array<string>
|
||||
): Array<string> => {
|
||||
const catLabelSort = (isUserAnno, values) => {
|
||||
/* this sort could be memoized for perf */
|
||||
|
||||
const strings: string[] = [];
|
||||
const ints: string[] = [];
|
||||
const unassignedOrNaN: string[] = [];
|
||||
const strings = [];
|
||||
const ints = [];
|
||||
const unassignedOrNaN = [];
|
||||
|
||||
values.forEach((v: string) => {
|
||||
values.forEach((v) => {
|
||||
if (isUserAnno && v === globals.unassignedCategoryLabel) {
|
||||
unassignedOrNaN.push(v);
|
||||
} else if (String(v).toLowerCase() === "nan") {
|
||||
@@ -1,6 +1,5 @@
|
||||
import quantile from "./quantile";
|
||||
import { memoize } from "./dataframe/util";
|
||||
import { Dataframe } from "./dataframe";
|
||||
import { unassignedCategoryLabel } from "../globals";
|
||||
import {
|
||||
createCategorySummaryFromDfCol,
|
||||
@@ -24,13 +23,11 @@ label -> {
|
||||
}
|
||||
*/
|
||||
const getCoordinatesByLabel = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: any,
|
||||
categoryName: string,
|
||||
categoryDf: Dataframe,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: any,
|
||||
layoutDf: Dataframe
|
||||
schema,
|
||||
categoryName,
|
||||
categoryDf,
|
||||
layoutChoice,
|
||||
layoutDf
|
||||
) => {
|
||||
const coordsByCategoryLabel = new Map();
|
||||
// If the coloredBy is not a categorical col
|
||||
@@ -72,7 +69,6 @@ const getCoordinatesByLabel = (
|
||||
let coords = coordsByCategoryLabel.get(label);
|
||||
if (coords === undefined) {
|
||||
// Get the number of cells which are in the label
|
||||
// @ts-expect-error ts-migrate(2538) FIXME: Blocked by StateManager/ControlsHelpers
|
||||
const numInLabel = categoryValueCounts[labelIndex];
|
||||
coords = {
|
||||
hasFinite: false,
|
||||
@@ -98,20 +94,18 @@ const getCoordinatesByLabel = (
|
||||
return coordsByCategoryLabel;
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
calcMedianCentroid calculates the median coordinates for labels in a given category
|
||||
|
||||
label -> [x-Coordinate, y-Coordinate]
|
||||
*/
|
||||
|
||||
const calcMedianCentroid = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: any,
|
||||
categoryName: string,
|
||||
categoryDf: Dataframe,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: any,
|
||||
layoutDf: Dataframe
|
||||
schema,
|
||||
categoryName,
|
||||
categoryDf,
|
||||
layoutChoice,
|
||||
layoutDf
|
||||
) => {
|
||||
// generate a map describing the coordinates for each label within the given category
|
||||
const dataMap = getCoordinatesByLabel(
|
||||
@@ -146,15 +140,12 @@ const calcMedianCentroid = (
|
||||
|
||||
// A simple function to hash the parameters
|
||||
const hashMedianCentroid = (
|
||||
// @ts-expect-error ts-migrate(6133) FIXME: 'schema' is declared but its value is never read.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: any,
|
||||
categoryName: string,
|
||||
categoryDf: Dataframe,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
layoutChoice: any,
|
||||
layoutDf: Dataframe
|
||||
): string => {
|
||||
schema,
|
||||
categoryName,
|
||||
categoryDf,
|
||||
layoutChoice,
|
||||
layoutDf
|
||||
) => {
|
||||
const category = categoryDf.col(categoryName);
|
||||
const layoutDimNames = layoutChoice.currentDimNames;
|
||||
const layoutX = layoutDf.col(layoutDimNames[0]);
|
||||
@@ -8,6 +8,6 @@
|
||||
* @returns a number
|
||||
*/
|
||||
|
||||
export default function clamp(val: number, rng: [number, number]): number {
|
||||
export default function clamp(val, rng) {
|
||||
return Math.max(Math.min(val, rng[1]), rng[0]);
|
||||
}
|
||||
@@ -1,23 +1,16 @@
|
||||
import { NumberArray } from "../common/types/arraytypes";
|
||||
|
||||
/*
|
||||
clip - clip all values in a Array or TypedArray, IN PLACE.
|
||||
|
||||
Values in array are clipped if less than `lower` or greater than `upper`.
|
||||
|
||||
If `setTo` is undefined, values less than `lower` will be set to `lower`,
|
||||
and values greater than `upper` will be set to `upper`.
|
||||
and values greater than `upper` will be set to `upper`.
|
||||
|
||||
If `setTo` is not undefined, values outside the [lower, upper] range will be set to
|
||||
If `setTo` is not undefined, values outside the [lower, upper] range will be set to
|
||||
`setTo`.
|
||||
|
||||
*/
|
||||
export default function clip(
|
||||
arr: NumberArray,
|
||||
lower: number,
|
||||
upper: number,
|
||||
setTo?: number
|
||||
): NumberArray {
|
||||
export default function clip(arr, lower, upper, setTo) {
|
||||
const lowerSet = setTo === undefined ? lower : setTo;
|
||||
const upperSet = setTo === undefined ? upper : setTo;
|
||||
for (let i = 0, l = arr.length; i < l; i += 1) {
|
||||
@@ -10,18 +10,17 @@ objects.
|
||||
*/
|
||||
|
||||
import { memoize } from "./util";
|
||||
import Dataframe from "./dataframe";
|
||||
|
||||
function hashDataframe(df: Dataframe): string {
|
||||
function hashDataframe(df) {
|
||||
if (df.isEmpty()) return "";
|
||||
return df.__columnsAccessor.map((c) => c.__id).join(",");
|
||||
}
|
||||
|
||||
function noop(df: Dataframe): Dataframe {
|
||||
function noop(df) {
|
||||
return df;
|
||||
}
|
||||
|
||||
const dataframeMemo = (capacity = 100): ((df: Dataframe) => Dataframe) =>
|
||||
const dataframeMemo = (capacity = 100) =>
|
||||
memoize(noop, hashDataframe, capacity);
|
||||
|
||||
export default dataframeMemo;
|
||||
@@ -1,44 +1,30 @@
|
||||
import { callOnceLazy, memoize, __getMemoId } from "./util";
|
||||
import { IdentityInt32Index, isLabelIndex } from "./labelIndex";
|
||||
// weird cross-dependency that we should clean up someday...
|
||||
import {
|
||||
isTypedArray,
|
||||
isAnyArray,
|
||||
AnyArray,
|
||||
GenericArrayConstructor,
|
||||
} from "../../common/types/arraytypes";
|
||||
import { IdentityInt32Index, LabelIndex, isLabelIndex } from "./labelIndex";
|
||||
isArrayOrTypedArray,
|
||||
callOnceLazy,
|
||||
memoize,
|
||||
__getMemoId,
|
||||
} from "./util";
|
||||
import {
|
||||
summarizeContinuous as _summarizeContinuous,
|
||||
summarizeContinuous,
|
||||
summarizeCategorical as _summarizeCategorical,
|
||||
} from "./summarize";
|
||||
import {
|
||||
histogramCategorical as _histogramCategorical,
|
||||
histogramCategoricalBy as _histogramCategoricalBy,
|
||||
hashCategorical,
|
||||
hashCategoricalBy,
|
||||
histogramContinuous as _histogramContinuous,
|
||||
histogramContinuousBy as _histogramContinuousBy,
|
||||
histogramContinuous,
|
||||
hashContinuous,
|
||||
hashContinuousBy,
|
||||
} from "./histogram";
|
||||
import {
|
||||
DataframeValue,
|
||||
DataframeValueArray,
|
||||
DataframeColumn,
|
||||
OffsetType,
|
||||
OffsetArray,
|
||||
LabelType,
|
||||
LabelArray,
|
||||
ContinuousHistogram,
|
||||
ContinuousHistogramBy,
|
||||
} from "./types";
|
||||
|
||||
/*
|
||||
Dataframe is an immutable 2D matrix similar to Python Pandas Dataframe,
|
||||
Dataframe is an immutable 2D matrix similiar to Python Pandas Dataframe,
|
||||
but (currently) without all of the surrounding support functions.
|
||||
Data is stored in column-major layout, and each column is monomorphic.
|
||||
|
||||
It supports:
|
||||
* Relatively efficient create, clone and subset operations
|
||||
* Relatively efficient creation, cloning and subsetting
|
||||
* Very efficient columnar access (eg, sum down a column), and access
|
||||
to the underlying column arrays.
|
||||
* Data access by row/col offset or label. Labels are reasonably well
|
||||
@@ -46,10 +32,10 @@ It supports:
|
||||
|
||||
It does not currently support:
|
||||
* Views on matrix subset - for currently known access patterns,
|
||||
it is more efficient to copy on subsetting, optimizing for access
|
||||
it is more effiicent to copy on subsetting, optimizing for access
|
||||
speed over memory use.
|
||||
* JS iterators - they are too slow. Use explicit iteration over
|
||||
offset or labels.
|
||||
offest or labels.
|
||||
|
||||
Important assumptions embedded in the API:
|
||||
* Columns are implicitly categorical if they are a JS Array and numeric
|
||||
@@ -86,55 +72,24 @@ dominant pattern in cellxgene.
|
||||
Dataframe
|
||||
**/
|
||||
|
||||
interface DataframeConstructor {
|
||||
new (...args: ConstructorParameters<typeof Dataframe>): Dataframe;
|
||||
}
|
||||
|
||||
export type MapColumnsCallbackFn = (
|
||||
data: DataframeValueArray,
|
||||
idx: number,
|
||||
df: Dataframe
|
||||
) => DataframeValueArray;
|
||||
|
||||
/** @internal */
|
||||
function raiseIsNotContinuous<R = void>(): R {
|
||||
throw TypeError("Column is not a continuous data type.");
|
||||
}
|
||||
|
||||
class Dataframe {
|
||||
/** @internal */
|
||||
__columns: DataframeValueArray[];
|
||||
|
||||
/** @internal */
|
||||
__columnsAccessor: DataframeColumn[] = [];
|
||||
|
||||
__id: string;
|
||||
|
||||
colIndex: LabelIndex;
|
||||
|
||||
dims: [number, number];
|
||||
|
||||
length: number;
|
||||
|
||||
rowIndex: LabelIndex;
|
||||
|
||||
/**
|
||||
Constructors & factories
|
||||
**/
|
||||
|
||||
constructor(
|
||||
dims: [number, number],
|
||||
columnarData: DataframeValueArray[],
|
||||
rowIndex?: LabelIndex | null,
|
||||
colIndex?: LabelIndex | null,
|
||||
__columnsAccessor: (DataframeColumn | null)[] = [] // private interface
|
||||
dims,
|
||||
columnarData,
|
||||
rowIndex = null,
|
||||
colIndex = null,
|
||||
__columnsAccessor = [] // private interface
|
||||
) {
|
||||
/*
|
||||
The base constructor is relatively hard to use - as an alternative,
|
||||
see factory methods and clone/slice, below.
|
||||
|
||||
Parameters:
|
||||
* dims - 2D array describing intended dimensionality: [nRows,nCols].
|
||||
* dims - 2D array describing intendend dimensionality: [nRows,nCols].
|
||||
* columnarData - JS array, nCols in length, containing array
|
||||
or TypedArray of length nRows.
|
||||
* rowIndex/colIndex - null (create default index using offsets as key),
|
||||
@@ -167,20 +122,14 @@ class Dataframe {
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static __errorChecks(
|
||||
dims: [number, number],
|
||||
columnarData: AnyArray[],
|
||||
rowIndex: LabelIndex,
|
||||
colIndex: LabelIndex
|
||||
): void | never {
|
||||
static __errorChecks(dims, columnarData, rowIndex, colIndex) {
|
||||
const [nRows, nCols] = dims;
|
||||
|
||||
/* check for expected types */
|
||||
if (!Array.isArray(columnarData)) {
|
||||
throw new TypeError("Dataframe constructor requires array of columns");
|
||||
}
|
||||
if (!columnarData.every((c) => isAnyArray(c))) {
|
||||
if (!columnarData.every((c) => isArrayOrTypedArray(c))) {
|
||||
throw new TypeError("Dataframe columns must all be Array or TypedArray");
|
||||
}
|
||||
if (!isLabelIndex(rowIndex)) {
|
||||
@@ -211,12 +160,7 @@ class Dataframe {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static __compileColumn(
|
||||
column: DataframeValueArray,
|
||||
getRowOffset: (label: LabelType) => OffsetType | -1,
|
||||
getRowLabel: (offset: number) => LabelType | undefined
|
||||
): DataframeColumn {
|
||||
static __compileColumn(column, getRowByOffset, getRowByLabel) {
|
||||
/*
|
||||
Each column accessor is a function which will lookup data by
|
||||
index (ie, is equivalent to dataframe.get(row, col), where 'col'
|
||||
@@ -249,17 +193,14 @@ class Dataframe {
|
||||
*/
|
||||
const { length } = column;
|
||||
const __id = __getMemoId();
|
||||
const isContinuous = isTypedArray(column);
|
||||
|
||||
/* get value by row label */
|
||||
const get = function get(rlabel: LabelType): DataframeValue | undefined {
|
||||
const idx = getRowOffset(rlabel);
|
||||
if (idx === -1) return undefined;
|
||||
return column[idx];
|
||||
const get = function get(rlabel) {
|
||||
return column[getRowByOffset(rlabel)];
|
||||
};
|
||||
|
||||
/* get value by row offset */
|
||||
const iget = function iget(roffset: OffsetType) {
|
||||
const iget = function iget(roffset) {
|
||||
return column[roffset];
|
||||
};
|
||||
|
||||
@@ -269,12 +210,12 @@ class Dataframe {
|
||||
};
|
||||
|
||||
/* test for row label inclusion in column */
|
||||
const has = function has(rlabel: LabelType) {
|
||||
const offset = getRowOffset(rlabel);
|
||||
const has = function has(rlabel) {
|
||||
const offset = getRowByOffset(rlabel);
|
||||
return offset >= 0 && offset < length;
|
||||
};
|
||||
|
||||
const ihas = function ihas(offset: OffsetType) {
|
||||
const ihas = function ihas(offset) {
|
||||
return offset >= 0 && offset < length;
|
||||
};
|
||||
|
||||
@@ -285,88 +226,76 @@ class Dataframe {
|
||||
NOTE: not found return is DIFFERENT than the default Array.indexOf as
|
||||
-1 is a plausible Dataframe row/col label.
|
||||
*/
|
||||
const _indexOf = function _indexOf(value: DataframeValue) {
|
||||
let offset: number;
|
||||
if (isTypedArray(column)) offset = column.indexOf(value as number);
|
||||
else offset = column.indexOf(value);
|
||||
const indexOf = function indexOf(value) {
|
||||
const offset = column.indexOf(value);
|
||||
if (offset === -1) {
|
||||
return undefined;
|
||||
}
|
||||
return getRowLabel(offset);
|
||||
return getRowByLabel(offset);
|
||||
};
|
||||
|
||||
/*
|
||||
Summarize the column data. Lazy eval, memoized
|
||||
*/
|
||||
get.summarizeCategorical = callOnceLazy(() =>
|
||||
const summarizeCategorical = callOnceLazy(() =>
|
||||
_summarizeCategorical(column)
|
||||
);
|
||||
get.summarizeContinuous = isContinuous
|
||||
? callOnceLazy(() => _summarizeContinuous(column))
|
||||
: raiseIsNotContinuous;
|
||||
const summarize = callOnceLazy(() =>
|
||||
isTypedArray(column)
|
||||
? summarizeContinuous(column)
|
||||
: summarizeCategorical(column)
|
||||
);
|
||||
|
||||
/*
|
||||
Create histogram bins for this column. Memoized.
|
||||
*/
|
||||
get.histogramContinuous = isContinuous
|
||||
? (bins: number, domain: [number, number]): ContinuousHistogram =>
|
||||
memoize(_histogramContinuous, hashContinuous)(get, bins, domain)
|
||||
: raiseIsNotContinuous;
|
||||
get.histogramContinuousBy = isContinuous
|
||||
? (
|
||||
bins: number,
|
||||
domain: [number, number],
|
||||
by: DataframeColumn
|
||||
): ContinuousHistogramBy =>
|
||||
memoize(_histogramContinuousBy, hashContinuousBy)(
|
||||
get,
|
||||
bins,
|
||||
domain,
|
||||
by
|
||||
)
|
||||
: raiseIsNotContinuous;
|
||||
get.histogramCategorical = () =>
|
||||
memoize(_histogramCategorical, hashCategorical)(get);
|
||||
get.histogramCategoricalBy = (by: DataframeColumn) =>
|
||||
memoize(_histogramCategoricalBy, hashCategoricalBy)(get, by);
|
||||
const _memoHistoCat = memoize(_histogramCategorical, hashCategorical);
|
||||
const histogramCategorical = (by) => _memoHistoCat(get, by);
|
||||
let histogram = null;
|
||||
if (isTypedArray(column)) {
|
||||
const mFn = memoize(histogramContinuous, hashContinuous);
|
||||
histogram = (bins, domain, by) => mFn(get, bins, domain, by);
|
||||
} else {
|
||||
histogram = histogramCategorical;
|
||||
}
|
||||
|
||||
get.summarize = summarize;
|
||||
get.summarizeCategorical = summarizeCategorical;
|
||||
get.histogram = histogram;
|
||||
get.histogramCategorical = histogramCategorical;
|
||||
get.asArray = asArray;
|
||||
get.has = has;
|
||||
get.ihas = ihas;
|
||||
get.indexOf = _indexOf;
|
||||
get.indexOf = indexOf;
|
||||
get.iget = iget;
|
||||
get.__id = __id;
|
||||
get.isContinuous = isContinuous;
|
||||
|
||||
Object.freeze(get);
|
||||
return get;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__compile(accessors: (DataframeColumn | null)[]): void {
|
||||
__compile(accessors) {
|
||||
/*
|
||||
Compile data accessors for each column.
|
||||
|
||||
Use an existing accessor if provided, else compile a new one.
|
||||
*/
|
||||
const getRowOffset = this.rowIndex.getOffset.bind(this.rowIndex);
|
||||
const getRowLabel = this.rowIndex.getLabel.bind(this.rowIndex);
|
||||
this.__columnsAccessor = this.__columns.map(
|
||||
(column, idx): DataframeColumn => {
|
||||
if (accessors[idx]) {
|
||||
return accessors[idx] as DataframeColumn;
|
||||
}
|
||||
return Dataframe.__compileColumn(column, getRowOffset, getRowLabel);
|
||||
const getRowByOffset = this.rowIndex.getOffset.bind(this.rowIndex);
|
||||
const getRowByLabel = this.rowIndex.getLabel.bind(this.rowIndex);
|
||||
this.__columnsAccessor = this.__columns.map((column, idx) => {
|
||||
if (accessors[idx]) {
|
||||
return accessors[idx];
|
||||
}
|
||||
);
|
||||
return Dataframe.__compileColumn(column, getRowByOffset, getRowByLabel);
|
||||
});
|
||||
Object.freeze(this.__columnsAccessor);
|
||||
}
|
||||
|
||||
clone(): Dataframe {
|
||||
clone() {
|
||||
/*
|
||||
Clone this dataframe
|
||||
*/
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
this.dims,
|
||||
[...this.__columns],
|
||||
this.rowIndex,
|
||||
@@ -375,11 +304,7 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
withCol(
|
||||
label: LabelType,
|
||||
colData: DataframeValueArray,
|
||||
withRowIndex?: LabelIndex
|
||||
): Dataframe {
|
||||
withCol(label, colData, withRowIndex = null) {
|
||||
/*
|
||||
Create a new DF, which is `this` plus the new column. Example:
|
||||
const newDf = df.withCol("foo", [1,2,3]);
|
||||
@@ -394,10 +319,11 @@ class Dataframe {
|
||||
the rowIndex from `this` will be used (ie, the rowIndex is
|
||||
unchanged).
|
||||
*/
|
||||
let dims: [number, number];
|
||||
let rowIndex: LabelIndex | null = null;
|
||||
let dims;
|
||||
let rowIndex;
|
||||
if (this.isEmpty()) {
|
||||
dims = [colData.length, 1];
|
||||
rowIndex = null;
|
||||
} else {
|
||||
dims = [this.dims[0], this.dims[1] + 1];
|
||||
({ rowIndex } = this);
|
||||
@@ -411,7 +337,7 @@ class Dataframe {
|
||||
columns.push(colData);
|
||||
const colIndex = this.colIndex.withLabel(label);
|
||||
const columnsAccessor = [...this.__columnsAccessor];
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
dims,
|
||||
columns,
|
||||
rowIndex,
|
||||
@@ -420,10 +346,7 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
withColsFrom(
|
||||
dataframe: Dataframe,
|
||||
labels?: Record<string | number, LabelType> | LabelType[]
|
||||
): Dataframe {
|
||||
withColsFrom(dataframe, labels) {
|
||||
/*
|
||||
return a new dataframe containing all columns from both `this` and the
|
||||
provided dataframe argument.
|
||||
@@ -449,8 +372,8 @@ class Dataframe {
|
||||
*/
|
||||
|
||||
// resolve the source and dest label names.
|
||||
let srcLabels: LabelArray;
|
||||
let dstLabels: LabelArray;
|
||||
let srcLabels;
|
||||
let dstLabels;
|
||||
if (!labels) {
|
||||
// combine all columns
|
||||
dstLabels = dataframe.colIndex.labels();
|
||||
@@ -485,9 +408,9 @@ class Dataframe {
|
||||
return dataframe;
|
||||
}
|
||||
|
||||
// otherwise, build a new dataframe combining columns from both
|
||||
const srcOffsets = Array.from(dataframe.colIndex.getOffsets(srcLabels));
|
||||
if (srcOffsets.some((i) => i === -1)) throw RangeError("Unknown label.");
|
||||
// otherwise, bulid a new dataframe combining columns from both
|
||||
|
||||
const srcOffsets = srcLabels.map((l) => dataframe.colIndex.getOffset(l));
|
||||
|
||||
// check for label collisions
|
||||
if (dstLabels.some(this.hasCol, this)) {
|
||||
@@ -495,12 +418,9 @@ class Dataframe {
|
||||
}
|
||||
|
||||
// const dims = [this.dims[0], this.dims[1] + dataframe.dims[1]];
|
||||
const dims: [number, number] = [
|
||||
this.dims[0],
|
||||
this.dims[1] + srcOffsets.length,
|
||||
];
|
||||
const dims = [this.dims[0], this.dims[1] + srcOffsets.length];
|
||||
const { rowIndex } = this;
|
||||
const columns: DataframeValueArray[] = [
|
||||
const columns = [
|
||||
...this.__columns,
|
||||
...srcOffsets.map((i) => dataframe.__columns[i]),
|
||||
];
|
||||
@@ -510,7 +430,7 @@ class Dataframe {
|
||||
...srcOffsets.map((i) => dataframe.__columnsAccessor[i]),
|
||||
];
|
||||
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
dims,
|
||||
columns,
|
||||
rowIndex,
|
||||
@@ -519,12 +439,12 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
withColsFromAll(dataframes: Dataframe[] = []): Dataframe {
|
||||
withColsFromAll(dataframes = []) {
|
||||
dataframes = Array.isArray(dataframes) ? dataframes : [dataframes];
|
||||
return dataframes.reduce((acc, df) => acc.withColsFrom(df), this);
|
||||
}
|
||||
|
||||
dropCol(label: LabelType): Dataframe {
|
||||
dropCol(label) {
|
||||
/*
|
||||
Create a new dataframe, omitting one columns.
|
||||
|
||||
@@ -543,15 +463,14 @@ class Dataframe {
|
||||
return Dataframe.empty();
|
||||
}
|
||||
|
||||
const dims: [number, number] = [this.dims[0], this.dims[1] - 1];
|
||||
const dims = [this.dims[0], this.dims[1] - 1];
|
||||
const coffset = this.colIndex.getOffset(label);
|
||||
if (coffset === -1) throw new RangeError("Unknown label.");
|
||||
const columns = [...this.__columns];
|
||||
columns.splice(coffset, 1);
|
||||
const colIndex = this.colIndex.dropLabel(label);
|
||||
const columnsAccessor = [...this.__columnsAccessor];
|
||||
columnsAccessor.splice(coffset, 1);
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
dims,
|
||||
columns,
|
||||
this.rowIndex,
|
||||
@@ -560,12 +479,11 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
renameCol(oldLabel: LabelType, newLabel: LabelType): Dataframe {
|
||||
renameCol(oldLabel, newLabel) {
|
||||
/*
|
||||
Accelerator for dropping a column and then adding it again with a new label
|
||||
*/
|
||||
const coffset = this.colIndex.getOffset(oldLabel);
|
||||
if (coffset === -1) throw new RangeError("Unknown label.");
|
||||
const colIndex = this.colIndex.dropLabel(oldLabel).withLabel(newLabel);
|
||||
|
||||
const columns = [...this.__columns];
|
||||
@@ -576,7 +494,7 @@ class Dataframe {
|
||||
columnsAccessor.push(columnsAccessor[coffset]);
|
||||
columnsAccessor.splice(coffset, 1);
|
||||
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
this.dims,
|
||||
columns,
|
||||
this.rowIndex,
|
||||
@@ -585,21 +503,18 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
replaceColData(label: LabelType, newColData: DataframeValueArray): Dataframe {
|
||||
replaceColData(label, newColData) {
|
||||
/*
|
||||
Accelerator for dropping a column then adding it again with same
|
||||
label and different values.
|
||||
*/
|
||||
const coffset = this.colIndex.getOffset(label);
|
||||
if (coffset === -1) throw RangeError("Unknown column label.");
|
||||
const columns = [...this.__columns];
|
||||
columns[coffset] = newColData;
|
||||
const columnsAccessor: (DataframeColumn | null)[] = [
|
||||
...this.__columnsAccessor,
|
||||
];
|
||||
const columnsAccessor = [...this.__columnsAccessor];
|
||||
columnsAccessor[coffset] = null;
|
||||
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
this.dims,
|
||||
columns,
|
||||
this.rowIndex,
|
||||
@@ -608,8 +523,8 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
static empty(rowIndex?: LabelIndex, colIndex?: LabelIndex): Dataframe {
|
||||
const dims: [number, number] = [
|
||||
static empty(rowIndex = null, colIndex = null) {
|
||||
const dims = [
|
||||
rowIndex ? rowIndex.size() : 0,
|
||||
colIndex ? colIndex.size() : 0,
|
||||
];
|
||||
@@ -617,10 +532,7 @@ class Dataframe {
|
||||
return new Dataframe(dims, new Array(dims[1]), rowIndex, colIndex);
|
||||
}
|
||||
|
||||
static create(
|
||||
dims: [number, number],
|
||||
columnarData: DataframeValueArray[]
|
||||
): Dataframe {
|
||||
static create(dims, columnarData) {
|
||||
/*
|
||||
Create a dataframe from raw columnar data. All column arrays
|
||||
must have the same length. Identity indexing will be used.
|
||||
@@ -628,15 +540,11 @@ class Dataframe {
|
||||
Example:
|
||||
const df = Dataframe.create([2,2], [new Uint32Array(2), new Float32Array(2)]);
|
||||
*/
|
||||
return new Dataframe(dims, columnarData);
|
||||
return new Dataframe(dims, columnarData, null, null);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__subset(
|
||||
newRowIndex: LabelIndex | null,
|
||||
newColIndex: LabelIndex | null
|
||||
): Dataframe {
|
||||
const dims: [number, number] = [...this.dims];
|
||||
__subset(newRowIndex, newColIndex) {
|
||||
const dims = [...this.dims];
|
||||
|
||||
/* subset columns */
|
||||
let { __columns, colIndex, __columnsAccessor } = this;
|
||||
@@ -645,10 +553,8 @@ class Dataframe {
|
||||
__columns = new Array(colOffsets.length);
|
||||
__columnsAccessor = new Array(colOffsets.length);
|
||||
for (let i = 0, l = colOffsets.length; i < l; i += 1) {
|
||||
const colOffset = colOffsets[i];
|
||||
if (colOffset === -1) throw new RangeError("Unexpected column offset.");
|
||||
__columns[i] = this.__columns[colOffset];
|
||||
__columnsAccessor[i] = this.__columnsAccessor[colOffset];
|
||||
__columns[i] = this.__columns[colOffsets[i]];
|
||||
__columnsAccessor[i] = this.__columnsAccessor[colOffsets[i]];
|
||||
}
|
||||
colIndex = newColIndex;
|
||||
dims[1] = colOffsets.length;
|
||||
@@ -658,13 +564,9 @@ class Dataframe {
|
||||
if (newRowIndex) {
|
||||
const rowOffsets = this.rowIndex.getOffsets(newRowIndex.labels());
|
||||
__columns = __columns.map((col) => {
|
||||
const newCol = new (col.constructor as GenericArrayConstructor<
|
||||
typeof col
|
||||
>)(rowOffsets.length);
|
||||
const newCol = new col.constructor(rowOffsets.length);
|
||||
for (let i = 0, l = rowOffsets.length; i < l; i += 1) {
|
||||
const rowOffset = rowOffsets[i];
|
||||
if (rowOffset === -1) throw new RangeError("Unexpected row offset.");
|
||||
newCol[i] = col[rowOffset];
|
||||
newCol[i] = col[rowOffsets[i]];
|
||||
}
|
||||
return newCol;
|
||||
});
|
||||
@@ -683,11 +585,7 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
subset(
|
||||
rowLabels: LabelArray | null,
|
||||
colLabels: LabelArray | null,
|
||||
withRowIndex?: LabelIndex | null
|
||||
): Dataframe {
|
||||
subset(rowLabels, colLabels = null, withRowIndex = null) {
|
||||
/*
|
||||
Subset by row/col labels.
|
||||
|
||||
@@ -709,11 +607,7 @@ class Dataframe {
|
||||
return this.__subset(rowIndex, colIndex);
|
||||
}
|
||||
|
||||
isubset(
|
||||
rowOffsets: OffsetArray | null,
|
||||
colOffsets: OffsetArray | null,
|
||||
withRowIndex?: LabelIndex | null
|
||||
): Dataframe {
|
||||
isubset(rowOffsets, colOffsets = null, withRowIndex = null) {
|
||||
/*
|
||||
Subset by row/col offset.
|
||||
|
||||
@@ -722,14 +616,14 @@ class Dataframe {
|
||||
indexing. If withRowIndex is a label index object, it will be used
|
||||
for the new dataframe.
|
||||
*/
|
||||
let rowIndex: LabelIndex | null = null;
|
||||
let rowIndex = null;
|
||||
if (withRowIndex) {
|
||||
rowIndex = withRowIndex;
|
||||
} else if (rowOffsets) {
|
||||
rowIndex = this.rowIndex.isubset(rowOffsets);
|
||||
}
|
||||
|
||||
let colIndex: LabelIndex | null = null;
|
||||
let colIndex = null;
|
||||
if (colOffsets) {
|
||||
colIndex = this.colIndex.isubset(colOffsets);
|
||||
}
|
||||
@@ -737,11 +631,7 @@ class Dataframe {
|
||||
return this.__subset(rowIndex, colIndex);
|
||||
}
|
||||
|
||||
isubsetMask(
|
||||
rowMask: Uint8Array | boolean[] | null,
|
||||
colMask: Uint8Array | boolean[] | null,
|
||||
withRowIndex?: LabelIndex | null
|
||||
): Dataframe {
|
||||
isubsetMask(rowMask, colMask = null, withRowIndex = null) {
|
||||
/*
|
||||
Subset on row/column based upon a truthy/falsey array (a mask).
|
||||
|
||||
@@ -759,10 +649,7 @@ class Dataframe {
|
||||
}
|
||||
|
||||
/* convert masks to lists - method wastes space, but is fast */
|
||||
const toList = (
|
||||
mask: Uint8Array | boolean[] | null | undefined,
|
||||
maxSize: number
|
||||
) => {
|
||||
const toList = (mask, maxSize) => {
|
||||
if (!mask) {
|
||||
return null;
|
||||
}
|
||||
@@ -785,12 +672,12 @@ class Dataframe {
|
||||
Data access with row/col.
|
||||
**/
|
||||
|
||||
columns(): DataframeColumn[] {
|
||||
columns() {
|
||||
/* return all column accessors as an array, in offset order */
|
||||
return [...this.__columnsAccessor];
|
||||
}
|
||||
|
||||
col(columnLabel: LabelType): DataframeColumn {
|
||||
col(columnLabel) {
|
||||
/*
|
||||
Return accessor bound to a column. Allows random row access
|
||||
based upon the row indexing. Returns undefined if the
|
||||
@@ -807,44 +694,36 @@ class Dataframe {
|
||||
See __compile() for the functions available in a column accessor.
|
||||
*/
|
||||
const coff = this.colIndex.getOffset(columnLabel);
|
||||
if (coff === -1) throw RangeError("Unknown label.");
|
||||
return this.__columnsAccessor[coff];
|
||||
}
|
||||
|
||||
icol(columnOffset: OffsetType): DataframeColumn {
|
||||
icol(columnOffset) {
|
||||
/*
|
||||
Return column accessor by offset.
|
||||
*/
|
||||
if (
|
||||
Number.isInteger(columnOffset) &&
|
||||
columnOffset >= 0 &&
|
||||
columnOffset < this.__columnsAccessor.length
|
||||
) {
|
||||
return this.__columnsAccessor[columnOffset];
|
||||
}
|
||||
throw new RangeError("Unknown offset.");
|
||||
return Number.isInteger(columnOffset)
|
||||
? this.__columnsAccessor[columnOffset]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
at(r: LabelType, c: LabelType): DataframeValue {
|
||||
at(r, c) {
|
||||
/*
|
||||
Access a single value, for a row/col label pair.
|
||||
|
||||
For performance reasons, there are no bounds or existence
|
||||
For performance reasons, there are no bounds or existance
|
||||
checks on labels, and no defined behavior when these are supplied.
|
||||
May return undefined, throw an Error, or do something else for
|
||||
non-existent labels. If you want predictable out-of-bounds
|
||||
non-existant labels. If you want predictable out-of-bounds
|
||||
behavior, use has(), eg,
|
||||
|
||||
const myVal = df.has(r,l) ? df.at(r,l) : undefined;
|
||||
*/
|
||||
const coff = this.colIndex.getOffset(c);
|
||||
const roff = this.rowIndex.getOffset(r);
|
||||
if (coff === undefined || roff === undefined)
|
||||
throw new RangeError("Unknown row or column label.");
|
||||
return this.__columns[coff][roff];
|
||||
}
|
||||
|
||||
iat(r: OffsetType, c: OffsetType): DataframeValue {
|
||||
iat(r, c) {
|
||||
/*
|
||||
Access a single value, for a row/col offset (integer) position.
|
||||
|
||||
@@ -854,12 +733,10 @@ class Dataframe {
|
||||
|
||||
const myVal = df.ihas(r, c) ? df.iat(r, c) : undefined;
|
||||
*/
|
||||
if (c >= 0 && c < this.dims[1] && r >= 0 && r < this.dims[0])
|
||||
return this.__columns[c][r];
|
||||
throw new RangeError("Unknown row or column index.");
|
||||
return this.__columns[c][r];
|
||||
}
|
||||
|
||||
has(r: LabelType, c: LabelType): boolean {
|
||||
has(r, c) {
|
||||
/*
|
||||
Test if row/col labels exist in the dataframe - returns true/false
|
||||
*/
|
||||
@@ -869,7 +746,7 @@ class Dataframe {
|
||||
return coff >= 0 && coff < nCols && roff >= 0 && roff < nRows;
|
||||
}
|
||||
|
||||
ihas(r: number, c: number): boolean {
|
||||
ihas(r, c) {
|
||||
/*
|
||||
Test if row/col offset (integer) position exists in the
|
||||
dataframe - returns true/false
|
||||
@@ -885,23 +762,14 @@ class Dataframe {
|
||||
);
|
||||
}
|
||||
|
||||
hasCol(c: LabelType): boolean {
|
||||
hasCol(c) {
|
||||
/*
|
||||
Test if col label exists - return true/false
|
||||
*/
|
||||
const coff = this.colIndex.getOffset(c);
|
||||
return coff !== -1;
|
||||
return !!this.col(c);
|
||||
}
|
||||
|
||||
ihasCol(i: number): boolean {
|
||||
/*
|
||||
Test if col offset exists - return true/false
|
||||
*/
|
||||
const [, nCols] = this.dims;
|
||||
return i >= 0 && i < nCols;
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
isEmpty() {
|
||||
/*
|
||||
Return true if this is an empty dataframe, ie, has dimensions [0,0]
|
||||
*/
|
||||
@@ -916,7 +784,7 @@ class Dataframe {
|
||||
add these as useful.
|
||||
****/
|
||||
|
||||
mapColumns(callback: MapColumnsCallbackFn): Dataframe {
|
||||
mapColumns(callback) {
|
||||
/*
|
||||
map all columns in the dataframe, returning a new dataframe comprised of the
|
||||
return values, with the same index as the original dataframe.
|
||||
@@ -926,10 +794,10 @@ class Dataframe {
|
||||
const columns = this.__columns.map((colData, colIdx) =>
|
||||
callback(colData, colIdx, this)
|
||||
);
|
||||
const columnsAccessor: (DataframeColumn | null)[] = columns.map((c, idx) =>
|
||||
this.__columns[idx] === c ? this.__columnsAccessor[idx] : null
|
||||
const columnsAccessor = columns.map((c, idx) =>
|
||||
this.__columns[idx] === c ? this.__columnsAccessor[idx] : undefined
|
||||
);
|
||||
return new (this.constructor as DataframeConstructor)(
|
||||
return new this.constructor(
|
||||
this.dims,
|
||||
columns,
|
||||
this.rowIndex,
|
||||
@@ -937,6 +805,29 @@ class Dataframe {
|
||||
columnsAccessor
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Map & reduce of column or row
|
||||
|
||||
TODO remainder of map/reduce functions: mapCol, mapRow, reduceRow, ...
|
||||
*/
|
||||
/* comment out until we have a use for this
|
||||
|
||||
reduceCol(clabel, callback, initialValue) {
|
||||
const coff = this.colIndex.getOffset(clabel);
|
||||
const column = this.__columns[coff];
|
||||
let start = 0;
|
||||
let acc = initialValue;
|
||||
if (initialValue === undefined) {
|
||||
acc = column[0];
|
||||
start = 1;
|
||||
}
|
||||
for (let i = start, l = column.length; i < l; i += 1) {
|
||||
acc = callback(acc, column[i]);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
export default Dataframe;
|
||||
@@ -1,27 +1,15 @@
|
||||
/*
|
||||
Dataframe histogram
|
||||
*/
|
||||
import { NumberArray } from "../../common/types/arraytypes";
|
||||
import {
|
||||
DataframeColumn,
|
||||
ContinuousHistogram,
|
||||
ContinuousHistogramBy,
|
||||
CategoricalHistogram,
|
||||
CategoricalHistogramBy,
|
||||
} from "./types";
|
||||
import { isTypedArray } from "./util";
|
||||
|
||||
export function histogramContinuous(
|
||||
column: DataframeColumn,
|
||||
bins: number,
|
||||
domain: [number, number]
|
||||
): ContinuousHistogram {
|
||||
function _histogramContinuous(column, bins, min, max) {
|
||||
const valBins = new Array(bins).fill(0);
|
||||
if (!column) {
|
||||
return valBins;
|
||||
}
|
||||
const [min, max] = domain;
|
||||
const binWidth = (max - min) / bins;
|
||||
const colArray: NumberArray = column.asArray() as NumberArray;
|
||||
const colArray = column.asArray();
|
||||
for (let r = 0, len = colArray.length; r < len; r += 1) {
|
||||
const val = colArray[r];
|
||||
if (val <= max && val >= min) {
|
||||
@@ -33,20 +21,14 @@ export function histogramContinuous(
|
||||
return valBins;
|
||||
}
|
||||
|
||||
export function histogramContinuousBy(
|
||||
column: DataframeColumn,
|
||||
bins: number,
|
||||
domain: [number, number],
|
||||
by: DataframeColumn
|
||||
): ContinuousHistogramBy {
|
||||
function _histogramContinuousBy(column, bins, min, max, by) {
|
||||
const byMap = new Map();
|
||||
if (!column || !by) {
|
||||
return byMap;
|
||||
}
|
||||
const [min, max] = domain;
|
||||
const binWidth = (max - min) / bins;
|
||||
const byArray = by.asArray();
|
||||
const colArray = column.asArray() as NumberArray;
|
||||
const colArray = column.asArray();
|
||||
for (let r = 0, len = colArray.length; r < len; r += 1) {
|
||||
const byBin = byArray[r];
|
||||
let valBins = byMap.get(byBin);
|
||||
@@ -64,9 +46,7 @@ export function histogramContinuousBy(
|
||||
return byMap;
|
||||
}
|
||||
|
||||
export function histogramCategorical(
|
||||
column: DataframeColumn
|
||||
): CategoricalHistogram {
|
||||
function _histogramCategorical(column) {
|
||||
const valMap = new Map();
|
||||
if (!column) {
|
||||
return valMap;
|
||||
@@ -83,10 +63,7 @@ export function histogramCategorical(
|
||||
return valMap;
|
||||
}
|
||||
|
||||
export function histogramCategoricalBy(
|
||||
column: DataframeColumn,
|
||||
by: DataframeColumn
|
||||
): CategoricalHistogramBy {
|
||||
function _histogramCategoricalBy(column, by) {
|
||||
const byMap = new Map();
|
||||
if (!column || !by) {
|
||||
return byMap;
|
||||
@@ -110,38 +87,49 @@ export function histogramCategoricalBy(
|
||||
return byMap;
|
||||
}
|
||||
|
||||
/*
|
||||
Count category occupancy. Optional group-by category.
|
||||
*/
|
||||
export function histogramCategorical(column, by) {
|
||||
if (by && isTypedArray(by)) {
|
||||
throw new Error("Group by column must be categorical");
|
||||
}
|
||||
return by
|
||||
? _histogramCategoricalBy(column, by)
|
||||
: _histogramCategorical(column);
|
||||
}
|
||||
|
||||
/*
|
||||
Memoization hash for histogramCategorical()
|
||||
*/
|
||||
export function hashCategorical(column: DataframeColumn): string {
|
||||
export function hashCategorical(column, by) {
|
||||
if (by) {
|
||||
return `${column.__id}:${by.__id}`;
|
||||
}
|
||||
return `${column.__id}:`;
|
||||
}
|
||||
|
||||
export function hashCategoricalBy(
|
||||
column: DataframeColumn,
|
||||
by: DataframeColumn
|
||||
): string {
|
||||
return `${column.__id}:${by.__id}`;
|
||||
/*
|
||||
Bin counts for continuous/scalar values, with optional group-by category.
|
||||
Values outside domain are ignored.
|
||||
*/
|
||||
export function histogramContinuous(column, bins = 40, domain = [0, 1], by) {
|
||||
if (by && isTypedArray(by)) {
|
||||
throw new Error("Group by column must be categorical");
|
||||
}
|
||||
const [min, max] = domain;
|
||||
return by
|
||||
? _histogramContinuousBy(column, bins, min, max, by)
|
||||
: _histogramContinuous(column, bins, min, max);
|
||||
}
|
||||
|
||||
/*
|
||||
Memoization hash for histogramContinuous
|
||||
*/
|
||||
export function hashContinuous(
|
||||
column: DataframeColumn,
|
||||
bins: number,
|
||||
domain: [number, number]
|
||||
): string {
|
||||
export function hashContinuous(column, bins = "", domain = [0, 0], by) {
|
||||
const [min, max] = domain;
|
||||
if (by) {
|
||||
return `${column.__id}:${bins}:${min}:${max}:${by.__id}`;
|
||||
}
|
||||
return `${column.__id}::${bins}:${min}:${max}`;
|
||||
}
|
||||
|
||||
export function hashContinuousBy(
|
||||
column: DataframeColumn,
|
||||
bins: number,
|
||||
domain: [number, number],
|
||||
by: DataframeColumn
|
||||
): string {
|
||||
const [min, max] = domain;
|
||||
return `${column.__id}:${bins}:${min}:${max}:${by.__id}`;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export { default as Dataframe } from "./dataframe";
|
||||
export {
|
||||
DenseInt32Index,
|
||||
IdentityInt32Index,
|
||||
KeyIndex,
|
||||
isLabelIndex,
|
||||
} from "./labelIndex";
|
||||
export { default as dataframeMemo } from "./cache";
|
||||
@@ -1,21 +0,0 @@
|
||||
export { default as Dataframe } from "./dataframe";
|
||||
export {
|
||||
DenseInt32Index,
|
||||
IdentityInt32Index,
|
||||
KeyIndex,
|
||||
isLabelIndex,
|
||||
} from "./labelIndex";
|
||||
export { default as dataframeMemo } from "./cache";
|
||||
export type {
|
||||
LabelType,
|
||||
DataframeValue,
|
||||
DataframeValueArray,
|
||||
DataframeColumn,
|
||||
ContinuousHistogram,
|
||||
ContinuousHistogramBy,
|
||||
CategoricalHistogram,
|
||||
CategoricalHistogramBy,
|
||||
ContinuousColumnSummary,
|
||||
CategoricalColumnSummary,
|
||||
} from "./types";
|
||||
export type { LabelIndex } from "./labelIndex";
|
||||
@@ -0,0 +1,396 @@
|
||||
/* eslint-disable max-classes-per-file -- Classes are interrelated*/
|
||||
/**
|
||||
Label indexing - map a label to & from an integer offset. See Dataframe
|
||||
for how this is used.
|
||||
**/
|
||||
|
||||
import { rangeFill as fillRange } from "../range";
|
||||
import { __getMemoId } from "./util";
|
||||
|
||||
/*
|
||||
Private utility functions
|
||||
*/
|
||||
function extent(tarr) {
|
||||
let min = 0x7fffffff;
|
||||
let max = ~min; // eslint-disable-line no-bitwise -- Establishes 0 of same size
|
||||
for (let i = 0, l = tarr.length; i < l; i += 1) {
|
||||
const v = tarr[i];
|
||||
if (v < min) {
|
||||
min = v;
|
||||
}
|
||||
if (v > max) {
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
class IdentityInt32Index {
|
||||
/*
|
||||
identity/noop index, with small assumptions that labels are int32
|
||||
*/
|
||||
constructor(maxOffset) {
|
||||
this.maxOffset = maxOffset;
|
||||
}
|
||||
|
||||
get __id() {
|
||||
return `IdentityInt32Index_${this.maxOffset}`;
|
||||
}
|
||||
|
||||
labels() {
|
||||
// memoize
|
||||
const k = fillRange(new Int32Array(this.maxOffset));
|
||||
this.labels = function labels() {
|
||||
return k;
|
||||
};
|
||||
return k;
|
||||
}
|
||||
|
||||
getOffset(i) {
|
||||
// label to offset
|
||||
return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined;
|
||||
}
|
||||
|
||||
getOffsets(arr) {
|
||||
// labels to offsets
|
||||
return arr.map((i) => this.getOffset(i));
|
||||
}
|
||||
|
||||
getLabel(i) {
|
||||
// offset to label
|
||||
return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined;
|
||||
}
|
||||
|
||||
getLabels(arr) {
|
||||
// offsets to labels
|
||||
return arr.map((i) => this.getLabel(i));
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.maxOffset;
|
||||
}
|
||||
|
||||
__promote(labelArray) {
|
||||
/*
|
||||
time/space decision - based on the resulting density
|
||||
*/
|
||||
const [minLabel, maxLabel] = extent(labelArray);
|
||||
if (minLabel === 0 && maxLabel === labelArray.length - 1)
|
||||
return new IdentityInt32Index(labelArray.length);
|
||||
|
||||
const labelSpaceSize = maxLabel - minLabel + 1;
|
||||
const density = labelSpaceSize / this.maxOffset;
|
||||
/* 0.1 is a magic number, that needs testing to optimize */
|
||||
if (density < 0.1) {
|
||||
return new KeyIndex(labelArray);
|
||||
}
|
||||
return new DenseInt32Index(labelArray, [minLabel, maxLabel]);
|
||||
}
|
||||
|
||||
subset(labels) {
|
||||
/* validate subset */
|
||||
const { maxOffset } = this;
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
if (!Number.isInteger(label) || label < 0 || label >= maxOffset)
|
||||
throw new RangeError(`offset or label: ${label}`);
|
||||
}
|
||||
return this.__promote(labels);
|
||||
}
|
||||
|
||||
/* identity index - labels are offsets */
|
||||
isubset(offsets) {
|
||||
return this.subset(offsets);
|
||||
}
|
||||
|
||||
/* identity index - labels are offsets */
|
||||
isubsetMask(mask) {
|
||||
let count = 0;
|
||||
if (mask.length !== this.maxOffset) {
|
||||
throw new RangeError("mask has invalid length for index");
|
||||
}
|
||||
let labels = new Int32Array(mask.length);
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
if (mask[i]) {
|
||||
labels[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
labels = labels.slice(0, count);
|
||||
return this.subset(labels);
|
||||
}
|
||||
|
||||
withLabel(label) {
|
||||
if (label === this.maxOffset) {
|
||||
return new IdentityInt32Index(label + 1);
|
||||
}
|
||||
return this.__promote([...this.labels(), label]);
|
||||
}
|
||||
|
||||
withLabels(labels) {
|
||||
return this.__promote([...this.labels(), ...labels]);
|
||||
}
|
||||
|
||||
dropLabel(label) {
|
||||
if (label === this.maxOffset - 1) {
|
||||
return new IdentityInt32Index(label);
|
||||
}
|
||||
const labelArray = [...this.labels()];
|
||||
labelArray.splice(labelArray.indexOf(label), 1);
|
||||
return this.__promote(labelArray);
|
||||
}
|
||||
}
|
||||
|
||||
class DenseInt32Index {
|
||||
/*
|
||||
DenseInt32Index indexes integer labels, and uses Int32Array typed arrays
|
||||
for both forward and reverse indexing. This means that the min/max range
|
||||
of the forward index labels must be known a priori (so that the index
|
||||
array can be pre-allocated).
|
||||
*/
|
||||
constructor(labels, labelRange = null) {
|
||||
if (labels.constructor !== Int32Array) {
|
||||
labels = new Int32Array(labels);
|
||||
}
|
||||
|
||||
if (!labelRange) {
|
||||
labelRange = extent(labels);
|
||||
}
|
||||
const [minLabel, maxLabel] = labelRange;
|
||||
const labelSpaceSize = maxLabel - minLabel + 1;
|
||||
const index = new Int32Array(labelSpaceSize).fill(-1);
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
index[label - minLabel] = i;
|
||||
}
|
||||
|
||||
this.minLabel = minLabel;
|
||||
this.rindex = labels;
|
||||
this.index = index;
|
||||
this.__id = __getMemoId();
|
||||
this.__compile();
|
||||
}
|
||||
|
||||
__compile() {
|
||||
const { minLabel, index, rindex } = this;
|
||||
this.getOffset = function getOffset(l) {
|
||||
if (!Number.isInteger(l)) return undefined;
|
||||
const offset = index[l - minLabel];
|
||||
return offset === -1 ? undefined : offset;
|
||||
};
|
||||
|
||||
this.getOffsets = function getOffsets(arr) {
|
||||
return arr.map((i) => this.getOffset(i));
|
||||
};
|
||||
|
||||
this.getLabel = function getLabel(i) {
|
||||
return Number.isInteger(i) ? rindex[i] : undefined;
|
||||
};
|
||||
|
||||
this.getLabels = function getLabels(arr) {
|
||||
return arr.map((i) => this.getLabel(i));
|
||||
};
|
||||
}
|
||||
|
||||
labels() {
|
||||
return this.rindex;
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.rindex.length;
|
||||
}
|
||||
|
||||
__promote(labelArray) {
|
||||
/*
|
||||
time/space decision - if we are going to use less than 10% of the
|
||||
dense index space, switch to a KeyIndex (which is slower, but uses
|
||||
less memory for sparse label spaces).
|
||||
*/
|
||||
const [minLabel, maxLabel] = extent(labelArray);
|
||||
const labelSpaceSize = maxLabel - minLabel + 1;
|
||||
const density = labelSpaceSize / this.rindex.length;
|
||||
/* 0.1 is a magic number, that needs testing to optimize */
|
||||
if (density < 0.1) {
|
||||
return new KeyIndex(labelArray);
|
||||
}
|
||||
return new DenseInt32Index(labelArray, [minLabel, maxLabel]);
|
||||
}
|
||||
|
||||
subset(labels) {
|
||||
/* validate subset */
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
const offset = this.getOffset(label);
|
||||
if (offset === undefined || offset === -1)
|
||||
throw new RangeError(`unknown label: ${label}`);
|
||||
}
|
||||
return this.__promote(labels);
|
||||
}
|
||||
|
||||
isubset(offsets) {
|
||||
/* validate subset */
|
||||
const { rindex } = this;
|
||||
const maxOffset = rindex.length;
|
||||
const labels = new Int32Array(offsets.length);
|
||||
for (let i = 0, l = offsets.length; i < l; i += 1) {
|
||||
const offset = offsets[i];
|
||||
if (offset < 0 || offset >= maxOffset)
|
||||
throw new RangeError(`out of bounds offset: ${offset}`);
|
||||
labels[i] = rindex[offset];
|
||||
}
|
||||
return this.__promote(labels);
|
||||
}
|
||||
|
||||
isubsetMask(mask) {
|
||||
const { rindex } = this;
|
||||
if (mask.length !== rindex.length)
|
||||
throw new RangeError("mask has invalid length for index");
|
||||
let count = 0;
|
||||
let labels = new Int32Array(mask.length);
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
if (mask[i]) {
|
||||
labels[count] = rindex[i];
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
labels = labels.slice(0, count);
|
||||
return this.__promote(labels);
|
||||
}
|
||||
|
||||
withLabel(label) {
|
||||
return this.__promote([...this.labels(), label]);
|
||||
}
|
||||
|
||||
withLabels(labels) {
|
||||
return this.__promote([...this.labels(), ...labels]);
|
||||
}
|
||||
|
||||
dropLabel(label) {
|
||||
const labelArray = [...this.labels()];
|
||||
labelArray.splice(labelArray.indexOf(label), 1);
|
||||
return this.__promote(labelArray);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyIndex {
|
||||
/*
|
||||
KeyIndex indexes arbitrary JS primitive types, and uses a Map()
|
||||
as its core data structure.
|
||||
*/
|
||||
constructor(labels) {
|
||||
const index = new Map();
|
||||
if (labels === undefined) {
|
||||
labels = [];
|
||||
}
|
||||
const rindex = labels;
|
||||
labels.forEach((v, i) => {
|
||||
index.set(v, i);
|
||||
});
|
||||
|
||||
if (index.size !== rindex.length) {
|
||||
/* if true, there was a duplicate in the keys */
|
||||
throw new Error("duplicate label provided to KeyIndex");
|
||||
}
|
||||
|
||||
this.index = index;
|
||||
this.rindex = rindex;
|
||||
this.__id = __getMemoId();
|
||||
this.__compile();
|
||||
}
|
||||
|
||||
__compile() {
|
||||
const { index, rindex } = this;
|
||||
this.getOffset = function getOffset(k) {
|
||||
return index.get(k);
|
||||
};
|
||||
|
||||
this.getOffsets = function getOffsets(arr) {
|
||||
return arr.map((l) => this.getOffset(l));
|
||||
};
|
||||
|
||||
this.getLabel = function getLabel(i) {
|
||||
return Number.isInteger(i) ? rindex[i] : undefined;
|
||||
};
|
||||
|
||||
this.getLabels = function getLabels(arr) {
|
||||
return arr.map((i) => this.getLabel(i));
|
||||
};
|
||||
}
|
||||
|
||||
labels() {
|
||||
return this.rindex;
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.rindex.length;
|
||||
}
|
||||
|
||||
subset(labels) {
|
||||
/* validate subset */
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
const offset = this.getOffset(label);
|
||||
if (offset === undefined || offset === -1) {
|
||||
throw new RangeError(`unknown label: ${label}`);
|
||||
}
|
||||
}
|
||||
|
||||
return new KeyIndex(labels);
|
||||
}
|
||||
|
||||
isubset(offsets) {
|
||||
const { rindex } = this;
|
||||
const maxOffset = rindex.length;
|
||||
const labels = new Array(offsets.length);
|
||||
for (let i = 0, l = offsets.length; i < l; i += 1) {
|
||||
const offset = offsets[i];
|
||||
if (offset < 0 || offset >= maxOffset)
|
||||
throw new RangeError(`out of bounds offset: ${offset}`);
|
||||
labels[i] = rindex[offset];
|
||||
}
|
||||
|
||||
return new KeyIndex(labels);
|
||||
}
|
||||
|
||||
isubsetMask(mask) {
|
||||
const { rindex } = this;
|
||||
if (mask.length !== rindex.length)
|
||||
throw new RangeError("mask has invalid length for index");
|
||||
let labels = new Array(mask.length);
|
||||
let count = 0;
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
if (mask[i]) {
|
||||
labels[count] = rindex[i];
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
labels = labels.slice(0, count);
|
||||
return new KeyIndex(labels);
|
||||
}
|
||||
|
||||
withLabel(label) {
|
||||
return new KeyIndex([...this.rindex, label]);
|
||||
}
|
||||
|
||||
withLabels(labels) {
|
||||
return new KeyIndex([...this.rindex, ...labels]);
|
||||
}
|
||||
|
||||
dropLabel(label) {
|
||||
const idx = this.rindex.indexOf(label);
|
||||
const labelArray = [...this.rindex];
|
||||
labelArray.splice(idx, 1);
|
||||
return new KeyIndex(labelArray);
|
||||
}
|
||||
}
|
||||
|
||||
function isLabelIndex(i) {
|
||||
return (
|
||||
i instanceof IdentityInt32Index ||
|
||||
i instanceof DenseInt32Index ||
|
||||
i instanceof KeyIndex
|
||||
);
|
||||
}
|
||||
|
||||
export { DenseInt32Index, IdentityInt32Index, KeyIndex, isLabelIndex };
|
||||
/* eslint-enable max-classes-per-file -- enable*/
|
||||
@@ -1,495 +0,0 @@
|
||||
/* eslint-disable max-classes-per-file -- Classes are interrelated*/
|
||||
|
||||
/**
|
||||
Label indexing - map a label to & from an integer offset. See Dataframe
|
||||
for how this is used.
|
||||
**/
|
||||
|
||||
import { rangeFill as fillRange } from "../range";
|
||||
import { __getMemoId } from "./util";
|
||||
import { OffsetArray, LabelType, LabelArray, GenericLabelArray } from "./types";
|
||||
|
||||
export abstract class LabelIndexBase {
|
||||
readonly __id: string; // memoization helper
|
||||
|
||||
constructor(id: string) {
|
||||
this.__id = id;
|
||||
}
|
||||
|
||||
abstract labels(): LabelArray;
|
||||
|
||||
/**
|
||||
* Look up the offset for the label.
|
||||
*
|
||||
* @param label - label to look up
|
||||
* @returns - offset number or -1 if not found.
|
||||
*/
|
||||
abstract getOffset(label: LabelType): number;
|
||||
|
||||
getOffsets(labels: LabelArray): Int32Array {
|
||||
// labels to offsets
|
||||
const result = new Int32Array(labels.length);
|
||||
for (let i = 0; i < labels.length; i += 1) {
|
||||
result[i] = this.getOffset(labels[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the label for the offset.
|
||||
*
|
||||
* @param offset - offset to look up
|
||||
* @returns - label or undefined if not found.
|
||||
*/
|
||||
abstract getLabel(offset: number): LabelType | undefined;
|
||||
|
||||
getLabels(offsets: OffsetArray): (LabelType | undefined)[] {
|
||||
// offsets to labels
|
||||
const result = new Array(offsets.length);
|
||||
for (let i = 0; i < offsets.length; i += 1) {
|
||||
result[i] = this.getLabel(offsets[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
abstract size(): number;
|
||||
|
||||
abstract subset(labels: LabelArray): LabelIndexBase;
|
||||
|
||||
abstract isubset(offsets: OffsetArray): LabelIndexBase;
|
||||
|
||||
abstract isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase;
|
||||
|
||||
abstract withLabel(label: LabelType): LabelIndexBase;
|
||||
|
||||
abstract withLabels(labels: LabelArray): LabelIndexBase;
|
||||
|
||||
abstract dropLabel(label: LabelType): LabelIndexBase;
|
||||
}
|
||||
|
||||
export class IdentityInt32Index extends LabelIndexBase {
|
||||
readonly maxOffset: number;
|
||||
|
||||
/*
|
||||
identity/noop index, with small assumptions that labels are int32
|
||||
*/
|
||||
constructor(maxOffset: number) {
|
||||
super(`IdentityInt32Index_${maxOffset}`);
|
||||
this.maxOffset = maxOffset;
|
||||
}
|
||||
|
||||
labels(): LabelArray {
|
||||
// memoize
|
||||
const k = fillRange(new Int32Array(this.maxOffset));
|
||||
this.labels = function labels() {
|
||||
return k;
|
||||
};
|
||||
return k;
|
||||
}
|
||||
|
||||
getOffset(label: LabelType): number {
|
||||
// label to offset
|
||||
return Number.isInteger(label) && label >= 0 && label < this.maxOffset
|
||||
? (label as number)
|
||||
: -1;
|
||||
}
|
||||
|
||||
getLabel(offset: number): number | undefined {
|
||||
// offset to label
|
||||
return Number.isInteger(offset) && offset >= 0 && offset < this.maxOffset
|
||||
? offset
|
||||
: undefined;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.maxOffset;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__promote(labelArray: LabelArray, allInts: boolean): LabelIndexBase {
|
||||
/*
|
||||
time/space decision - based on the resulting density
|
||||
*/
|
||||
if (labelArray.length === 0) return new KeyIndex(Array.from(labelArray));
|
||||
if (allInts) {
|
||||
const [minLabel, maxLabel] = extent(
|
||||
labelArray as GenericLabelArray<number> // safe, as allInts is true
|
||||
);
|
||||
if (minLabel === 0 && maxLabel === labelArray.length - 1)
|
||||
return new IdentityInt32Index(labelArray.length);
|
||||
|
||||
const labelSpaceSize = maxLabel - minLabel + 1;
|
||||
const density = labelSpaceSize / this.maxOffset;
|
||||
/* 0.1 is a magic number which needs testing to optimize */
|
||||
if (density < 0.1) {
|
||||
return new KeyIndex(Array.from(labelArray));
|
||||
}
|
||||
return new DenseInt32Index(labelArray as GenericLabelArray<number>, [
|
||||
minLabel,
|
||||
maxLabel,
|
||||
]);
|
||||
}
|
||||
return new KeyIndex(Array.from(labelArray));
|
||||
}
|
||||
|
||||
subset(labels: LabelArray): LabelIndexBase {
|
||||
/* validate subset */
|
||||
const { maxOffset } = this;
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
if (!Number.isInteger(label) || label < 0 || label >= maxOffset)
|
||||
throw new RangeError(`label: ${label}`);
|
||||
}
|
||||
return this.__promote(labels, true);
|
||||
}
|
||||
|
||||
/* identity index - labels are offsets */
|
||||
isubset(offsets: OffsetArray): LabelIndexBase {
|
||||
/* validate isubset */
|
||||
const { maxOffset } = this;
|
||||
for (let i = 0, l = offsets.length; i < l; i += 1) {
|
||||
const offset = offsets[i];
|
||||
if (!Number.isInteger(offset) || offset < 0 || offset >= maxOffset)
|
||||
throw new RangeError(`offset: ${offset}`);
|
||||
}
|
||||
if (!(offsets instanceof Int32Array)) {
|
||||
offsets = new Int32Array(offsets);
|
||||
}
|
||||
return this.__promote(offsets, true);
|
||||
}
|
||||
|
||||
/* identity index - labels are offsets */
|
||||
isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase {
|
||||
let count = 0;
|
||||
if (mask.length !== this.maxOffset) {
|
||||
throw new RangeError("mask has invalid length for index");
|
||||
}
|
||||
let labels = new Int32Array(mask.length);
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
if (mask[i]) {
|
||||
labels[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
labels = labels.slice(0, count);
|
||||
return this.subset(labels);
|
||||
}
|
||||
|
||||
withLabel(label: LabelType): LabelIndexBase {
|
||||
if (label === this.maxOffset) {
|
||||
return new IdentityInt32Index(label + 1);
|
||||
}
|
||||
return this.__promote([...this.labels(), label], Number.isInteger(label));
|
||||
}
|
||||
|
||||
withLabels(labels: LabelArray): LabelIndexBase {
|
||||
return this.__promote(
|
||||
[...this.labels(), ...labels],
|
||||
labels.every(Number.isInteger)
|
||||
);
|
||||
}
|
||||
|
||||
dropLabel(label: LabelType): LabelIndexBase {
|
||||
if (!Number.isInteger(label) || label < 0 || label > this.maxOffset - 1)
|
||||
throw new RangeError("Invalid label.");
|
||||
if (label === this.maxOffset - 1) {
|
||||
return new IdentityInt32Index(label);
|
||||
}
|
||||
const labelArray = [...this.labels()];
|
||||
labelArray.splice(labelArray.indexOf(label as number), 1);
|
||||
return this.__promote(labelArray, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class DenseInt32Index extends LabelIndexBase {
|
||||
getLabel: (offset: number) => number | undefined;
|
||||
|
||||
getOffset: (label: LabelType) => number;
|
||||
|
||||
index: Int32Array;
|
||||
|
||||
minLabel: number;
|
||||
|
||||
rindex: Int32Array;
|
||||
|
||||
/*
|
||||
DenseInt32Index indexes integer labels, and uses Int32Array typed arrays
|
||||
for both forward and reverse indexing. This means that the min/max range
|
||||
of the forward index labels must be known a priori (so that the index
|
||||
array can be pre-allocated).
|
||||
*/
|
||||
constructor(
|
||||
labels: GenericLabelArray<number>,
|
||||
labelRange?: [number, number]
|
||||
) {
|
||||
super(__getMemoId());
|
||||
const int32Labels =
|
||||
labels instanceof Int32Array ? labels : new Int32Array(labels);
|
||||
if (!labelRange) {
|
||||
labelRange = extent(int32Labels);
|
||||
}
|
||||
const [minLabel, maxLabel] = labelRange;
|
||||
const labelSpaceSize = maxLabel - minLabel + 1;
|
||||
const index = new Int32Array(labelSpaceSize).fill(-1);
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
index[label - minLabel] = i;
|
||||
}
|
||||
|
||||
this.minLabel = minLabel;
|
||||
this.rindex = int32Labels;
|
||||
this.index = index;
|
||||
|
||||
this.getOffset = function getOffset(label: LabelType) {
|
||||
if (!Number.isInteger(label)) return -1;
|
||||
const lblIdx: number = <number>label - minLabel;
|
||||
if (lblIdx < 0 || lblIdx >= index.length) return -1;
|
||||
const offset = index[lblIdx];
|
||||
return offset;
|
||||
};
|
||||
|
||||
this.getLabel = function getLabel(offset: number) {
|
||||
return Number.isInteger(offset) ? labels[offset] : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
labels(): LabelArray {
|
||||
return this.rindex;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.rindex.length;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__promote(labelArray: LabelArray, allInts: boolean): LabelIndexBase {
|
||||
/*
|
||||
time/space decision - if we are going to use less than 10% of the
|
||||
dense index space, switch to a KeyIndex (which is slower, but uses
|
||||
less memory for sparse label spaces).
|
||||
*/
|
||||
if (labelArray.length === 0) return new KeyIndex(Array.from(labelArray));
|
||||
if (allInts) {
|
||||
if (!(labelArray instanceof Int32Array)) {
|
||||
labelArray = new Int32Array(labelArray as number[]);
|
||||
}
|
||||
const [minLabel, maxLabel] = extent(
|
||||
labelArray as GenericLabelArray<number> // safe, as allInts is true
|
||||
);
|
||||
const labelSpaceSize = maxLabel - minLabel + 1;
|
||||
const density = labelSpaceSize / this.rindex.length;
|
||||
/* 0.1 is a magic number, that needs testing to optimize */
|
||||
if (density < 0.1) {
|
||||
return new KeyIndex(Array.from(labelArray));
|
||||
}
|
||||
return new DenseInt32Index(labelArray as GenericLabelArray<number>, [
|
||||
minLabel,
|
||||
maxLabel,
|
||||
]);
|
||||
}
|
||||
return new KeyIndex(Array.from(labelArray));
|
||||
}
|
||||
|
||||
subset(labels: LabelArray): LabelIndexBase {
|
||||
/* validate subset */
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i]; // if not a number, getOffset will error
|
||||
const offset = this.getOffset(label as number);
|
||||
if (offset === -1) throw new RangeError(`unknown label: ${label}`);
|
||||
}
|
||||
return this.__promote(labels as GenericLabelArray<number>, true);
|
||||
}
|
||||
|
||||
isubset(offsets: OffsetArray): LabelIndexBase {
|
||||
/* validate subset */
|
||||
const { rindex } = this;
|
||||
const maxOffset = rindex.length;
|
||||
const labels = new Int32Array(offsets.length);
|
||||
for (let i = 0, l = offsets.length; i < l; i += 1) {
|
||||
const offset = offsets[i];
|
||||
if (offset < 0 || offset >= maxOffset)
|
||||
throw new RangeError(`out of bounds offset: ${offset}`);
|
||||
labels[i] = rindex[offset];
|
||||
}
|
||||
return this.__promote(labels, true);
|
||||
}
|
||||
|
||||
isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase {
|
||||
const { rindex } = this;
|
||||
if (mask.length !== rindex.length)
|
||||
throw new RangeError("mask has invalid length for index");
|
||||
let count = 0;
|
||||
let labels = new Int32Array(mask.length);
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
if (mask[i]) {
|
||||
labels[count] = rindex[i];
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
labels = labels.slice(0, count);
|
||||
return this.__promote(labels, true);
|
||||
}
|
||||
|
||||
withLabel(label: LabelType): LabelIndexBase {
|
||||
return this.__promote([...this.labels(), label], Number.isInteger(label));
|
||||
}
|
||||
|
||||
withLabels(labels: LabelArray): LabelIndexBase {
|
||||
return this.__promote(
|
||||
[...this.labels(), ...labels],
|
||||
labels.every(Number.isInteger)
|
||||
);
|
||||
}
|
||||
|
||||
dropLabel(label: LabelType): LabelIndexBase {
|
||||
if (!Number.isInteger(label)) throw new RangeError("Invalid label.");
|
||||
const labelArray = [...this.labels()];
|
||||
labelArray.splice(labelArray.indexOf(label as number), 1);
|
||||
return this.__promote(
|
||||
new Int32Array(labelArray as GenericLabelArray<number>),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyIndex extends LabelIndexBase {
|
||||
getLabel: (offset: number) => LabelType | undefined;
|
||||
|
||||
getOffset: (label: LabelType) => number | -1;
|
||||
|
||||
index: Map<string | number, number>;
|
||||
|
||||
rindex: (string | number)[];
|
||||
|
||||
/*
|
||||
KeyIndex indexes arbitrary JS primitive types, and uses a Map()
|
||||
as its core data structure.
|
||||
*/
|
||||
constructor(labels: Array<string | number>) {
|
||||
super(__getMemoId());
|
||||
const index = new Map<string | number, number>();
|
||||
if (labels === undefined) {
|
||||
labels = [];
|
||||
}
|
||||
if (!Array.isArray(labels)) {
|
||||
labels = Array.from(labels);
|
||||
}
|
||||
const rindex = labels;
|
||||
labels.forEach((v, i) => {
|
||||
index.set(v, i);
|
||||
});
|
||||
|
||||
if (index.size !== rindex.length) {
|
||||
/* if true, there was a duplicate in the keys */
|
||||
throw new Error("duplicate label provided to KeyIndex");
|
||||
}
|
||||
|
||||
this.index = index;
|
||||
this.rindex = rindex;
|
||||
|
||||
this.getOffset = function getOffset(label: LabelType) {
|
||||
const offset = index.get(label);
|
||||
if (offset === undefined) return -1;
|
||||
return offset;
|
||||
};
|
||||
|
||||
this.getLabel = function getLabel(offset: number) {
|
||||
return Number.isInteger(offset) ? rindex[offset] : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
labels(): LabelArray {
|
||||
return this.rindex;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.rindex.length;
|
||||
}
|
||||
|
||||
subset(labels: (string | number)[]): LabelIndexBase {
|
||||
/* validate subset */
|
||||
for (let i = 0, l = labels.length; i < l; i += 1) {
|
||||
const label = labels[i];
|
||||
const offset = this.getOffset(label);
|
||||
if (offset === undefined || offset === -1) {
|
||||
throw new RangeError(`unknown label: ${label}`);
|
||||
}
|
||||
}
|
||||
|
||||
return new KeyIndex(labels);
|
||||
}
|
||||
|
||||
isubset(offsets: OffsetArray): LabelIndexBase {
|
||||
const { rindex } = this;
|
||||
const maxOffset = rindex.length;
|
||||
const labels = new Array(offsets.length);
|
||||
for (let i = 0, l = offsets.length; i < l; i += 1) {
|
||||
const offset = offsets[i];
|
||||
if (offset < 0 || offset >= maxOffset)
|
||||
throw new RangeError(`out of bounds offset: ${offset}`);
|
||||
labels[i] = rindex[offset];
|
||||
}
|
||||
|
||||
return new KeyIndex(labels);
|
||||
}
|
||||
|
||||
isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase {
|
||||
const { rindex } = this;
|
||||
if (mask.length !== rindex.length)
|
||||
throw new RangeError("mask has invalid length for index");
|
||||
let labels = new Array(mask.length);
|
||||
let count = 0;
|
||||
for (let i = 0, l = mask.length; i < l; i += 1) {
|
||||
if (mask[i]) {
|
||||
labels[count] = rindex[i];
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
labels = labels.slice(0, count);
|
||||
return new KeyIndex(labels);
|
||||
}
|
||||
|
||||
withLabel(label: LabelType): LabelIndexBase {
|
||||
return new KeyIndex([...this.rindex, label]);
|
||||
}
|
||||
|
||||
withLabels(labels: LabelArray): LabelIndexBase {
|
||||
return new KeyIndex([...this.rindex, ...labels]);
|
||||
}
|
||||
|
||||
dropLabel(label: LabelType): LabelIndexBase {
|
||||
const idx = this.rindex.indexOf(label);
|
||||
const labelArray = [...this.rindex];
|
||||
labelArray.splice(idx, 1);
|
||||
return new KeyIndex(labelArray);
|
||||
}
|
||||
}
|
||||
|
||||
export type LabelIndex = LabelIndexBase;
|
||||
|
||||
export function isLabelIndex(i: unknown): i is LabelIndex {
|
||||
return (
|
||||
i instanceof LabelIndexBase ||
|
||||
i instanceof IdentityInt32Index ||
|
||||
i instanceof DenseInt32Index ||
|
||||
i instanceof KeyIndex
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function extent(tarr: GenericLabelArray<number>): [number, number] {
|
||||
let min = 0x7fffffff;
|
||||
let max = ~min; // eslint-disable-line no-bitwise -- Establishes 0 of same size
|
||||
for (let i = 0, l = tarr.length; i < l; i += 1) {
|
||||
const v = tarr[i];
|
||||
if (v < min) {
|
||||
min = v;
|
||||
}
|
||||
if (v > max) {
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
/* eslint-enable max-classes-per-file -- enable*/
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Private dataframe support functions
|
||||
|
||||
TODO / XXX: for scalar/continuous data, this uses a naive method
|
||||
of computing quantiles. Would be good to switch from sort to
|
||||
partition at some point.
|
||||
*/
|
||||
|
||||
import quantile from "../quantile";
|
||||
import { sortArray } from "../typedCrossfilter/sort";
|
||||
|
||||
// [ 0, 0.01, 0.02, ..., 1.0]
|
||||
const centileNames = new Array(101).fill(0).map((v, idx) => idx / 100);
|
||||
|
||||
export function summarizeContinuous(col) {
|
||||
let min;
|
||||
let max;
|
||||
let nan = 0;
|
||||
let pinf = 0;
|
||||
let ninf = 0;
|
||||
let percentiles;
|
||||
if (col) {
|
||||
// -Inf < finite < Inf < NaN
|
||||
const sortedCol = sortArray(new col.constructor(col));
|
||||
|
||||
// count non-finites, which are at each end of sorted data
|
||||
for (let i = sortedCol.length - 1; i >= 0; i -= 1) {
|
||||
if (!Number.isNaN(sortedCol[i])) {
|
||||
nan = sortedCol.length - i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0, l = sortedCol.length; i < l; i += 1) {
|
||||
if (sortedCol[i] !== Number.NEGATIVE_INFINITY) {
|
||||
ninf = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = sortedCol.length - nan - 1; i >= 0; i -= 1) {
|
||||
if (sortedCol[i] !== Number.POSITIVE_INFINITY) {
|
||||
pinf = sortedCol.length - i - nan - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// compute percentiles on finite data ONLY
|
||||
const sortedColFiniteOnly = sortedCol.slice(
|
||||
ninf,
|
||||
sortedCol.length - nan - pinf
|
||||
);
|
||||
percentiles = quantile(centileNames, sortedColFiniteOnly, true);
|
||||
min = percentiles[0];
|
||||
max = percentiles[100];
|
||||
}
|
||||
return {
|
||||
categorical: false,
|
||||
min,
|
||||
max,
|
||||
nan,
|
||||
pinf,
|
||||
ninf,
|
||||
percentiles,
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeCategorical(col) {
|
||||
const categoryCounts = new Map();
|
||||
if (col) {
|
||||
for (let r = 0, l = col.length; r < l; r += 1) {
|
||||
const val = col[r];
|
||||
let curCount = categoryCounts.get(val);
|
||||
if (curCount === undefined) curCount = 0;
|
||||
categoryCounts.set(val, curCount + 1);
|
||||
}
|
||||
}
|
||||
const sortedCategoryByCounts = new Map(
|
||||
[...categoryCounts.entries()].sort((a, b) => b[1] - a[1])
|
||||
);
|
||||
return {
|
||||
categorical: true,
|
||||
categories: [...sortedCategoryByCounts.keys()],
|
||||
categoryCounts: sortedCategoryByCounts,
|
||||
numCategories: sortedCategoryByCounts.size,
|
||||
};
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
Private dataframe support functions
|
||||
|
||||
TODO / XXX: for scalar/continuous data, this uses a naive method
|
||||
of computing quantiles. Would be good to switch from sort to
|
||||
partition at some point.
|
||||
*/
|
||||
import {
|
||||
AnyArray,
|
||||
GenericArrayConstructor,
|
||||
} from "../../common/types/arraytypes";
|
||||
import { ContinuousColumnSummary, CategoricalColumnSummary } from "./types";
|
||||
import quantile from "../quantile";
|
||||
import { sortArray } from "../typedCrossfilter/sort";
|
||||
|
||||
// [ 0, 0.01, 0.02, ..., 1.0]
|
||||
const centileNames = new Array(101).fill(0).map((_v, idx) => idx / 100);
|
||||
|
||||
export function summarizeContinuous(col: AnyArray): ContinuousColumnSummary {
|
||||
let nan = 0;
|
||||
let pinf = 0;
|
||||
let ninf = 0;
|
||||
|
||||
// -Inf < finite < Inf < NaN
|
||||
const sortedCol = sortArray(
|
||||
new (col.constructor as GenericArrayConstructor<typeof col>)(col)
|
||||
);
|
||||
|
||||
// count non-finites, which are at each end of sorted data
|
||||
for (let i = sortedCol.length - 1; i >= 0; i -= 1) {
|
||||
if (!Number.isNaN(sortedCol[i])) {
|
||||
nan = sortedCol.length - i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0, l = sortedCol.length; i < l; i += 1) {
|
||||
if (sortedCol[i] !== Number.NEGATIVE_INFINITY) {
|
||||
ninf = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = sortedCol.length - nan - 1; i >= 0; i -= 1) {
|
||||
if (sortedCol[i] !== Number.POSITIVE_INFINITY) {
|
||||
pinf = sortedCol.length - i - nan - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// compute percentiles on finite data ONLY
|
||||
const sortedColFiniteOnly = sortedCol.slice(
|
||||
ninf,
|
||||
sortedCol.length - nan - pinf
|
||||
);
|
||||
const percentiles = quantile(centileNames, sortedColFiniteOnly, true);
|
||||
const min = percentiles[0];
|
||||
const max = percentiles[100];
|
||||
|
||||
return {
|
||||
categorical: false,
|
||||
min,
|
||||
max,
|
||||
nan,
|
||||
pinf,
|
||||
ninf,
|
||||
percentiles,
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeCategorical(col: AnyArray): CategoricalColumnSummary {
|
||||
const categoryCounts = new Map();
|
||||
if (col) {
|
||||
for (let r = 0, l = col.length; r < l; r += 1) {
|
||||
const val = col[r];
|
||||
let curCount = categoryCounts.get(val);
|
||||
if (curCount === undefined) curCount = 0;
|
||||
categoryCounts.set(val, curCount + 1);
|
||||
}
|
||||
}
|
||||
const sortedCategoryByCounts = new Map(
|
||||
[...categoryCounts.entries()].sort((a, b) => b[1] - a[1])
|
||||
);
|
||||
return {
|
||||
categorical: true,
|
||||
categories: [...sortedCategoryByCounts.keys()],
|
||||
categoryCounts: sortedCategoryByCounts,
|
||||
numCategories: sortedCategoryByCounts.size,
|
||||
};
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import { TypedArray } from "../../common/types/arraytypes";
|
||||
|
||||
export type LabelType = number | string;
|
||||
|
||||
type CommonProps<A, B> = {
|
||||
[K in keyof A & keyof B]: A[K] | B[K];
|
||||
};
|
||||
export type GenericLabelArray<T> = CommonProps<Array<T>, Int32Array>;
|
||||
export type LabelArray = GenericLabelArray<number | string>;
|
||||
|
||||
export type OffsetType = number;
|
||||
export type OffsetArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| number[];
|
||||
|
||||
export type ContinuousColumnSummary = {
|
||||
categorical: false;
|
||||
min: number;
|
||||
max: number;
|
||||
nan: number;
|
||||
pinf: number;
|
||||
ninf: number;
|
||||
percentiles: number[];
|
||||
};
|
||||
|
||||
export type CategoricalColumnSummary = {
|
||||
categorical: true;
|
||||
categories: (number | string | boolean)[];
|
||||
categoryCounts: Map<number | string | boolean, number>;
|
||||
numCategories: number;
|
||||
};
|
||||
|
||||
export type ColumnSummary = ContinuousColumnSummary | CategoricalColumnSummary;
|
||||
|
||||
export type ContinuousHistogram = number[];
|
||||
export type ContinuousHistogramBy = Map<DataframeValue, ContinuousHistogram>;
|
||||
export type CategoricalHistogram = Map<DataframeValue, number>;
|
||||
export type CategoricalHistogramBy = Map<DataframeValue, CategoricalHistogram>;
|
||||
|
||||
export type DataframeValue = number | string | boolean;
|
||||
|
||||
export type DataframeValueArray = DataframeValue[] | TypedArray;
|
||||
|
||||
export type DataframeColumnGetter = (
|
||||
label: LabelType
|
||||
) => DataframeValue | undefined;
|
||||
|
||||
/**
|
||||
* Interface representing a Dataframe column. Eg, returned by
|
||||
* Dataframe.col().
|
||||
*/
|
||||
export interface DataframeColumn extends DataframeColumnGetter {
|
||||
/**
|
||||
* __id is unique per Dataframe and DataframeColumn, and is used as a memoization key.
|
||||
*/
|
||||
readonly __id: string;
|
||||
|
||||
/**
|
||||
* Boolean indicating if the underlying data supports continuous operations, eg,
|
||||
* summarizeContinuous.
|
||||
*/
|
||||
isContinuous: boolean;
|
||||
|
||||
/**
|
||||
* Return underlying column data as an array-like object.
|
||||
*/
|
||||
asArray: () => DataframeValueArray;
|
||||
|
||||
/**
|
||||
* Continuous data summary. Will throw if !isContinuous.
|
||||
*/
|
||||
summarizeContinuous: () => ContinuousColumnSummary;
|
||||
|
||||
/**
|
||||
* Categorical data summary.
|
||||
*/
|
||||
summarizeCategorical: () => CategoricalColumnSummary;
|
||||
|
||||
/**
|
||||
* Continuous bin/histogram. Will throw if !isContinuous.
|
||||
* @param bins - array of bin boundary fractions, in range [0., 1.]
|
||||
* @param domain - data domain [min, max]
|
||||
*/
|
||||
histogramContinuous: (
|
||||
bins: number,
|
||||
domain: [number, number]
|
||||
) => ContinuousHistogram;
|
||||
|
||||
/**
|
||||
* Continuous bin/histogram, grouped by another categorical column. Will throw if !isContinuous.
|
||||
* @param bins - array of bin boundary fractions, in range [0., 1.]
|
||||
* @param domain - data domain [min, max]
|
||||
* @param by - group by categorical column
|
||||
*/
|
||||
histogramContinuousBy: (
|
||||
bins: number,
|
||||
domain: [number, number],
|
||||
by: DataframeColumn
|
||||
) => ContinuousHistogramBy;
|
||||
|
||||
/**
|
||||
* Categorical bin/histogram.
|
||||
*/
|
||||
histogramCategorical: () => CategoricalHistogram;
|
||||
|
||||
/**
|
||||
* Categorical bin/histogram grouped by another column.
|
||||
* @param by - group by categorical column
|
||||
*/
|
||||
histogramCategoricalBy: (by: DataframeColumn) => CategoricalHistogramBy;
|
||||
|
||||
/**
|
||||
* Return true if the column contains the row label.
|
||||
*/
|
||||
has: (rlabel: LabelType) => boolean;
|
||||
|
||||
/**
|
||||
* Return true if the column contains the row offset. Identical to
|
||||
* (offset >= 0 && offset < dataframe.length)
|
||||
*/
|
||||
ihas: (offset: OffsetType) => boolean;
|
||||
|
||||
/**
|
||||
* Return index of the value, as a _label_. Returns undefined if
|
||||
* not present. *NOTE*: unlike Array.indexOf, does not return an
|
||||
* offset.
|
||||
*/
|
||||
indexOf: (value: DataframeValue) => LabelType | undefined;
|
||||
|
||||
/**
|
||||
* Return the value at the given offset, or undefined if not present.
|
||||
*/
|
||||
iget: (offset: OffsetType) => DataframeValue | undefined;
|
||||
}
|
||||
@@ -2,19 +2,18 @@
|
||||
Private utility code for dataframe
|
||||
*/
|
||||
|
||||
export function callOnceLazy<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- a legitimate use of any.
|
||||
T extends (...args: any[]) => any = (...args: any[]) => any
|
||||
>(fn: T): (...args: Parameters<T>) => ReturnType<T> {
|
||||
export { isTypedArray, isArrayOrTypedArray } from "../typeHelpers";
|
||||
|
||||
export function callOnceLazy(f) {
|
||||
/*
|
||||
call function once, and save the result, regardless of arguments (this is not
|
||||
the same as typical memoization).
|
||||
*/
|
||||
let value: ReturnType<T>;
|
||||
let value;
|
||||
let calledOnce = false;
|
||||
const result = function result(...args: Parameters<T>): ReturnType<T> {
|
||||
const result = function result(...args) {
|
||||
if (!calledOnce) {
|
||||
value = fn(...args);
|
||||
value = f(...args);
|
||||
calledOnce = true;
|
||||
}
|
||||
return value;
|
||||
@@ -22,14 +21,7 @@ export function callOnceLazy<
|
||||
return result;
|
||||
}
|
||||
|
||||
export function memoize<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- a legitimate use of any.
|
||||
T extends (...args: any[]) => any = (...args: any[]) => any
|
||||
>(
|
||||
fn: T,
|
||||
hashFn: (...args: Parameters<T>) => string,
|
||||
maxResultsCached = -1
|
||||
): (...args: Parameters<T>) => ReturnType<T> {
|
||||
export function memoize(fn, hashFn, maxResultsCached = -1) {
|
||||
/*
|
||||
function memoization, with user-provided hash. hashFn must return a
|
||||
key which will be unique as a Map key (ie, obeys "sameValueZero" algorithm
|
||||
@@ -37,7 +29,7 @@ export function memoize<
|
||||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality
|
||||
*/
|
||||
const cache = new Map();
|
||||
const wrap = function wrap(...args: Parameters<T>): ReturnType<T> {
|
||||
const wrap = function wrap(...args) {
|
||||
const key = hashFn(...args);
|
||||
if (cache.has(key)) {
|
||||
return cache.get(key);
|
||||
@@ -62,11 +54,11 @@ export function memoize<
|
||||
}
|
||||
|
||||
/**
|
||||
*memoization helpers - just a global counter.
|
||||
*/
|
||||
memoization helpers - just a global counter.
|
||||
**/
|
||||
let __DataframeMemoId__ = 0;
|
||||
export function __getMemoId(): string {
|
||||
export function __getMemoId() {
|
||||
const id = __DataframeMemoId__;
|
||||
__DataframeMemoId__ += 1;
|
||||
return id.toString();
|
||||
return id;
|
||||
}
|
||||
@@ -6,11 +6,7 @@ If undefined or empty array, or array contains only non-finite numbers,
|
||||
will return [undefined, undefined]
|
||||
*/
|
||||
|
||||
import type { TypedArray } from "../common/types/arraytypes";
|
||||
|
||||
function finiteExtent(
|
||||
tarr: TypedArray
|
||||
): [number, number] | [undefined, undefined] {
|
||||
function finiteExtent(tarr) {
|
||||
let min;
|
||||
let max;
|
||||
let i;
|
||||
@@ -24,17 +20,14 @@ function finiteExtent(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (min !== undefined && max !== undefined) {
|
||||
for (; i < tarr.length; i += 1) {
|
||||
const val = tarr[i];
|
||||
if (Number.isFinite(val)) {
|
||||
if (min > val) min = val;
|
||||
if (max < val) max = val;
|
||||
}
|
||||
for (; i < tarr.length; i += 1) {
|
||||
const val = tarr[i];
|
||||
if (Number.isFinite(val)) {
|
||||
if (min > val) min = val;
|
||||
if (max < val) max = val;
|
||||
}
|
||||
return [min, max];
|
||||
}
|
||||
return [undefined, undefined];
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
export default finiteExtent;
|
||||
@@ -0,0 +1,13 @@
|
||||
export default function fromEntries(arr) {
|
||||
/*
|
||||
Similar to Object.fromEntries, but only handles array.
|
||||
This could be replaced with the standard fucnction once it
|
||||
is widely available. As of 3/20/2019, it has not yet
|
||||
been released in the Chrome stable channel.
|
||||
*/
|
||||
const obj = {};
|
||||
for (let i = 0, l = arr.length; i < l; i += 1) {
|
||||
obj[arr[i][0]] = arr[i][1];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
export default function fromEntries<T = unknown>(
|
||||
arr: [string | number, T][]
|
||||
): { [key: string]: T } {
|
||||
/*
|
||||
Similar to Object.fromEntries, but only handles array.
|
||||
This could be replaced with the standard function once it
|
||||
is widely available. As of 3/20/2019, it has not yet
|
||||
been released in the Chrome stable channel.
|
||||
*/
|
||||
const obj: { [key: string]: T } = {};
|
||||
|
||||
for (let i = 0; i < arr.length; i += 1) {
|
||||
obj[arr[i][0]] = arr[i][1];
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ScaleLinear } from "d3";
|
||||
import significantDigits from "./significantDigits";
|
||||
|
||||
/**
|
||||
@@ -11,14 +10,12 @@ import significantDigits from "./significantDigits";
|
||||
* @returns - the number formatted as scientific, if it's big enough
|
||||
*/
|
||||
|
||||
export default function maybeScientific(
|
||||
x: ScaleLinear<number, number, never>
|
||||
): string {
|
||||
export default function maybeScientific(x) {
|
||||
let format = ",";
|
||||
const _ticks = x.ticks(4);
|
||||
|
||||
if (x.domain().some((n: number) => Math.abs(n) >= 10000)) {
|
||||
/*
|
||||
if (x.domain().some((n) => Math.abs(n) >= 10000)) {
|
||||
/*
|
||||
heuristic: if the last tick d3 wants to render has one significant
|
||||
digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render
|
||||
4.20e+n
|
||||
@@ -13,40 +13,29 @@ anno matrix namespaces. It is still used by the component tier.
|
||||
|
||||
*/
|
||||
|
||||
const makeDimensionName = (namespace: string, key: string): string =>
|
||||
`${namespace}_${key}`;
|
||||
const makeDimensionName = (namespace, key) => `${namespace}_${key}`;
|
||||
|
||||
export const layoutDimensionName = (key: string): string =>
|
||||
makeDimensionName("layout", key);
|
||||
|
||||
export const obsAnnoDimensionName = (key: string): string =>
|
||||
makeDimensionName("obsAnno", key);
|
||||
|
||||
export const diffexpDimensionName = (key: string): string =>
|
||||
export const layoutDimensionName = (key) => makeDimensionName("layout", key);
|
||||
export const obsAnnoDimensionName = (key) => makeDimensionName("obsAnno", key);
|
||||
export const diffexpDimensionName = (key) =>
|
||||
makeDimensionName("varData_diffexp", key);
|
||||
|
||||
export const userDefinedDimensionName = (key: string): string =>
|
||||
export const userDefinedDimensionName = (key) =>
|
||||
makeDimensionName("varData_userDefined", key);
|
||||
|
||||
export const geneSetSummaryDimensionName = (key: string): string =>
|
||||
export const geneSetSummaryDimensionName = (key) =>
|
||||
makeDimensionName("geneSetSummary", key);
|
||||
|
||||
export interface ContinuousNamespace {
|
||||
isObs?: boolean;
|
||||
isDiffExp?: boolean;
|
||||
isUserDefined?: boolean;
|
||||
isGeneSetSummary?: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
continuousNamespace = {
|
||||
isObs: true,
|
||||
isDiffExp: false,
|
||||
isUserDefined: false,
|
||||
isGeneSet: false,
|
||||
}
|
||||
|
||||
ie., makeContinuousDimensionName(continuousNamespace = {isObs: true}, "total_reads")
|
||||
see: histogram brush, as it doesn't know what type of continuous it was with only field
|
||||
*/
|
||||
export const makeContinuousDimensionName = (
|
||||
continuousNamespace: ContinuousNamespace,
|
||||
key: string
|
||||
): string => {
|
||||
export const makeContinuousDimensionName = (continuousNamespace, key) => {
|
||||
let name;
|
||||
if (continuousNamespace.isObs) {
|
||||
name = obsAnnoDimensionName(key);
|
||||
@@ -6,6 +6,6 @@
|
||||
import pull from "lodash.pull";
|
||||
import uniq from "lodash.uniq";
|
||||
|
||||
export default function parseBulkGeneString(geneString: string): Array<string> {
|
||||
export default function parseBulkGeneString(geneString) {
|
||||
return pull(uniq(geneString.split(/[ ,]+/)), "");
|
||||
}
|
||||
@@ -4,16 +4,14 @@ import scaleRGB from "./scaleRGB";
|
||||
// to do this operation. This lets us have speed, but keep the pleasant ability
|
||||
// to talk about colors by their text description eg, 'rgb(0,0,1)'
|
||||
//
|
||||
const colorCache = {} as { [key: string]: [number, number, number] };
|
||||
const colorCache = {};
|
||||
|
||||
function parseColorName(c: string): [number, number, number] {
|
||||
function parseColorName(c) {
|
||||
if (c[0] !== "#") {
|
||||
const _c = c.replace(/[^\d,.]/g, "").split(",");
|
||||
return [scaleRGB(+_c[0]), scaleRGB(+_c[1]), scaleRGB(+_c[2])];
|
||||
}
|
||||
const parsedHex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
|
||||
if (!parsedHex || parsedHex.length < 4)
|
||||
throw new Error(`Invalid hex color: ${c}`);
|
||||
return [
|
||||
scaleRGB(parseInt(parsedHex[1], 16)),
|
||||
scaleRGB(parseInt(parsedHex[2], 16)),
|
||||
@@ -21,7 +19,7 @@ function parseColorName(c: string): [number, number, number] {
|
||||
];
|
||||
}
|
||||
|
||||
export default (c: string): [number, number, number] => {
|
||||
export default (c) => {
|
||||
let cv = colorCache[c];
|
||||
if (!cv) {
|
||||
cv = parseColorName(c);
|
||||
@@ -28,55 +28,28 @@ Priority is a numeric value. Lower first. Stable ordering.
|
||||
|
||||
import TinyQueue from "tinyqueue";
|
||||
|
||||
function compare<T>(
|
||||
a: PromiseLimitQueueItem<T>,
|
||||
b: PromiseLimitQueueItem<T>
|
||||
): number {
|
||||
function compare(a, b) {
|
||||
const diff = a.priority - b.priority;
|
||||
if (diff) return diff;
|
||||
return a.order - b.order;
|
||||
}
|
||||
|
||||
interface PromiseLimitQueueItem<T> {
|
||||
priority: number;
|
||||
order: number;
|
||||
resolve: (value: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: unknown) => void;
|
||||
fn: (...args: Array<unknown>) => Promise<T>;
|
||||
args: Array<unknown>;
|
||||
}
|
||||
|
||||
export default class PromiseLimit<T> {
|
||||
queue: TinyQueue<PromiseLimitQueueItem<T>>;
|
||||
|
||||
maxConcurrency: number;
|
||||
|
||||
pending: number;
|
||||
|
||||
insertCounter: number;
|
||||
|
||||
export default class PromiseLimit {
|
||||
constructor(maxConcurrency = 5) {
|
||||
this.queue = new TinyQueue<PromiseLimitQueueItem<T>>(
|
||||
new Array<PromiseLimitQueueItem<T>>(),
|
||||
compare
|
||||
);
|
||||
this.queue = new TinyQueue([], compare);
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
this.pending = 0;
|
||||
this.insertCounter = 0;
|
||||
}
|
||||
|
||||
priorityAdd(
|
||||
p: number,
|
||||
fn: () => Promise<T>,
|
||||
...args: Array<unknown>
|
||||
): Promise<T> {
|
||||
priorityAdd(p, fn, ...args) {
|
||||
// p - numermic priority (lower first)
|
||||
// fn - must return a promise
|
||||
// args - will be passed to fn
|
||||
return this._push(p, fn, args);
|
||||
}
|
||||
|
||||
add(fn: () => Promise<T>, ...args: Array<unknown>): Promise<T> {
|
||||
add(fn, ...args) {
|
||||
// fn - must return a promise
|
||||
// args - will be passed to fn
|
||||
return this._push(0, fn, args);
|
||||
@@ -86,24 +59,20 @@ export default class PromiseLimit<T> {
|
||||
Private below
|
||||
**/
|
||||
|
||||
_push(
|
||||
priority: number,
|
||||
fn: () => Promise<T>,
|
||||
args: Array<unknown>
|
||||
): Promise<T> {
|
||||
const order = this.insertCounter;
|
||||
this.insertCounter += 1;
|
||||
_push(priority, fn, args) {
|
||||
const order = this.insertCount;
|
||||
this.insertCount += 1;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({ priority, order, fn, args, resolve, reject });
|
||||
this._resolveNext(false);
|
||||
});
|
||||
}
|
||||
|
||||
_resolveNext = (completed = true): void => {
|
||||
_resolveNext = (completed = true) => {
|
||||
if (completed) this.pending -= 1;
|
||||
|
||||
while (this.queue.length > 0 && this.pending < this.maxConcurrency) {
|
||||
const task = this.queue.pop() as PromiseLimitQueueItem<T>; // order of insertion
|
||||
const task = this.queue.pop(); // order of insertion
|
||||
this.pending += 1;
|
||||
const { resolve, reject, fn, args } = task;
|
||||
|
||||
@@ -12,25 +12,13 @@ Arguments:
|
||||
|
||||
*/
|
||||
|
||||
import { NumberArray, TypedArrayConstructor } from "../common/types/arraytypes";
|
||||
|
||||
import { sortArray } from "./typedCrossfilter/sort";
|
||||
|
||||
export default function quantile(
|
||||
quantArr: number[],
|
||||
tarr: NumberArray,
|
||||
sorted = false
|
||||
): number[] {
|
||||
export default function quantile(quantArr, tarr, sorted = false) {
|
||||
/*
|
||||
start with the naive (sort) implementation. Later, use a faster partition
|
||||
*/
|
||||
|
||||
if (tarr.length === 0) {
|
||||
return new Array(quantArr.length).fill(0);
|
||||
}
|
||||
|
||||
const Ctor: TypedArrayConstructor = tarr.constructor as TypedArrayConstructor;
|
||||
const arr = sorted ? tarr : sortArray(new Ctor(tarr)); // copy
|
||||
const arr = sorted ? tarr : sortArray(new tarr.constructor(tarr)); // copy
|
||||
const len = arr.length;
|
||||
return quantArr.map((q) => {
|
||||
if (q === 1) {
|
||||
@@ -22,44 +22,29 @@ rangeFill(array, start, step) -> array
|
||||
|
||||
*/
|
||||
|
||||
import { TypedArray, NumberArray } from "../common/types/arraytypes";
|
||||
|
||||
function _doFill<T extends NumberArray>(
|
||||
arr: T,
|
||||
start: number,
|
||||
step: number,
|
||||
count: number
|
||||
): T {
|
||||
function _doFill(arr, start, step, count) {
|
||||
for (let idx = 0, val = start; idx < count; idx += 1, val += step) {
|
||||
arr[idx] = val;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function rangeFill(arr: NumberArray, start = 0, step = 1): NumberArray {
|
||||
export function rangeFill(arr, start = 0, step = 1) {
|
||||
return _doFill(arr, start, step, arr.length);
|
||||
}
|
||||
|
||||
export function range(
|
||||
start: number,
|
||||
stop?: number,
|
||||
step?: number
|
||||
): Array<number> {
|
||||
export function range(start, stop, step) {
|
||||
if (start === undefined) return [];
|
||||
if (stop === undefined) {
|
||||
stop = start;
|
||||
start = 0;
|
||||
}
|
||||
step = step || 1; // catch undefined and zero
|
||||
step = step || 1; // catch undefind and zero
|
||||
const len = Math.max(Math.ceil((stop - start) / step), 0);
|
||||
return _doFill(new Array(len), start, step, len);
|
||||
}
|
||||
|
||||
export function linspace(
|
||||
start: number,
|
||||
stop: number,
|
||||
nsteps: number
|
||||
): Array<number> {
|
||||
const delta = (stop - start) / Number((nsteps - 1).toFixed());
|
||||
export function linspace(start, stop, nsteps) {
|
||||
const delta = (stop - start) / (nsteps - 1).toFixed();
|
||||
return range(0, nsteps, 1).map((i) => start + i * delta);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export default function renderThrottle(callback) {
|
||||
/*
|
||||
This wraps a call to requestAnimationFrame(), enforcing a single
|
||||
render callback at any given time (ie, you can call this any number
|
||||
of times, and it will coallesce multiple inter-frame calls into a
|
||||
single render).
|
||||
*/
|
||||
let rafCurrentlyInProgress = null;
|
||||
return function f() {
|
||||
if (rafCurrentlyInProgress) return;
|
||||
const context = this;
|
||||
rafCurrentlyInProgress = window.requestAnimationFrame(() => {
|
||||
callback.apply(context);
|
||||
rafCurrentlyInProgress = null;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export default function renderThrottle<T>(
|
||||
callback: (this: T) => void
|
||||
): (this: T) => void {
|
||||
/*
|
||||
This wraps a call to requestAnimationFrame(), enforcing a single
|
||||
render callback at any given time (ie, you can call this any number
|
||||
of times, and it will coallesce multiple inter-frame calls into a
|
||||
single render).
|
||||
*/
|
||||
let rafCurrentlyInProgress: number | null = null;
|
||||
return function f(this: T) {
|
||||
if (rafCurrentlyInProgress) return; // eslint-disable-next-line @typescript-eslint/no-this-alias --- required for functionality
|
||||
rafCurrentlyInProgress = window.requestAnimationFrame(() => {
|
||||
callback.call<T, never, void>(this);
|
||||
rafCurrentlyInProgress = null;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -6,18 +6,15 @@
|
||||
// myScale(0) === -1
|
||||
// this is is equivalent to d3.scaleLinear().domain([0,1]).range([-1,1])
|
||||
|
||||
export default (
|
||||
domain: [number, number],
|
||||
range: [number, number]
|
||||
): ((value: number) => number) => {
|
||||
export default (domain, range) => {
|
||||
const domainStart = domain[0];
|
||||
const scale = (range[1] - range[0]) / (domain[1] - domain[0]);
|
||||
const invScale = 1 / scale;
|
||||
const rangeStart = range[0];
|
||||
const f = (value: number) => (value - domainStart) * scale + rangeStart;
|
||||
const f = (value) => (value - domainStart) * scale + rangeStart;
|
||||
|
||||
// inverter
|
||||
f.invert = (value: number) => (value - rangeStart) * invScale + domainStart;
|
||||
f.invert = (value) => (value - rangeStart) * invScale + domainStart;
|
||||
|
||||
return f;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
export default (input: number): number => {
|
||||
export default (input) => {
|
||||
const outputMax = 1;
|
||||
const outputMin = 0;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/*
|
||||
/*
|
||||
via https://github.com/nodef/extra-number/blob/master/scripts/significantDigits.js
|
||||
*/
|
||||
|
||||
const significantDigits = (n: number): number =>
|
||||
n
|
||||
export default (n) => n
|
||||
.toExponential()
|
||||
.replace(/e[+\-0-9]*$/, "")
|
||||
.replace(/^0\.?0*|\./, "").length;
|
||||
|
||||
export default significantDigits;
|
||||
+12
-30
@@ -3,9 +3,6 @@ Helper functions for user-editable annotations state management.
|
||||
See also reducers/annotations.js
|
||||
*/
|
||||
|
||||
import { Schema } from "../../common/types/schema";
|
||||
import { Dataframe, LabelType } from "../dataframe";
|
||||
|
||||
/*
|
||||
There are a number of state constraints assumed throughout the
|
||||
application:
|
||||
@@ -19,51 +16,37 @@ application:
|
||||
In addition, the current state management only allows for
|
||||
categorical annotations to be writable.
|
||||
*/
|
||||
export function isCategoricalAnnotation(
|
||||
schema: Schema,
|
||||
name: string
|
||||
): boolean | undefined {
|
||||
/*
|
||||
|
||||
export function isCategoricalAnnotation(schema, name) {
|
||||
/*
|
||||
we treat any string, categorical or boolean as a categorical.
|
||||
Return true/false/undefined (for unkonwn fields)
|
||||
*/
|
||||
const colSchema = schema.annotations.obsByName[name];
|
||||
|
||||
if (colSchema === undefined) return undefined;
|
||||
|
||||
const { type } = colSchema;
|
||||
|
||||
return type === "string" || type === "boolean" || type === "categorical";
|
||||
}
|
||||
|
||||
export function isContinuousAnnotation(
|
||||
schema: Schema,
|
||||
name: string
|
||||
): boolean | undefined {
|
||||
export function isContinuousAnnotation(schema, name) {
|
||||
/*
|
||||
Return true/false/undefined
|
||||
*/
|
||||
const colSchema = schema.annotations.obsByName[name];
|
||||
|
||||
if (colSchema === undefined) return undefined;
|
||||
|
||||
const { type } = colSchema;
|
||||
|
||||
return !(type === "string" || type === "boolean" || type === "categorical");
|
||||
}
|
||||
|
||||
function _isUserAnnotation(schema: Schema, name: string): boolean {
|
||||
function _isUserAnnotation(schema, name) {
|
||||
return schema.annotations.obsByName[name]?.writable || false;
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function isUserAnnotation(annoMatrix, name) {
|
||||
return _isUserAnnotation(annoMatrix.schema, name);
|
||||
}
|
||||
|
||||
export function allHaveLabelByMask(
|
||||
df: Dataframe,
|
||||
colName: LabelType,
|
||||
label: string,
|
||||
mask: Uint8Array
|
||||
): boolean {
|
||||
export function allHaveLabelByMask(df, colName, label, mask) {
|
||||
// return true if all rows as indicated by mask have the colname set to label.
|
||||
// False if not.
|
||||
const col = df.col(colName);
|
||||
@@ -80,8 +63,7 @@ export function allHaveLabelByMask(
|
||||
}
|
||||
|
||||
const legalCharacters = /^(\w|[ .()-])+$/;
|
||||
|
||||
export function annotationNameIsErroneous(name: string): boolean | string {
|
||||
export function annotationNameIsErroneous(name) {
|
||||
/*
|
||||
Validate the name - return:
|
||||
* false - a valid name
|
||||
@@ -108,6 +90,6 @@ export function annotationNameIsErroneous(name: string): boolean | string {
|
||||
}
|
||||
}
|
||||
|
||||
/* all is well! Indicate not erroneous with a false */
|
||||
/* all is well! Indicte not erroneous with a false */
|
||||
return false;
|
||||
}
|
||||
+18
-64
@@ -7,23 +7,12 @@ import memoize from "memoize-one";
|
||||
import * as globals from "../../globals";
|
||||
import parseRGB from "../parseRGB";
|
||||
import { range } from "../range";
|
||||
import { Dataframe, LabelType } from "../dataframe";
|
||||
|
||||
/*
|
||||
given a color mode & accessor, generate an annoMatrix query that will
|
||||
fulfill it
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function createColorQuery(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colorMode: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
colorByAccessor: 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.
|
||||
genesets: any
|
||||
) {
|
||||
export function createColorQuery(colorMode, colorByAccessor, schema, genesets) {
|
||||
if (!colorMode || !colorByAccessor || !schema || !genesets) return null;
|
||||
|
||||
switch (colorMode) {
|
||||
@@ -72,8 +61,7 @@ export function createColorQuery(
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function _defaultColors(nObs: any) {
|
||||
function _defaultColors(nObs) {
|
||||
const defaultCellColor = parseRGB(globals.defaultCellColor);
|
||||
return {
|
||||
rgb: new Array(nObs).fill(defaultCellColor),
|
||||
@@ -96,40 +84,33 @@ Returns:
|
||||
}
|
||||
*/
|
||||
function _createColorTable(
|
||||
colorMode: string | null,
|
||||
colorByAccessor: LabelType | null,
|
||||
colorByData: Dataframe | null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
schema: any,
|
||||
colorMode,
|
||||
colorByAccessor,
|
||||
colorByData,
|
||||
schema,
|
||||
userColors = null
|
||||
) {
|
||||
if (colorMode === null || colorByData === null)
|
||||
return defaultColors(schema.dataframe.nObs);
|
||||
|
||||
switch (colorMode) {
|
||||
case "color by categorical metadata": {
|
||||
if (colorByAccessor === null) return defaultColors(schema.dataframe.nObs);
|
||||
const data = colorByData.col(colorByAccessor).asArray();
|
||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||
if (userColors && colorByAccessor in userColors) {
|
||||
return createUserColors(data, colorByAccessor, schema, userColors);
|
||||
}
|
||||
return createColorsByCategoricalMetadata(data, colorByAccessor, schema);
|
||||
}
|
||||
case "color by continuous metadata": {
|
||||
if (colorByAccessor === null) return defaultColors(schema.dataframe.nObs);
|
||||
const col = colorByData.col(colorByAccessor);
|
||||
const { min, max } = col.summarizeContinuous();
|
||||
const { min, max } = col.summarize();
|
||||
return createColorsByContinuousMetadata(col.asArray(), min, max);
|
||||
}
|
||||
case "color by expression": {
|
||||
const col = colorByData.icol(0);
|
||||
const { min, max } = col.summarizeContinuous();
|
||||
const { min, max } = col.summarize();
|
||||
return createColorsByContinuousMetadata(col.asArray(), min, max);
|
||||
}
|
||||
case "color by geneset mean expression": {
|
||||
const col = colorByData.icol(0);
|
||||
const { min, max } = col.summarizeContinuous();
|
||||
const { min, max } = col.summarize();
|
||||
return createColorsByContinuousMetadata(col.asArray(), min, max);
|
||||
}
|
||||
default: {
|
||||
@@ -145,40 +126,25 @@ export const createColorTable = memoize(_createColorTable);
|
||||
* - scale: function which given label returns d3 color scale for label
|
||||
* Order doesn't matter - everything is keyed by label value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function loadUserColorConfig(userColors: any) {
|
||||
export function loadUserColorConfig(userColors) {
|
||||
const convertedUserColors = {};
|
||||
Object.keys(userColors).forEach((category) => {
|
||||
const [colors, scaleMap] = Object.keys(userColors[category]).reduce(
|
||||
(acc, label) => {
|
||||
const color = parseRGB(userColors[category][label]);
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
acc[0][label] = color;
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
acc[1][label] = d3.rgb(255 * color[0], 255 * color[1], 255 * color[2]);
|
||||
return acc;
|
||||
},
|
||||
[{}, {}]
|
||||
);
|
||||
// @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.
|
||||
const scale = (label: any) => scaleMap[label];
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
const scale = (label) => scaleMap[label];
|
||||
convertedUserColors[category] = { colors, scale };
|
||||
});
|
||||
return convertedUserColors;
|
||||
}
|
||||
|
||||
function _createUserColors(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
data: any,
|
||||
// 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.
|
||||
schema: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
userColors: any
|
||||
) {
|
||||
function _createUserColors(data, colorAccessor, schema, userColors) {
|
||||
const { colors, scale: scaleByLabel } = userColors[colorAccessor];
|
||||
const rgb = createRgbArray(data, colors);
|
||||
|
||||
@@ -186,23 +152,14 @@ function _createUserColors(
|
||||
// See createColorsByCategoricalMetadata() for another example.
|
||||
const { categories } = schema.annotations.obsByName[colorAccessor];
|
||||
const categoryMap = new Map();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
categories.forEach((label: any, idx: any) => categoryMap.set(idx, label));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const scale = (idx: any) => scaleByLabel(categoryMap.get(idx));
|
||||
categories.forEach((label, idx) => categoryMap.set(idx, label));
|
||||
const scale = (idx) => scaleByLabel(categoryMap.get(idx));
|
||||
|
||||
return { rgb, scale };
|
||||
}
|
||||
const createUserColors = memoize(_createUserColors);
|
||||
|
||||
function _createColorsByCategoricalMetadata(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
data: any,
|
||||
// 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.
|
||||
schema: any
|
||||
) {
|
||||
function _createColorsByCategoricalMetadata(data, colorAccessor, schema) {
|
||||
const { categories } = schema.annotations.obsByName[colorAccessor];
|
||||
|
||||
const scale = d3
|
||||
@@ -210,8 +167,7 @@ function _createColorsByCategoricalMetadata(
|
||||
.domain([0, categories.length]);
|
||||
|
||||
/* pre-create colors - much faster than doing it for each obs */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
const colors = categories.reduce((acc: any, cat: any, idx: any) => {
|
||||
const colors = categories.reduce((acc, cat, idx) => {
|
||||
acc[cat] = parseRGB(scale(idx));
|
||||
return acc;
|
||||
}, {});
|
||||
@@ -223,8 +179,7 @@ const createColorsByCategoricalMetadata = memoize(
|
||||
_createColorsByCategoricalMetadata
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function createRgbArray(data: any, colors: any) {
|
||||
function createRgbArray(data, colors) {
|
||||
const rgb = new Array(data.length);
|
||||
for (let i = 0, len = data.length; i < len; i += 1) {
|
||||
const label = data[i];
|
||||
@@ -233,8 +188,7 @@ function createRgbArray(data: any, colors: any) {
|
||||
return rgb;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function _createColorsByContinuousMetadata(data: any, min: any, max: any) {
|
||||
function _createColorsByContinuousMetadata(data, min, max) {
|
||||
const colorBins = 100;
|
||||
const scale = d3
|
||||
.scaleQuantile()
|
||||
+10
-23
@@ -30,9 +30,7 @@ Remember that option values can be ANY js type, except undefined/null.
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function isSelectableCategoryName(schema: any, name: any) {
|
||||
export function isSelectableCategoryName(schema, name) {
|
||||
const { index } = schema.annotations.obs;
|
||||
const colSchema = schema.annotations.obsByName[name];
|
||||
return (
|
||||
@@ -42,8 +40,7 @@ export function isSelectableCategoryName(schema: any, name: any) {
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function selectableCategoryNames(schema: any, names: any) {
|
||||
export function selectableCategoryNames(schema, names) {
|
||||
/*
|
||||
return all obs annotation names that are categorical AND have a
|
||||
"reasonably" small number of categories AND are not the index column.
|
||||
@@ -51,14 +48,11 @@ export function selectableCategoryNames(schema: any, names: any) {
|
||||
If the initial name list not provided, use everything in the schema.
|
||||
*/
|
||||
if (!schema) return [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
if (!names) names = schema.annotations.obs.columns.map((c: any) => c.name);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
return names.filter((name: any) => isSelectableCategoryName(schema, name));
|
||||
if (!names) names = schema.annotations.obs.columns.map((c) => c.name);
|
||||
return names.filter((name) => isSelectableCategoryName(schema, name));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function createCategorySummaryFromDfCol(dfCol: any, colSchema: any) {
|
||||
export function createCategorySummaryFromDfCol(dfCol, colSchema) {
|
||||
const { writable: isUserAnno } = colSchema;
|
||||
|
||||
/*
|
||||
@@ -70,13 +64,9 @@ export function createCategorySummaryFromDfCol(dfCol: any, colSchema: any) {
|
||||
const { categories: allCategoryValues } = colSchema;
|
||||
const categoryValues = allCategoryValues;
|
||||
const categoryValueCounts = allCategoryValues.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
(cat: any) => summary.categoryCounts.get(cat) ?? 0
|
||||
);
|
||||
const categoryValueIndices = new Map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
categoryValues.map((v: any, i: any) => [v, i])
|
||||
(cat) => summary.categoryCounts.get(cat) ?? 0
|
||||
);
|
||||
const categoryValueIndices = new Map(categoryValues.map((v, i) => [v, i]));
|
||||
const numCategoryValues = categoryValueIndices.size;
|
||||
|
||||
return {
|
||||
@@ -89,14 +79,11 @@ export function createCategorySummaryFromDfCol(dfCol: any, colSchema: any) {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function createCategoricalSelection(names: any) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
return fromEntries(names.map((name: any) => [name, new Map()]));
|
||||
export function createCategoricalSelection(names) {
|
||||
return fromEntries(names.map((name) => [name, new Map()]));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function pruneVarDataCache(varData: any, needed: any) {
|
||||
export function pruneVarDataCache(varData, needed) {
|
||||
/*
|
||||
Remove any unneeded columns from the varData dataframe. Will only
|
||||
prune / remove if the total column count exceeds VarDataCacheLowWatermark
|
||||
@@ -1,10 +1,6 @@
|
||||
import { flatbuffers } from "flatbuffers";
|
||||
import { NetEncoding } from "./matrix_generated";
|
||||
import {
|
||||
TypedArray,
|
||||
isTypedArray,
|
||||
isFloatTypedArray,
|
||||
} from "../../common/types/arraytypes";
|
||||
import { isTypedArray, isFpTypedArray } from "../typeHelpers";
|
||||
import {
|
||||
Dataframe,
|
||||
IdentityInt32Index,
|
||||
@@ -21,14 +17,12 @@ Matrix flatbuffer decoding support. See fbs/matrix.fbs
|
||||
/*
|
||||
Decode NetEncoding.TypedArray
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function decodeTypedArray(uType: any, uValF: any, inplace = false) {
|
||||
function decodeTypedArray(uType, uValF, inplace = false) {
|
||||
if (uType === NetEncoding.TypedArray.NONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert to a JS class that supports this type
|
||||
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
|
||||
const TypeClass = NetEncoding[NetEncoding.TypedArray[uType]];
|
||||
// Create a TypedArray that references the underlying buffer
|
||||
let arr = uValF(new TypeClass()).dataArray();
|
||||
@@ -54,8 +48,7 @@ Returns: object containing decoded Matrix:
|
||||
colIdx: []|null
|
||||
}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function decodeMatrixFBS(arrayBuffer: any, inplace = false) {
|
||||
export function decodeMatrixFBS(arrayBuffer, inplace = false) {
|
||||
const bb = new flatbuffers.ByteBuffer(new Uint8Array(arrayBuffer));
|
||||
const matrix = NetEncoding.Matrix.getRootAsMatrix(bb);
|
||||
|
||||
@@ -86,11 +79,8 @@ export function decodeMatrixFBS(arrayBuffer: any, inplace = false) {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function encodeTypedArray(builder: any, uType: any, uData: any) {
|
||||
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
|
||||
function encodeTypedArray(builder, uType, uData) {
|
||||
const uTypeName = NetEncoding.TypedArray[uType];
|
||||
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
|
||||
const ArrayType = NetEncoding[uTypeName];
|
||||
const dv = ArrayType.createDataVector(builder, uData);
|
||||
builder.startObject(1);
|
||||
@@ -98,18 +88,17 @@ function encodeTypedArray(builder: any, uType: any, uData: any) {
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
export function encodeMatrixFBS(df: Dataframe): Uint8Array {
|
||||
export function encodeMatrixFBS(df) {
|
||||
/*
|
||||
encode the dataframe as an FBS Matrix
|
||||
*/
|
||||
|
||||
/* row indexing not supported currently */
|
||||
if (!(df.rowIndex instanceof IdentityInt32Index)) {
|
||||
if (df.rowIndex.constructor !== IdentityInt32Index) {
|
||||
throw new Error("FBS does not support row index encoding at this time");
|
||||
}
|
||||
|
||||
const shape = df.dims;
|
||||
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||
const utf8Encoder = new TextEncoder("utf-8");
|
||||
const builder = new flatbuffers.Builder(1024);
|
||||
|
||||
@@ -124,7 +113,6 @@ export function encodeMatrixFBS(df: Dataframe): Uint8Array {
|
||||
let uType;
|
||||
let tarr;
|
||||
if (isTypedArray(carr)) {
|
||||
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
|
||||
uType = NetEncoding.TypedArray[carr.constructor.name];
|
||||
tarr = encodeTypedArray(builder, uType, carr);
|
||||
} else {
|
||||
@@ -180,7 +168,7 @@ export function encodeMatrixFBS(df: Dataframe): Uint8Array {
|
||||
return builder.asUint8Array();
|
||||
}
|
||||
|
||||
function promoteTypedArray(o: TypedArray) {
|
||||
function promoteTypedArray(o) {
|
||||
/*
|
||||
Decide what internal data type to use for the data returned from
|
||||
the server.
|
||||
@@ -188,7 +176,7 @@ function promoteTypedArray(o: TypedArray) {
|
||||
TODO - future optimization: not all int32/uint32 data series require
|
||||
promotion to float64. We COULD simply look at the data to decide.
|
||||
*/
|
||||
if (isFloatTypedArray(o) || Array.isArray(o)) return o;
|
||||
if (isFpTypedArray(o) || Array.isArray(o)) return o;
|
||||
|
||||
let TypedArrayCtor;
|
||||
switch (o.constructor) {
|
||||
@@ -212,9 +200,7 @@ function promoteTypedArray(o: TypedArray) {
|
||||
return new TypedArrayCtor(o);
|
||||
}
|
||||
|
||||
export function matrixFBSToDataframe(
|
||||
arrayBuffers: ArrayBuffer | ArrayBuffer[]
|
||||
): Dataframe {
|
||||
export function matrixFBSToDataframe(arrayBuffers) {
|
||||
/*
|
||||
Convert array of Matrix FBS to a Dataframe.
|
||||
|
||||
@@ -230,7 +216,7 @@ export function matrixFBSToDataframe(
|
||||
arrayBuffers = [arrayBuffers];
|
||||
}
|
||||
if (arrayBuffers.length === 0) {
|
||||
return Dataframe.empty();
|
||||
return Dataframe.Dataframe.empty();
|
||||
}
|
||||
|
||||
const fbs = arrayBuffers.map((ab) => decodeMatrixFBS(ab, true)); // leave in place
|
||||
@@ -243,7 +229,7 @@ export function matrixFBSToDataframe(
|
||||
const columns = fbs
|
||||
.map((fb) =>
|
||||
fb.columns.map((c) => {
|
||||
if (isFloatTypedArray(c) || Array.isArray(c)) return c;
|
||||
if (isFpTypedArray(c) || Array.isArray(c)) return c;
|
||||
return promoteTypedArray(c);
|
||||
})
|
||||
)
|
||||
@@ -648,9 +648,9 @@ NetEncoding.Column.getRootAsColumn = function (bb, obj) {
|
||||
NetEncoding.Column.prototype.uType = function () {
|
||||
var offset = this.bb.__offset(this.bb_pos, 4);
|
||||
return offset
|
||||
? /** @type {NetEncoding.TypedArray} */ (this.bb.readUint8(
|
||||
this.bb_pos + offset
|
||||
))
|
||||
? /** @type {NetEncoding.TypedArray} */ (
|
||||
this.bb.readUint8(this.bb_pos + offset)
|
||||
)
|
||||
: NetEncoding.TypedArray.NONE;
|
||||
};
|
||||
|
||||
@@ -778,9 +778,9 @@ NetEncoding.Matrix.prototype.columnsLength = function () {
|
||||
NetEncoding.Matrix.prototype.colIndexType = function () {
|
||||
var offset = this.bb.__offset(this.bb_pos, 10);
|
||||
return offset
|
||||
? /** @type {NetEncoding.TypedArray} */ (this.bb.readUint8(
|
||||
this.bb_pos + offset
|
||||
))
|
||||
? /** @type {NetEncoding.TypedArray} */ (
|
||||
this.bb.readUint8(this.bb_pos + offset)
|
||||
)
|
||||
: NetEncoding.TypedArray.NONE;
|
||||
};
|
||||
|
||||
@@ -799,9 +799,9 @@ NetEncoding.Matrix.prototype.colIndex = function (obj) {
|
||||
NetEncoding.Matrix.prototype.rowIndexType = function () {
|
||||
var offset = this.bb.__offset(this.bb_pos, 14);
|
||||
return offset
|
||||
? /** @type {NetEncoding.TypedArray} */ (this.bb.readUint8(
|
||||
this.bb_pos + offset
|
||||
))
|
||||
? /** @type {NetEncoding.TypedArray} */ (
|
||||
this.bb.readUint8(this.bb_pos + offset)
|
||||
)
|
||||
: NetEncoding.TypedArray.NONE;
|
||||
};
|
||||
|
||||
|
||||
+23
-49
@@ -8,13 +8,6 @@ import cloneDeep from "lodash.clonedeep";
|
||||
|
||||
import fromEntries from "../fromEntries";
|
||||
import catLabelSort from "../catLabelSort";
|
||||
import {
|
||||
RawSchema,
|
||||
Schema,
|
||||
EmbeddingSchema,
|
||||
AnnotationColumnSchema,
|
||||
} from "../../common/types/schema";
|
||||
import { LabelType } from "../dataframe/types";
|
||||
|
||||
/*
|
||||
System wide schema assumptions:
|
||||
@@ -22,25 +15,25 @@ System wide schema assumptions:
|
||||
- schema will be internally self-consistent (eg, index matches columns)
|
||||
*/
|
||||
|
||||
export function indexEntireSchema(schema: RawSchema): Schema {
|
||||
export function indexEntireSchema(schema) {
|
||||
/* Index schema for ease of use */
|
||||
(schema as Schema).annotations.obsByName = fromEntries(
|
||||
schema.annotations?.obs?.columns?.map((v) => [v.name, v]) || []
|
||||
schema.annotations.obsByName = fromEntries(
|
||||
schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? []
|
||||
);
|
||||
(schema as Schema).annotations.varByName = fromEntries(
|
||||
schema.annotations?.var?.columns?.map((v) => [v.name, v]) || []
|
||||
schema.annotations.varByName = fromEntries(
|
||||
schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? []
|
||||
);
|
||||
(schema as Schema).layout.obsByName = fromEntries(
|
||||
schema.layout?.obs?.map((v) => [v.name, v]) || []
|
||||
schema.layout.obsByName = fromEntries(
|
||||
schema.layout?.obs?.map((v) => [v.name, v]) ?? []
|
||||
);
|
||||
(schema as Schema).layout.varByName = fromEntries(
|
||||
schema.layout?.var?.map((v) => [v.name, v]) || []
|
||||
schema.layout.varByName = fromEntries(
|
||||
schema.layout?.var?.map((v) => [v.name, v]) ?? []
|
||||
);
|
||||
|
||||
return schema as Schema;
|
||||
return schema;
|
||||
}
|
||||
|
||||
function _copyObsAnno(schema: Schema): Schema {
|
||||
function _copyObsAnno(schema) {
|
||||
/* redux copy conventions - WARNING, only for modifying obs annotations */
|
||||
return {
|
||||
...schema,
|
||||
@@ -51,7 +44,7 @@ function _copyObsAnno(schema: Schema): Schema {
|
||||
};
|
||||
}
|
||||
|
||||
function _copyObsLayout(schema: Schema): Schema {
|
||||
function _copyObsLayout(schema) {
|
||||
return {
|
||||
...schema,
|
||||
layout: {
|
||||
@@ -61,7 +54,7 @@ function _copyObsLayout(schema: Schema): Schema {
|
||||
};
|
||||
}
|
||||
|
||||
function _reindexObsAnno(schema: Schema): Schema {
|
||||
function _reindexObsAnno(schema) {
|
||||
/* reindex obs annotations ONLY */
|
||||
schema.annotations.obsByName = fromEntries(
|
||||
schema.annotations.obs.columns.map((v) => [v.name, v])
|
||||
@@ -69,14 +62,14 @@ function _reindexObsAnno(schema: Schema): Schema {
|
||||
return schema;
|
||||
}
|
||||
|
||||
function _reindexObsLayout(schema: Schema) {
|
||||
function _reindexObsLayout(schema) {
|
||||
schema.layout.obsByName = fromEntries(
|
||||
schema.layout.obs.map((v) => [v.name, v])
|
||||
);
|
||||
return schema;
|
||||
}
|
||||
|
||||
export function removeObsAnnoColumn(schema: Schema, name: LabelType): Schema {
|
||||
export function removeObsAnnoColumn(schema, name) {
|
||||
const newSchema = _copyObsAnno(schema);
|
||||
newSchema.annotations.obs.columns = schema.annotations.obs.columns.filter(
|
||||
(v) => v.name !== name
|
||||
@@ -84,44 +77,29 @@ export function removeObsAnnoColumn(schema: Schema, name: LabelType): Schema {
|
||||
return _reindexObsAnno(newSchema);
|
||||
}
|
||||
|
||||
export function addObsAnnoColumn(
|
||||
schema: Schema,
|
||||
_: string,
|
||||
defn: AnnotationColumnSchema
|
||||
): Schema {
|
||||
export function addObsAnnoColumn(schema, name, defn) {
|
||||
const newSchema = _copyObsAnno(schema);
|
||||
|
||||
newSchema.annotations.obs.columns.push(defn);
|
||||
|
||||
return _reindexObsAnno(newSchema);
|
||||
}
|
||||
|
||||
export function removeObsAnnoCategory(
|
||||
schema: Schema,
|
||||
name: LabelType,
|
||||
category: string
|
||||
): Schema {
|
||||
export function removeObsAnnoCategory(schema, name, category) {
|
||||
/* remove a category from a categorical annotation */
|
||||
const categories = schema.annotations.obsByName[name]?.categories;
|
||||
|
||||
if (!categories) {
|
||||
if (!categories)
|
||||
throw new Error("column does not exist or is not categorical");
|
||||
}
|
||||
|
||||
const idx = categories.indexOf(category);
|
||||
|
||||
if (idx === -1) throw new Error("category does not exist");
|
||||
|
||||
const newSchema = _reindexObsAnno(_copyObsAnno(schema));
|
||||
|
||||
/* remove category. Do not need to resort as this can't change presentation order */
|
||||
newSchema.annotations.obsByName[name].categories?.splice(idx, 1);
|
||||
|
||||
newSchema.annotations.obsByName[name].categories.splice(idx, 1);
|
||||
return newSchema;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function addObsAnnoCategory(schema: any, name: any, category: any) {
|
||||
export function addObsAnnoCategory(schema, name, category) {
|
||||
/* add a category to a categorical annotation */
|
||||
const categories = schema.annotations.obsByName[name]?.categories;
|
||||
if (!categories)
|
||||
@@ -134,27 +112,23 @@ export function addObsAnnoCategory(schema: any, name: any, category: any) {
|
||||
|
||||
/* add category, retaining presentation sort order */
|
||||
const catAnno = newSchema.annotations.obsByName[name];
|
||||
|
||||
catAnno.categories = catLabelSort(catAnno.writable, [
|
||||
...(catAnno.categories || []),
|
||||
...catAnno.categories,
|
||||
category,
|
||||
]);
|
||||
|
||||
return newSchema;
|
||||
}
|
||||
|
||||
export function addObsLayout(schema: Schema, layout: EmbeddingSchema): Schema {
|
||||
export function addObsLayout(schema, layout) {
|
||||
/* add or replace a layout */
|
||||
const newSchema = _copyObsLayout(schema);
|
||||
newSchema.layout.obs.push(layout);
|
||||
return _reindexObsLayout(newSchema);
|
||||
}
|
||||
|
||||
export function removeObsLayout(schema: Schema, name: string): Schema {
|
||||
export function removeObsLayout(schema, name) {
|
||||
/* remove a layout */
|
||||
const newSchema = _copyObsLayout(schema);
|
||||
|
||||
newSchema.layout.obs = schema.layout.obs.filter((v) => v.name !== name);
|
||||
|
||||
return _reindexObsLayout(newSchema);
|
||||
}
|
||||
+4
-23
@@ -37,10 +37,7 @@ Views can be interogated for their type with the following:
|
||||
|
||||
import { clip, isubsetMask, isubset } from "../../annoMatrix";
|
||||
import { memoize } from "../dataframe/util";
|
||||
import { Dataframe, LabelIndex } from "../dataframe";
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function _clipAnnoMatrix(annoMatrix, min, max) {
|
||||
/*
|
||||
clip the annoMatrix.
|
||||
@@ -50,8 +47,6 @@ export function _clipAnnoMatrix(annoMatrix, min, max) {
|
||||
: clip(annoMatrix, min, max);
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function _userSubsetAnnoMatrix(annoMatrix, mask) {
|
||||
/*
|
||||
user-requested row subset of annoMatrix, to be added on top of any
|
||||
@@ -66,15 +61,12 @@ export function _userSubsetAnnoMatrix(annoMatrix, mask) {
|
||||
annoMatrix.userFlags.isUserSubsetView = true;
|
||||
|
||||
if (clipRange) {
|
||||
// @ts-expect-error ts-migrate(2556) FIXME: Expected 3 arguments, but got 1 or more.
|
||||
annoMatrix = clip(annoMatrix, ...clipRange);
|
||||
}
|
||||
|
||||
return annoMatrix;
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function _userResetSubsetAnnoMatrix(annoMatrix) {
|
||||
/*
|
||||
Reset/remove all user-requested subsets. Do not remove clip or embedding subset.
|
||||
@@ -93,16 +85,13 @@ export function _userResetSubsetAnnoMatrix(annoMatrix) {
|
||||
|
||||
/* re-apply the clip, if any */
|
||||
if (clipRange) {
|
||||
// @ts-expect-error ts-migrate(2556) FIXME: Expected 3 arguments, but got 1 or more.
|
||||
annoMatrix = clip(annoMatrix, ...clipRange);
|
||||
}
|
||||
|
||||
return annoMatrix;
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function _setEmbeddingSubset(annoMatrix, embeddingDf: Dataframe) {
|
||||
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.
|
||||
@@ -135,17 +124,13 @@ export function _setEmbeddingSubset(annoMatrix, embeddingDf: Dataframe) {
|
||||
|
||||
/* re-apply clip, if needed */
|
||||
if (clipRange) {
|
||||
// @ts-expect-error ts-migrate(2556) FIXME: Expected 3 arguments, but got 1 or more.
|
||||
annoMatrix = clip(annoMatrix, ...clipRange);
|
||||
}
|
||||
|
||||
return annoMatrix;
|
||||
}
|
||||
|
||||
function _getEmbeddingRowOffsets(
|
||||
_baseRowIndex: LabelIndex,
|
||||
embeddingDf: Dataframe
|
||||
) {
|
||||
function _getEmbeddingRowOffsets(baseRowIndex, embeddingDf) {
|
||||
/*
|
||||
given a dataframe containing an embedding:
|
||||
- if the embedding contains no NaN coordinates, return null
|
||||
@@ -170,20 +155,16 @@ function _getEmbeddingRowOffsets(
|
||||
return offsets.subarray(0, numOffsets);
|
||||
}
|
||||
|
||||
export function _getDiscreteCellEmbeddingRowIndex(
|
||||
embeddingDf: Dataframe
|
||||
): LabelIndex {
|
||||
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: Dataframe) => df.__id
|
||||
(df) => df.__id
|
||||
);
|
||||
|
||||
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function getEmbSubsetView(annoMatrix) {
|
||||
/* if there is an embedding subset in the view stack, return it. Falsish if not. */
|
||||
while (annoMatrix.isView) {
|
||||
@@ -13,17 +13,18 @@ Where:
|
||||
to: state_name_transitioning_to,
|
||||
from: state_name_transitioning_from,
|
||||
event: value_that_will_cause_transition,
|
||||
action: callback_upon_transition
|
||||
action: optional_callback_upon_transition
|
||||
}
|
||||
The transition will be provided to the action callback, so other data
|
||||
may be stored in the transition object for use by the action callback.
|
||||
* onErrorCallback - a callback function called if the FSM receives an event
|
||||
for which it has no defined transition.
|
||||
|
||||
|
||||
Interface:
|
||||
* states - property containing the state names. A Set(), containing the
|
||||
* states - property containing the state names. A Set(), contianing the
|
||||
union of to: and from: values.
|
||||
* events - property containing all of the accepted event values. Set().
|
||||
* events - property containing all of the accepted event values. Set().
|
||||
* graph - a Map of Maps, organized as graph[eventValue][fromStateValue]
|
||||
* clone() - clone the entire statemachine.
|
||||
* next(eventValue) - drive the FSM to the next state. If the event
|
||||
@@ -39,65 +40,24 @@ Example:
|
||||
const fsm = new StateMachine("A", transitions, () => { throw new Error("oops") });
|
||||
fsm.next("yo"); // returns 42
|
||||
|
||||
|
||||
*/
|
||||
|
||||
export type FsmState = number | string;
|
||||
export type FsmEvent = string; // by convention, we assume Events are redux action types, aka strings
|
||||
|
||||
export type FsmActionFn<ActionReturnType> = (
|
||||
fsm: StateMachine<ActionReturnType>,
|
||||
transition: FsmTransition<ActionReturnType>,
|
||||
data: unknown
|
||||
) => ActionReturnType;
|
||||
|
||||
export interface FsmTransition<ActionReturnType> {
|
||||
from: FsmState;
|
||||
to: FsmState;
|
||||
event: FsmEvent;
|
||||
action: FsmActionFn<ActionReturnType>;
|
||||
}
|
||||
|
||||
export type FsmErrorFn<ActionReturnType> = (
|
||||
fsm: StateMachine<ActionReturnType>,
|
||||
event: FsmEvent,
|
||||
state: FsmState
|
||||
) => ActionReturnType;
|
||||
|
||||
export class StateMachine<ActionReturnType> {
|
||||
events: Set<FsmEvent>;
|
||||
|
||||
graph: Map<FsmEvent, Map<FsmState, FsmTransition<ActionReturnType>>>;
|
||||
|
||||
onError: FsmErrorFn<ActionReturnType>;
|
||||
|
||||
state: FsmState;
|
||||
|
||||
states: Set<FsmState>;
|
||||
|
||||
constructor(
|
||||
initState: FsmState,
|
||||
transitions: FsmTransition<ActionReturnType>[],
|
||||
onError: FsmErrorFn<ActionReturnType>
|
||||
) {
|
||||
this.onError = onError;
|
||||
export default class StateMachine {
|
||||
constructor(initState, transitions, onError) {
|
||||
this.onError = onError || (() => undefined);
|
||||
this.state = initState;
|
||||
|
||||
// all states
|
||||
this.states = new Set(
|
||||
transitions.reduce(
|
||||
(names: Array<FsmState>, tsn: FsmTransition<ActionReturnType>) => {
|
||||
names.push(tsn.from);
|
||||
names.push(tsn.to);
|
||||
return names;
|
||||
},
|
||||
[]
|
||||
)
|
||||
transitions.reduce((names, tsn) => {
|
||||
names.push(tsn.from);
|
||||
names.push(tsn.to);
|
||||
return names;
|
||||
}, [])
|
||||
);
|
||||
|
||||
// all transition names (aka events)
|
||||
this.events = new Set(
|
||||
transitions.map((tsn: FsmTransition<ActionReturnType>) => tsn.event)
|
||||
);
|
||||
this.events = new Set(transitions.map((tsn) => tsn.event));
|
||||
|
||||
// the transition graph.
|
||||
// graph[event][from] -> transition
|
||||
@@ -110,23 +70,26 @@ export class StateMachine<ActionReturnType> {
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
clone(initState: FsmState): StateMachine<ActionReturnType> {
|
||||
const fsm = new StateMachine<ActionReturnType>(initState, [], this.onError);
|
||||
clone(initState) {
|
||||
const fsm = new StateMachine(initState, []);
|
||||
fsm.onError = this.onError;
|
||||
fsm.states = this.states;
|
||||
fsm.events = this.events;
|
||||
fsm.graph = this.graph;
|
||||
return fsm;
|
||||
}
|
||||
|
||||
next(event: FsmEvent, data: unknown): ActionReturnType {
|
||||
next(event, data) {
|
||||
const { graph, state } = this;
|
||||
const tsnMap = graph.get(event);
|
||||
if (!tsnMap) return this.onError(this, event, state);
|
||||
if (!tsnMap) return this.onError(this, event, state, undefined);
|
||||
|
||||
const transition = tsnMap.get(state);
|
||||
if (!transition) return this.onError(this, event, state);
|
||||
if (!transition) return this.onError(this, event, state, undefined);
|
||||
|
||||
this.state = transition.to;
|
||||
return transition.action(this, transition, data);
|
||||
return transition.action
|
||||
? transition.action(this, transition, data)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Various type and schema related helper functions.
|
||||
*/
|
||||
|
||||
/*
|
||||
Utility function to test for a typed array
|
||||
*/
|
||||
export function isTypedArray(x) {
|
||||
return (
|
||||
ArrayBuffer.isView(x) &&
|
||||
Object.prototype.toString.call(x) !== "[object DataView]"
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Test for float typed array, ie, Float32TypedArray or Float64TypedArray
|
||||
*/
|
||||
export function isFpTypedArray(x) {
|
||||
let constructor;
|
||||
const isFloatArray =
|
||||
x &&
|
||||
({ constructor } = x) &&
|
||||
(constructor === Float32Array || constructor === Float64Array);
|
||||
return isFloatArray;
|
||||
}
|
||||
|
||||
export function isArrayOrTypedArray(x) {
|
||||
return Array.isArray(x) || isTypedArray(x);
|
||||
}
|
||||
+14
-47
@@ -17,23 +17,7 @@
|
||||
// The underlying data structure uses TypedArrays for performance.
|
||||
//
|
||||
class BitArray {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
bitarray: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
bitmask: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dimensionCount: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
length: any;
|
||||
|
||||
// eslint-disable-next-line @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.
|
||||
constructor(length: any) {
|
||||
constructor(length) {
|
||||
// Initially allocate a 32 bit wide array. allocDimension() will expand
|
||||
// as necessary.
|
||||
//
|
||||
@@ -56,14 +40,12 @@ class BitArray {
|
||||
// Return the number of records that are selected, ie, have a one bit in
|
||||
// all allocated dimensions.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
selectionCount() {
|
||||
return this.countAllOnes();
|
||||
}
|
||||
|
||||
// Count all records that have a 'one' bit in allocated dimensions.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
countAllOnes() {
|
||||
let count = 0;
|
||||
const { bitarray, length, width } = this;
|
||||
@@ -94,8 +76,7 @@ class BitArray {
|
||||
|
||||
// count trailing zeros - hard to do fast in JS!
|
||||
// https://en.wikipedia.org/wiki/Find_first_set#CTZ
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static ctz(av: any) {
|
||||
static ctz(av) {
|
||||
let c = 32;
|
||||
let v = av;
|
||||
v &= -v; // isolate lowest non-zero bit
|
||||
@@ -109,7 +90,6 @@ class BitArray {
|
||||
}
|
||||
|
||||
// find a free dimension. Return undefined if none
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
_findFreeDimension() {
|
||||
let dim;
|
||||
for (let col = 0; col < this.width; col += 1) {
|
||||
@@ -125,7 +105,6 @@ class BitArray {
|
||||
|
||||
// allocate and return the dimension ID (bit position)
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
allocDimension() {
|
||||
let dim = this._findFreeDimension();
|
||||
|
||||
@@ -151,8 +130,7 @@ class BitArray {
|
||||
// free a dimension for later use. MUST deselect the dimension, as other
|
||||
// code assume the column will be zero valued.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
freeDimension(dim: any) {
|
||||
freeDimension(dim) {
|
||||
// all selection tests assume unallocated dimensions are zero valued.
|
||||
this.deselectAll(dim);
|
||||
const col = dim >>> 5;
|
||||
@@ -162,8 +140,7 @@ class BitArray {
|
||||
|
||||
// return true if this index is selected in ALL dimensions.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
isSelected(index: any) {
|
||||
isSelected(index) {
|
||||
const { width, length, bitarray } = this;
|
||||
|
||||
for (let w = 0; w < width; w += 1) {
|
||||
@@ -175,8 +152,7 @@ class BitArray {
|
||||
|
||||
// return true if this index is selected in ALL dimensions IGNORING dim
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
isSelectedIgnoringDim(index: any, dim: any) {
|
||||
isSelectedIgnoringDim(index, dim) {
|
||||
const ignoreOffset = dim >>> 5;
|
||||
const ignoreMask = ~(1 << dim % 32);
|
||||
|
||||
@@ -200,8 +176,7 @@ class BitArray {
|
||||
|
||||
// select index on dimension
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectOne(dim: any, index: any) {
|
||||
selectOne(dim, index) {
|
||||
const col = dim >>> 5;
|
||||
const before = this.bitarray[col * this.length + index];
|
||||
const after = before | (1 << dim % 32);
|
||||
@@ -210,8 +185,7 @@ class BitArray {
|
||||
|
||||
// deselect index on dimension
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
deselectOne(dim: any, index: any) {
|
||||
deselectOne(dim, index) {
|
||||
const col = dim >>> 5;
|
||||
const before = this.bitarray[col * this.length + index];
|
||||
const after = before & ~(1 << dim % 32);
|
||||
@@ -220,8 +194,7 @@ class BitArray {
|
||||
|
||||
// select all indices on dimension.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectAll(dim: any) {
|
||||
selectAll(dim) {
|
||||
const col = dim >> 5;
|
||||
const one = 1 << dim % 32;
|
||||
for (let i = col * this.length, len = i + this.length; i < len; i += 1) {
|
||||
@@ -231,8 +204,7 @@ class BitArray {
|
||||
|
||||
// deselect all indices on dimension
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
deselectAll(dim: any) {
|
||||
deselectAll(dim) {
|
||||
const col = dim >> 5;
|
||||
const zero = ~(1 << dim % 32);
|
||||
for (let i = col * this.length, len = i + this.length; i < len; i += 1) {
|
||||
@@ -242,8 +214,7 @@ class BitArray {
|
||||
|
||||
// select range of indices on a dimension
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectFromRange(dim: any, range: any) {
|
||||
selectFromRange(dim, range) {
|
||||
const col = dim >>> 5;
|
||||
const first = range[0];
|
||||
const last = range[1];
|
||||
@@ -257,8 +228,7 @@ class BitArray {
|
||||
// select range of indices on a dimension, indirect through a sort map.
|
||||
// Indirect functions are used to map between sort and natural order.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectIndirectFromRange(dim: any, indirect: any, range: any) {
|
||||
selectIndirectFromRange(dim, indirect, range) {
|
||||
const col = dim >>> 5;
|
||||
const first = range[0];
|
||||
const last = range[1];
|
||||
@@ -271,8 +241,7 @@ class BitArray {
|
||||
|
||||
// deselect range of indices on a dimension
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
deselectFromRange(dim: any, range: any) {
|
||||
deselectFromRange(dim, range) {
|
||||
const col = dim >>> 5;
|
||||
const first = range[0];
|
||||
const last = range[1];
|
||||
@@ -285,8 +254,7 @@ class BitArray {
|
||||
|
||||
// deselect range of indices on a dimension, indirect through a sort map.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
deselectIndirectFromRange(dim: any, indirect: any, range: any) {
|
||||
deselectIndirectFromRange(dim, indirect, range) {
|
||||
const col = dim >>> 5;
|
||||
const first = range[0];
|
||||
const last = range[1];
|
||||
@@ -300,8 +268,7 @@ class BitArray {
|
||||
// Fill the array with selected|deselected value based upon the
|
||||
// current selection state.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
fillBySelection(result: any, selectedValue: any, deselectedValue: any) {
|
||||
fillBySelection(result, selectedValue, deselectedValue) {
|
||||
// special case (width === 1) for performance
|
||||
if (this.width === 1) {
|
||||
const { bitmask, bitarray } = this;
|
||||
+48
-125
@@ -9,11 +9,10 @@ import {
|
||||
upperBoundIndirect,
|
||||
} from "./sort";
|
||||
import { makeSortIndex } from "./util";
|
||||
import { isAnyArray } from "../../common/types/arraytypes";
|
||||
|
||||
class NotImplementedError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg);
|
||||
constructor(...params) {
|
||||
super(...params);
|
||||
|
||||
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
||||
if (Error.captureStackTrace) {
|
||||
@@ -23,17 +22,7 @@ class NotImplementedError extends Error {
|
||||
}
|
||||
|
||||
export default class ImmutableTypedCrossfilter {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
data: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dimensions: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
selectionCache: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(data: any, dimensions = {}, selectionCache = {}) {
|
||||
constructor(data, dimensions = {}, selectionCache = {}) {
|
||||
/*
|
||||
Typically, parameter 'data' is one of:
|
||||
- Array of objects/records
|
||||
@@ -62,36 +51,31 @@ export default class ImmutableTypedCrossfilter {
|
||||
Object.preventExtensions(this);
|
||||
}
|
||||
|
||||
size(): number {
|
||||
size() {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
all() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
setData(data: any) {
|
||||
setData(data) {
|
||||
if (this.data === data) return this;
|
||||
// please leave, WIP
|
||||
// console.log("...crossfilter set data, will drop cache");
|
||||
return new ImmutableTypedCrossfilter(data, this.dimensions);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
dimensionNames() {
|
||||
/* return array of all dimensions (by name) */
|
||||
return Object.keys(this.dimensions);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
hasDimension(name: any) {
|
||||
hasDimension(name) {
|
||||
return !!this.dimensions[name];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
addDimension(name: any, type: any, ...rest: any[]) {
|
||||
addDimension(name, type, ...rest) {
|
||||
/*
|
||||
Add a new dimension to this crossfilter, of type DimensionType.
|
||||
Remainder of parameters are dimension-type-specific.
|
||||
@@ -110,7 +94,6 @@ export default class ImmutableTypedCrossfilter {
|
||||
id = bitArray.allocDimension();
|
||||
bitArray.selectAll(id);
|
||||
}
|
||||
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||
const DimensionType = DimTypes[type];
|
||||
const dim = new DimensionType(name, data, ...rest);
|
||||
Object.freeze(dim);
|
||||
@@ -129,8 +112,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
delDimension(name: any) {
|
||||
delDimension(name) {
|
||||
const { data } = this;
|
||||
const { bitArray } = this.selectionCache;
|
||||
const dimensions = { ...this.dimensions };
|
||||
@@ -150,8 +132,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
renameDimension(oldName: any, newName: any) {
|
||||
renameDimension(oldName, newName) {
|
||||
const { [oldName]: dim, ...dimensions } = this.dimensions;
|
||||
const { data, selectionCache } = this;
|
||||
const newDimensions = {
|
||||
@@ -165,8 +146,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
return new ImmutableTypedCrossfilter(data, newDimensions, selectionCache);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
select(name: any, spec: any) {
|
||||
select(name, spec) {
|
||||
/*
|
||||
select on named dimension, as indicated by `spec`. Spec is an object
|
||||
specifying the selection, and must contain at least a `mode` field.
|
||||
@@ -194,17 +174,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
return new ImmutableTypedCrossfilter(data, dimensions, newSelectionCache);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
static _dimSelnHasUpdated(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectionCache: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
id: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
newSeln: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
oldSeln: any
|
||||
) {
|
||||
static _dimSelnHasUpdated(selectionCache, id, newSeln, oldSeln) {
|
||||
/*
|
||||
Selection has updated from oldSeln to newSeln. Update the
|
||||
bit array if it exists. If not, we will lazy create it when
|
||||
@@ -238,29 +208,24 @@ export default class ImmutableTypedCrossfilter {
|
||||
If sort index exists in the dimension, assume sort ordered ranges.
|
||||
*/
|
||||
if (oldSeln.index) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dels.forEach((interval: any) =>
|
||||
dels.forEach((interval) =>
|
||||
bitArray.deselectIndirectFromRange(id, oldSeln.index, interval)
|
||||
);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
dels.forEach((interval: any) => bitArray.deselectFromRange(id, interval));
|
||||
dels.forEach((interval) => bitArray.deselectFromRange(id, interval));
|
||||
}
|
||||
|
||||
if (newSeln.index) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
adds.forEach((interval: any) =>
|
||||
adds.forEach((interval) =>
|
||||
bitArray.selectIndirectFromRange(id, newSeln.index, interval)
|
||||
);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
adds.forEach((interval: any) => bitArray.selectFromRange(id, interval));
|
||||
adds.forEach((interval) => bitArray.selectFromRange(id, interval));
|
||||
}
|
||||
|
||||
return { bitArray };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
_getSelectionCache() {
|
||||
if (!this.selectionCache) this.selectionCache = {};
|
||||
|
||||
@@ -272,8 +237,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
const id = bitArray.allocDimension();
|
||||
this.dimensions[name].id = id;
|
||||
const { ranges, index } = selection;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
ranges.forEach((range: any) => {
|
||||
ranges.forEach((range) => {
|
||||
if (index) {
|
||||
bitArray.selectIndirectFromRange(id, index, range);
|
||||
} else {
|
||||
@@ -286,19 +250,16 @@ export default class ImmutableTypedCrossfilter {
|
||||
return this.selectionCache;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
_clearSelectionCache() {
|
||||
this.selectionCache = {};
|
||||
return this.selectionCache;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
_setSelectionCache(vals = {}) {
|
||||
Object.assign(this.selectionCache, vals);
|
||||
return this.selectionCache;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
allSelected() {
|
||||
/*
|
||||
return array of all records currently selected by all dimensions
|
||||
@@ -319,7 +280,6 @@ export default class ImmutableTypedCrossfilter {
|
||||
return data.isubsetMask(this.allSelectedMask());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
allSelectedMask() {
|
||||
/*
|
||||
return Uint8Array containing selection state (truthy/falsey) for each record.
|
||||
@@ -338,7 +298,6 @@ export default class ImmutableTypedCrossfilter {
|
||||
return allSelectedMask;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
countSelected() {
|
||||
/*
|
||||
return number of records selected on all dimensions
|
||||
@@ -353,8 +312,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
return countSelected;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
isElementSelected(i: any) {
|
||||
isElementSelected(i) {
|
||||
/*
|
||||
return truthy/falsey if this record is selected on all dimensions
|
||||
*/
|
||||
@@ -362,8 +320,7 @@ export default class ImmutableTypedCrossfilter {
|
||||
return selectionCache.bitArray.isSelected(i);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
fillByIsSelected(array: any, selectedValue: any, deselectedValue: any) {
|
||||
fillByIsSelected(array, selectedValue, deselectedValue) {
|
||||
/*
|
||||
fill array with one of two values, based upon selection state.
|
||||
*/
|
||||
@@ -388,11 +345,7 @@ for a dimension:
|
||||
- name - the dimension name/label.
|
||||
*/
|
||||
class _ImmutableBaseDimension {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
name: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(name: any) {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -400,15 +353,13 @@ class _ImmutableBaseDimension {
|
||||
return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
rename(name: any) {
|
||||
rename(name) {
|
||||
const d = this.clone();
|
||||
d.name = name;
|
||||
return d;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
select(spec: any) {
|
||||
select(spec) {
|
||||
const { mode } = spec;
|
||||
if (mode === undefined) {
|
||||
throw new Error("select spec does not contain 'mode'");
|
||||
@@ -420,14 +371,7 @@ class _ImmutableBaseDimension {
|
||||
}
|
||||
|
||||
class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
index: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(name: any, data: any, value: any, ValueArrayType: any) {
|
||||
constructor(name, data, value, ValueArrayType) {
|
||||
super(name);
|
||||
|
||||
// Three modes - caller can provide a pre-created value array,
|
||||
@@ -449,13 +393,12 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
value,
|
||||
new ValueArrayType(data.length)
|
||||
);
|
||||
} else if (isAnyArray(value)) {
|
||||
} else if (isArrayOrTypedArray(value)) {
|
||||
// Create value array from user-provided array. Typically used
|
||||
// only by enumerated dimensions
|
||||
array = this._createValueArray(
|
||||
data,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
(i: any) => value[i],
|
||||
(i) => value[i],
|
||||
new ValueArrayType(data.length)
|
||||
);
|
||||
} else {
|
||||
@@ -469,8 +412,8 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
this.index = makeSortIndex(array);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- needed for polymorphism
|
||||
_createValueArray(data: any, mapf: any, array: any) {
|
||||
// eslint-disable-next-line class-methods-use-this -- needed for polymorphism
|
||||
_createValueArray(data, mapf, array) {
|
||||
// create dimension value array
|
||||
const len = data.length;
|
||||
const larray = array;
|
||||
@@ -480,8 +423,7 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
return larray;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
select(spec: any) {
|
||||
select(spec) {
|
||||
const { mode } = spec;
|
||||
const { index } = this;
|
||||
switch (mode) {
|
||||
@@ -498,8 +440,7 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectExact(spec: any) {
|
||||
selectExact(spec) {
|
||||
const { value, index } = this;
|
||||
let { values } = spec;
|
||||
if (!Array.isArray(values)) {
|
||||
@@ -518,8 +459,7 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
return { ranges, index };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectRange(spec: any) {
|
||||
selectRange(spec) {
|
||||
const { value, index } = this;
|
||||
/*
|
||||
if !inclusive: [lo, hi) else [lo, hi]
|
||||
@@ -538,13 +478,11 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension {
|
||||
}
|
||||
|
||||
class ImmutableEnumDimension extends ImmutableScalarDimension {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(name: any, data: any, value: any) {
|
||||
constructor(name, data, value) {
|
||||
super(name, data, value, Uint32Array);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
_createValueArray(data: any, mapf: any, array: any) {
|
||||
_createValueArray(data, mapf, array) {
|
||||
const len = data.length;
|
||||
const larray = array;
|
||||
|
||||
@@ -555,7 +493,6 @@ class ImmutableEnumDimension extends ImmutableScalarDimension {
|
||||
s.add(mapf(i, data));
|
||||
}
|
||||
const enumIndex = sortArray(Array.from(s));
|
||||
// @ts-expect-error FIXME Adding enumIndex as member variable results in "undefined" enumIndex value
|
||||
this.enumIndex = enumIndex;
|
||||
|
||||
// create dimension value array
|
||||
@@ -568,9 +505,7 @@ class ImmutableEnumDimension extends ImmutableScalarDimension {
|
||||
return larray;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectExact(spec: any) {
|
||||
// @ts-expect-error FIXME Adding enumIndex as member variable results in "undefined" enumIndex value
|
||||
selectExact(spec) {
|
||||
const { enumIndex } = this;
|
||||
let { values } = spec;
|
||||
if (!Array.isArray(values)) {
|
||||
@@ -578,35 +513,20 @@ class ImmutableEnumDimension extends ImmutableScalarDimension {
|
||||
}
|
||||
return super.selectExact({
|
||||
mode: spec.mode,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
values: values.map((v: any) =>
|
||||
values: values.map((v) =>
|
||||
binarySearch(enumIndex, v, 0, enumIndex.length)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-expect-error ts-migrate(2416) FIXME: Property 'selectRange' in type 'ImmutableEnumDimen... Remove this comment to see the full error message
|
||||
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- enables polymorphism
|
||||
// eslint-disable-next-line class-methods-use-this -- enables polymorphism
|
||||
selectRange() {
|
||||
throw new Error("range selection unsupported on Enumerated dimension");
|
||||
}
|
||||
}
|
||||
|
||||
class ImmutableSpatialDimension extends _ImmutableBaseDimension {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
X: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
Xindex: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
Y: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
Yindex: any;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
constructor(name: any, data: any, X: any, Y: any) {
|
||||
constructor(name, data, X, Y) {
|
||||
super(name);
|
||||
|
||||
if (X.length !== Y.length && X.length !== data.length) {
|
||||
@@ -621,8 +541,7 @@ class ImmutableSpatialDimension extends _ImmutableBaseDimension {
|
||||
this.Yindex = makeSortIndex(Y);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
select(spec: any) {
|
||||
select(spec) {
|
||||
const { mode } = spec;
|
||||
switch (mode) {
|
||||
case "all":
|
||||
@@ -638,8 +557,7 @@ class ImmutableSpatialDimension extends _ImmutableBaseDimension {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectWithinRect(spec: any) {
|
||||
selectWithinRect(spec) {
|
||||
/*
|
||||
{ mode: "within-rect", minX: 1, minY: 0, maxX: 3, maxY: 9 }
|
||||
*/
|
||||
@@ -672,8 +590,7 @@ class ImmutableSpatialDimension extends _ImmutableBaseDimension {
|
||||
* then the polygon test is applied
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
selectWithinPolygon(spec: any) {
|
||||
selectWithinPolygon(spec) {
|
||||
/*
|
||||
{ mode: "within-polygon", polygon: [ [x0, y0], ... ] }
|
||||
*/
|
||||
@@ -728,9 +645,16 @@ export const DimTypes = {
|
||||
spatial: ImmutableSpatialDimension,
|
||||
};
|
||||
|
||||
function isArrayOrTypedArray(x) {
|
||||
return (
|
||||
Array.isArray(x) ||
|
||||
(ArrayBuffer.isView(x) &&
|
||||
Object.prototype.toString.call(x) !== "[object DataView]")
|
||||
);
|
||||
}
|
||||
|
||||
/* return bounding box of the polygon */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function polygonBoundingBox(polygon: any) {
|
||||
function polygonBoundingBox(polygon) {
|
||||
let minX = Number.MAX_VALUE;
|
||||
let minY = Number.MAX_VALUE;
|
||||
let maxX = Number.MIN_VALUE;
|
||||
@@ -754,8 +678,7 @@ function polygonBoundingBox(polygon: any) {
|
||||
* @param {float} y - point y coordinate
|
||||
* @type {boolean}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function withinPolygon(polygon: any, x: any, y: any) {
|
||||
function withinPolygon(polygon, x, y) {
|
||||
const n = polygon.length;
|
||||
let p = polygon[n - 1];
|
||||
let x0 = p[0];
|
||||
+6
-12
@@ -16,12 +16,10 @@ class PositiveIntervals {
|
||||
// 1. no overlapping intervals
|
||||
// 2. sorted in order of interval min.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static canonicalize(A: any) {
|
||||
static canonicalize(A) {
|
||||
if (A.length <= 1) return A;
|
||||
const copy = A.slice();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
copy.sort((a: any, b: any) => a[0] - b[0]);
|
||||
copy.sort((a, b) => a[0] - b[0]);
|
||||
const res = [];
|
||||
res.push(copy[0]);
|
||||
for (let i = 1, len = copy.length; i < len; i += 1) {
|
||||
@@ -40,13 +38,11 @@ class PositiveIntervals {
|
||||
// Return interval with values belonging to both A and B. Essentially
|
||||
// a set union operation.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static union(A: any, B: any) {
|
||||
static union(A, B) {
|
||||
return PositiveIntervals.canonicalize([...A, ...B]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static _flatten(A: any, B: any) {
|
||||
static _flatten(A, B) {
|
||||
const points = []; /* point, A, start */
|
||||
for (let a = 0; a < A.length; a += 1) {
|
||||
points.push([A[a][0], true, true]);
|
||||
@@ -64,8 +60,7 @@ class PositiveIntervals {
|
||||
// A - B, ie, the interval with all values in A that are not in B. Essentially
|
||||
// a set difference operation.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static difference(A: any, B: any) {
|
||||
static difference(A, B) {
|
||||
// Corner cases
|
||||
if (A.length === 0 || B.length === 0) {
|
||||
return PositiveIntervals.canonicalize(A);
|
||||
@@ -101,8 +96,7 @@ class PositiveIntervals {
|
||||
// Return interval with values belonging to A or B. Essentially a set
|
||||
// intersection.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
static intersection(A: any, B: any) {
|
||||
static intersection(A, B) {
|
||||
if (A.length === 0 || B.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
import { isTypedArray, isFpTypedArray } from "../typeHelpers";
|
||||
|
||||
/* eslint-disable no-bitwise -- code relies on bitwise ops */
|
||||
|
||||
/*
|
||||
** fast sort and search, with separate code paths for floats (NaN ordering),
|
||||
** indirect and direct search/sort.
|
||||
*/
|
||||
|
||||
/*
|
||||
Comparators for float sort. -Infinity < finite < Infinity < NaN
|
||||
*/
|
||||
function lt(a, b) {
|
||||
if (Number.isNaN(b)) return !Number.isNaN(a);
|
||||
return a < b;
|
||||
}
|
||||
|
||||
function gt(a, b) {
|
||||
if (Number.isNaN(a)) return !Number.isNaN(b);
|
||||
return a > b;
|
||||
}
|
||||
|
||||
/*
|
||||
insertion sort, used for small arrays (controlled by SMALL_ARRAY constant)
|
||||
*/
|
||||
const SMALL_ARRAY = 32;
|
||||
function insertionsort(a, lo, hi) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
let j;
|
||||
for (j = i; j > lo && a[j - 1] > x; j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function insertionsortFloats(a, lo, hi) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
let j;
|
||||
for (j = i; j > lo && gt(a[j - 1], x); j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function insertionsortIndirect(a, s, lo, hi) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
const t = s[x];
|
||||
let j;
|
||||
for (j = i; j > lo && s[a[j - 1]] > t; j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function insertionsortFloatsIndirect(a, s, lo, hi) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
const t = s[x];
|
||||
let j;
|
||||
for (j = i; j > lo && gt(s[a[j - 1]], t); j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/*
|
||||
Quicksort - used for larger arrays
|
||||
*/
|
||||
function quicksort(a, lo, hi) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsort(a, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (a[i] < p);
|
||||
do {
|
||||
j -= 1;
|
||||
} while (a[j] > p);
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksort(a, lo, j);
|
||||
quicksort(a, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function quicksortFloats(a, lo, hi) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsortFloats(a, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (lt(a[i], p));
|
||||
do {
|
||||
j -= 1;
|
||||
} while (gt(a[j], p));
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksortFloats(a, lo, j);
|
||||
quicksortFloats(a, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function quicksortIndirect(a, s, lo, hi) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsortIndirect(a, s, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
const t = s[p];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (s[a[i]] < t);
|
||||
do {
|
||||
j -= 1;
|
||||
} while (s[a[j]] > t);
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksortIndirect(a, s, lo, j);
|
||||
quicksortIndirect(a, s, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function quicksortFloatsIndirect(a, s, lo, hi) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsortFloatsIndirect(a, s, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
const t = s[p];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (lt(s[a[i]], t));
|
||||
do {
|
||||
j -= 1;
|
||||
} while (gt(s[a[j]], t));
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksortFloatsIndirect(a, s, lo, j);
|
||||
quicksortFloatsIndirect(a, s, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/*
|
||||
Convenience wrappers, handling optimization paths and default
|
||||
handlers for NaN comparisons. Sorts in place.
|
||||
*/
|
||||
export function sortArray(arr) {
|
||||
if (Array.isArray(arr)) {
|
||||
return quicksort(arr, 0, arr.length - 1);
|
||||
}
|
||||
if (isTypedArray(arr)) {
|
||||
if (isFpTypedArray(arr)) {
|
||||
return quicksortFloats(arr, 0, arr.length - 1);
|
||||
}
|
||||
return quicksort(arr, 0, arr.length - 1);
|
||||
}
|
||||
/* else unsupported */
|
||||
throw new Error("sortArray received unsupported object type");
|
||||
}
|
||||
|
||||
export function sortIndex(index, source) {
|
||||
if (isFpTypedArray(source))
|
||||
return quicksortFloatsIndirect(index, source, 0, index.length - 1);
|
||||
return quicksortIndirect(index, source, 0, index.length - 1);
|
||||
}
|
||||
|
||||
// Search for `value` in the sorted array `arr`, in the range [first, last).
|
||||
// Return the first (left most) index where arr[index] >= value.
|
||||
//
|
||||
// In other words, return array index I where:
|
||||
// arr[i] < value for all tarr[lo:I]
|
||||
// arr[i] >= value for all tarr[I:last]
|
||||
//
|
||||
// The same semantics/behavior as:
|
||||
// C++: lower_bound()
|
||||
// Python: bisect.bisect_left()
|
||||
//
|
||||
function lowerBoundNonFloat(valueArray, value, first, last) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[middle] < value) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// lowerBound, but with NaN handling
|
||||
//
|
||||
// If the underlying array is a Float32Array or Float64Array, will enforce
|
||||
// the ordering -Infinity < finite < Infinity < NaN.
|
||||
//
|
||||
function lowerBoundFloat(valueArray, value, first, last) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (lt(valueArray[middle], value)) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
export function lowerBound(valueArray, value, first, last) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
return lowerBoundFloat(valueArray, value, first, last);
|
||||
}
|
||||
return lowerBoundNonFloat(valueArray, value, first, last);
|
||||
}
|
||||
|
||||
// Inlined performance optimization - used to indirect through a sort map.
|
||||
//
|
||||
function lowerBoundNonFloatIndirect(
|
||||
valueArray,
|
||||
indexArray,
|
||||
value,
|
||||
first,
|
||||
last
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[indexArray[middle]] < value) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
function lowerBoundFloatIndirect(valueArray, indexArray, value, first, last) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (lt(valueArray[indexArray[middle]], value)) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
return lowerBoundFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
return lowerBoundNonFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
|
||||
// Search for `value in the sorted array `arr`, in the range [first, last).
|
||||
// Return the first value where arr[index] > value.
|
||||
//
|
||||
// In other words, return array index I, where:
|
||||
// arr[i] <= value for all tarr[lo:I]
|
||||
// arr[i] > value for all tarr[I:last]
|
||||
//
|
||||
// The same semantics/behavior as:
|
||||
// C++: upper_bound()
|
||||
// Python: bisect.bisect_right()
|
||||
//
|
||||
function upperBoundNonFloat(valueArray, value, first, last) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[middle] > value) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
function upperBoundFloat(valueArray, value, first, last) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (gt(valueArray[middle], value)) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
export function upperBound(valueArray, value, first, last) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
return upperBoundFloat(valueArray, value, first, last);
|
||||
}
|
||||
return upperBoundNonFloat(valueArray, value, first, last);
|
||||
}
|
||||
|
||||
// Inline performance optimization
|
||||
//
|
||||
function upperBoundNonFloatIndirect(
|
||||
valueArray,
|
||||
indexArray,
|
||||
value,
|
||||
first,
|
||||
last
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[indexArray[middle]] > value) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
function upperBoundFloatIndirect(valueArray, indexArray, value, first, last) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (gt(valueArray[indexArray[middle]], value)) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||
if (isFpTypedArray(valueArray)) {
|
||||
return upperBoundFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
return upperBoundNonFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
|
||||
// Search for `value` in the sorted array `arr`, in the range [first, last).
|
||||
// Return the first index where arr[index] == value, OR if value not present,
|
||||
// return `last`
|
||||
//
|
||||
// The same semantics/behavior as:
|
||||
// C++: binary_search()
|
||||
//
|
||||
export function binarySearch(valueArray, value, first, last) {
|
||||
const index = lowerBound(valueArray, value, first, last);
|
||||
if (index !== last && value === valueArray[index]) return index;
|
||||
return last;
|
||||
}
|
||||
/* eslint-enable no-bitwise -- enable */
|
||||
@@ -1,529 +0,0 @@
|
||||
import { isTypedArray, isFloatTypedArray } from "../../common/types/arraytypes";
|
||||
|
||||
/* eslint-disable no-bitwise -- code relies on bitwise ops */
|
||||
|
||||
/*
|
||||
** fast sort and search, with separate code paths for floats (NaN ordering),
|
||||
** indirect and direct search/sort.
|
||||
*/
|
||||
|
||||
/*
|
||||
Comparators for float sort. -Infinity < finite < Infinity < NaN
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function lt(a: any, b: any) {
|
||||
if (Number.isNaN(b)) return !Number.isNaN(a);
|
||||
return a < b;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function gt(a: any, b: any) {
|
||||
if (Number.isNaN(a)) return !Number.isNaN(b);
|
||||
return a > b;
|
||||
}
|
||||
|
||||
/*
|
||||
insertion sort, used for small arrays (controlled by SMALL_ARRAY constant)
|
||||
*/
|
||||
const SMALL_ARRAY = 32;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function insertionsort(a: any, lo: any, hi: any) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
let j;
|
||||
for (j = i; j > lo && a[j - 1] > x; j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function insertionsortFloats(a: any, lo: any, hi: any) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
let j;
|
||||
for (j = i; j > lo && gt(a[j - 1], x); j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function insertionsortIndirect(a: any, s: any, lo: any, hi: any) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
const t = s[x];
|
||||
let j;
|
||||
for (j = i; j > lo && s[a[j - 1]] > t; j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function insertionsortFloatsIndirect(a: any, s: any, lo: any, hi: any) {
|
||||
for (let i = lo + 1; i < hi + 1; i += 1) {
|
||||
const x = a[i];
|
||||
const t = s[x];
|
||||
let j;
|
||||
for (j = i; j > lo && gt(s[a[j - 1]], t); j -= 1) {
|
||||
a[j] = a[j - 1];
|
||||
}
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/*
|
||||
Quicksort - used for larger arrays
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function quicksort(a: any, lo: any, hi: any) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsort(a, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (a[i] < p);
|
||||
do {
|
||||
j -= 1;
|
||||
} while (a[j] > p);
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksort(a, lo, j);
|
||||
quicksort(a, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function quicksortFloats(a: any, lo: any, hi: any) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsortFloats(a, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (lt(a[i], p));
|
||||
do {
|
||||
j -= 1;
|
||||
} while (gt(a[j], p));
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksortFloats(a, lo, j);
|
||||
quicksortFloats(a, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function quicksortIndirect(a: any, s: any, lo: any, hi: any) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsortIndirect(a, s, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
const t = s[p];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (s[a[i]] < t);
|
||||
do {
|
||||
j -= 1;
|
||||
} while (s[a[j]] > t);
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksortIndirect(a, s, lo, j);
|
||||
quicksortIndirect(a, s, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function quicksortFloatsIndirect(a: any, s: any, lo: any, hi: any) {
|
||||
if (hi - lo < SMALL_ARRAY) {
|
||||
return insertionsortFloatsIndirect(a, s, lo, hi);
|
||||
}
|
||||
if (lo < hi) {
|
||||
// partition
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
const p = a[mid];
|
||||
const t = s[p];
|
||||
let i = lo - 1;
|
||||
let j = hi + 1;
|
||||
while (i < j) {
|
||||
do {
|
||||
i += 1;
|
||||
} while (lt(s[a[i]], t));
|
||||
do {
|
||||
j -= 1;
|
||||
} while (gt(s[a[j]], t));
|
||||
if (i < j) {
|
||||
const tmp = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = tmp;
|
||||
}
|
||||
}
|
||||
// sort
|
||||
quicksortFloatsIndirect(a, s, lo, j);
|
||||
quicksortFloatsIndirect(a, s, j + 1, hi);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/*
|
||||
Convenience wrappers, handling optimization paths and default
|
||||
handlers for NaN comparisons. Sorts in place.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function sortArray(arr: any) {
|
||||
if (Array.isArray(arr)) {
|
||||
return quicksort(arr, 0, arr.length - 1);
|
||||
}
|
||||
if (isTypedArray(arr)) {
|
||||
if (isFloatTypedArray(arr)) {
|
||||
return quicksortFloats(arr, 0, arr.length - 1);
|
||||
}
|
||||
return quicksort(arr, 0, arr.length - 1);
|
||||
}
|
||||
/* else unsupported */
|
||||
throw new Error("sortArray received unsupported object type");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function sortIndex(index: any, source: any) {
|
||||
if (isFloatTypedArray(source))
|
||||
return quicksortFloatsIndirect(index, source, 0, index.length - 1);
|
||||
return quicksortIndirect(index, source, 0, index.length - 1);
|
||||
}
|
||||
|
||||
// Search for `value` in the sorted array `arr`, in the range [first, last).
|
||||
// Return the first (left most) index where arr[index] >= value.
|
||||
//
|
||||
// In other words, return array index I where:
|
||||
// arr[i] < value for all tarr[lo:I]
|
||||
// arr[i] >= value for all tarr[I:last]
|
||||
//
|
||||
// The same semantics/behavior as:
|
||||
// C++: lower_bound()
|
||||
// Python: bisect.bisect_left()
|
||||
//
|
||||
function lowerBoundNonFloat(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[middle] < value) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// lowerBound, but with NaN handling
|
||||
//
|
||||
// If the underlying array is a Float32Array or Float64Array, will enforce
|
||||
// the ordering -Infinity < finite < Infinity < NaN.
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function lowerBoundFloat(valueArray: any, value: any, first: any, last: any) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (lt(valueArray[middle], value)) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function lowerBound(valueArray: any, value: any, first: any, last: any) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return lowerBoundFloat(valueArray, value, first, last);
|
||||
}
|
||||
return lowerBoundNonFloat(valueArray, value, first, last);
|
||||
}
|
||||
|
||||
// Inlined performance optimization - used to indirect through a sort map.
|
||||
//
|
||||
function lowerBoundNonFloatIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
indexArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[indexArray[middle]] < value) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
function lowerBoundFloatIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
indexArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (lt(valueArray[indexArray[middle]], value)) {
|
||||
lfirst = middle + 1;
|
||||
} else {
|
||||
llast = middle;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function lowerBoundIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
indexArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return lowerBoundFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
return lowerBoundNonFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
|
||||
// Search for `value in the sorted array `arr`, in the range [first, last).
|
||||
// Return the first value where arr[index] > value.
|
||||
//
|
||||
// In other words, return array index I, where:
|
||||
// arr[i] <= value for all tarr[lo:I]
|
||||
// arr[i] > value for all tarr[I:last]
|
||||
//
|
||||
// The same semantics/behavior as:
|
||||
// C++: upper_bound()
|
||||
// Python: bisect.bisect_right()
|
||||
//
|
||||
function upperBoundNonFloat(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[middle] > value) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
function upperBoundFloat(valueArray: any, value: any, first: any, last: any) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (gt(valueArray[middle], value)) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function upperBound(valueArray: any, value: any, first: any, last: any) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return upperBoundFloat(valueArray, value, first, last);
|
||||
}
|
||||
return upperBoundNonFloat(valueArray, value, first, last);
|
||||
}
|
||||
|
||||
// Inline performance optimization
|
||||
//
|
||||
function upperBoundNonFloatIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
indexArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (valueArray[indexArray[middle]] > value) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
function upperBoundFloatIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
indexArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
let lfirst = first;
|
||||
let llast = last;
|
||||
// this is just a binary search
|
||||
while (lfirst < llast) {
|
||||
const middle = (lfirst + llast) >>> 1;
|
||||
if (gt(valueArray[indexArray[middle]], value)) {
|
||||
llast = middle;
|
||||
} else {
|
||||
lfirst = middle + 1;
|
||||
}
|
||||
}
|
||||
return lfirst;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function upperBoundIndirect(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
indexArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
if (isFloatTypedArray(valueArray)) {
|
||||
return upperBoundFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
return upperBoundNonFloatIndirect(valueArray, indexArray, value, first, last);
|
||||
}
|
||||
|
||||
// Search for `value` in the sorted array `arr`, in the range [first, last).
|
||||
// Return the first index where arr[index] == value, OR if value not present,
|
||||
// return `last`
|
||||
//
|
||||
// The same semantics/behavior as:
|
||||
// C++: binary_search()
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||
export function binarySearch(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
valueArray: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
value: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
first: any,
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
last: any
|
||||
) {
|
||||
const index = lowerBound(valueArray, value, first, last);
|
||||
if (index !== last && value === valueArray[index]) return index;
|
||||
return last;
|
||||
}
|
||||
/* eslint-enable no-bitwise -- enable */
|
||||
@@ -7,8 +7,7 @@ import { rangeFill as fillRange } from "../range";
|
||||
|
||||
// slice out of one array into another, using an index array
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function sliceByIndex(src: any, index: any) {
|
||||
export function sliceByIndex(src, index) {
|
||||
if (index === undefined || index === null) {
|
||||
return src;
|
||||
}
|
||||
@@ -19,8 +18,7 @@ export function sliceByIndex(src: any, index: any) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||
export function makeSortIndex(src: any) {
|
||||
export function makeSortIndex(src) {
|
||||
const index = fillRange(new Uint32Array(src.length));
|
||||
sortIndex(index, src);
|
||||
return index;
|
||||
Reference in New Issue
Block a user