🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@hema-to/regl-scatterplot

Package Overview
Dependencies
Maintainers
2
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hema-to/regl-scatterplot - npm Package Compare versions

Comparing version
1.16.0-hemato.0
to
1.16.0-hemato.1
+82
src/create-state-texture-data.js
import { CATEGORICAL, CONTINUOUS, VALUE_ZW_DATA_TYPES } from './constants.js';
/**
* Pack the per-point state texture `Float32Array` (x, y, z=value1, w=value2) and derive the
* texture resolution + z/w data types. Accepts EITHER array-oriented points (`number[][]`, the
* legacy path) OR a columnar descriptor from `toColumnarPoints` (typed-array accessors, the
* fast path that avoids the array-of-arrays allocation). The two paths are byte-identical: the
* `|| 0` coercion, the `Float32Array` narrowing on assignment, and the int-detection that
* selects `CATEGORICAL`/`CONTINUOUS` all operate on the same fetched values.
* @param {number[][] | { length: number, getX: (i: number) => number, getY: (i: number) => number, getZ?: (i: number) => number, getW?: (i: number) => number }} newPoints
* @param {{ z?: string, w?: string }} dataTypes
* @return {{ data: Float32Array, stateTexRes: number, stateTexEps: number, valueZDataType: string, valueWDataType: string }}
*/
export const createStateTextureData = (newPoints, dataTypes = {}) => {
const isColumnar = typeof newPoints.getX === 'function';
const numNewPoints = newPoints.length;
const stateTexRes = Math.max(2, Math.ceil(Math.sqrt(numNewPoints)));
const stateTexEps = 0.5 / stateTexRes;
const data = new Float32Array(stateTexRes ** 2 * 4);
let zIsInts = true;
let wIsInts = true;
let k = 0;
let z = 0;
let w = 0;
if (isColumnar) {
// Index the x/y typed arrays directly (`newPoints.x`/`y`), not via the `getX`/`getY` closures —
// one property load hoisted out of the loop instead of 2 function calls per point. Byte-identical:
// `getX(i)` is defined as `x[i]`. `getZ`/`getW` stay as closures (optional, resolved by name).
const x = newPoints.x;
const y = newPoints.y;
const { getZ, getW } = newPoints;
for (let i = 0; i < numNewPoints; ++i) {
k = i * 4;
data[k] = x[i]; // x
data[k + 1] = y[i]; // y
z = (getZ ? getZ(i) : 0) || 0;
w = (getW ? getW(i) : 0) || 0;
data[k + 2] = z; // z: value 1
data[k + 3] = w; // w: value 2
zIsInts &&= Number.isInteger(z);
wIsInts &&= Number.isInteger(w);
}
} else {
for (let i = 0; i < numNewPoints; ++i) {
k = i * 4;
data[k] = newPoints[i][0]; // x
data[k + 1] = newPoints[i][1]; // y
z = newPoints[i][2] || 0;
w = newPoints[i][3] || 0;
data[k + 2] = z; // z: value 1
data[k + 3] = w; // w: value 2
zIsInts &&= Number.isInteger(z);
wIsInts &&= Number.isInteger(w);
}
}
let valueZDataType;
let valueWDataType;
if (dataTypes.z && VALUE_ZW_DATA_TYPES.includes(dataTypes.z)) {
valueZDataType = dataTypes.z;
} else {
valueZDataType = zIsInts ? CATEGORICAL : CONTINUOUS;
}
if (dataTypes.w && VALUE_ZW_DATA_TYPES.includes(dataTypes.w)) {
valueWDataType = dataTypes.w;
} else {
valueWDataType = wIsInts ? CATEGORICAL : CONTINUOUS;
}
return { data, stateTexRes, stateTexEps, valueZDataType, valueWDataType };
};
+6
-0

@@ -140,2 +140,8 @@ type Hex = string;

deselectOnEscape: boolean;
/**
* When true, `lassoEnd` skips its native findPointsInLasso + select so the host app owns
* lasso selection, resolving it from the published `lassoEnd` coordinates. Avoids regl's
* main-thread point-in-polygon winding.
*/
externalLassoSelection: boolean;
actionKeyMap: KeyMap;

@@ -142,0 +148,0 @@ keyMap: KeyMap;

