TS Revert (1) (#2402)

* revert all commits to before Typescript migration

* update compat workflow to match latest deps (#2335)

* update compat workflow to match latest deps

* attempt to debug

* attempt to debug

* remove debugging code

* typo

* update deps to match desktop (#2340)

* fix: don't run lint with `--fix` on push tests (#2273)

* fix: don't run lint with `--fix` on push tests

* npx

Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>

* rename X_approx_distribution to X_approximate_distribution (#2337)

* Correctly handle non-finite numbers in heuristic determination of X distribution (#2342)

* handle non-finites explicitly

* improve and test edge case handling for distribution estimation

* revert debugging changes

* code readability

* clean up type inferencing (#2332)

* unit tests for 64 bit conversion

* clean up type handling

* type inference tests

* more type inference fixes

* use schema to determine user intent for data typing

* stop using deprecated API

* fbs type encoding test

* add missing test

* add more tests

* correctly infer X type for CXG adaptor

* lint

* fix typo

* ts migration

* cleanup from PR review

* lint

* PR review changes

* remove unused packages from client (#2359)

* remove unused packages from client

* add missing peer dep

* fix: disable FE auth testing on compatibility tests (#2377)

* update: release process (#2277)

Co-authored-by: maniarathi <mani.arathi@gmail.com>

* fix: remove spaces in param setup (#2380)

* delete deploy workflow (#2396)

* undo reformatting which now does not pass lint

* fix snapshots which changed due to npm dep changes

* add missing quoting to snapshot

* another snapshot typo fix

* TS Revert (2) - replay PR #2347 and #2354 (#2403)

* replay edits from PR 2347

* TS Revert (3) - replay edits in PR #2327 (#2404)

* replay edits in PR 2327

* TS Revert (4) - replay PR #2355 (#2405)

* replay edits in PR 2355

* add additional babel config

* reformat with new prettier config

Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
This commit is contained in:
Bruce Martin
2021-08-23 15:01:36 -07:00
committed by GitHub
co-authored by maniarathi Madison Dunitz Severiano Badajoz
parent 295590a7c6
commit eaae6df5e3
253 changed files with 11175 additions and 22271 deletions
@@ -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}`;
}
+8
View File
@@ -0,0 +1,8 @@
export { default as Dataframe } from "./dataframe";
export {
DenseInt32Index,
IdentityInt32Index,
KeyIndex,
isLabelIndex,
} from "./labelIndex";
export { default as dataframeMemo } from "./cache";
-21
View File
@@ -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";
+396
View File
@@ -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*/
-495
View File
@@ -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*/
+85
View File
@@ -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,
};
}
-88
View File
@@ -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,
};
}
-139
View File
@@ -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;
+13
View File
@@ -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;
}
-17
View File
@@ -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);
}
+17
View File
@@ -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;
});
};
}
-18
View File
@@ -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;
@@ -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;
}
@@ -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()
@@ -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;
};
@@ -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);
}
@@ -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;
}
}
+29
View File
@@ -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);
}
@@ -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;
@@ -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];
@@ -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 [];
}
+429
View File
@@ -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 */
-529
View File
@@ -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;