gladly-plot 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/package.json +10 -11
- package/src/axes/Axis.js +401 -0
- package/src/{AxisLink.js → axes/AxisLink.js} +6 -2
- package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
- package/src/axes/AxisRegistry.js +179 -0
- package/src/axes/Camera.js +47 -0
- package/src/axes/ColorAxisRegistry.js +101 -0
- package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
- package/src/axes/TickLabelAtlas.js +99 -0
- package/src/axes/ZoomController.js +463 -0
- package/src/colorscales/BivariateColorscales.js +205 -0
- package/src/colorscales/ColorscaleRegistry.js +144 -0
- package/src/compute/ComputationRegistry.js +179 -0
- package/src/compute/axisFilter.js +59 -0
- package/src/compute/conv.js +286 -0
- package/src/compute/elementwise.js +72 -0
- package/src/compute/fft.js +378 -0
- package/src/compute/filter.js +229 -0
- package/src/compute/hist.js +285 -0
- package/src/compute/kde.js +120 -0
- package/src/compute/scatter2dInterpolate.js +277 -0
- package/src/compute/util.js +196 -0
- package/src/core/ComputePipeline.js +153 -0
- package/src/core/GlBase.js +141 -0
- package/src/core/Layer.js +59 -0
- package/src/core/LayerType.js +433 -0
- package/src/core/Plot.js +1213 -0
- package/src/core/PlotGroup.js +204 -0
- package/src/core/ShaderQueue.js +73 -0
- package/src/data/ColumnData.js +269 -0
- package/src/data/Computation.js +95 -0
- package/src/data/Data.js +270 -0
- package/src/{Colorbar.js → floats/Colorbar.js} +19 -5
- package/src/floats/Colorbar2d.js +77 -0
- package/src/{Filterbar.js → floats/Filterbar.js} +18 -4
- package/src/{FilterbarFloat.js → floats/Float.js} +73 -30
- package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
- package/src/index.js +47 -22
- package/src/layers/BarsLayer.js +168 -0
- package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +12 -16
- package/src/layers/ColorbarLayer2d.js +86 -0
- package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +6 -5
- package/src/layers/LinesLayer.js +185 -0
- package/src/layers/PointsLayer.js +118 -0
- package/src/layers/ScatterShared.js +98 -0
- package/src/{TileLayer.js → layers/TileLayer.js} +24 -20
- package/src/math/mat4.js +100 -0
- package/src/Axis.js +0 -48
- package/src/AxisRegistry.js +0 -54
- package/src/ColorAxisRegistry.js +0 -49
- package/src/ColorscaleRegistry.js +0 -52
- package/src/Data.js +0 -67
- package/src/Float.js +0 -159
- package/src/Layer.js +0 -44
- package/src/LayerType.js +0 -209
- package/src/Plot.js +0 -1073
- package/src/ScatterLayer.js +0 -287
- /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
- /package/src/{LayerTypeRegistry.js → core/LayerTypeRegistry.js} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { AXIS_GEOMETRY } from "../axes/AxisRegistry.js"
|
|
3
|
+
import { Data } from "../data/Data.js"
|
|
4
|
+
import { computationSchema, EXPRESSION_REF, EXPRESSION_REF_OPT, resolveQuantityKind } from "../compute/ComputationRegistry.js"
|
|
5
|
+
|
|
6
|
+
const X_AXES = Object.keys(AXIS_GEOMETRY).filter(a => AXIS_GEOMETRY[a].dir === 'x')
|
|
7
|
+
const Y_AXES = Object.keys(AXIS_GEOMETRY).filter(a => AXIS_GEOMETRY[a].dir === 'y')
|
|
8
|
+
const Z_AXES = Object.keys(AXIS_GEOMETRY).filter(a => AXIS_GEOMETRY[a].dir === 'z')
|
|
9
|
+
|
|
10
|
+
export class ScatterLayerTypeBase extends LayerType {
|
|
11
|
+
_getAxisConfig(parameters, data) {
|
|
12
|
+
const d = Data.wrap(data)
|
|
13
|
+
const {
|
|
14
|
+
xData, yData, zData: zDataRaw,
|
|
15
|
+
vData: vDataRaw, vData2: vData2Raw, fData: fDataRaw,
|
|
16
|
+
xAxis = "xaxis_bottom", yAxis = "yaxis_left", zAxis = "none",
|
|
17
|
+
} = parameters
|
|
18
|
+
const vDataIn = (vDataRaw == null || vDataRaw === "none") ? null : vDataRaw
|
|
19
|
+
const vData2In = (vData2Raw == null || vData2Raw === "none") ? null : vData2Raw
|
|
20
|
+
const fData = (fDataRaw == null || fDataRaw === "none") ? null : fDataRaw
|
|
21
|
+
const zData = (zDataRaw == null || zDataRaw === "none") ? null : zDataRaw
|
|
22
|
+
const zAxisResolved = (zAxis == null || zAxis === "none") ? null : zAxis
|
|
23
|
+
const vData = vDataIn
|
|
24
|
+
const vData2 = vData2In
|
|
25
|
+
const colorAxisQuantityKinds = {}
|
|
26
|
+
const vQK = vData ? resolveQuantityKind(vData, d) : null
|
|
27
|
+
const vQK2 = vData2 ? resolveQuantityKind(vData2, d) : null
|
|
28
|
+
if (vQK) colorAxisQuantityKinds[''] = vQK
|
|
29
|
+
if (vQK2) colorAxisQuantityKinds['2'] = vQK2
|
|
30
|
+
const colorAxis2dQuantityKinds = vData && vData2 ? { '': ['', '2'] } : {}
|
|
31
|
+
const filterAxisQuantityKinds = fData ? { '': resolveQuantityKind(fData, d) } : {}
|
|
32
|
+
return {
|
|
33
|
+
xAxis,
|
|
34
|
+
xAxisQuantityKind: resolveQuantityKind(xData, d) ?? undefined,
|
|
35
|
+
yAxis,
|
|
36
|
+
yAxisQuantityKind: resolveQuantityKind(yData, d) ?? undefined,
|
|
37
|
+
zAxis: zData ? (zAxisResolved ?? "zaxis_bottom_left") : null,
|
|
38
|
+
zAxisQuantityKind: zData ? (resolveQuantityKind(zData, d) ?? undefined) : undefined,
|
|
39
|
+
colorAxisQuantityKinds,
|
|
40
|
+
colorAxis2dQuantityKinds,
|
|
41
|
+
filterAxisQuantityKinds,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_commonSchemaProperties(data) {
|
|
46
|
+
return {
|
|
47
|
+
xData: EXPRESSION_REF,
|
|
48
|
+
yData: EXPRESSION_REF,
|
|
49
|
+
zData: EXPRESSION_REF_OPT,
|
|
50
|
+
vData: EXPRESSION_REF_OPT,
|
|
51
|
+
vData2: EXPRESSION_REF_OPT,
|
|
52
|
+
fData: EXPRESSION_REF_OPT,
|
|
53
|
+
xAxis: {
|
|
54
|
+
type: "string",
|
|
55
|
+
enum: X_AXES,
|
|
56
|
+
default: "xaxis_bottom",
|
|
57
|
+
description: "Which x-axis to use for this layer",
|
|
58
|
+
},
|
|
59
|
+
yAxis: {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: Y_AXES,
|
|
62
|
+
default: "yaxis_left",
|
|
63
|
+
description: "Which y-axis to use for this layer",
|
|
64
|
+
},
|
|
65
|
+
zAxis: {
|
|
66
|
+
type: "string",
|
|
67
|
+
enum: ["none", ...Z_AXES],
|
|
68
|
+
default: "none",
|
|
69
|
+
description: "Which z-axis to use for this layer (enables 3D mode)",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_buildDomains(d, xData, yData, zData, vData, vData2, xQK, yQK, zQK, vQK, vQK2) {
|
|
75
|
+
const domains = {}
|
|
76
|
+
if (xQK && typeof xData === 'string') {
|
|
77
|
+
const xDomain = d.getDomain(xData)
|
|
78
|
+
if (xDomain) domains[xQK] = xDomain
|
|
79
|
+
}
|
|
80
|
+
if (yQK && typeof yData === 'string') {
|
|
81
|
+
const yDomain = d.getDomain(yData)
|
|
82
|
+
if (yDomain) domains[yQK] = yDomain
|
|
83
|
+
}
|
|
84
|
+
if (zData && zQK && typeof zData === 'string') {
|
|
85
|
+
const zDomain = d.getDomain(zData)
|
|
86
|
+
if (zDomain) domains[zQK] = zDomain
|
|
87
|
+
}
|
|
88
|
+
if (vData && vQK && typeof vData === 'string') {
|
|
89
|
+
const vDomain = d.getDomain(vData)
|
|
90
|
+
if (vDomain) domains[vQK] = vDomain
|
|
91
|
+
}
|
|
92
|
+
if (vData2 && vQK2 && typeof vData2 === 'string') {
|
|
93
|
+
const vDomain2 = d.getDomain(vData2)
|
|
94
|
+
if (vDomain2) domains[vQK2] = vDomain2
|
|
95
|
+
}
|
|
96
|
+
return domains
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import proj4 from 'proj4'
|
|
2
|
-
import { LayerType } from '
|
|
3
|
-
import { AXES } from '
|
|
4
|
-
import { registerLayerType } from '
|
|
5
|
-
import { parseCrsCode, crsToQkX, crsToQkY, ensureCrsDefined } from '
|
|
2
|
+
import { LayerType } from '../core/LayerType.js'
|
|
3
|
+
import { AXES } from '../axes/AxisRegistry.js'
|
|
4
|
+
import { registerLayerType } from '../core/LayerTypeRegistry.js'
|
|
5
|
+
import { parseCrsCode, crsToQkX, crsToQkY, ensureCrsDefined } from '../geo/EpsgUtils.js'
|
|
6
6
|
|
|
7
7
|
// ─── Tile math (standard Web Mercator / "slippy map" grid) ────────────────────
|
|
8
8
|
|
|
@@ -410,15 +410,15 @@ class TileManager {
|
|
|
410
410
|
|
|
411
411
|
// ─── GLSL ─────────────────────────────────────────────────────────────────────
|
|
412
412
|
|
|
413
|
-
const TILE_VERT =
|
|
413
|
+
const TILE_VERT = `#version 300 es
|
|
414
414
|
precision mediump float;
|
|
415
|
-
|
|
416
|
-
|
|
415
|
+
in vec2 position;
|
|
416
|
+
in vec2 uv;
|
|
417
417
|
uniform vec2 xDomain;
|
|
418
418
|
uniform vec2 yDomain;
|
|
419
419
|
uniform float xScaleType;
|
|
420
420
|
uniform float yScaleType;
|
|
421
|
-
|
|
421
|
+
out vec2 vUv;
|
|
422
422
|
|
|
423
423
|
float normalize_axis(float v, vec2 domain, float scaleType) {
|
|
424
424
|
float vt = scaleType > 0.5 ? log(v) : v;
|
|
@@ -426,24 +426,28 @@ const TILE_VERT = `
|
|
|
426
426
|
float d1 = scaleType > 0.5 ? log(domain.y) : domain.y;
|
|
427
427
|
return (vt - d0) / (d1 - d0);
|
|
428
428
|
}
|
|
429
|
+
vec4 plot_pos(vec2 pos) {
|
|
430
|
+
float nx = normalize_axis(pos.x, xDomain, xScaleType);
|
|
431
|
+
float ny = normalize_axis(pos.y, yDomain, yScaleType);
|
|
432
|
+
return vec4(nx * 2.0 - 1.0, ny * 2.0 - 1.0, 0.0, 1.0);
|
|
433
|
+
}
|
|
429
434
|
|
|
430
435
|
void main() {
|
|
431
|
-
|
|
432
|
-
float ny = normalize_axis(position.y, yDomain, yScaleType);
|
|
433
|
-
gl_Position = vec4(nx * 2.0 - 1.0, ny * 2.0 - 1.0, 0.0, 1.0);
|
|
436
|
+
gl_Position = plot_pos(position);
|
|
434
437
|
vUv = uv;
|
|
435
438
|
}
|
|
436
439
|
`
|
|
437
440
|
|
|
438
|
-
const TILE_FRAG =
|
|
441
|
+
const TILE_FRAG = `#version 300 es
|
|
439
442
|
precision mediump float;
|
|
440
443
|
uniform sampler2D tileTexture;
|
|
441
444
|
uniform float opacity;
|
|
442
|
-
|
|
445
|
+
in vec2 vUv;
|
|
446
|
+
out vec4 fragColor;
|
|
443
447
|
|
|
444
448
|
void main() {
|
|
445
|
-
vec4 color =
|
|
446
|
-
|
|
449
|
+
vec4 color = texture(tileTexture, vUv);
|
|
450
|
+
fragColor = vec4(color.rgb, color.a * opacity);
|
|
447
451
|
}
|
|
448
452
|
`
|
|
449
453
|
|
|
@@ -587,12 +591,12 @@ class TileLayerType extends LayerType {
|
|
|
587
591
|
xAxisQuantityKind: crsToQkX(effectivePlotCrs),
|
|
588
592
|
yAxis,
|
|
589
593
|
yAxisQuantityKind: crsToQkY(effectivePlotCrs),
|
|
590
|
-
colorAxisQuantityKinds:
|
|
591
|
-
filterAxisQuantityKinds:
|
|
594
|
+
colorAxisQuantityKinds: {},
|
|
595
|
+
filterAxisQuantityKinds: {},
|
|
592
596
|
}
|
|
593
597
|
}
|
|
594
598
|
|
|
595
|
-
createLayer(parameters, _data) {
|
|
599
|
+
createLayer(regl, parameters, _data) {
|
|
596
600
|
const {
|
|
597
601
|
xAxis = 'xaxis_bottom',
|
|
598
602
|
yAxis = 'yaxis_left',
|
|
@@ -609,8 +613,8 @@ class TileLayerType extends LayerType {
|
|
|
609
613
|
yAxis,
|
|
610
614
|
xAxisQuantityKind: crsToQkX(effectivePlotCrs),
|
|
611
615
|
yAxisQuantityKind: crsToQkY(effectivePlotCrs),
|
|
612
|
-
colorAxes:
|
|
613
|
-
filterAxes:
|
|
616
|
+
colorAxes: {},
|
|
617
|
+
filterAxes: {},
|
|
614
618
|
vertexCount: 0,
|
|
615
619
|
instanceCount: null,
|
|
616
620
|
attributes: {},
|
package/src/math/mat4.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Column-major 4×4 matrix utilities matching WebGL/GLSL convention.
|
|
2
|
+
// Element at row r, column c is stored at index c*4+r.
|
|
3
|
+
|
|
4
|
+
export function mat4Identity() {
|
|
5
|
+
return new Float32Array([
|
|
6
|
+
1, 0, 0, 0,
|
|
7
|
+
0, 1, 0, 0,
|
|
8
|
+
0, 0, 1, 0,
|
|
9
|
+
0, 0, 0, 1,
|
|
10
|
+
])
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// a * b (left-multiplies a by b).
|
|
14
|
+
export function mat4Multiply(a, b) {
|
|
15
|
+
const out = new Float32Array(16)
|
|
16
|
+
for (let col = 0; col < 4; col++) {
|
|
17
|
+
for (let row = 0; row < 4; row++) {
|
|
18
|
+
let s = 0
|
|
19
|
+
for (let k = 0; k < 4; k++) s += a[k * 4 + row] * b[col * 4 + k]
|
|
20
|
+
out[col * 4 + row] = s
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Perspective projection. fovY in radians, aspect = width/height.
|
|
27
|
+
export function mat4Perspective(fovY, aspect, near, far) {
|
|
28
|
+
const f = 1 / Math.tan(fovY / 2)
|
|
29
|
+
const nf = 1 / (near - far)
|
|
30
|
+
return new Float32Array([
|
|
31
|
+
f / aspect, 0, 0, 0,
|
|
32
|
+
0, f, 0, 0,
|
|
33
|
+
0, 0, (far + near) * nf, -1,
|
|
34
|
+
0, 0, 2 * far * near * nf, 0,
|
|
35
|
+
])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// View matrix looking from eye toward center with the given up vector.
|
|
39
|
+
export function mat4LookAt(eye, center, up) {
|
|
40
|
+
const f = vec3Normalize([center[0] - eye[0], center[1] - eye[1], center[2] - eye[2]])
|
|
41
|
+
const r = vec3Normalize(vec3Cross(f, up))
|
|
42
|
+
const u = vec3Cross(r, f)
|
|
43
|
+
return new Float32Array([
|
|
44
|
+
r[0], u[0], -f[0], 0,
|
|
45
|
+
r[1], u[1], -f[1], 0,
|
|
46
|
+
r[2], u[2], -f[2], 0,
|
|
47
|
+
-vec3Dot(r, eye), -vec3Dot(u, eye), vec3Dot(f, eye), 1,
|
|
48
|
+
])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Multiply a 4×4 column-major matrix by a vec4.
|
|
52
|
+
export function mat4MulVec4(m, v) {
|
|
53
|
+
return [
|
|
54
|
+
m[0]*v[0] + m[4]*v[1] + m[8] *v[2] + m[12]*v[3],
|
|
55
|
+
m[1]*v[0] + m[5]*v[1] + m[9] *v[2] + m[13]*v[3],
|
|
56
|
+
m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3],
|
|
57
|
+
m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3],
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function vec3Normalize(v) {
|
|
62
|
+
const len = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
|
|
63
|
+
return len > 1e-10 ? [v[0]/len, v[1]/len, v[2]/len] : [0, 0, 0]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function vec3Cross(a, b) {
|
|
67
|
+
return [
|
|
68
|
+
a[1]*b[2] - a[2]*b[1],
|
|
69
|
+
a[2]*b[0] - a[0]*b[2],
|
|
70
|
+
a[0]*b[1] - a[1]*b[0],
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function vec3Dot(a, b) {
|
|
75
|
+
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Convert spherical coords (azimuth theta around y-axis, elevation phi from equator)
|
|
79
|
+
// to Cartesian.
|
|
80
|
+
export function sphericalToCartesian(theta, phi, r) {
|
|
81
|
+
return [
|
|
82
|
+
r * Math.cos(phi) * Math.sin(theta),
|
|
83
|
+
r * Math.sin(phi),
|
|
84
|
+
r * Math.cos(phi) * Math.cos(theta),
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Project a 3D model-space point to HTML screen coordinates (y from top) using an MVP
|
|
89
|
+
// matrix that maps model space to full-canvas NDC, and the canvas dimensions.
|
|
90
|
+
// Returns [htmlX, htmlY] or null if the point is behind the camera (w ≤ 0).
|
|
91
|
+
export function projectToScreen(point, mvp, canvasWidth, canvasHeight) {
|
|
92
|
+
const clip = mat4MulVec4(mvp, [point[0], point[1], point[2], 1.0])
|
|
93
|
+
if (clip[3] <= 0) return null
|
|
94
|
+
const ndcX = clip[0] / clip[3]
|
|
95
|
+
const ndcY = clip[1] / clip[3]
|
|
96
|
+
return [
|
|
97
|
+
(ndcX + 1) * 0.5 * canvasWidth,
|
|
98
|
+
(1 - (ndcY + 1) * 0.5) * canvasHeight,
|
|
99
|
+
]
|
|
100
|
+
}
|
package/src/Axis.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An Axis represents a single data axis on a plot. Axis instances are stable across
|
|
3
|
-
* plot.update() calls and can be linked together with linkAxes().
|
|
4
|
-
*
|
|
5
|
-
* Public interface (duck-typing compatible):
|
|
6
|
-
* - axis.quantityKind — string | null
|
|
7
|
-
* - axis.getDomain() — [min, max] | null
|
|
8
|
-
* - axis.setDomain(domain) — update domain, schedule render, notify subscribers
|
|
9
|
-
* - axis.subscribe(callback) — callback([min, max]) called on domain changes
|
|
10
|
-
* - axis.unsubscribe(callback) — remove a previously added callback
|
|
11
|
-
*/
|
|
12
|
-
export class Axis {
|
|
13
|
-
constructor(plot, name) {
|
|
14
|
-
this._plot = plot
|
|
15
|
-
this._name = name
|
|
16
|
-
this._listeners = new Set()
|
|
17
|
-
this._propagating = false
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** The quantity kind for this axis, or null if the plot hasn't been initialized yet. */
|
|
21
|
-
get quantityKind() { return this._plot.getAxisQuantityKind(this._name) }
|
|
22
|
-
|
|
23
|
-
/** Returns [min, max], or null if the axis has no domain yet. */
|
|
24
|
-
getDomain() { return this._plot.getAxisDomain(this._name) }
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Sets the axis domain, schedules a render on the owning plot, and notifies all
|
|
28
|
-
* subscribers (e.g. linked axes). A _propagating guard prevents infinite loops
|
|
29
|
-
* when axes are linked bidirectionally.
|
|
30
|
-
*/
|
|
31
|
-
setDomain(domain) {
|
|
32
|
-
if (this._propagating) return
|
|
33
|
-
this._propagating = true
|
|
34
|
-
try {
|
|
35
|
-
this._plot.setAxisDomain(this._name, domain)
|
|
36
|
-
this._plot.scheduleRender()
|
|
37
|
-
for (const cb of this._listeners) cb(domain)
|
|
38
|
-
} finally {
|
|
39
|
-
this._propagating = false
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Add a subscriber. callback([min, max]) is called after every setDomain(). */
|
|
44
|
-
subscribe(callback) { this._listeners.add(callback) }
|
|
45
|
-
|
|
46
|
-
/** Remove a previously added subscriber. */
|
|
47
|
-
unsubscribe(callback) { this._listeners.delete(callback) }
|
|
48
|
-
}
|
package/src/AxisRegistry.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import * as d3 from "d3-scale"
|
|
2
|
-
import { getAxisQuantityKind } from "./AxisQuantityKindRegistry.js"
|
|
3
|
-
|
|
4
|
-
export const AXES = ["xaxis_bottom","xaxis_top","yaxis_left","yaxis_right"]
|
|
5
|
-
|
|
6
|
-
export class AxisRegistry {
|
|
7
|
-
constructor(width, height) {
|
|
8
|
-
this.scales = {}
|
|
9
|
-
this.axisQuantityKinds = {}
|
|
10
|
-
this.width = width
|
|
11
|
-
this.height = height
|
|
12
|
-
AXES.forEach(a => {
|
|
13
|
-
this.scales[a] = null
|
|
14
|
-
this.axisQuantityKinds[a] = null
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
ensureAxis(axisName, axisQuantityKind, scaleOverride) {
|
|
19
|
-
if (!AXES.includes(axisName)) throw new Error(`Unknown axis ${axisName}`)
|
|
20
|
-
if (this.axisQuantityKinds[axisName] && this.axisQuantityKinds[axisName] !== axisQuantityKind)
|
|
21
|
-
throw new Error(`Axis quantity kind mismatch on axis ${axisName}: ${this.axisQuantityKinds[axisName]} vs ${axisQuantityKind}`)
|
|
22
|
-
|
|
23
|
-
if (!this.scales[axisName]) {
|
|
24
|
-
const quantityKindDef = getAxisQuantityKind(axisQuantityKind)
|
|
25
|
-
const scaleType = scaleOverride ?? quantityKindDef.scale
|
|
26
|
-
this.scales[axisName] = scaleType === "log"
|
|
27
|
-
? d3.scaleLog().range(axisName.includes("y") ? [this.height,0] : [0,this.width])
|
|
28
|
-
: d3.scaleLinear().range(axisName.includes("y") ? [this.height,0] : [0,this.width])
|
|
29
|
-
this.axisQuantityKinds[axisName] = axisQuantityKind
|
|
30
|
-
}
|
|
31
|
-
return this.scales[axisName]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
getScale(axisName) { return this.scales[axisName] }
|
|
35
|
-
|
|
36
|
-
isLogScale(axisName) {
|
|
37
|
-
const scale = this.scales[axisName]
|
|
38
|
-
return !!scale && typeof scale.base === 'function'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
setScaleType(axisName, scaleType) {
|
|
42
|
-
const scale = this.scales[axisName]
|
|
43
|
-
if (!scale) return
|
|
44
|
-
const currentIsLog = typeof scale.base === 'function'
|
|
45
|
-
const wantLog = scaleType === "log"
|
|
46
|
-
if (currentIsLog === wantLog) return
|
|
47
|
-
const currentDomain = scale.domain()
|
|
48
|
-
const newScale = wantLog
|
|
49
|
-
? d3.scaleLog().range(axisName.includes("y") ? [this.height, 0] : [0, this.width])
|
|
50
|
-
: d3.scaleLinear().range(axisName.includes("y") ? [this.height, 0] : [0, this.width])
|
|
51
|
-
newScale.domain(currentDomain)
|
|
52
|
-
this.scales[axisName] = newScale
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/ColorAxisRegistry.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { getAxisQuantityKind } from './AxisQuantityKindRegistry.js'
|
|
2
|
-
import { getColorscaleIndex } from './ColorscaleRegistry.js'
|
|
3
|
-
|
|
4
|
-
export class ColorAxisRegistry {
|
|
5
|
-
constructor() {
|
|
6
|
-
this._axes = new Map()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
ensureColorAxis(quantityKind, colorscaleOverride = null) {
|
|
10
|
-
if (!this._axes.has(quantityKind)) {
|
|
11
|
-
this._axes.set(quantityKind, { colorscaleOverride, range: null })
|
|
12
|
-
} else if (colorscaleOverride !== null) {
|
|
13
|
-
this._axes.get(quantityKind).colorscaleOverride = colorscaleOverride
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
setRange(quantityKind, min, max) {
|
|
18
|
-
if (!this._axes.has(quantityKind)) {
|
|
19
|
-
throw new Error(`Color axis '${quantityKind}' not found in registry`)
|
|
20
|
-
}
|
|
21
|
-
this._axes.get(quantityKind).range = [min, max]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getRange(quantityKind) {
|
|
25
|
-
return this._axes.get(quantityKind)?.range ?? null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getColorscale(quantityKind) {
|
|
29
|
-
const entry = this._axes.get(quantityKind)
|
|
30
|
-
if (!entry) return null
|
|
31
|
-
if (entry.colorscaleOverride) return entry.colorscaleOverride
|
|
32
|
-
const unitDef = getAxisQuantityKind(quantityKind)
|
|
33
|
-
return unitDef.colorscale ?? null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getColorscaleIndex(quantityKind) {
|
|
37
|
-
const colorscale = this.getColorscale(quantityKind)
|
|
38
|
-
if (colorscale === null) return 0
|
|
39
|
-
return getColorscaleIndex(colorscale)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
hasAxis(quantityKind) {
|
|
43
|
-
return this._axes.has(quantityKind)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
getQuantityKinds() {
|
|
47
|
-
return Array.from(this._axes.keys())
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
const colorscales = new Map()
|
|
2
|
-
|
|
3
|
-
export function registerColorscale(name, glslFn) {
|
|
4
|
-
colorscales.set(name, glslFn)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getRegisteredColorscales() {
|
|
8
|
-
return colorscales
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function getColorscaleIndex(name) {
|
|
12
|
-
let idx = 0
|
|
13
|
-
for (const key of colorscales.keys()) {
|
|
14
|
-
if (key === name) return idx
|
|
15
|
-
idx++
|
|
16
|
-
}
|
|
17
|
-
return 0
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function buildColorGlsl() {
|
|
21
|
-
if (colorscales.size === 0) return ''
|
|
22
|
-
|
|
23
|
-
const parts = []
|
|
24
|
-
|
|
25
|
-
for (const glslFn of colorscales.values()) {
|
|
26
|
-
parts.push(glslFn)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
parts.push('vec4 map_color(int cs, vec2 range, float value) {')
|
|
30
|
-
parts.push(' float t = clamp((value - range.x) / (range.y - range.x), 0.0, 1.0);')
|
|
31
|
-
|
|
32
|
-
let idx = 0
|
|
33
|
-
for (const name of colorscales.keys()) {
|
|
34
|
-
parts.push(` if (cs == ${idx}) return colorscale_${name}(t);`)
|
|
35
|
-
idx++
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
parts.push(' return vec4(0.5, 0.5, 0.5, 1.0);')
|
|
39
|
-
parts.push('}')
|
|
40
|
-
|
|
41
|
-
parts.push('vec4 map_color_s(int cs, vec2 range, float v, float scaleType, float useAlpha) {')
|
|
42
|
-
parts.push(' float vt = scaleType > 0.5 ? log(v) : v;')
|
|
43
|
-
parts.push(' float r0 = scaleType > 0.5 ? log(range.x) : range.x;')
|
|
44
|
-
parts.push(' float r1 = scaleType > 0.5 ? log(range.y) : range.y;')
|
|
45
|
-
parts.push(' float t = clamp((vt - r0) / (r1 - r0), 0.0, 1.0);')
|
|
46
|
-
parts.push(' vec4 color = map_color(cs, vec2(r0, r1), vt);')
|
|
47
|
-
parts.push(' if (useAlpha > 0.5) color.a = t;')
|
|
48
|
-
parts.push(' return gladly_apply_color(color);')
|
|
49
|
-
parts.push('}')
|
|
50
|
-
|
|
51
|
-
return parts.join('\n')
|
|
52
|
-
}
|
package/src/Data.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export class Data {
|
|
2
|
-
constructor(raw) {
|
|
3
|
-
raw = raw ?? {}
|
|
4
|
-
// Columnar format: { data: {col: Float32Array}, quantity_kinds?: {...}, domains?: {...} }
|
|
5
|
-
// Detected by the presence of a top-level `data` property that is a plain object.
|
|
6
|
-
if (raw.data != null && typeof raw.data === 'object' && !(raw.data instanceof Float32Array)) {
|
|
7
|
-
this._columnar = true
|
|
8
|
-
this._data = raw.data
|
|
9
|
-
this._quantityKinds = raw.quantity_kinds ?? {}
|
|
10
|
-
this._rawDomains = raw.domains ?? {}
|
|
11
|
-
} else {
|
|
12
|
-
this._columnar = false
|
|
13
|
-
this._raw = raw
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
static wrap(data) {
|
|
18
|
-
if (data != null && typeof data.columns === 'function' && typeof data.getData === 'function') {
|
|
19
|
-
return data
|
|
20
|
-
}
|
|
21
|
-
return new Data(data)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
_entry(col) {
|
|
25
|
-
if (this._columnar) {
|
|
26
|
-
const rawDomain = this._rawDomains[col]
|
|
27
|
-
let domain
|
|
28
|
-
if (Array.isArray(rawDomain)) {
|
|
29
|
-
domain = [rawDomain[0], rawDomain[1]]
|
|
30
|
-
} else if (rawDomain && typeof rawDomain === 'object') {
|
|
31
|
-
domain = [rawDomain.min, rawDomain.max]
|
|
32
|
-
}
|
|
33
|
-
return { data: this._data[col], quantityKind: this._quantityKinds[col], domain }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const v = this._raw[col]
|
|
37
|
-
if (v instanceof Float32Array) {
|
|
38
|
-
return { data: v, quantityKind: undefined, domain: undefined }
|
|
39
|
-
}
|
|
40
|
-
if (v && typeof v === 'object') {
|
|
41
|
-
let domain
|
|
42
|
-
if (Array.isArray(v.domain)) {
|
|
43
|
-
domain = [v.domain[0], v.domain[1]]
|
|
44
|
-
} else if (v.domain && typeof v.domain === 'object') {
|
|
45
|
-
domain = [v.domain.min, v.domain.max]
|
|
46
|
-
}
|
|
47
|
-
return { data: v.data, quantityKind: v.quantity_kind, domain }
|
|
48
|
-
}
|
|
49
|
-
return { data: undefined, quantityKind: undefined, domain: undefined }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
columns() {
|
|
53
|
-
return this._columnar ? Object.keys(this._data) : Object.keys(this._raw)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
getData(col) {
|
|
57
|
-
return this._entry(col).data
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
getQuantityKind(col) {
|
|
61
|
-
return this._entry(col).quantityKind
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getDomain(col) {
|
|
65
|
-
return this._entry(col).domain
|
|
66
|
-
}
|
|
67
|
-
}
|