+1
-1
{
"name": "@hema-to/regl-scatterplot",
"version": "1.16.0-hemato.0",
"version": "1.16.0-hemato.1",
"description": "A WebGL-Powered Scalable Interactive Scatter Plot Library",

@@ -5,0 +5,0 @@ "author": "Fritz Lekschas",

@@ -69,2 +69,6 @@ import {

export const DEFAULT_LASSO_ON_LONG_PRESS = false;
// When true, `lassoEnd` skips its native findPointsInLasso + select so the host app owns
// lasso selection — avoids paying regl's main-thread point-in-polygon winding when the app
// computes the selection itself.
export const DEFAULT_EXTERNAL_LASSO_SELECTION = false;
export const DEFAULT_LASSO_LONG_PRESS_TIME = 750;

@@ -71,0 +75,0 @@ export const DEFAULT_LASSO_LONG_PRESS_AFTER_EFFECT_TIME = 500;

export default () => {
addEventListener('message', (event) => {
const points = event.data.points;
const { points, columnar, length, nodeSize } = event.data;
const numPoints = columnar ? length : points.length;
if (points.length === 0) {
if (numPoints === 0) {
self.postMessage({ error: new Error('Invalid point data') });
}
const index = new KDBush(points.length, event.data.nodeSize);
const index = new KDBush(numPoints, nodeSize);
for (const [x, y] of points) {
index.add(x, y);
if (columnar) {
const { x, y } = columnar;
for (let i = 0; i < numPoints; i++) {
index.add(x[i], y[i]);
}
} else {
for (const [x, y] of points) {
index.add(x, y);
}
}

@@ -14,0 +22,0 @@

@@ -39,3 +39,13 @@ import createKDBushClass from './kdbush-class.js';

resolve(KDBush.from(pointsOrIndex));
} else if (
return;
}
// Columnar descriptor (from `toColumnarPoints`): build the index straight from the x/y typed
// arrays. The coordinates are the same numbers the array-oriented path reads (`p[i][0]`/`[1]`),
// so the resulting KDBush is byte-identical.
const isColumnar =
!Array.isArray(pointsOrIndex) &&
(Array.isArray(pointsOrIndex.x) || ArrayBuffer.isView(pointsOrIndex.x));
if (
(pointsOrIndex.length < WORKER_THRESHOLD ||

@@ -46,4 +56,12 @@ options.useWorker === false) &&

const index = new KDBush(pointsOrIndex.length, options.nodeSize);
for (const pointOrIndex of pointsOrIndex) {
index.add(pointOrIndex[0], pointOrIndex[1]);
if (isColumnar) {
const { x, y } = pointsOrIndex;
const n = pointsOrIndex.length;
for (let i = 0; i < n; i++) {
index.add(x[i], y[i]);
}
} else {
for (const pointOrIndex of pointsOrIndex) {
index.add(pointOrIndex[0], pointOrIndex[1]);
}
}

@@ -64,3 +82,14 @@ index.finish();

worker.postMessage({ points: pointsOrIndex, nodeSize: options.nodeSize });
if (isColumnar) {
worker.postMessage({
columnar: { x: pointsOrIndex.x, y: pointsOrIndex.y },
length: pointsOrIndex.length,
nodeSize: options.nodeSize,
});
} else {
worker.postMessage({
points: pointsOrIndex,
nodeSize: options.nodeSize,
});
}
}

@@ -67,0 +96,0 @@ });

@@ -140,2 +140,8 @@ type Hex = string;

deselectOnEscape: boolean;
/**
* When true, `lassoEnd` skips its native findPointsInLasso + select so the host app owns
* lasso selection, resolving it from the published `lassoEnd` coordinates. Avoids regl's
* main-thread point-in-polygon winding.
*/
externalLassoSelection: boolean;
actionKeyMap: KeyMap;

@@ -142,0 +148,0 @@ keyMap: KeyMap;

@@ -560,2 +560,47 @@ import createOriginalRegl from 'regl';

/**
* Resolve columnar point input to typed-array accessors WITHOUT materializing an
* array-of-arrays. Returns `null` unless the input is the columnar fast-path shape
* (an object with `x`/`y` arrays and no `line`/`lineOrder` connection component); the
* caller then falls back to {@link toArrayOrientedPoints}. `getZ`/`getW` are resolved
* from the exact same `Z_NAMES`/`W_NAMES` component lookup as `toArrayOrientedPoints`,
* so the two paths read byte-identical values.
* @param {import('./types').Points} points - The point data
* @return {{ length: number, x: any, y: any, getX: (i: number) => number, getY: (i: number) => number, getZ: ((i: number) => number) | undefined, getW: ((i: number) => number) | undefined } | null}
*/
export const toColumnarPoints = (points) => {
if (!points || Array.isArray(points)) {
return null;
}
const isArr = (a) => Array.isArray(a) || ArrayBuffer.isView(a);
if (!isArr(points.x) || !isArr(points.y)) {
return null;
}
// Point-connection shapes go through the untouched array-oriented path.
if (isArr(points.line) || isArr(points.lineOrder)) {
return null;
}
const components = Object.keys(points);
const zName = components.find((c) => Z_NAMES.has(c));
const getZ =
zName && isArr(points[zName]) ? (i) => points[zName][i] : undefined;
const wName = components.find((c) => W_NAMES.has(c));
const getW =
wName && isArr(points[wName]) ? (i) => points[wName][i] : undefined;
return {
length: points.x.length,
x: points.x,
y: points.y,
getX: (i) => points.x[i],
getY: (i) => points.y[i],
getZ,
getW,
};
};
export const isHorizontalLine = (annotation) =>

@@ -562,0 +607,0 @@ Number.isFinite(annotation.y) && !('x' in annotation);

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display