@photonviz/core 0.3.0 → 0.3.1
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/dist/index.d.ts +666 -5
- package/dist/index.js +1978 -727
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -150,6 +150,9 @@ function toColorCss(c) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// src/gl/program.ts
|
|
153
|
+
function bufferUsage(gl, renderType) {
|
|
154
|
+
return renderType === "dynamic" ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;
|
|
155
|
+
}
|
|
153
156
|
function compileShader(gl, type, source) {
|
|
154
157
|
const shader = gl.createShader(type);
|
|
155
158
|
if (!shader) throw new Error("Failed to create shader");
|
|
@@ -273,6 +276,7 @@ var AreaLayer = class {
|
|
|
273
276
|
this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
|
|
274
277
|
this.name = opts.name ?? this.id;
|
|
275
278
|
this.yAxis = opts.yAxis ?? "y";
|
|
279
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
276
280
|
const n = Math.min(opts.x.length, opts.y.length);
|
|
277
281
|
this.vertexCount = n * 2;
|
|
278
282
|
const baseAt = (i) => opts.base == null ? 0 : typeof opts.base === "number" ? opts.base : opts.base[i];
|
|
@@ -299,7 +303,7 @@ var AreaLayer = class {
|
|
|
299
303
|
this.buffer = buffer;
|
|
300
304
|
gl.bindVertexArray(vao);
|
|
301
305
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
302
|
-
gl.bufferData(gl.ARRAY_BUFFER, data,
|
|
306
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
|
|
303
307
|
gl.enableVertexAttribArray(0);
|
|
304
308
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
305
309
|
gl.bindVertexArray(null);
|
|
@@ -328,7 +332,7 @@ var AreaLayer = class {
|
|
|
328
332
|
this.xBounds = [minX, maxX];
|
|
329
333
|
this.yBounds = [minY, maxY];
|
|
330
334
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
331
|
-
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.
|
|
335
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.usage);
|
|
332
336
|
}
|
|
333
337
|
bounds() {
|
|
334
338
|
if (this.vertexCount === 0) return null;
|
|
@@ -357,9 +361,12 @@ var VERT2 = (
|
|
|
357
361
|
precision highp float;
|
|
358
362
|
layout(location = 0) in vec2 aCorner; // unit rect [0,1]^2
|
|
359
363
|
layout(location = 1) in vec4 aRect; // (x0,y0,x1,y1) offset data space
|
|
364
|
+
layout(location = 2) in vec4 aColor; // per-bar color
|
|
360
365
|
${TRANSFORM_GLSL}
|
|
366
|
+
out vec4 vColor;
|
|
361
367
|
void main() {
|
|
362
368
|
vec2 p = mix(aRect.xy, aRect.zw, aCorner);
|
|
369
|
+
vColor = aColor;
|
|
363
370
|
gl_Position = vec4(dataToClip(p), 0.0, 1.0);
|
|
364
371
|
}`
|
|
365
372
|
);
|
|
@@ -367,9 +374,9 @@ var FRAG2 = (
|
|
|
367
374
|
/* glsl */
|
|
368
375
|
`#version 300 es
|
|
369
376
|
precision highp float;
|
|
370
|
-
|
|
377
|
+
in vec4 vColor;
|
|
371
378
|
out vec4 outColor;
|
|
372
|
-
void main() { outColor = vec4(
|
|
379
|
+
void main() { outColor = vec4(vColor.rgb * vColor.a, vColor.a); }`
|
|
373
380
|
);
|
|
374
381
|
var CORNERS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1]);
|
|
375
382
|
var programCache2 = /* @__PURE__ */ new WeakMap();
|
|
@@ -381,6 +388,9 @@ function getProgram2(gl) {
|
|
|
381
388
|
}
|
|
382
389
|
return p;
|
|
383
390
|
}
|
|
391
|
+
function toColor(input) {
|
|
392
|
+
return Array.isArray(input) ? input : parseColor(input);
|
|
393
|
+
}
|
|
384
394
|
function medianSpacing(x, n) {
|
|
385
395
|
if (n < 2) return 1;
|
|
386
396
|
const diffs = [];
|
|
@@ -404,12 +414,15 @@ var BarLayer = class {
|
|
|
404
414
|
this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
|
|
405
415
|
this.name = opts.name ?? this.id;
|
|
406
416
|
this.yAxis = opts.yAxis ?? "y";
|
|
417
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
407
418
|
const n = Math.min(opts.x.length, opts.y.length);
|
|
408
419
|
this.count = n;
|
|
409
420
|
this.barWidth = opts.width ?? medianSpacing(opts.x, n) * 0.8;
|
|
410
421
|
this.offset = opts.offset ?? 0;
|
|
411
422
|
this.orientation = opts.orientation ?? "v";
|
|
412
423
|
const rects = this.buildRects(opts.x, opts.y, opts.base, n);
|
|
424
|
+
this.colorsInput = opts.colors;
|
|
425
|
+
const colors = this.buildColors(n);
|
|
413
426
|
const vao = gl.createVertexArray();
|
|
414
427
|
this.vao = vao;
|
|
415
428
|
gl.bindVertexArray(vao);
|
|
@@ -420,13 +433,32 @@ var BarLayer = class {
|
|
|
420
433
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
421
434
|
const rectBuf = gl.createBuffer();
|
|
422
435
|
gl.bindBuffer(gl.ARRAY_BUFFER, rectBuf);
|
|
423
|
-
gl.bufferData(gl.ARRAY_BUFFER, rects,
|
|
436
|
+
gl.bufferData(gl.ARRAY_BUFFER, rects, this.usage);
|
|
424
437
|
gl.enableVertexAttribArray(1);
|
|
425
438
|
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
426
439
|
gl.vertexAttribDivisor(1, 1);
|
|
440
|
+
const colorBuf = gl.createBuffer();
|
|
441
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
442
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
443
|
+
gl.enableVertexAttribArray(2);
|
|
444
|
+
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
|
|
445
|
+
gl.vertexAttribDivisor(2, 1);
|
|
427
446
|
gl.bindVertexArray(null);
|
|
428
|
-
this.buffers = [cornerBuf, rectBuf];
|
|
429
|
-
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS
|
|
447
|
+
this.buffers = [cornerBuf, rectBuf, colorBuf];
|
|
448
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
449
|
+
}
|
|
450
|
+
/** Per-bar color array (each bar uses its `colors[i]` or falls back to `color`). */
|
|
451
|
+
buildColors(n) {
|
|
452
|
+
const arr = new Float32Array(n * 4);
|
|
453
|
+
const per = this.colorsInput;
|
|
454
|
+
for (let i = 0; i < n; i++) {
|
|
455
|
+
const c = per && per[i] != null ? toColor(per[i]) : this.color;
|
|
456
|
+
arr[i * 4] = c[0];
|
|
457
|
+
arr[i * 4 + 1] = c[1];
|
|
458
|
+
arr[i * 4 + 2] = c[2];
|
|
459
|
+
arr[i * 4 + 3] = c[3];
|
|
460
|
+
}
|
|
461
|
+
return arr;
|
|
430
462
|
}
|
|
431
463
|
buildRects(x, y, base, n) {
|
|
432
464
|
const width = this.barWidth, off = this.offset;
|
|
@@ -459,13 +491,17 @@ var BarLayer = class {
|
|
|
459
491
|
this.yBounds = [minY, maxY];
|
|
460
492
|
return rects;
|
|
461
493
|
}
|
|
462
|
-
/** Replace bar values and re-upload
|
|
463
|
-
setData(x, y, base) {
|
|
494
|
+
/** Replace bar values (and optionally per-bar colors) and re-upload for streaming. */
|
|
495
|
+
setData(x, y, base, colors) {
|
|
464
496
|
const n = Math.min(x.length, y.length);
|
|
465
497
|
this.count = n;
|
|
466
498
|
const rects = this.buildRects(x, y, base, n);
|
|
467
499
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers[1]);
|
|
468
|
-
this.gl.bufferData(this.gl.ARRAY_BUFFER, rects, this.
|
|
500
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, rects, this.usage);
|
|
501
|
+
if (colors !== void 0) this.colorsInput = colors;
|
|
502
|
+
const colorArr = this.buildColors(n);
|
|
503
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers[2]);
|
|
504
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, colorArr, this.usage);
|
|
469
505
|
}
|
|
470
506
|
bounds() {
|
|
471
507
|
if (this.count === 0) return null;
|
|
@@ -476,7 +512,6 @@ var BarLayer = class {
|
|
|
476
512
|
const gl = state.gl;
|
|
477
513
|
gl.useProgram(this.program);
|
|
478
514
|
setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
|
|
479
|
-
gl.uniform4f(this.uniforms.uColor, this.color[0], this.color[1], this.color[2], this.color[3]);
|
|
480
515
|
gl.bindVertexArray(this.vao);
|
|
481
516
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
|
|
482
517
|
gl.bindVertexArray(null);
|
|
@@ -719,15 +754,39 @@ var BoxLayer = class {
|
|
|
719
754
|
this.yRef = 0;
|
|
720
755
|
this.xBounds = [0, 0];
|
|
721
756
|
this.yBounds = [0, 0];
|
|
757
|
+
this.triCount = 0;
|
|
758
|
+
this.lineStart = 0;
|
|
759
|
+
this.lineCount = 0;
|
|
760
|
+
this.pointStart = 0;
|
|
761
|
+
this.pointCount = 0;
|
|
722
762
|
this.id = `box-${counter3++}`;
|
|
723
763
|
this.gl = gl;
|
|
724
764
|
this.program = getProgram3(gl);
|
|
765
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
725
766
|
this.yAxis = opts.yAxis ?? "y";
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
const
|
|
767
|
+
this.width = opts.width ?? 0.6;
|
|
768
|
+
this.showBox = opts.box !== false;
|
|
769
|
+
this.showViolin = opts.violin === true;
|
|
770
|
+
const all = this.build(opts.groups);
|
|
771
|
+
const vao = gl.createVertexArray();
|
|
772
|
+
const buffer = gl.createBuffer();
|
|
773
|
+
this.vao = vao;
|
|
774
|
+
this.buffer = buffer;
|
|
775
|
+
gl.bindVertexArray(vao);
|
|
776
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
777
|
+
gl.bufferData(gl.ARRAY_BUFFER, all, this.usage);
|
|
778
|
+
gl.enableVertexAttribArray(0);
|
|
779
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 24, 0);
|
|
780
|
+
gl.enableVertexAttribArray(1);
|
|
781
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 24, 8);
|
|
782
|
+
gl.bindVertexArray(null);
|
|
783
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uPointSize", "uIsPoint"]);
|
|
784
|
+
}
|
|
785
|
+
/** Build box/whisker/violin geometry from the groups; sets refs, bounds and draw ranges. */
|
|
786
|
+
build(groups) {
|
|
787
|
+
const hw = this.width / 2;
|
|
788
|
+
const showBox = this.showBox;
|
|
789
|
+
const showViolin = this.showViolin;
|
|
731
790
|
this.xRef = groups.length ? groups[0].position : 0;
|
|
732
791
|
this.yRef = groups.length && groups[0].values.length ? groups[0].values[0] : 0;
|
|
733
792
|
const tris = new Mesh();
|
|
@@ -812,25 +871,19 @@ var BoxLayer = class {
|
|
|
812
871
|
}
|
|
813
872
|
this.xBounds = [minX, maxX];
|
|
814
873
|
this.yBounds = [minY, maxY];
|
|
815
|
-
const all = [...tris.data, ...lines.data, ...points.data];
|
|
816
874
|
this.triCount = tris.count;
|
|
817
875
|
this.lineStart = tris.count;
|
|
818
876
|
this.lineCount = lines.count;
|
|
819
877
|
this.pointStart = tris.count + lines.count;
|
|
820
878
|
this.pointCount = points.count;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
gl.
|
|
828
|
-
gl.
|
|
829
|
-
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 24, 0);
|
|
830
|
-
gl.enableVertexAttribArray(1);
|
|
831
|
-
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 24, 8);
|
|
832
|
-
gl.bindVertexArray(null);
|
|
833
|
-
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uPointSize", "uIsPoint"]);
|
|
879
|
+
return new Float32Array([...tris.data, ...lines.data, ...points.data]);
|
|
880
|
+
}
|
|
881
|
+
/** Replace the box groups and rebuild the geometry (for streaming). */
|
|
882
|
+
setData(groups, width) {
|
|
883
|
+
if (width != null) this.width = width;
|
|
884
|
+
const all = this.build(groups);
|
|
885
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
886
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, all, this.usage);
|
|
834
887
|
}
|
|
835
888
|
bounds() {
|
|
836
889
|
if (this.triCount + this.lineCount + this.pointCount === 0) return null;
|
|
@@ -931,6 +984,18 @@ var counter4 = 0;
|
|
|
931
984
|
var CandlestickLayer = class {
|
|
932
985
|
constructor(gl, opts) {
|
|
933
986
|
this.buffers = [];
|
|
987
|
+
/** Body width in data units (median-derived unless the user fixed it). */
|
|
988
|
+
this.bodyWidth = 1;
|
|
989
|
+
// Retained OHLC (plain arrays so streaming can mutate/grow in place).
|
|
990
|
+
this.xs = [];
|
|
991
|
+
this.os = [];
|
|
992
|
+
this.hs = [];
|
|
993
|
+
this.ls = [];
|
|
994
|
+
this.cs = [];
|
|
995
|
+
this.rects = new Float32Array(0);
|
|
996
|
+
this.wicks = new Float32Array(0);
|
|
997
|
+
this.colors = new Float32Array(0);
|
|
998
|
+
this.count = 0;
|
|
934
999
|
this.xRef = 0;
|
|
935
1000
|
this.yRef = 0;
|
|
936
1001
|
this.xBounds = [0, 0];
|
|
@@ -941,41 +1006,13 @@ var CandlestickLayer = class {
|
|
|
941
1006
|
this.name = opts.name ?? this.id;
|
|
942
1007
|
this.yAxis = opts.yAxis ?? "y";
|
|
943
1008
|
this.wickWidth = opts.wickWidth ?? 1.5;
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
this.
|
|
947
|
-
|
|
948
|
-
this.
|
|
949
|
-
|
|
950
|
-
this.
|
|
951
|
-
this.yRef = n > 0 ? opts.open[0] : 0;
|
|
952
|
-
const rects = new Float32Array(n * 4);
|
|
953
|
-
const wicks = new Float32Array(n * 4);
|
|
954
|
-
const colors = new Float32Array(n * 4);
|
|
955
|
-
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
956
|
-
for (let i = 0; i < n; i++) {
|
|
957
|
-
const cx = opts.x[i], o = opts.open[i], h = opts.high[i], l = opts.low[i], c = opts.close[i];
|
|
958
|
-
const x0 = cx - width / 2, x1 = cx + width / 2;
|
|
959
|
-
rects[i * 4] = x0 - this.xRef;
|
|
960
|
-
rects[i * 4 + 1] = o - this.yRef;
|
|
961
|
-
rects[i * 4 + 2] = x1 - this.xRef;
|
|
962
|
-
rects[i * 4 + 3] = c - this.yRef;
|
|
963
|
-
wicks[i * 4] = cx - this.xRef;
|
|
964
|
-
wicks[i * 4 + 1] = l - this.yRef;
|
|
965
|
-
wicks[i * 4 + 2] = cx - this.xRef;
|
|
966
|
-
wicks[i * 4 + 3] = h - this.yRef;
|
|
967
|
-
const col = c >= o ? up : down;
|
|
968
|
-
colors[i * 4] = col[0];
|
|
969
|
-
colors[i * 4 + 1] = col[1];
|
|
970
|
-
colors[i * 4 + 2] = col[2];
|
|
971
|
-
colors[i * 4 + 3] = col[3];
|
|
972
|
-
minX = Math.min(minX, x0);
|
|
973
|
-
maxX = Math.max(maxX, x1);
|
|
974
|
-
minY = Math.min(minY, l);
|
|
975
|
-
maxY = Math.max(maxY, h);
|
|
976
|
-
}
|
|
977
|
-
this.xBounds = [minX, maxX];
|
|
978
|
-
this.yBounds = [minY, maxY];
|
|
1009
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
1010
|
+
this.up = toColor2(opts.upColor ?? "#26a69a");
|
|
1011
|
+
this.down = toColor2(opts.downColor ?? "#ef5350");
|
|
1012
|
+
this.colorCss = toColorCss(this.up);
|
|
1013
|
+
this.explicitWidth = opts.width;
|
|
1014
|
+
this.ingest(opts);
|
|
1015
|
+
this.rebuild();
|
|
979
1016
|
const cornerRect = gl.createBuffer();
|
|
980
1017
|
gl.bindBuffer(gl.ARRAY_BUFFER, cornerRect);
|
|
981
1018
|
gl.bufferData(gl.ARRAY_BUFFER, RECT_CORNERS, gl.STATIC_DRAW);
|
|
@@ -984,13 +1021,13 @@ var CandlestickLayer = class {
|
|
|
984
1021
|
gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS, gl.STATIC_DRAW);
|
|
985
1022
|
const rectBuf = gl.createBuffer();
|
|
986
1023
|
gl.bindBuffer(gl.ARRAY_BUFFER, rectBuf);
|
|
987
|
-
gl.bufferData(gl.ARRAY_BUFFER, rects,
|
|
1024
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.rects, this.usage);
|
|
988
1025
|
const wickBuf = gl.createBuffer();
|
|
989
1026
|
gl.bindBuffer(gl.ARRAY_BUFFER, wickBuf);
|
|
990
|
-
gl.bufferData(gl.ARRAY_BUFFER, wicks,
|
|
1027
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.wicks, this.usage);
|
|
991
1028
|
const colorBuf = gl.createBuffer();
|
|
992
1029
|
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
993
|
-
gl.bufferData(gl.ARRAY_BUFFER, colors,
|
|
1030
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.colors, this.usage);
|
|
994
1031
|
this.buffers = [cornerRect, cornerSeg, rectBuf, wickBuf, colorBuf];
|
|
995
1032
|
this.bodyVao = gl.createVertexArray();
|
|
996
1033
|
gl.bindVertexArray(this.bodyVao);
|
|
@@ -1022,6 +1059,112 @@ var CandlestickLayer = class {
|
|
|
1022
1059
|
this.uBody = uniformLocations(gl, this.progs.body, [...TRANSFORM_UNIFORMS]);
|
|
1023
1060
|
this.uWick = uniformLocations(gl, this.progs.wick, [...TRANSFORM_UNIFORMS, "uResolution", "uWidth"]);
|
|
1024
1061
|
}
|
|
1062
|
+
/** Copy the incoming OHLC arrays into the retained plain arrays. */
|
|
1063
|
+
ingest(d) {
|
|
1064
|
+
const n = Math.min(d.x.length, d.open.length, d.high.length, d.low.length, d.close.length);
|
|
1065
|
+
this.xs = Array.from({ length: n }, (_, i) => d.x[i]);
|
|
1066
|
+
this.os = Array.from({ length: n }, (_, i) => d.open[i]);
|
|
1067
|
+
this.hs = Array.from({ length: n }, (_, i) => d.high[i]);
|
|
1068
|
+
this.ls = Array.from({ length: n }, (_, i) => d.low[i]);
|
|
1069
|
+
this.cs = Array.from({ length: n }, (_, i) => d.close[i]);
|
|
1070
|
+
if (d.width != null) this.explicitWidth = d.width;
|
|
1071
|
+
}
|
|
1072
|
+
/** Write candle `i`'s body rect, wick segment and up/down color into the arrays. */
|
|
1073
|
+
emitCandle(i) {
|
|
1074
|
+
const cx = this.xs[i], o = this.os[i], h = this.hs[i], l = this.ls[i], c = this.cs[i];
|
|
1075
|
+
const x0 = cx - this.bodyWidth / 2, x1 = cx + this.bodyWidth / 2;
|
|
1076
|
+
const r = this.rects, wk = this.wicks, col = this.colors;
|
|
1077
|
+
r[i * 4] = x0 - this.xRef;
|
|
1078
|
+
r[i * 4 + 1] = o - this.yRef;
|
|
1079
|
+
r[i * 4 + 2] = x1 - this.xRef;
|
|
1080
|
+
r[i * 4 + 3] = c - this.yRef;
|
|
1081
|
+
wk[i * 4] = cx - this.xRef;
|
|
1082
|
+
wk[i * 4 + 1] = l - this.yRef;
|
|
1083
|
+
wk[i * 4 + 2] = cx - this.xRef;
|
|
1084
|
+
wk[i * 4 + 3] = h - this.yRef;
|
|
1085
|
+
const cc = c >= o ? this.up : this.down;
|
|
1086
|
+
col[i * 4] = cc[0];
|
|
1087
|
+
col[i * 4 + 1] = cc[1];
|
|
1088
|
+
col[i * 4 + 2] = cc[2];
|
|
1089
|
+
col[i * 4 + 3] = cc[3];
|
|
1090
|
+
}
|
|
1091
|
+
/** Recompute width/refs/bounds and refill the vertex arrays from the retained OHLC. */
|
|
1092
|
+
rebuild() {
|
|
1093
|
+
const n = this.xs.length;
|
|
1094
|
+
this.count = n;
|
|
1095
|
+
this.bodyWidth = this.explicitWidth ?? medianSpacing2(this.xs, n) * 0.7;
|
|
1096
|
+
this.rects = new Float32Array(n * 4);
|
|
1097
|
+
this.wicks = new Float32Array(n * 4);
|
|
1098
|
+
this.colors = new Float32Array(n * 4);
|
|
1099
|
+
this.xRef = n > 0 ? this.xs[0] : 0;
|
|
1100
|
+
this.yRef = n > 0 ? this.os[0] : 0;
|
|
1101
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1102
|
+
for (let i = 0; i < n; i++) {
|
|
1103
|
+
this.emitCandle(i);
|
|
1104
|
+
minX = Math.min(minX, this.xs[i] - this.bodyWidth / 2);
|
|
1105
|
+
maxX = Math.max(maxX, this.xs[i] + this.bodyWidth / 2);
|
|
1106
|
+
minY = Math.min(minY, this.ls[i]);
|
|
1107
|
+
maxY = Math.max(maxY, this.hs[i]);
|
|
1108
|
+
}
|
|
1109
|
+
this.xBounds = n > 0 ? [minX, maxX] : [0, 0];
|
|
1110
|
+
this.yBounds = n > 0 ? [minY, maxY] : [0, 0];
|
|
1111
|
+
}
|
|
1112
|
+
/** Re-upload all three per-candle buffers (after a full rebuild). */
|
|
1113
|
+
upload() {
|
|
1114
|
+
const gl = this.gl;
|
|
1115
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
1116
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.rects, this.usage);
|
|
1117
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[3]);
|
|
1118
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.wicks, this.usage);
|
|
1119
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[4]);
|
|
1120
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.colors, this.usage);
|
|
1121
|
+
}
|
|
1122
|
+
/** Replace every candle and re-upload (for streaming a whole new window). */
|
|
1123
|
+
setData(data) {
|
|
1124
|
+
this.ingest(data);
|
|
1125
|
+
this.rebuild();
|
|
1126
|
+
this.upload();
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Append one new candle (grows the series). Prefer {@link updateLast} for the
|
|
1130
|
+
* in-progress candle and `appendCandle` only when a bar closes and a new one opens.
|
|
1131
|
+
*/
|
|
1132
|
+
appendCandle(c) {
|
|
1133
|
+
this.xs.push(c.x);
|
|
1134
|
+
this.os.push(c.open);
|
|
1135
|
+
this.hs.push(c.high);
|
|
1136
|
+
this.ls.push(c.low);
|
|
1137
|
+
this.cs.push(c.close);
|
|
1138
|
+
this.rebuild();
|
|
1139
|
+
this.upload();
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Update the most recent candle in place — the cheap hot path for a live
|
|
1143
|
+
* (forming) bar. Only that candle's 12 floats are re-uploaded; bounds are
|
|
1144
|
+
* extended (never shrunk) to include it. No-op when the series is empty.
|
|
1145
|
+
*/
|
|
1146
|
+
updateLast(c) {
|
|
1147
|
+
const i = this.xs.length - 1;
|
|
1148
|
+
if (i < 0) return;
|
|
1149
|
+
this.xs[i] = c.x;
|
|
1150
|
+
this.os[i] = c.open;
|
|
1151
|
+
this.hs[i] = c.high;
|
|
1152
|
+
this.ls[i] = c.low;
|
|
1153
|
+
this.cs[i] = c.close;
|
|
1154
|
+
this.emitCandle(i);
|
|
1155
|
+
const gl = this.gl, off = i * 4;
|
|
1156
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
1157
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, off * 4, this.rects.subarray(off, off + 4));
|
|
1158
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[3]);
|
|
1159
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, off * 4, this.wicks.subarray(off, off + 4));
|
|
1160
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[4]);
|
|
1161
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, off * 4, this.colors.subarray(off, off + 4));
|
|
1162
|
+
this.xBounds = [
|
|
1163
|
+
Math.min(this.xBounds[0], c.x - this.bodyWidth / 2),
|
|
1164
|
+
Math.max(this.xBounds[1], c.x + this.bodyWidth / 2)
|
|
1165
|
+
];
|
|
1166
|
+
this.yBounds = [Math.min(this.yBounds[0], c.low), Math.max(this.yBounds[1], c.high)];
|
|
1167
|
+
}
|
|
1025
1168
|
bounds() {
|
|
1026
1169
|
if (this.count === 0) return null;
|
|
1027
1170
|
return { x: this.xBounds, y: this.yBounds };
|
|
@@ -1048,9 +1191,238 @@ var CandlestickLayer = class {
|
|
|
1048
1191
|
for (const b of this.buffers) this.gl.deleteBuffer(b);
|
|
1049
1192
|
}
|
|
1050
1193
|
};
|
|
1051
|
-
function
|
|
1194
|
+
function toColor2(input) {
|
|
1195
|
+
return Array.isArray(input) ? input : parseColor(input);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/layers/ohlc.ts
|
|
1199
|
+
var SEG_VERT = (
|
|
1200
|
+
/* glsl */
|
|
1201
|
+
`#version 300 es
|
|
1202
|
+
precision highp float;
|
|
1203
|
+
layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
|
|
1204
|
+
layout(location = 1) in vec4 aSeg; // (x0,y0,x1,y1) offset data space
|
|
1205
|
+
layout(location = 2) in vec4 aColor;
|
|
1206
|
+
uniform vec2 uResolution;
|
|
1207
|
+
uniform float uWidth;
|
|
1208
|
+
${TRANSFORM_GLSL}
|
|
1209
|
+
out vec4 vColor;
|
|
1210
|
+
void main() {
|
|
1211
|
+
vec2 s0 = (dataToClip(aSeg.xy) * 0.5 + 0.5) * uResolution;
|
|
1212
|
+
vec2 s1 = (dataToClip(aSeg.zw) * 0.5 + 0.5) * uResolution;
|
|
1213
|
+
vec2 d = s1 - s0;
|
|
1214
|
+
float len = length(d);
|
|
1215
|
+
vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
|
|
1216
|
+
vec2 nrm = vec2(-dir.y, dir.x);
|
|
1217
|
+
vec2 pos = mix(s0, s1, aCorner.x) + nrm * (aCorner.y * uWidth * 0.5);
|
|
1218
|
+
vColor = aColor;
|
|
1219
|
+
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
1220
|
+
}`
|
|
1221
|
+
);
|
|
1222
|
+
var FRAG5 = (
|
|
1223
|
+
/* glsl */
|
|
1224
|
+
`#version 300 es
|
|
1225
|
+
precision highp float;
|
|
1226
|
+
in vec4 vColor;
|
|
1227
|
+
out vec4 outColor;
|
|
1228
|
+
void main() { outColor = vec4(vColor.rgb * vColor.a, vColor.a); }`
|
|
1229
|
+
);
|
|
1230
|
+
var SEG_CORNERS2 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
|
|
1231
|
+
var SEGS_PER_BAR = 3;
|
|
1232
|
+
var programCache4 = /* @__PURE__ */ new WeakMap();
|
|
1233
|
+
function getProgram4(gl) {
|
|
1234
|
+
let p = programCache4.get(gl);
|
|
1235
|
+
if (!p) {
|
|
1236
|
+
p = createProgram(gl, SEG_VERT, FRAG5);
|
|
1237
|
+
programCache4.set(gl, p);
|
|
1238
|
+
}
|
|
1239
|
+
return p;
|
|
1240
|
+
}
|
|
1241
|
+
function medianSpacing3(x, n) {
|
|
1242
|
+
if (n < 2) return 1;
|
|
1243
|
+
const diffs = [];
|
|
1244
|
+
for (let i = 1; i < n; i++) diffs.push(Math.abs(x[i] - x[i - 1]));
|
|
1245
|
+
diffs.sort((a, b) => a - b);
|
|
1246
|
+
return diffs[Math.floor(diffs.length / 2)] || 1;
|
|
1247
|
+
}
|
|
1248
|
+
function toColor3(input) {
|
|
1052
1249
|
return Array.isArray(input) ? input : parseColor(input);
|
|
1053
1250
|
}
|
|
1251
|
+
var counter5 = 0;
|
|
1252
|
+
var OhlcLayer = class {
|
|
1253
|
+
constructor(gl, opts) {
|
|
1254
|
+
this.buffers = [];
|
|
1255
|
+
this.barWidth = 1;
|
|
1256
|
+
this.xs = [];
|
|
1257
|
+
this.os = [];
|
|
1258
|
+
this.hs = [];
|
|
1259
|
+
this.ls = [];
|
|
1260
|
+
this.cs = [];
|
|
1261
|
+
this.segs = new Float32Array(0);
|
|
1262
|
+
this.colors = new Float32Array(0);
|
|
1263
|
+
this.count = 0;
|
|
1264
|
+
// number of bars
|
|
1265
|
+
this.xRef = 0;
|
|
1266
|
+
this.yRef = 0;
|
|
1267
|
+
this.xBounds = [0, 0];
|
|
1268
|
+
this.yBounds = [0, 0];
|
|
1269
|
+
this.id = `ohlc-${counter5++}`;
|
|
1270
|
+
this.gl = gl;
|
|
1271
|
+
this.program = getProgram4(gl);
|
|
1272
|
+
this.name = opts.name ?? this.id;
|
|
1273
|
+
this.yAxis = opts.yAxis ?? "y";
|
|
1274
|
+
this.lineWidth = opts.lineWidth ?? 1.5;
|
|
1275
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
1276
|
+
this.up = toColor3(opts.upColor ?? "#26a69a");
|
|
1277
|
+
this.down = toColor3(opts.downColor ?? "#ef5350");
|
|
1278
|
+
this.colorCss = toColorCss(this.up);
|
|
1279
|
+
this.explicitWidth = opts.width;
|
|
1280
|
+
this.ingest(opts);
|
|
1281
|
+
this.rebuild();
|
|
1282
|
+
const vao = gl.createVertexArray();
|
|
1283
|
+
this.vao = vao;
|
|
1284
|
+
gl.bindVertexArray(vao);
|
|
1285
|
+
const cornerBuf = gl.createBuffer();
|
|
1286
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, cornerBuf);
|
|
1287
|
+
gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS2, gl.STATIC_DRAW);
|
|
1288
|
+
gl.enableVertexAttribArray(0);
|
|
1289
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
1290
|
+
const segBuf = gl.createBuffer();
|
|
1291
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
|
|
1292
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.segs, this.usage);
|
|
1293
|
+
gl.enableVertexAttribArray(1);
|
|
1294
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
1295
|
+
gl.vertexAttribDivisor(1, 1);
|
|
1296
|
+
const colorBuf = gl.createBuffer();
|
|
1297
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
1298
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.colors, this.usage);
|
|
1299
|
+
gl.enableVertexAttribArray(2);
|
|
1300
|
+
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
|
|
1301
|
+
gl.vertexAttribDivisor(2, 1);
|
|
1302
|
+
gl.bindVertexArray(null);
|
|
1303
|
+
this.buffers = [cornerBuf, segBuf, colorBuf];
|
|
1304
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uResolution", "uWidth"]);
|
|
1305
|
+
}
|
|
1306
|
+
ingest(d) {
|
|
1307
|
+
const n = Math.min(d.x.length, d.open.length, d.high.length, d.low.length, d.close.length);
|
|
1308
|
+
this.xs = Array.from({ length: n }, (_, i) => d.x[i]);
|
|
1309
|
+
this.os = Array.from({ length: n }, (_, i) => d.open[i]);
|
|
1310
|
+
this.hs = Array.from({ length: n }, (_, i) => d.high[i]);
|
|
1311
|
+
this.ls = Array.from({ length: n }, (_, i) => d.low[i]);
|
|
1312
|
+
this.cs = Array.from({ length: n }, (_, i) => d.close[i]);
|
|
1313
|
+
if (d.width != null) this.explicitWidth = d.width;
|
|
1314
|
+
}
|
|
1315
|
+
/** Write bar `i`'s three segments (+ colors) at instance base `i * SEGS_PER_BAR`. */
|
|
1316
|
+
emitBar(i) {
|
|
1317
|
+
const cx = this.xs[i], o = this.os[i], h = this.hs[i], l = this.ls[i], c = this.cs[i];
|
|
1318
|
+
const half = this.barWidth / 2;
|
|
1319
|
+
const s = this.segs, col = this.colors;
|
|
1320
|
+
const b = i * SEGS_PER_BAR * 4;
|
|
1321
|
+
s[b] = cx - this.xRef;
|
|
1322
|
+
s[b + 1] = l - this.yRef;
|
|
1323
|
+
s[b + 2] = cx - this.xRef;
|
|
1324
|
+
s[b + 3] = h - this.yRef;
|
|
1325
|
+
s[b + 4] = cx - half - this.xRef;
|
|
1326
|
+
s[b + 5] = o - this.yRef;
|
|
1327
|
+
s[b + 6] = cx - this.xRef;
|
|
1328
|
+
s[b + 7] = o - this.yRef;
|
|
1329
|
+
s[b + 8] = cx - this.xRef;
|
|
1330
|
+
s[b + 9] = c - this.yRef;
|
|
1331
|
+
s[b + 10] = cx + half - this.xRef;
|
|
1332
|
+
s[b + 11] = c - this.yRef;
|
|
1333
|
+
const cc = c >= o ? this.up : this.down;
|
|
1334
|
+
for (let k = 0; k < SEGS_PER_BAR; k++) {
|
|
1335
|
+
const o4 = (i * SEGS_PER_BAR + k) * 4;
|
|
1336
|
+
col[o4] = cc[0];
|
|
1337
|
+
col[o4 + 1] = cc[1];
|
|
1338
|
+
col[o4 + 2] = cc[2];
|
|
1339
|
+
col[o4 + 3] = cc[3];
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
rebuild() {
|
|
1343
|
+
const n = this.xs.length;
|
|
1344
|
+
this.count = n;
|
|
1345
|
+
this.barWidth = this.explicitWidth ?? medianSpacing3(this.xs, n) * 0.7;
|
|
1346
|
+
this.segs = new Float32Array(n * SEGS_PER_BAR * 4);
|
|
1347
|
+
this.colors = new Float32Array(n * SEGS_PER_BAR * 4);
|
|
1348
|
+
this.xRef = n > 0 ? this.xs[0] : 0;
|
|
1349
|
+
this.yRef = n > 0 ? this.os[0] : 0;
|
|
1350
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1351
|
+
for (let i = 0; i < n; i++) {
|
|
1352
|
+
this.emitBar(i);
|
|
1353
|
+
minX = Math.min(minX, this.xs[i] - this.barWidth / 2);
|
|
1354
|
+
maxX = Math.max(maxX, this.xs[i] + this.barWidth / 2);
|
|
1355
|
+
minY = Math.min(minY, this.ls[i]);
|
|
1356
|
+
maxY = Math.max(maxY, this.hs[i]);
|
|
1357
|
+
}
|
|
1358
|
+
this.xBounds = n > 0 ? [minX, maxX] : [0, 0];
|
|
1359
|
+
this.yBounds = n > 0 ? [minY, maxY] : [0, 0];
|
|
1360
|
+
}
|
|
1361
|
+
upload() {
|
|
1362
|
+
const gl = this.gl;
|
|
1363
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
1364
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.segs, this.usage);
|
|
1365
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
1366
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.colors, this.usage);
|
|
1367
|
+
}
|
|
1368
|
+
/** Replace every bar and re-upload (for streaming a whole new window). */
|
|
1369
|
+
setData(data) {
|
|
1370
|
+
this.ingest(data);
|
|
1371
|
+
this.rebuild();
|
|
1372
|
+
this.upload();
|
|
1373
|
+
}
|
|
1374
|
+
/** Append one new bar (grows the series). */
|
|
1375
|
+
appendCandle(c) {
|
|
1376
|
+
this.xs.push(c.x);
|
|
1377
|
+
this.os.push(c.open);
|
|
1378
|
+
this.hs.push(c.high);
|
|
1379
|
+
this.ls.push(c.low);
|
|
1380
|
+
this.cs.push(c.close);
|
|
1381
|
+
this.rebuild();
|
|
1382
|
+
this.upload();
|
|
1383
|
+
}
|
|
1384
|
+
/** Update the most recent bar in place — the cheap hot path for a live bar. */
|
|
1385
|
+
updateLast(c) {
|
|
1386
|
+
const i = this.xs.length - 1;
|
|
1387
|
+
if (i < 0) return;
|
|
1388
|
+
this.xs[i] = c.x;
|
|
1389
|
+
this.os[i] = c.open;
|
|
1390
|
+
this.hs[i] = c.high;
|
|
1391
|
+
this.ls[i] = c.low;
|
|
1392
|
+
this.cs[i] = c.close;
|
|
1393
|
+
this.emitBar(i);
|
|
1394
|
+
const gl = this.gl, off = i * SEGS_PER_BAR * 4;
|
|
1395
|
+
const span = SEGS_PER_BAR * 4;
|
|
1396
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
1397
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, off * 4, this.segs.subarray(off, off + span));
|
|
1398
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
1399
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, off * 4, this.colors.subarray(off, off + span));
|
|
1400
|
+
this.xBounds = [
|
|
1401
|
+
Math.min(this.xBounds[0], c.x - this.barWidth / 2),
|
|
1402
|
+
Math.max(this.xBounds[1], c.x + this.barWidth / 2)
|
|
1403
|
+
];
|
|
1404
|
+
this.yBounds = [Math.min(this.yBounds[0], c.low), Math.max(this.yBounds[1], c.high)];
|
|
1405
|
+
}
|
|
1406
|
+
bounds() {
|
|
1407
|
+
if (this.count === 0) return null;
|
|
1408
|
+
return { x: this.xBounds, y: this.yBounds };
|
|
1409
|
+
}
|
|
1410
|
+
draw(state) {
|
|
1411
|
+
if (this.count === 0) return;
|
|
1412
|
+
const gl = state.gl;
|
|
1413
|
+
gl.useProgram(this.program);
|
|
1414
|
+
setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
|
|
1415
|
+
gl.uniform2f(this.uniforms.uResolution, state.pixelWidth, state.pixelHeight);
|
|
1416
|
+
gl.uniform1f(this.uniforms.uWidth, this.lineWidth * state.dpr);
|
|
1417
|
+
gl.bindVertexArray(this.vao);
|
|
1418
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count * SEGS_PER_BAR);
|
|
1419
|
+
gl.bindVertexArray(null);
|
|
1420
|
+
}
|
|
1421
|
+
dispose() {
|
|
1422
|
+
this.gl.deleteVertexArray(this.vao);
|
|
1423
|
+
for (const b of this.buffers) this.gl.deleteBuffer(b);
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1054
1426
|
|
|
1055
1427
|
// src/color/colormap.ts
|
|
1056
1428
|
var ANCHORS = {
|
|
@@ -1136,7 +1508,7 @@ ${TRANSFORM_GLSL}
|
|
|
1136
1508
|
out vec4 vColor;
|
|
1137
1509
|
void main() { vColor = aColor; gl_Position = vec4(dataToClip(aPos), 0.0, 1.0); }`
|
|
1138
1510
|
);
|
|
1139
|
-
var
|
|
1511
|
+
var FRAG6 = (
|
|
1140
1512
|
/* glsl */
|
|
1141
1513
|
`#version 300 es
|
|
1142
1514
|
precision highp float;
|
|
@@ -1162,37 +1534,61 @@ var CASES = [
|
|
|
1162
1534
|
[[0, 3]],
|
|
1163
1535
|
[]
|
|
1164
1536
|
];
|
|
1165
|
-
var
|
|
1166
|
-
function
|
|
1167
|
-
let p =
|
|
1537
|
+
var programCache5 = /* @__PURE__ */ new WeakMap();
|
|
1538
|
+
function getProgram5(gl) {
|
|
1539
|
+
let p = programCache5.get(gl);
|
|
1168
1540
|
if (!p) {
|
|
1169
|
-
p = createProgram(gl, VERT4,
|
|
1170
|
-
|
|
1541
|
+
p = createProgram(gl, VERT4, FRAG6);
|
|
1542
|
+
programCache5.set(gl, p);
|
|
1171
1543
|
}
|
|
1172
1544
|
return p;
|
|
1173
1545
|
}
|
|
1174
|
-
var
|
|
1546
|
+
var counter6 = 0;
|
|
1175
1547
|
var ContourLayer = class {
|
|
1176
1548
|
constructor(gl, opts) {
|
|
1177
|
-
this.
|
|
1549
|
+
this.vertexCount = 0;
|
|
1550
|
+
this.id = `contour-${counter6++}`;
|
|
1178
1551
|
this.gl = gl;
|
|
1179
|
-
this.program =
|
|
1552
|
+
this.program = getProgram5(gl);
|
|
1553
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
1180
1554
|
this.yAxis = opts.yAxis ?? "y";
|
|
1181
1555
|
this.ext = opts.extent;
|
|
1182
|
-
|
|
1183
|
-
|
|
1556
|
+
this.cols = opts.cols;
|
|
1557
|
+
this.rows = opts.rows;
|
|
1558
|
+
const [x0, y0] = [opts.extent.x[0], opts.extent.y[0]];
|
|
1184
1559
|
this.xRef = x0;
|
|
1185
1560
|
this.yRef = y0;
|
|
1561
|
+
this.levelsOpt = opts.levels;
|
|
1562
|
+
this.cmap = colormap(opts.colormap ?? "viridis");
|
|
1563
|
+
this.fixedColor = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : null;
|
|
1564
|
+
const data = this.build(opts.values, opts.cols, opts.rows);
|
|
1565
|
+
const vao = gl.createVertexArray();
|
|
1566
|
+
const buffer = gl.createBuffer();
|
|
1567
|
+
this.vao = vao;
|
|
1568
|
+
this.buffer = buffer;
|
|
1569
|
+
gl.bindVertexArray(vao);
|
|
1570
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1571
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
|
|
1572
|
+
gl.enableVertexAttribArray(0);
|
|
1573
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 24, 0);
|
|
1574
|
+
gl.enableVertexAttribArray(1);
|
|
1575
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 24, 8);
|
|
1576
|
+
gl.bindVertexArray(null);
|
|
1577
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
1578
|
+
}
|
|
1579
|
+
/** Marching-squares the scalar field into iso-line segments; sets vertexCount. */
|
|
1580
|
+
build(values, cols, rows) {
|
|
1581
|
+
const [x0, x1] = this.ext.x, [y0, y1] = this.ext.y;
|
|
1186
1582
|
let vmin = Infinity, vmax = -Infinity;
|
|
1187
1583
|
for (let i = 0; i < values.length; i++) {
|
|
1188
1584
|
const v = values[i];
|
|
1189
1585
|
if (v < vmin) vmin = v;
|
|
1190
1586
|
if (v > vmax) vmax = v;
|
|
1191
1587
|
}
|
|
1192
|
-
const count = typeof
|
|
1193
|
-
const levels = Array.isArray(
|
|
1194
|
-
const cmap =
|
|
1195
|
-
const fixed =
|
|
1588
|
+
const count = typeof this.levelsOpt === "number" ? this.levelsOpt : 8;
|
|
1589
|
+
const levels = Array.isArray(this.levelsOpt) ? this.levelsOpt : Array.from({ length: count }, (_, i) => vmin + (vmax - vmin) * (i + 1) / (count + 1));
|
|
1590
|
+
const cmap = this.cmap;
|
|
1591
|
+
const fixed = this.fixedColor;
|
|
1196
1592
|
const gx = (c) => x0 + c / (cols - 1) * (x1 - x0) - this.xRef;
|
|
1197
1593
|
const gy = (r) => y0 + r / (rows - 1) * (y1 - y0) - this.yRef;
|
|
1198
1594
|
const at = (c, r) => values[r * cols + c];
|
|
@@ -1223,19 +1619,15 @@ var ContourLayer = class {
|
|
|
1223
1619
|
}
|
|
1224
1620
|
}
|
|
1225
1621
|
this.vertexCount = data.length / 6;
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
gl.
|
|
1234
|
-
gl.
|
|
1235
|
-
gl.enableVertexAttribArray(1);
|
|
1236
|
-
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 24, 8);
|
|
1237
|
-
gl.bindVertexArray(null);
|
|
1238
|
-
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
1622
|
+
return new Float32Array(data);
|
|
1623
|
+
}
|
|
1624
|
+
/** Replace the scalar field and recompute the iso lines (for streaming). */
|
|
1625
|
+
setData(values, cols = this.cols, rows = this.rows) {
|
|
1626
|
+
this.cols = cols;
|
|
1627
|
+
this.rows = rows;
|
|
1628
|
+
const data = this.build(values, cols, rows);
|
|
1629
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
1630
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.usage);
|
|
1239
1631
|
}
|
|
1240
1632
|
bounds() {
|
|
1241
1633
|
return { x: this.ext.x, y: this.ext.y };
|
|
@@ -1256,7 +1648,7 @@ var ContourLayer = class {
|
|
|
1256
1648
|
};
|
|
1257
1649
|
|
|
1258
1650
|
// src/layers/errorbar.ts
|
|
1259
|
-
var
|
|
1651
|
+
var SEG_VERT2 = (
|
|
1260
1652
|
/* glsl */
|
|
1261
1653
|
`#version 300 es
|
|
1262
1654
|
precision highp float;
|
|
@@ -1309,14 +1701,14 @@ uniform vec4 uColor;
|
|
|
1309
1701
|
out vec4 outColor;
|
|
1310
1702
|
void main() { outColor = vec4(uColor.rgb * uColor.a, uColor.a); }`
|
|
1311
1703
|
);
|
|
1312
|
-
var
|
|
1704
|
+
var SEG_CORNERS3 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
|
|
1313
1705
|
var QUAD_CORNERS = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
1314
1706
|
var cache2 = /* @__PURE__ */ new WeakMap();
|
|
1315
1707
|
function programs2(gl) {
|
|
1316
1708
|
let p = cache2.get(gl);
|
|
1317
1709
|
if (!p) {
|
|
1318
1710
|
p = {
|
|
1319
|
-
seg: createProgram(gl,
|
|
1711
|
+
seg: createProgram(gl, SEG_VERT2, SOLID_FRAG),
|
|
1320
1712
|
cap: createProgram(gl, CAP_VERT, SOLID_FRAG),
|
|
1321
1713
|
band: createProgram(gl, BAND_VERT, SOLID_FRAG)
|
|
1322
1714
|
};
|
|
@@ -1325,7 +1717,7 @@ function programs2(gl) {
|
|
|
1325
1717
|
return p;
|
|
1326
1718
|
}
|
|
1327
1719
|
var errAt = (e, i) => e == null ? 0 : typeof e === "number" ? e : e[i] ?? 0;
|
|
1328
|
-
var
|
|
1720
|
+
var counter7 = 0;
|
|
1329
1721
|
var ErrorBarLayer = class {
|
|
1330
1722
|
constructor(gl, opts) {
|
|
1331
1723
|
this.buffers = [];
|
|
@@ -1336,7 +1728,7 @@ var ErrorBarLayer = class {
|
|
|
1336
1728
|
this.yRef = 0;
|
|
1337
1729
|
this.xBounds = [0, 0];
|
|
1338
1730
|
this.yBounds = [0, 0];
|
|
1339
|
-
this.id = `errorbar-${
|
|
1731
|
+
this.id = `errorbar-${counter7++}`;
|
|
1340
1732
|
this.gl = gl;
|
|
1341
1733
|
this.progs = programs2(gl);
|
|
1342
1734
|
const colorInput = opts.color ?? "#3b82f6";
|
|
@@ -1349,62 +1741,23 @@ var ErrorBarLayer = class {
|
|
|
1349
1741
|
this.bandOpacity = opts.bandOpacity ?? 0.2;
|
|
1350
1742
|
this.showBand = opts.band === true;
|
|
1351
1743
|
this.showWhiskers = opts.whiskers ?? true;
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
this.yRef = n > 0 ? opts.y[0] : 0;
|
|
1355
|
-
const segs = [];
|
|
1356
|
-
const caps = [];
|
|
1357
|
-
const lowPts = [];
|
|
1358
|
-
const highPts = [];
|
|
1359
|
-
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1360
|
-
const hasY = opts.yerr != null || opts.yerrLow != null || opts.yerrHigh != null;
|
|
1361
|
-
const hasX = opts.xerr != null;
|
|
1362
|
-
for (let i = 0; i < n; i++) {
|
|
1363
|
-
const x = opts.x[i], y = opts.y[i];
|
|
1364
|
-
const eyLo = opts.yerrLow != null ? errAt(opts.yerrLow, i) : errAt(opts.yerr, i);
|
|
1365
|
-
const eyHi = opts.yerrHigh != null ? errAt(opts.yerrHigh, i) : errAt(opts.yerr, i);
|
|
1366
|
-
const ex = errAt(opts.xerr, i);
|
|
1367
|
-
const yLo = y - eyLo, yHi = y + eyHi;
|
|
1368
|
-
const xLo = x - ex, xHi = x + ex;
|
|
1369
|
-
if (hasY) {
|
|
1370
|
-
segs.push(x - this.xRef, yLo - this.yRef, x - this.xRef, yHi - this.yRef);
|
|
1371
|
-
caps.push(x - this.xRef, yLo - this.yRef, 0, x - this.xRef, yHi - this.yRef, 0);
|
|
1372
|
-
}
|
|
1373
|
-
if (hasX) {
|
|
1374
|
-
segs.push(xLo - this.xRef, y - this.yRef, xHi - this.xRef, y - this.yRef);
|
|
1375
|
-
caps.push(xLo - this.xRef, y - this.yRef, 1, xHi - this.xRef, y - this.yRef, 1);
|
|
1376
|
-
}
|
|
1377
|
-
lowPts.push([x - this.xRef, yLo - this.yRef]);
|
|
1378
|
-
highPts.push([x - this.xRef, yHi - this.yRef]);
|
|
1379
|
-
minX = Math.min(minX, xLo);
|
|
1380
|
-
maxX = Math.max(maxX, xHi);
|
|
1381
|
-
minY = Math.min(minY, yLo);
|
|
1382
|
-
maxY = Math.max(maxY, yHi);
|
|
1383
|
-
}
|
|
1384
|
-
this.xBounds = [minX, maxX];
|
|
1385
|
-
this.yBounds = [minY, maxY];
|
|
1386
|
-
this.segCount = segs.length / 4;
|
|
1387
|
-
this.capCount = this.capSize > 0 ? caps.length / 3 : 0;
|
|
1388
|
-
const band = [];
|
|
1389
|
-
for (let i = 0; i < n; i++) {
|
|
1390
|
-
band.push(highPts[i][0], highPts[i][1], lowPts[i][0], lowPts[i][1]);
|
|
1391
|
-
}
|
|
1392
|
-
this.bandVerts = n * 2;
|
|
1744
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
1745
|
+
const { segs, caps, band } = this.build(opts);
|
|
1393
1746
|
const cornerSeg = gl.createBuffer();
|
|
1394
1747
|
gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
|
|
1395
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
1748
|
+
gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS3, gl.STATIC_DRAW);
|
|
1396
1749
|
const cornerQuad = gl.createBuffer();
|
|
1397
1750
|
gl.bindBuffer(gl.ARRAY_BUFFER, cornerQuad);
|
|
1398
1751
|
gl.bufferData(gl.ARRAY_BUFFER, QUAD_CORNERS, gl.STATIC_DRAW);
|
|
1399
1752
|
const segBuf = gl.createBuffer();
|
|
1400
1753
|
gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
|
|
1401
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
1754
|
+
gl.bufferData(gl.ARRAY_BUFFER, segs, this.usage);
|
|
1402
1755
|
const capBuf = gl.createBuffer();
|
|
1403
1756
|
gl.bindBuffer(gl.ARRAY_BUFFER, capBuf);
|
|
1404
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
1757
|
+
gl.bufferData(gl.ARRAY_BUFFER, caps, this.usage);
|
|
1405
1758
|
const bandBuf = gl.createBuffer();
|
|
1406
1759
|
gl.bindBuffer(gl.ARRAY_BUFFER, bandBuf);
|
|
1407
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
1760
|
+
gl.bufferData(gl.ARRAY_BUFFER, band, this.usage);
|
|
1408
1761
|
this.buffers = [cornerSeg, cornerQuad, segBuf, capBuf, bandBuf];
|
|
1409
1762
|
this.segVao = gl.createVertexArray();
|
|
1410
1763
|
gl.bindVertexArray(this.segVao);
|
|
@@ -1434,6 +1787,62 @@ var ErrorBarLayer = class {
|
|
|
1434
1787
|
this.uCap = uniformLocations(gl, this.progs.cap, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uCapSize", "uWidth"]);
|
|
1435
1788
|
this.uBand = uniformLocations(gl, this.progs.band, [...TRANSFORM_UNIFORMS, "uColor"]);
|
|
1436
1789
|
}
|
|
1790
|
+
/** Recompute refs/bounds/counts and the whisker/cap/band vertex arrays from new data. */
|
|
1791
|
+
build(d) {
|
|
1792
|
+
const n = Math.min(d.x.length, d.y.length);
|
|
1793
|
+
this.xRef = n > 0 ? d.x[0] : 0;
|
|
1794
|
+
this.yRef = n > 0 ? d.y[0] : 0;
|
|
1795
|
+
const segs = [];
|
|
1796
|
+
const caps = [];
|
|
1797
|
+
const lowPts = [];
|
|
1798
|
+
const highPts = [];
|
|
1799
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1800
|
+
const hasY = d.yerr != null || d.yerrLow != null || d.yerrHigh != null;
|
|
1801
|
+
const hasX = d.xerr != null;
|
|
1802
|
+
for (let i = 0; i < n; i++) {
|
|
1803
|
+
const x = d.x[i], y = d.y[i];
|
|
1804
|
+
const eyLo = d.yerrLow != null ? errAt(d.yerrLow, i) : errAt(d.yerr, i);
|
|
1805
|
+
const eyHi = d.yerrHigh != null ? errAt(d.yerrHigh, i) : errAt(d.yerr, i);
|
|
1806
|
+
const ex = errAt(d.xerr, i);
|
|
1807
|
+
const yLo = y - eyLo, yHi = y + eyHi;
|
|
1808
|
+
const xLo = x - ex, xHi = x + ex;
|
|
1809
|
+
if (hasY) {
|
|
1810
|
+
segs.push(x - this.xRef, yLo - this.yRef, x - this.xRef, yHi - this.yRef);
|
|
1811
|
+
caps.push(x - this.xRef, yLo - this.yRef, 0, x - this.xRef, yHi - this.yRef, 0);
|
|
1812
|
+
}
|
|
1813
|
+
if (hasX) {
|
|
1814
|
+
segs.push(xLo - this.xRef, y - this.yRef, xHi - this.xRef, y - this.yRef);
|
|
1815
|
+
caps.push(xLo - this.xRef, y - this.yRef, 1, xHi - this.xRef, y - this.yRef, 1);
|
|
1816
|
+
}
|
|
1817
|
+
lowPts.push([x - this.xRef, yLo - this.yRef]);
|
|
1818
|
+
highPts.push([x - this.xRef, yHi - this.yRef]);
|
|
1819
|
+
minX = Math.min(minX, xLo);
|
|
1820
|
+
maxX = Math.max(maxX, xHi);
|
|
1821
|
+
minY = Math.min(minY, yLo);
|
|
1822
|
+
maxY = Math.max(maxY, yHi);
|
|
1823
|
+
}
|
|
1824
|
+
this.xBounds = [minX, maxX];
|
|
1825
|
+
this.yBounds = [minY, maxY];
|
|
1826
|
+
this.segCount = segs.length / 4;
|
|
1827
|
+
this.capCount = this.capSize > 0 ? caps.length / 3 : 0;
|
|
1828
|
+
const band = [];
|
|
1829
|
+
for (let i = 0; i < n; i++) {
|
|
1830
|
+
band.push(highPts[i][0], highPts[i][1], lowPts[i][0], lowPts[i][1]);
|
|
1831
|
+
}
|
|
1832
|
+
this.bandVerts = n * 2;
|
|
1833
|
+
return { segs: new Float32Array(segs), caps: new Float32Array(caps), band: new Float32Array(band) };
|
|
1834
|
+
}
|
|
1835
|
+
/** Replace the data and re-upload (for streaming). */
|
|
1836
|
+
setData(data) {
|
|
1837
|
+
const gl = this.gl;
|
|
1838
|
+
const { segs, caps, band } = this.build(data);
|
|
1839
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
1840
|
+
gl.bufferData(gl.ARRAY_BUFFER, segs, this.usage);
|
|
1841
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[3]);
|
|
1842
|
+
gl.bufferData(gl.ARRAY_BUFFER, caps, this.usage);
|
|
1843
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[4]);
|
|
1844
|
+
gl.bufferData(gl.ARRAY_BUFFER, band, this.usage);
|
|
1845
|
+
}
|
|
1437
1846
|
bounds() {
|
|
1438
1847
|
if (this.segCount === 0 && this.bandVerts === 0) return null;
|
|
1439
1848
|
return { x: this.xBounds, y: this.yBounds };
|
|
@@ -1479,6 +1888,64 @@ var ErrorBarLayer = class {
|
|
|
1479
1888
|
}
|
|
1480
1889
|
};
|
|
1481
1890
|
|
|
1891
|
+
// src/graph/force.ts
|
|
1892
|
+
function forceLayout(nodeCount, edges, opts = {}) {
|
|
1893
|
+
const n = nodeCount;
|
|
1894
|
+
const x = new Float64Array(n);
|
|
1895
|
+
const y = new Float64Array(n);
|
|
1896
|
+
for (let i = 0; i < n; i++) {
|
|
1897
|
+
const a = i / n * Math.PI * 2;
|
|
1898
|
+
x[i] = Math.cos(a);
|
|
1899
|
+
y[i] = Math.sin(a);
|
|
1900
|
+
}
|
|
1901
|
+
if (n < 2) return { x, y };
|
|
1902
|
+
const iterations = opts.iterations ?? 300;
|
|
1903
|
+
const area2 = opts.area ?? 1;
|
|
1904
|
+
const k = Math.sqrt(area2 / n);
|
|
1905
|
+
const gravity = opts.gravity ?? 0.05;
|
|
1906
|
+
const dispX = new Float64Array(n);
|
|
1907
|
+
const dispY = new Float64Array(n);
|
|
1908
|
+
let temp = 0.1;
|
|
1909
|
+
for (let it = 0; it < iterations; it++) {
|
|
1910
|
+
dispX.fill(0);
|
|
1911
|
+
dispY.fill(0);
|
|
1912
|
+
for (let i = 0; i < n; i++) {
|
|
1913
|
+
for (let j = i + 1; j < n; j++) {
|
|
1914
|
+
const dx = x[i] - x[j];
|
|
1915
|
+
const dy = y[i] - y[j];
|
|
1916
|
+
const d = Math.hypot(dx, dy) || 1e-6;
|
|
1917
|
+
const f = k * k / d;
|
|
1918
|
+
const ux = dx / d, uy = dy / d;
|
|
1919
|
+
dispX[i] += ux * f;
|
|
1920
|
+
dispY[i] += uy * f;
|
|
1921
|
+
dispX[j] -= ux * f;
|
|
1922
|
+
dispY[j] -= uy * f;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
for (const [a, b] of edges) {
|
|
1926
|
+
const dx = x[a] - x[b];
|
|
1927
|
+
const dy = y[a] - y[b];
|
|
1928
|
+
const d = Math.hypot(dx, dy) || 1e-6;
|
|
1929
|
+
const f = d * d / k;
|
|
1930
|
+
const ux = dx / d, uy = dy / d;
|
|
1931
|
+
dispX[a] -= ux * f;
|
|
1932
|
+
dispY[a] -= uy * f;
|
|
1933
|
+
dispX[b] += ux * f;
|
|
1934
|
+
dispY[b] += uy * f;
|
|
1935
|
+
}
|
|
1936
|
+
for (let i = 0; i < n; i++) {
|
|
1937
|
+
dispX[i] -= x[i] * gravity;
|
|
1938
|
+
dispY[i] -= y[i] * gravity;
|
|
1939
|
+
const d = Math.hypot(dispX[i], dispY[i]) || 1e-6;
|
|
1940
|
+
const lim = Math.min(d, temp);
|
|
1941
|
+
x[i] += dispX[i] / d * lim;
|
|
1942
|
+
y[i] += dispY[i] / d * lim;
|
|
1943
|
+
}
|
|
1944
|
+
temp *= 0.99;
|
|
1945
|
+
}
|
|
1946
|
+
return { x, y };
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1482
1949
|
// src/layers/graph.ts
|
|
1483
1950
|
var EDGE_VERT = (
|
|
1484
1951
|
/* glsl */
|
|
@@ -1522,26 +1989,29 @@ void main() {
|
|
|
1522
1989
|
outColor = vec4(uColor.rgb * uColor.a * a, uColor.a * a);
|
|
1523
1990
|
}`
|
|
1524
1991
|
);
|
|
1525
|
-
var
|
|
1992
|
+
var programCache6 = /* @__PURE__ */ new WeakMap();
|
|
1526
1993
|
function getPrograms(gl) {
|
|
1527
|
-
let p =
|
|
1994
|
+
let p = programCache6.get(gl);
|
|
1528
1995
|
if (!p) {
|
|
1529
1996
|
p = { edge: createProgram(gl, EDGE_VERT, SOLID_FRAG2), node: createProgram(gl, NODE_VERT, NODE_FRAG) };
|
|
1530
|
-
|
|
1997
|
+
programCache6.set(gl, p);
|
|
1531
1998
|
}
|
|
1532
1999
|
return p;
|
|
1533
2000
|
}
|
|
1534
|
-
var
|
|
2001
|
+
var counter8 = 0;
|
|
1535
2002
|
var GraphLayer = class {
|
|
1536
2003
|
constructor(gl, opts) {
|
|
1537
2004
|
this.buffers = [];
|
|
2005
|
+
this.nodeCount = 0;
|
|
2006
|
+
this.edgeVerts = 0;
|
|
1538
2007
|
this.xRef = 0;
|
|
1539
2008
|
this.yRef = 0;
|
|
1540
2009
|
this.xBounds = [0, 0];
|
|
1541
2010
|
this.yBounds = [0, 0];
|
|
1542
|
-
this.id = `graph-${
|
|
2011
|
+
this.id = `graph-${counter8++}`;
|
|
1543
2012
|
this.gl = gl;
|
|
1544
2013
|
this.progs = getPrograms(gl);
|
|
2014
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
1545
2015
|
this.name = opts.name ?? this.id;
|
|
1546
2016
|
this.yAxis = opts.yAxis ?? "y";
|
|
1547
2017
|
const nc = opts.nodeColor ?? "#60a5fa";
|
|
@@ -1550,14 +2020,36 @@ var GraphLayer = class {
|
|
|
1550
2020
|
const ec = opts.edgeColor ?? "rgba(148,163,184,0.5)";
|
|
1551
2021
|
this.edgeColor = Array.isArray(ec) ? ec : parseColor(ec);
|
|
1552
2022
|
this.nodeSize = opts.nodeSize ?? 10;
|
|
1553
|
-
const
|
|
2023
|
+
const { nodePos, edgePos } = this.build(opts.x, opts.y, opts.edges);
|
|
2024
|
+
const nodeBuf = gl.createBuffer();
|
|
2025
|
+
const edgeBuf = gl.createBuffer();
|
|
2026
|
+
this.buffers = [nodeBuf, edgeBuf];
|
|
2027
|
+
this.nodeVao = gl.createVertexArray();
|
|
2028
|
+
gl.bindVertexArray(this.nodeVao);
|
|
2029
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, nodeBuf);
|
|
2030
|
+
gl.bufferData(gl.ARRAY_BUFFER, nodePos, this.usage);
|
|
2031
|
+
gl.enableVertexAttribArray(0);
|
|
2032
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
2033
|
+
this.edgeVao = gl.createVertexArray();
|
|
2034
|
+
gl.bindVertexArray(this.edgeVao);
|
|
2035
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, edgeBuf);
|
|
2036
|
+
gl.bufferData(gl.ARRAY_BUFFER, edgePos, this.usage);
|
|
2037
|
+
gl.enableVertexAttribArray(0);
|
|
2038
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
2039
|
+
gl.bindVertexArray(null);
|
|
2040
|
+
this.edgeUniforms = uniformLocations(gl, this.progs.edge, [...TRANSFORM_UNIFORMS, "uColor"]);
|
|
2041
|
+
this.nodeUniforms = uniformLocations(gl, this.progs.node, [...TRANSFORM_UNIFORMS, "uColor", "uSize"]);
|
|
2042
|
+
}
|
|
2043
|
+
/** Build node/edge vertex arrays from positions + edges; sets counts, refs and bounds. */
|
|
2044
|
+
build(x, y, edges) {
|
|
2045
|
+
const n = Math.min(x.length, y.length);
|
|
1554
2046
|
this.nodeCount = n;
|
|
1555
|
-
this.xRef = n > 0 ?
|
|
1556
|
-
this.yRef = n > 0 ?
|
|
2047
|
+
this.xRef = n > 0 ? x[0] : 0;
|
|
2048
|
+
this.yRef = n > 0 ? y[0] : 0;
|
|
1557
2049
|
const nodePos = new Float32Array(n * 2);
|
|
1558
2050
|
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1559
2051
|
for (let i = 0; i < n; i++) {
|
|
1560
|
-
const px =
|
|
2052
|
+
const px = x[i], py = y[i];
|
|
1561
2053
|
nodePos[i * 2] = px - this.xRef;
|
|
1562
2054
|
nodePos[i * 2 + 1] = py - this.yRef;
|
|
1563
2055
|
if (px < minX) minX = px;
|
|
@@ -1568,29 +2060,34 @@ var GraphLayer = class {
|
|
|
1568
2060
|
this.xBounds = [minX, maxX];
|
|
1569
2061
|
this.yBounds = [minY, maxY];
|
|
1570
2062
|
const edgePos = [];
|
|
1571
|
-
for (const [a, b] of
|
|
2063
|
+
for (const [a, b] of edges) {
|
|
1572
2064
|
if (a < 0 || b < 0 || a >= n || b >= n) continue;
|
|
1573
|
-
edgePos.push(
|
|
2065
|
+
edgePos.push(x[a] - this.xRef, y[a] - this.yRef, x[b] - this.xRef, y[b] - this.yRef);
|
|
1574
2066
|
}
|
|
1575
2067
|
this.edgeVerts = edgePos.length / 2;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
2068
|
+
return { nodePos, edgePos: new Float32Array(edgePos) };
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Replace nodes and edges (for streaming). When `x`/`y` are omitted, node
|
|
2072
|
+
* positions are recomputed with the package's force layout from the edges.
|
|
2073
|
+
*/
|
|
2074
|
+
setData(data) {
|
|
2075
|
+
let x = data.x, y = data.y;
|
|
2076
|
+
if (x == null || y == null) {
|
|
2077
|
+
let nc = data.nodeCount ?? 0;
|
|
2078
|
+
if (data.nodeCount == null) {
|
|
2079
|
+
for (const [a, b] of data.edges) nc = Math.max(nc, a + 1, b + 1);
|
|
2080
|
+
}
|
|
2081
|
+
const laid = forceLayout(nc, data.edges);
|
|
2082
|
+
x = laid.x;
|
|
2083
|
+
y = laid.y;
|
|
2084
|
+
}
|
|
2085
|
+
const { nodePos, edgePos } = this.build(x, y, data.edges);
|
|
2086
|
+
const gl = this.gl;
|
|
2087
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[0]);
|
|
2088
|
+
gl.bufferData(gl.ARRAY_BUFFER, nodePos, this.usage);
|
|
2089
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
2090
|
+
gl.bufferData(gl.ARRAY_BUFFER, edgePos, this.usage);
|
|
1594
2091
|
}
|
|
1595
2092
|
bounds() {
|
|
1596
2093
|
if (this.nodeCount === 0) return null;
|
|
@@ -1635,7 +2132,7 @@ void main() {
|
|
|
1635
2132
|
gl_Position = vec4(dataToClip(aPos), 0.0, 1.0);
|
|
1636
2133
|
}`
|
|
1637
2134
|
);
|
|
1638
|
-
var
|
|
2135
|
+
var FRAG7 = (
|
|
1639
2136
|
/* glsl */
|
|
1640
2137
|
`#version 300 es
|
|
1641
2138
|
precision highp float;
|
|
@@ -1647,60 +2144,41 @@ void main() {
|
|
|
1647
2144
|
outColor = vec4(c.rgb * c.a, c.a);
|
|
1648
2145
|
}`
|
|
1649
2146
|
);
|
|
1650
|
-
var
|
|
1651
|
-
function
|
|
1652
|
-
let p =
|
|
2147
|
+
var programCache7 = /* @__PURE__ */ new WeakMap();
|
|
2148
|
+
function getProgram6(gl) {
|
|
2149
|
+
let p = programCache7.get(gl);
|
|
1653
2150
|
if (!p) {
|
|
1654
|
-
p = createProgram(gl, VERT5,
|
|
1655
|
-
|
|
2151
|
+
p = createProgram(gl, VERT5, FRAG7);
|
|
2152
|
+
programCache7.set(gl, p);
|
|
1656
2153
|
}
|
|
1657
2154
|
return p;
|
|
1658
2155
|
}
|
|
1659
|
-
var
|
|
2156
|
+
var counter9 = 0;
|
|
1660
2157
|
var HeatmapLayer = class {
|
|
1661
2158
|
constructor(gl, opts) {
|
|
1662
|
-
this.id = `heatmap-${
|
|
2159
|
+
this.id = `heatmap-${counter9++}`;
|
|
1663
2160
|
this.gl = gl;
|
|
1664
|
-
this.program =
|
|
2161
|
+
this.program = getProgram6(gl);
|
|
1665
2162
|
this.yAxis = opts.yAxis ?? "y";
|
|
1666
2163
|
this.ext = opts.extent;
|
|
1667
2164
|
const [x0, x1] = opts.extent.x;
|
|
1668
2165
|
const [y0, y1] = opts.extent.y;
|
|
1669
2166
|
this.xRef = x0;
|
|
1670
2167
|
this.yRef = y0;
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
if (!opts.domain) {
|
|
1676
|
-
for (let i = 0; i < values.length; i++) {
|
|
1677
|
-
const v = values[i];
|
|
1678
|
-
if (v < lo) lo = v;
|
|
1679
|
-
if (v > hi) hi = v;
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
const span = hi - lo || 1;
|
|
1683
|
-
const pixels = new Uint8Array(cols * rows * 4);
|
|
1684
|
-
for (let i = 0; i < cols * rows; i++) {
|
|
1685
|
-
let t = (values[i] - lo) / span;
|
|
1686
|
-
t = t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
1687
|
-
const j = (t * 255 | 0) * 3;
|
|
1688
|
-
pixels[i * 4] = lut[j] * 255 + 0.5 | 0;
|
|
1689
|
-
pixels[i * 4 + 1] = lut[j + 1] * 255 + 0.5 | 0;
|
|
1690
|
-
pixels[i * 4 + 2] = lut[j + 2] * 255 + 0.5 | 0;
|
|
1691
|
-
pixels[i * 4 + 3] = 255;
|
|
1692
|
-
}
|
|
2168
|
+
this.lut = colormapLUT(opts.colormap ?? "viridis");
|
|
2169
|
+
this.fixedDomain = opts.domain;
|
|
2170
|
+
this.cols = opts.cols;
|
|
2171
|
+
this.rows = opts.rows;
|
|
1693
2172
|
const texture = gl.createTexture();
|
|
1694
2173
|
this.texture = texture;
|
|
1695
2174
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
1696
|
-
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
1697
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, cols, rows, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
1698
2175
|
const filter = opts.smooth === false ? gl.NEAREST : gl.LINEAR;
|
|
1699
2176
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
1700
2177
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
1701
2178
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1702
2179
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1703
2180
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2181
|
+
this.uploadValues(opts.values, opts.cols, opts.rows);
|
|
1704
2182
|
const data = new Float32Array([
|
|
1705
2183
|
x0 - this.xRef,
|
|
1706
2184
|
y0 - this.yRef,
|
|
@@ -1741,6 +2219,41 @@ var HeatmapLayer = class {
|
|
|
1741
2219
|
gl.bindVertexArray(null);
|
|
1742
2220
|
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uTex"]);
|
|
1743
2221
|
}
|
|
2222
|
+
/** Bake a `cols*rows` value grid into the RGBA data texture via the colormap. */
|
|
2223
|
+
uploadValues(values, cols, rows) {
|
|
2224
|
+
const gl = this.gl;
|
|
2225
|
+
const lut = this.lut;
|
|
2226
|
+
let lo = this.fixedDomain?.[0] ?? Infinity;
|
|
2227
|
+
let hi = this.fixedDomain?.[1] ?? -Infinity;
|
|
2228
|
+
if (!this.fixedDomain) {
|
|
2229
|
+
for (let i = 0; i < values.length; i++) {
|
|
2230
|
+
const v = values[i];
|
|
2231
|
+
if (v < lo) lo = v;
|
|
2232
|
+
if (v > hi) hi = v;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
const span = hi - lo || 1;
|
|
2236
|
+
const pixels = new Uint8Array(cols * rows * 4);
|
|
2237
|
+
for (let i = 0; i < cols * rows; i++) {
|
|
2238
|
+
let t = (values[i] - lo) / span;
|
|
2239
|
+
t = t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
2240
|
+
const j = (t * 255 | 0) * 3;
|
|
2241
|
+
pixels[i * 4] = lut[j] * 255 + 0.5 | 0;
|
|
2242
|
+
pixels[i * 4 + 1] = lut[j + 1] * 255 + 0.5 | 0;
|
|
2243
|
+
pixels[i * 4 + 2] = lut[j + 2] * 255 + 0.5 | 0;
|
|
2244
|
+
pixels[i * 4 + 3] = 255;
|
|
2245
|
+
}
|
|
2246
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2247
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
2248
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, cols, rows, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
2249
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2250
|
+
}
|
|
2251
|
+
/** Replace the value grid and re-bake the data texture (for streaming). */
|
|
2252
|
+
setData(values, cols = this.cols, rows = this.rows) {
|
|
2253
|
+
this.cols = cols;
|
|
2254
|
+
this.rows = rows;
|
|
2255
|
+
this.uploadValues(values, cols, rows);
|
|
2256
|
+
}
|
|
1744
2257
|
bounds() {
|
|
1745
2258
|
return { x: this.ext.x, y: this.ext.y };
|
|
1746
2259
|
}
|
|
@@ -1778,7 +2291,7 @@ void main() {
|
|
|
1778
2291
|
gl_Position = vec4(dataToClip(aCenter + aCorner * uRadius), 0.0, 1.0);
|
|
1779
2292
|
}`
|
|
1780
2293
|
);
|
|
1781
|
-
var
|
|
2294
|
+
var FRAG8 = (
|
|
1782
2295
|
/* glsl */
|
|
1783
2296
|
`#version 300 es
|
|
1784
2297
|
precision highp float;
|
|
@@ -1797,46 +2310,79 @@ var HEX = (() => {
|
|
|
1797
2310
|
}
|
|
1798
2311
|
return new Float32Array(verts);
|
|
1799
2312
|
})();
|
|
1800
|
-
var
|
|
1801
|
-
function
|
|
1802
|
-
let p =
|
|
2313
|
+
var programCache8 = /* @__PURE__ */ new WeakMap();
|
|
2314
|
+
function getProgram7(gl) {
|
|
2315
|
+
let p = programCache8.get(gl);
|
|
1803
2316
|
if (!p) {
|
|
1804
|
-
p = createProgram(gl, VERT6,
|
|
1805
|
-
|
|
2317
|
+
p = createProgram(gl, VERT6, FRAG8);
|
|
2318
|
+
programCache8.set(gl, p);
|
|
1806
2319
|
}
|
|
1807
2320
|
return p;
|
|
1808
2321
|
}
|
|
1809
|
-
var
|
|
2322
|
+
var counter10 = 0;
|
|
1810
2323
|
var HexbinLayer = class {
|
|
1811
2324
|
constructor(gl, opts) {
|
|
1812
2325
|
this.buffers = [];
|
|
2326
|
+
this.radius = 1;
|
|
2327
|
+
this.cellCount = 0;
|
|
1813
2328
|
this.xRef = 0;
|
|
1814
2329
|
this.yRef = 0;
|
|
1815
2330
|
this.xBounds = [0, 0];
|
|
1816
2331
|
this.yBounds = [0, 0];
|
|
1817
|
-
this.id = `hexbin-${
|
|
2332
|
+
this.id = `hexbin-${counter10++}`;
|
|
1818
2333
|
this.gl = gl;
|
|
1819
|
-
this.program =
|
|
2334
|
+
this.program = getProgram7(gl);
|
|
2335
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
1820
2336
|
this.yAxis = opts.yAxis ?? "y";
|
|
1821
|
-
|
|
2337
|
+
this.cmap = colormap(opts.colormap ?? "viridis");
|
|
2338
|
+
this.explicitRadius = opts.radius;
|
|
2339
|
+
this.domain = opts.domain;
|
|
2340
|
+
const { centers, colors } = this.build(opts.x, opts.y);
|
|
2341
|
+
const vao = gl.createVertexArray();
|
|
2342
|
+
this.vao = vao;
|
|
2343
|
+
gl.bindVertexArray(vao);
|
|
2344
|
+
const hexBuf = gl.createBuffer();
|
|
2345
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, hexBuf);
|
|
2346
|
+
gl.bufferData(gl.ARRAY_BUFFER, HEX, gl.STATIC_DRAW);
|
|
2347
|
+
gl.enableVertexAttribArray(0);
|
|
2348
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
2349
|
+
const centerBuf = gl.createBuffer();
|
|
2350
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, centerBuf);
|
|
2351
|
+
gl.bufferData(gl.ARRAY_BUFFER, centers, this.usage);
|
|
2352
|
+
gl.enableVertexAttribArray(1);
|
|
2353
|
+
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
|
|
2354
|
+
gl.vertexAttribDivisor(1, 1);
|
|
2355
|
+
const colorBuf = gl.createBuffer();
|
|
2356
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
2357
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
2358
|
+
gl.enableVertexAttribArray(2);
|
|
2359
|
+
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
|
|
2360
|
+
gl.vertexAttribDivisor(2, 1);
|
|
2361
|
+
gl.bindVertexArray(null);
|
|
2362
|
+
this.buffers = [hexBuf, centerBuf, colorBuf];
|
|
2363
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uRadius"]);
|
|
2364
|
+
}
|
|
2365
|
+
/** Bin the points into hex cells; sets radius, refs, bounds and cell count. */
|
|
2366
|
+
build(x, y) {
|
|
2367
|
+
const n = Math.min(x.length, y.length);
|
|
1822
2368
|
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1823
2369
|
for (let i = 0; i < n; i++) {
|
|
1824
|
-
const
|
|
1825
|
-
if (
|
|
1826
|
-
if (
|
|
1827
|
-
if (
|
|
1828
|
-
if (
|
|
2370
|
+
const px = x[i], py = y[i];
|
|
2371
|
+
if (px < minX) minX = px;
|
|
2372
|
+
if (px > maxX) maxX = px;
|
|
2373
|
+
if (py < minY) minY = py;
|
|
2374
|
+
if (py > maxY) maxY = py;
|
|
1829
2375
|
}
|
|
1830
2376
|
this.xBounds = [minX, maxX];
|
|
1831
2377
|
this.yBounds = [minY, maxY];
|
|
1832
|
-
const r =
|
|
2378
|
+
const r = this.explicitRadius ?? ((maxX - minX) / 30 || 1);
|
|
1833
2379
|
this.radius = r;
|
|
1834
2380
|
const dx = r * 2 * Math.sin(Math.PI / 3);
|
|
1835
2381
|
const dy = r * 1.5;
|
|
1836
2382
|
const cells = /* @__PURE__ */ new Map();
|
|
1837
2383
|
let maxCount = 1;
|
|
1838
2384
|
for (let i = 0; i < n; i++) {
|
|
1839
|
-
const px =
|
|
2385
|
+
const px = x[i], py = y[i];
|
|
1840
2386
|
const pj = Math.round(py / dy);
|
|
1841
2387
|
const pi = Math.round(px / dx - (pj & 1) / 2);
|
|
1842
2388
|
const key = `${pi},${pj}`;
|
|
@@ -1853,44 +2399,30 @@ var HexbinLayer = class {
|
|
|
1853
2399
|
this.yRef = minY;
|
|
1854
2400
|
const centers = new Float32Array(this.cellCount * 2);
|
|
1855
2401
|
const colors = new Float32Array(this.cellCount * 4);
|
|
1856
|
-
const
|
|
1857
|
-
const
|
|
1858
|
-
const hi = opts.domain?.[1] ?? maxCount;
|
|
2402
|
+
const lo = this.domain?.[0] ?? 1;
|
|
2403
|
+
const hi = this.domain?.[1] ?? maxCount;
|
|
1859
2404
|
const span = hi - lo || 1;
|
|
1860
2405
|
let k = 0;
|
|
1861
2406
|
for (const cell of cells.values()) {
|
|
1862
2407
|
centers[k * 2] = cell.cx - this.xRef;
|
|
1863
2408
|
centers[k * 2 + 1] = cell.cy - this.yRef;
|
|
1864
|
-
const [cr, cg, cb] = cmap((cell.count - lo) / span);
|
|
2409
|
+
const [cr, cg, cb] = this.cmap((cell.count - lo) / span);
|
|
1865
2410
|
colors[k * 4] = cr;
|
|
1866
2411
|
colors[k * 4 + 1] = cg;
|
|
1867
2412
|
colors[k * 4 + 2] = cb;
|
|
1868
2413
|
colors[k * 4 + 3] = 1;
|
|
1869
2414
|
k++;
|
|
1870
2415
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
gl
|
|
1877
|
-
gl.
|
|
1878
|
-
gl.
|
|
1879
|
-
|
|
1880
|
-
gl.
|
|
1881
|
-
gl.bufferData(gl.ARRAY_BUFFER, centers, gl.STATIC_DRAW);
|
|
1882
|
-
gl.enableVertexAttribArray(1);
|
|
1883
|
-
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
|
|
1884
|
-
gl.vertexAttribDivisor(1, 1);
|
|
1885
|
-
const colorBuf = gl.createBuffer();
|
|
1886
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
1887
|
-
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
|
|
1888
|
-
gl.enableVertexAttribArray(2);
|
|
1889
|
-
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
|
|
1890
|
-
gl.vertexAttribDivisor(2, 1);
|
|
1891
|
-
gl.bindVertexArray(null);
|
|
1892
|
-
this.buffers = [hexBuf, centerBuf, colorBuf];
|
|
1893
|
-
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uRadius"]);
|
|
2416
|
+
return { centers, colors };
|
|
2417
|
+
}
|
|
2418
|
+
/** Replace the point arrays, re-bin and re-upload the cells (for streaming). */
|
|
2419
|
+
setData(x, y) {
|
|
2420
|
+
const { centers, colors } = this.build(x, y);
|
|
2421
|
+
const gl = this.gl;
|
|
2422
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
2423
|
+
gl.bufferData(gl.ARRAY_BUFFER, centers, this.usage);
|
|
2424
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
2425
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
1894
2426
|
}
|
|
1895
2427
|
bounds() {
|
|
1896
2428
|
if (this.cellCount === 0) return null;
|
|
@@ -1926,7 +2458,7 @@ void main() {
|
|
|
1926
2458
|
gl_Position = vec4(dataToClip(aPos), 0.0, 1.0);
|
|
1927
2459
|
}`
|
|
1928
2460
|
);
|
|
1929
|
-
var
|
|
2461
|
+
var FRAG9 = (
|
|
1930
2462
|
/* glsl */
|
|
1931
2463
|
`#version 300 es
|
|
1932
2464
|
precision highp float;
|
|
@@ -1940,24 +2472,24 @@ void main() {
|
|
|
1940
2472
|
outColor = vec4(c.rgb * a, a);
|
|
1941
2473
|
}`
|
|
1942
2474
|
);
|
|
1943
|
-
var
|
|
1944
|
-
function
|
|
1945
|
-
let p =
|
|
2475
|
+
var programCache9 = /* @__PURE__ */ new WeakMap();
|
|
2476
|
+
function getProgram8(gl) {
|
|
2477
|
+
let p = programCache9.get(gl);
|
|
1946
2478
|
if (!p) {
|
|
1947
|
-
p = createProgram(gl, VERT7,
|
|
1948
|
-
|
|
2479
|
+
p = createProgram(gl, VERT7, FRAG9);
|
|
2480
|
+
programCache9.set(gl, p);
|
|
1949
2481
|
}
|
|
1950
2482
|
return p;
|
|
1951
2483
|
}
|
|
1952
|
-
var
|
|
2484
|
+
var counter11 = 0;
|
|
1953
2485
|
var ImageLayer = class {
|
|
1954
2486
|
constructor(gl, opts) {
|
|
1955
2487
|
this.colorCss = "#94a3b8";
|
|
1956
2488
|
this.ready = false;
|
|
1957
2489
|
this.img = null;
|
|
1958
|
-
this.id = `image-${
|
|
2490
|
+
this.id = `image-${counter11++}`;
|
|
1959
2491
|
this.gl = gl;
|
|
1960
|
-
this.program =
|
|
2492
|
+
this.program = getProgram8(gl);
|
|
1961
2493
|
this.name = opts.name ?? this.id;
|
|
1962
2494
|
this.yAxis = opts.yAxis ?? "y";
|
|
1963
2495
|
this.ext = opts.extent;
|
|
@@ -1971,18 +2503,7 @@ var ImageLayer = class {
|
|
|
1971
2503
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
1972
2504
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));
|
|
1973
2505
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
1974
|
-
|
|
1975
|
-
const img = new Image();
|
|
1976
|
-
this.img = img;
|
|
1977
|
-
img.crossOrigin = "anonymous";
|
|
1978
|
-
img.onload = () => {
|
|
1979
|
-
this.upload(img);
|
|
1980
|
-
opts.onLoad?.();
|
|
1981
|
-
};
|
|
1982
|
-
img.src = opts.source;
|
|
1983
|
-
} else {
|
|
1984
|
-
this.upload(opts.source);
|
|
1985
|
-
}
|
|
2506
|
+
this.setSource(opts.source, opts.onLoad);
|
|
1986
2507
|
const data = new Float32Array([
|
|
1987
2508
|
x0 - this.xRef,
|
|
1988
2509
|
y0 - this.yRef,
|
|
@@ -2023,6 +2544,27 @@ var ImageLayer = class {
|
|
|
2023
2544
|
gl.bindVertexArray(null);
|
|
2024
2545
|
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS, "uTex", "uOpacity"]);
|
|
2025
2546
|
}
|
|
2547
|
+
/** Point the texture at a new source: load a URL async, or upload a bitmap now. */
|
|
2548
|
+
setSource(source, onLoad) {
|
|
2549
|
+
if (typeof source === "string") {
|
|
2550
|
+
if (this.img) this.img.onload = null;
|
|
2551
|
+
const img = new Image();
|
|
2552
|
+
this.img = img;
|
|
2553
|
+
img.crossOrigin = "anonymous";
|
|
2554
|
+
img.onload = () => {
|
|
2555
|
+
this.upload(img);
|
|
2556
|
+
onLoad?.();
|
|
2557
|
+
};
|
|
2558
|
+
img.src = source;
|
|
2559
|
+
} else {
|
|
2560
|
+
this.img = null;
|
|
2561
|
+
this.upload(source);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
/** Replace the image source and re-upload the texture (for streaming). */
|
|
2565
|
+
setData(source, onLoad) {
|
|
2566
|
+
this.setSource(source, onLoad);
|
|
2567
|
+
}
|
|
2026
2568
|
upload(src) {
|
|
2027
2569
|
const gl = this.gl;
|
|
2028
2570
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
@@ -2154,9 +2696,9 @@ ${log}`);
|
|
|
2154
2696
|
}
|
|
2155
2697
|
return sh;
|
|
2156
2698
|
}
|
|
2157
|
-
var
|
|
2699
|
+
var programCache10 = /* @__PURE__ */ new WeakMap();
|
|
2158
2700
|
function getDecProgram(gl) {
|
|
2159
|
-
if (
|
|
2701
|
+
if (programCache10.has(gl)) return programCache10.get(gl);
|
|
2160
2702
|
let result = null;
|
|
2161
2703
|
try {
|
|
2162
2704
|
const vs = compile(gl, gl.VERTEX_SHADER, DEC_VERT);
|
|
@@ -2180,7 +2722,7 @@ function getDecProgram(gl) {
|
|
|
2180
2722
|
} catch {
|
|
2181
2723
|
result = null;
|
|
2182
2724
|
}
|
|
2183
|
-
|
|
2725
|
+
programCache10.set(gl, result);
|
|
2184
2726
|
return result;
|
|
2185
2727
|
}
|
|
2186
2728
|
var GpuDecimator = class {
|
|
@@ -2327,7 +2869,7 @@ void main() {
|
|
|
2327
2869
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
2328
2870
|
}`
|
|
2329
2871
|
);
|
|
2330
|
-
var
|
|
2872
|
+
var FRAG10 = (
|
|
2331
2873
|
/* glsl */
|
|
2332
2874
|
`#version 300 es
|
|
2333
2875
|
precision highp float;
|
|
@@ -2447,12 +2989,12 @@ void main() {
|
|
|
2447
2989
|
}`
|
|
2448
2990
|
);
|
|
2449
2991
|
var CORNERS2 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
|
|
2450
|
-
var
|
|
2451
|
-
function
|
|
2452
|
-
let p =
|
|
2992
|
+
var programCache11 = /* @__PURE__ */ new WeakMap();
|
|
2993
|
+
function getProgram9(gl) {
|
|
2994
|
+
let p = programCache11.get(gl);
|
|
2453
2995
|
if (!p) {
|
|
2454
|
-
p = createProgram(gl, VERT8,
|
|
2455
|
-
|
|
2996
|
+
p = createProgram(gl, VERT8, FRAG10);
|
|
2997
|
+
programCache11.set(gl, p);
|
|
2456
2998
|
}
|
|
2457
2999
|
return p;
|
|
2458
3000
|
}
|
|
@@ -2494,7 +3036,7 @@ function upperBound(a, v) {
|
|
|
2494
3036
|
}
|
|
2495
3037
|
return lo;
|
|
2496
3038
|
}
|
|
2497
|
-
var
|
|
3039
|
+
var counter12 = 0;
|
|
2498
3040
|
var LineLayer = class {
|
|
2499
3041
|
constructor(gl, opts) {
|
|
2500
3042
|
this.gpuDec = null;
|
|
@@ -2504,9 +3046,9 @@ var LineLayer = class {
|
|
|
2504
3046
|
this.yBounds = [0, 0];
|
|
2505
3047
|
this.decKey = "";
|
|
2506
3048
|
this.decSegments = 0;
|
|
2507
|
-
this.id = `line-${
|
|
3049
|
+
this.id = `line-${counter12++}`;
|
|
2508
3050
|
this.gl = gl;
|
|
2509
|
-
this.program =
|
|
3051
|
+
this.program = getProgram9(gl);
|
|
2510
3052
|
this.joinProgram = getJoinProgram(gl);
|
|
2511
3053
|
this.width = opts.width ?? 1.5;
|
|
2512
3054
|
this.joinStyle = opts.join ?? "round";
|
|
@@ -2519,6 +3061,7 @@ var LineLayer = class {
|
|
|
2519
3061
|
this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
|
|
2520
3062
|
this.name = opts.name ?? this.id;
|
|
2521
3063
|
this.yAxis = opts.yAxis ?? "y";
|
|
3064
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
2522
3065
|
let n = Math.min(opts.x.length, opts.y.length);
|
|
2523
3066
|
let xs = opts.x, ys = opts.y;
|
|
2524
3067
|
if (opts.step && n >= 2) {
|
|
@@ -2559,7 +3102,7 @@ var LineLayer = class {
|
|
|
2559
3102
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.cornerBuf);
|
|
2560
3103
|
gl.bufferData(gl.ARRAY_BUFFER, CORNERS2, gl.STATIC_DRAW);
|
|
2561
3104
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuf);
|
|
2562
|
-
gl.bufferData(gl.ARRAY_BUFFER, data,
|
|
3105
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
|
|
2563
3106
|
this.configureVao(this.fullVao, this.posBuf);
|
|
2564
3107
|
this.configureVao(this.decVao, this.decBuf);
|
|
2565
3108
|
this.configureJoinVao(this.joinFullVao, this.posBuf);
|
|
@@ -2670,7 +3213,7 @@ var LineLayer = class {
|
|
|
2670
3213
|
this.monotonic = mono;
|
|
2671
3214
|
this.decKey = "";
|
|
2672
3215
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.posBuf);
|
|
2673
|
-
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.
|
|
3216
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.usage);
|
|
2674
3217
|
this.syncGpu(data, n);
|
|
2675
3218
|
}
|
|
2676
3219
|
/**
|
|
@@ -2755,64 +3298,6 @@ var LineLayer = class {
|
|
|
2755
3298
|
}
|
|
2756
3299
|
};
|
|
2757
3300
|
|
|
2758
|
-
// src/graph/force.ts
|
|
2759
|
-
function forceLayout(nodeCount, edges, opts = {}) {
|
|
2760
|
-
const n = nodeCount;
|
|
2761
|
-
const x = new Float64Array(n);
|
|
2762
|
-
const y = new Float64Array(n);
|
|
2763
|
-
for (let i = 0; i < n; i++) {
|
|
2764
|
-
const a = i / n * Math.PI * 2;
|
|
2765
|
-
x[i] = Math.cos(a);
|
|
2766
|
-
y[i] = Math.sin(a);
|
|
2767
|
-
}
|
|
2768
|
-
if (n < 2) return { x, y };
|
|
2769
|
-
const iterations = opts.iterations ?? 300;
|
|
2770
|
-
const area2 = opts.area ?? 1;
|
|
2771
|
-
const k = Math.sqrt(area2 / n);
|
|
2772
|
-
const gravity = opts.gravity ?? 0.05;
|
|
2773
|
-
const dispX = new Float64Array(n);
|
|
2774
|
-
const dispY = new Float64Array(n);
|
|
2775
|
-
let temp = 0.1;
|
|
2776
|
-
for (let it = 0; it < iterations; it++) {
|
|
2777
|
-
dispX.fill(0);
|
|
2778
|
-
dispY.fill(0);
|
|
2779
|
-
for (let i = 0; i < n; i++) {
|
|
2780
|
-
for (let j = i + 1; j < n; j++) {
|
|
2781
|
-
const dx = x[i] - x[j];
|
|
2782
|
-
const dy = y[i] - y[j];
|
|
2783
|
-
const d = Math.hypot(dx, dy) || 1e-6;
|
|
2784
|
-
const f = k * k / d;
|
|
2785
|
-
const ux = dx / d, uy = dy / d;
|
|
2786
|
-
dispX[i] += ux * f;
|
|
2787
|
-
dispY[i] += uy * f;
|
|
2788
|
-
dispX[j] -= ux * f;
|
|
2789
|
-
dispY[j] -= uy * f;
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
for (const [a, b] of edges) {
|
|
2793
|
-
const dx = x[a] - x[b];
|
|
2794
|
-
const dy = y[a] - y[b];
|
|
2795
|
-
const d = Math.hypot(dx, dy) || 1e-6;
|
|
2796
|
-
const f = d * d / k;
|
|
2797
|
-
const ux = dx / d, uy = dy / d;
|
|
2798
|
-
dispX[a] -= ux * f;
|
|
2799
|
-
dispY[a] -= uy * f;
|
|
2800
|
-
dispX[b] += ux * f;
|
|
2801
|
-
dispY[b] += uy * f;
|
|
2802
|
-
}
|
|
2803
|
-
for (let i = 0; i < n; i++) {
|
|
2804
|
-
dispX[i] -= x[i] * gravity;
|
|
2805
|
-
dispY[i] -= y[i] * gravity;
|
|
2806
|
-
const d = Math.hypot(dispX[i], dispY[i]) || 1e-6;
|
|
2807
|
-
const lim = Math.min(d, temp);
|
|
2808
|
-
x[i] += dispX[i] / d * lim;
|
|
2809
|
-
y[i] += dispY[i] / d * lim;
|
|
2810
|
-
}
|
|
2811
|
-
temp *= 0.99;
|
|
2812
|
-
}
|
|
2813
|
-
return { x, y };
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
3301
|
// src/geo/earcut.ts
|
|
2817
3302
|
var Z_ORDER_THRESHOLD = 80;
|
|
2818
3303
|
var Node = class {
|
|
@@ -3256,16 +3741,16 @@ in vec4 vColor;
|
|
|
3256
3741
|
out vec4 outColor;
|
|
3257
3742
|
void main() { outColor = vec4(vColor.rgb * vColor.a, vColor.a); }`
|
|
3258
3743
|
);
|
|
3259
|
-
var
|
|
3744
|
+
var programCache12 = /* @__PURE__ */ new WeakMap();
|
|
3260
3745
|
function getFillProgram(gl) {
|
|
3261
|
-
let p =
|
|
3746
|
+
let p = programCache12.get(gl);
|
|
3262
3747
|
if (!p) {
|
|
3263
3748
|
p = createProgram(gl, FILL_VERT, FILL_FRAG);
|
|
3264
|
-
|
|
3749
|
+
programCache12.set(gl, p);
|
|
3265
3750
|
}
|
|
3266
3751
|
return p;
|
|
3267
3752
|
}
|
|
3268
|
-
var
|
|
3753
|
+
var counter13 = 0;
|
|
3269
3754
|
var PatchesLayer = class {
|
|
3270
3755
|
constructor(gl, opts) {
|
|
3271
3756
|
this.buffers = [];
|
|
@@ -3274,20 +3759,43 @@ var PatchesLayer = class {
|
|
|
3274
3759
|
this.yRef = 0;
|
|
3275
3760
|
this.xBounds = [0, 0];
|
|
3276
3761
|
this.yBounds = [0, 0];
|
|
3277
|
-
this.id = `patches-${
|
|
3762
|
+
this.id = `patches-${counter13++}`;
|
|
3278
3763
|
this.gl = gl;
|
|
3279
3764
|
this.program = getFillProgram(gl);
|
|
3280
3765
|
this.name = opts.name ?? this.id;
|
|
3281
3766
|
this.yAxis = opts.yAxis ?? "y";
|
|
3282
3767
|
const defColor = opts.color ?? "#3b82f6";
|
|
3283
|
-
|
|
3768
|
+
this.defRgba = Array.isArray(defColor) ? defColor : parseColor(defColor);
|
|
3284
3769
|
this.colorCss = typeof defColor === "string" ? defColor : "#3b82f6";
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3770
|
+
this.opacity = opts.opacity ?? 1;
|
|
3771
|
+
this.cmap = opts.colormap ? colormap(opts.colormap) : null;
|
|
3772
|
+
this.domainOpt = opts.domain;
|
|
3773
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
3774
|
+
const { positions, colors } = this.build(opts.patches);
|
|
3775
|
+
const vao = gl.createVertexArray();
|
|
3776
|
+
this.vao = vao;
|
|
3777
|
+
gl.bindVertexArray(vao);
|
|
3778
|
+
const posBuf = gl.createBuffer();
|
|
3779
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
3780
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, this.usage);
|
|
3781
|
+
gl.enableVertexAttribArray(0);
|
|
3782
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
3783
|
+
const colBuf = gl.createBuffer();
|
|
3784
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
|
|
3785
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
3786
|
+
gl.enableVertexAttribArray(1);
|
|
3787
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
3788
|
+
gl.bindVertexArray(null);
|
|
3789
|
+
this.buffers = [posBuf, colBuf];
|
|
3790
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
3791
|
+
}
|
|
3792
|
+
/** Triangulate the patches and recompute refs/bounds/vertexCount into position/color arrays. */
|
|
3793
|
+
build(patches) {
|
|
3794
|
+
const defRgba = this.defRgba, opacity = this.opacity, cmap = this.cmap;
|
|
3795
|
+
let lo = this.domainOpt?.[0] ?? Infinity;
|
|
3796
|
+
let hi = this.domainOpt?.[1] ?? -Infinity;
|
|
3797
|
+
if (cmap && !this.domainOpt) {
|
|
3798
|
+
for (const patch of patches) {
|
|
3291
3799
|
const v = patch.value;
|
|
3292
3800
|
if (v == null) continue;
|
|
3293
3801
|
if (v < lo) lo = v;
|
|
@@ -3295,7 +3803,9 @@ var PatchesLayer = class {
|
|
|
3295
3803
|
}
|
|
3296
3804
|
}
|
|
3297
3805
|
const span = hi - lo || 1;
|
|
3298
|
-
|
|
3806
|
+
this.xRef = 0;
|
|
3807
|
+
this.yRef = 0;
|
|
3808
|
+
for (const patch of patches) {
|
|
3299
3809
|
if (patch.x.length > 0) {
|
|
3300
3810
|
this.xRef = patch.x[0];
|
|
3301
3811
|
this.yRef = patch.y[0];
|
|
@@ -3305,7 +3815,7 @@ var PatchesLayer = class {
|
|
|
3305
3815
|
const positions = [];
|
|
3306
3816
|
const colors = [];
|
|
3307
3817
|
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
3308
|
-
for (const patch of
|
|
3818
|
+
for (const patch of patches) {
|
|
3309
3819
|
const n = Math.min(patch.x.length, patch.y.length);
|
|
3310
3820
|
if (n < 3) continue;
|
|
3311
3821
|
let rgba;
|
|
@@ -3341,22 +3851,16 @@ var PatchesLayer = class {
|
|
|
3341
3851
|
this.vertexCount = positions.length / 2;
|
|
3342
3852
|
this.xBounds = minX <= maxX ? [minX, maxX] : [0, 0];
|
|
3343
3853
|
this.yBounds = minY <= maxY ? [minY, maxY] : [0, 0];
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
gl.
|
|
3349
|
-
|
|
3350
|
-
gl.
|
|
3351
|
-
gl.
|
|
3352
|
-
|
|
3353
|
-
gl.
|
|
3354
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
|
|
3355
|
-
gl.enableVertexAttribArray(1);
|
|
3356
|
-
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
3357
|
-
gl.bindVertexArray(null);
|
|
3358
|
-
this.buffers = [posBuf, colBuf];
|
|
3359
|
-
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
3854
|
+
return { positions: new Float32Array(positions), colors: new Float32Array(colors) };
|
|
3855
|
+
}
|
|
3856
|
+
/** Replace the patches and re-upload (for streaming). */
|
|
3857
|
+
setData(patches) {
|
|
3858
|
+
const gl = this.gl;
|
|
3859
|
+
const { positions, colors } = this.build(patches);
|
|
3860
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[0]);
|
|
3861
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, this.usage);
|
|
3862
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
3863
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
3360
3864
|
}
|
|
3361
3865
|
bounds() {
|
|
3362
3866
|
if (this.vertexCount === 0) return null;
|
|
@@ -3390,33 +3894,59 @@ var DEFAULT_COLORS = [
|
|
|
3390
3894
|
"#60a5fa",
|
|
3391
3895
|
"#f59e0b"
|
|
3392
3896
|
];
|
|
3393
|
-
var
|
|
3897
|
+
var counter14 = 0;
|
|
3394
3898
|
var PieLayer = class {
|
|
3395
3899
|
constructor(gl, opts) {
|
|
3396
3900
|
this.buffers = [];
|
|
3397
3901
|
this.vertexCount = 0;
|
|
3398
|
-
this.id = `pie-${
|
|
3902
|
+
this.id = `pie-${counter14++}`;
|
|
3399
3903
|
this.gl = gl;
|
|
3400
3904
|
this.program = getFillProgram(gl);
|
|
3401
3905
|
this.name = opts.name ?? this.id;
|
|
3402
3906
|
this.yAxis = opts.yAxis ?? "y";
|
|
3403
3907
|
const [cx, cy] = opts.center ?? [0, 0];
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3908
|
+
this.cx = cx;
|
|
3909
|
+
this.cy = cy;
|
|
3910
|
+
this.R = opts.radius ?? 1;
|
|
3911
|
+
this.rIn = opts.innerRadius ?? 0;
|
|
3912
|
+
this.start = opts.startAngle ?? Math.PI / 2;
|
|
3407
3913
|
this.xRef = cx;
|
|
3408
3914
|
this.yRef = cy;
|
|
3409
|
-
this.xBounds = [cx - R, cx + R];
|
|
3410
|
-
this.yBounds = [cy - R, cy + R];
|
|
3915
|
+
this.xBounds = [cx - this.R, cx + this.R];
|
|
3916
|
+
this.yBounds = [cy - this.R, cy + this.R];
|
|
3411
3917
|
this.colorCss = "#3b82f6";
|
|
3412
|
-
|
|
3918
|
+
this.colorsOpt = opts.colors;
|
|
3919
|
+
this.cmap = opts.colormap ? colormap(opts.colormap) : null;
|
|
3920
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
3921
|
+
const { positions, colors } = this.build(opts.values);
|
|
3922
|
+
const vao = gl.createVertexArray();
|
|
3923
|
+
this.vao = vao;
|
|
3924
|
+
gl.bindVertexArray(vao);
|
|
3925
|
+
const posBuf = gl.createBuffer();
|
|
3926
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
3927
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, this.usage);
|
|
3928
|
+
gl.enableVertexAttribArray(0);
|
|
3929
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
3930
|
+
const colBuf = gl.createBuffer();
|
|
3931
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
|
|
3932
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
3933
|
+
gl.enableVertexAttribArray(1);
|
|
3934
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
3935
|
+
gl.bindVertexArray(null);
|
|
3936
|
+
this.buffers = [posBuf, colBuf];
|
|
3937
|
+
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
3938
|
+
}
|
|
3939
|
+
/** Recompute vertexCount and the position/color triangle soup from new slice values. */
|
|
3940
|
+
build(values) {
|
|
3941
|
+
const { cx, cy, R, rIn, start } = this;
|
|
3942
|
+
const n = values.length;
|
|
3413
3943
|
let total = 0;
|
|
3414
|
-
for (let i = 0; i < n; i++) total += Math.max(0,
|
|
3944
|
+
for (let i = 0; i < n; i++) total += Math.max(0, values[i]);
|
|
3415
3945
|
if (total <= 0) total = 1;
|
|
3416
|
-
const cmap =
|
|
3946
|
+
const cmap = this.cmap;
|
|
3417
3947
|
const colorAt = (i) => {
|
|
3418
|
-
if (
|
|
3419
|
-
const c =
|
|
3948
|
+
if (this.colorsOpt?.[i] != null) {
|
|
3949
|
+
const c = this.colorsOpt[i];
|
|
3420
3950
|
return Array.isArray(c) ? c : parseColor(c);
|
|
3421
3951
|
}
|
|
3422
3952
|
if (cmap) {
|
|
@@ -3433,7 +3963,7 @@ var PieLayer = class {
|
|
|
3433
3963
|
};
|
|
3434
3964
|
let a0 = start;
|
|
3435
3965
|
for (let i = 0; i < n; i++) {
|
|
3436
|
-
const frac = Math.max(0,
|
|
3966
|
+
const frac = Math.max(0, values[i]) / total;
|
|
3437
3967
|
const span = frac * Math.PI * 2;
|
|
3438
3968
|
if (span <= 0) continue;
|
|
3439
3969
|
const a1 = a0 - span;
|
|
@@ -3462,22 +3992,16 @@ var PieLayer = class {
|
|
|
3462
3992
|
a0 = a1;
|
|
3463
3993
|
}
|
|
3464
3994
|
this.vertexCount = positions.length / 2;
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
gl.
|
|
3470
|
-
|
|
3471
|
-
gl.
|
|
3472
|
-
gl.
|
|
3473
|
-
|
|
3474
|
-
gl.
|
|
3475
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
|
|
3476
|
-
gl.enableVertexAttribArray(1);
|
|
3477
|
-
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
|
|
3478
|
-
gl.bindVertexArray(null);
|
|
3479
|
-
this.buffers = [posBuf, colBuf];
|
|
3480
|
-
this.uniforms = uniformLocations(gl, this.program, [...TRANSFORM_UNIFORMS]);
|
|
3995
|
+
return { positions: new Float32Array(positions), colors: new Float32Array(colors) };
|
|
3996
|
+
}
|
|
3997
|
+
/** Replace the slice values and re-upload (for streaming). */
|
|
3998
|
+
setData(values) {
|
|
3999
|
+
const gl = this.gl;
|
|
4000
|
+
const { positions, colors } = this.build(values);
|
|
4001
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[0]);
|
|
4002
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, this.usage);
|
|
4003
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
4004
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
3481
4005
|
}
|
|
3482
4006
|
bounds() {
|
|
3483
4007
|
if (this.vertexCount === 0) return null;
|
|
@@ -3548,7 +4072,7 @@ void main() {
|
|
|
3548
4072
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
3549
4073
|
}`
|
|
3550
4074
|
);
|
|
3551
|
-
var
|
|
4075
|
+
var FRAG11 = (
|
|
3552
4076
|
/* glsl */
|
|
3553
4077
|
`#version 300 es
|
|
3554
4078
|
precision highp float;
|
|
@@ -3561,17 +4085,17 @@ void main() {
|
|
|
3561
4085
|
outColor = vec4(c.rgb * c.a, c.a);
|
|
3562
4086
|
}`
|
|
3563
4087
|
);
|
|
3564
|
-
var
|
|
4088
|
+
var SEG_CORNERS4 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
|
|
3565
4089
|
var cache3 = /* @__PURE__ */ new WeakMap();
|
|
3566
4090
|
function programs3(gl) {
|
|
3567
4091
|
let p = cache3.get(gl);
|
|
3568
4092
|
if (!p) {
|
|
3569
|
-
p = { shaft: createProgram(gl, SHAFT_VERT,
|
|
4093
|
+
p = { shaft: createProgram(gl, SHAFT_VERT, FRAG11), head: createProgram(gl, HEAD_VERT, FRAG11) };
|
|
3570
4094
|
cache3.set(gl, p);
|
|
3571
4095
|
}
|
|
3572
4096
|
return p;
|
|
3573
4097
|
}
|
|
3574
|
-
var
|
|
4098
|
+
var counter15 = 0;
|
|
3575
4099
|
var QuiverLayer = class {
|
|
3576
4100
|
constructor(gl, opts) {
|
|
3577
4101
|
this.buffers = [];
|
|
@@ -3579,7 +4103,7 @@ var QuiverLayer = class {
|
|
|
3579
4103
|
this.yRef = 0;
|
|
3580
4104
|
this.xBounds = [0, 0];
|
|
3581
4105
|
this.yBounds = [0, 0];
|
|
3582
|
-
this.id = `quiver-${
|
|
4106
|
+
this.id = `quiver-${counter15++}`;
|
|
3583
4107
|
this.gl = gl;
|
|
3584
4108
|
this.progs = programs3(gl);
|
|
3585
4109
|
const colorInput = opts.color ?? "#3b82f6";
|
|
@@ -3590,71 +4114,19 @@ var QuiverLayer = class {
|
|
|
3590
4114
|
this.width = opts.width ?? 1.5;
|
|
3591
4115
|
this.headSize = opts.headSize ?? 9;
|
|
3592
4116
|
this.useVertexColor = opts.colorBy != null;
|
|
3593
|
-
|
|
3594
|
-
this.
|
|
3595
|
-
this.
|
|
3596
|
-
|
|
3597
|
-
let maxMag = 0;
|
|
3598
|
-
for (let i = 0; i < n; i++) maxMag = Math.max(maxMag, Math.hypot(opts.u[i], opts.v[i]));
|
|
3599
|
-
let scale = opts.scale;
|
|
3600
|
-
if (scale == null) {
|
|
3601
|
-
let minX2 = Infinity, maxX2 = -Infinity, minY2 = Infinity, maxY2 = -Infinity;
|
|
3602
|
-
for (let i = 0; i < n; i++) {
|
|
3603
|
-
minX2 = Math.min(minX2, opts.x[i]);
|
|
3604
|
-
maxX2 = Math.max(maxX2, opts.x[i]);
|
|
3605
|
-
minY2 = Math.min(minY2, opts.y[i]);
|
|
3606
|
-
maxY2 = Math.max(maxY2, opts.y[i]);
|
|
3607
|
-
}
|
|
3608
|
-
const diag = Math.hypot(maxX2 - minX2, maxY2 - minY2) || 1;
|
|
3609
|
-
const cell = diag / Math.max(1, Math.sqrt(n));
|
|
3610
|
-
scale = maxMag > 0 ? 0.9 * cell / maxMag : 1;
|
|
3611
|
-
}
|
|
3612
|
-
const arrows = new Float32Array(n * 4);
|
|
3613
|
-
const colors = new Float32Array(n * 4);
|
|
3614
|
-
const cmap = colormap(opts.colorBy?.colormap ?? "viridis");
|
|
3615
|
-
const vals = opts.colorBy?.values;
|
|
3616
|
-
let lo = opts.colorBy?.domain?.[0] ?? Infinity;
|
|
3617
|
-
let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
|
|
3618
|
-
if (this.useVertexColor && !opts.colorBy?.domain) {
|
|
3619
|
-
for (let i = 0; i < n; i++) {
|
|
3620
|
-
const v = vals ? vals[i] : Math.hypot(opts.u[i], opts.v[i]);
|
|
3621
|
-
lo = Math.min(lo, v);
|
|
3622
|
-
hi = Math.max(hi, v);
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
const span = hi - lo || 1;
|
|
3626
|
-
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
3627
|
-
for (let i = 0; i < n; i++) {
|
|
3628
|
-
const x = opts.x[i], y = opts.y[i];
|
|
3629
|
-
const tx = x + opts.u[i] * scale, ty = y + opts.v[i] * scale;
|
|
3630
|
-
arrows[i * 4] = x - this.xRef;
|
|
3631
|
-
arrows[i * 4 + 1] = y - this.yRef;
|
|
3632
|
-
arrows[i * 4 + 2] = tx - this.xRef;
|
|
3633
|
-
arrows[i * 4 + 3] = ty - this.yRef;
|
|
3634
|
-
if (this.useVertexColor) {
|
|
3635
|
-
const v = vals ? vals[i] : Math.hypot(opts.u[i], opts.v[i]);
|
|
3636
|
-
const [r, g, b] = cmap((v - lo) / span);
|
|
3637
|
-
colors[i * 4] = r;
|
|
3638
|
-
colors[i * 4 + 1] = g;
|
|
3639
|
-
colors[i * 4 + 2] = b;
|
|
3640
|
-
colors[i * 4 + 3] = 1;
|
|
3641
|
-
}
|
|
3642
|
-
minX = Math.min(minX, x, tx);
|
|
3643
|
-
maxX = Math.max(maxX, x, tx);
|
|
3644
|
-
minY = Math.min(minY, y, ty);
|
|
3645
|
-
maxY = Math.max(maxY, y, ty);
|
|
3646
|
-
}
|
|
3647
|
-
this.xBounds = [minX, maxX];
|
|
3648
|
-
this.yBounds = [minY, maxY];
|
|
4117
|
+
this.explicitScale = opts.scale;
|
|
4118
|
+
this.colorBy = opts.colorBy;
|
|
4119
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
4120
|
+
const { arrows, colors } = this.build(opts.x, opts.y, opts.u, opts.v);
|
|
3649
4121
|
const cornerBuf = gl.createBuffer();
|
|
3650
4122
|
gl.bindBuffer(gl.ARRAY_BUFFER, cornerBuf);
|
|
3651
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
4123
|
+
gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS4, gl.STATIC_DRAW);
|
|
3652
4124
|
const arrowBuf = gl.createBuffer();
|
|
3653
4125
|
gl.bindBuffer(gl.ARRAY_BUFFER, arrowBuf);
|
|
3654
|
-
gl.bufferData(gl.ARRAY_BUFFER, arrows,
|
|
4126
|
+
gl.bufferData(gl.ARRAY_BUFFER, arrows, this.usage);
|
|
3655
4127
|
const colorBuf = gl.createBuffer();
|
|
3656
4128
|
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
3657
|
-
gl.bufferData(gl.ARRAY_BUFFER, colors,
|
|
4129
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
3658
4130
|
this.buffers = [cornerBuf, arrowBuf, colorBuf];
|
|
3659
4131
|
this.shaftVao = gl.createVertexArray();
|
|
3660
4132
|
gl.bindVertexArray(this.shaftVao);
|
|
@@ -3693,6 +4165,75 @@ var QuiverLayer = class {
|
|
|
3693
4165
|
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
|
|
3694
4166
|
gl.vertexAttribDivisor(2, 1);
|
|
3695
4167
|
}
|
|
4168
|
+
/** Recompute count/refs/bounds and the arrow+color arrays from new x/y/u/v. */
|
|
4169
|
+
build(x, y, u, v) {
|
|
4170
|
+
const n = Math.min(x.length, y.length, u.length, v.length);
|
|
4171
|
+
this.count = n;
|
|
4172
|
+
this.xRef = n > 0 ? x[0] : 0;
|
|
4173
|
+
this.yRef = n > 0 ? y[0] : 0;
|
|
4174
|
+
let maxMag = 0;
|
|
4175
|
+
for (let i = 0; i < n; i++) maxMag = Math.max(maxMag, Math.hypot(u[i], v[i]));
|
|
4176
|
+
let scale = this.explicitScale;
|
|
4177
|
+
if (scale == null) {
|
|
4178
|
+
let minX2 = Infinity, maxX2 = -Infinity, minY2 = Infinity, maxY2 = -Infinity;
|
|
4179
|
+
for (let i = 0; i < n; i++) {
|
|
4180
|
+
minX2 = Math.min(minX2, x[i]);
|
|
4181
|
+
maxX2 = Math.max(maxX2, x[i]);
|
|
4182
|
+
minY2 = Math.min(minY2, y[i]);
|
|
4183
|
+
maxY2 = Math.max(maxY2, y[i]);
|
|
4184
|
+
}
|
|
4185
|
+
const diag = Math.hypot(maxX2 - minX2, maxY2 - minY2) || 1;
|
|
4186
|
+
const cell = diag / Math.max(1, Math.sqrt(n));
|
|
4187
|
+
scale = maxMag > 0 ? 0.9 * cell / maxMag : 1;
|
|
4188
|
+
}
|
|
4189
|
+
const arrows = new Float32Array(n * 4);
|
|
4190
|
+
const colors = new Float32Array(n * 4);
|
|
4191
|
+
const cmap = colormap(this.colorBy?.colormap ?? "viridis");
|
|
4192
|
+
const vals = this.colorBy?.values;
|
|
4193
|
+
let lo = this.colorBy?.domain?.[0] ?? Infinity;
|
|
4194
|
+
let hi = this.colorBy?.domain?.[1] ?? -Infinity;
|
|
4195
|
+
if (this.useVertexColor && !this.colorBy?.domain) {
|
|
4196
|
+
for (let i = 0; i < n; i++) {
|
|
4197
|
+
const val = vals ? vals[i] : Math.hypot(u[i], v[i]);
|
|
4198
|
+
lo = Math.min(lo, val);
|
|
4199
|
+
hi = Math.max(hi, val);
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
const span = hi - lo || 1;
|
|
4203
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
4204
|
+
for (let i = 0; i < n; i++) {
|
|
4205
|
+
const xi = x[i], yi = y[i];
|
|
4206
|
+
const tx = xi + u[i] * scale, ty = yi + v[i] * scale;
|
|
4207
|
+
arrows[i * 4] = xi - this.xRef;
|
|
4208
|
+
arrows[i * 4 + 1] = yi - this.yRef;
|
|
4209
|
+
arrows[i * 4 + 2] = tx - this.xRef;
|
|
4210
|
+
arrows[i * 4 + 3] = ty - this.yRef;
|
|
4211
|
+
if (this.useVertexColor) {
|
|
4212
|
+
const val = vals ? vals[i] : Math.hypot(u[i], v[i]);
|
|
4213
|
+
const [r, g, b] = cmap((val - lo) / span);
|
|
4214
|
+
colors[i * 4] = r;
|
|
4215
|
+
colors[i * 4 + 1] = g;
|
|
4216
|
+
colors[i * 4 + 2] = b;
|
|
4217
|
+
colors[i * 4 + 3] = 1;
|
|
4218
|
+
}
|
|
4219
|
+
minX = Math.min(minX, xi, tx);
|
|
4220
|
+
maxX = Math.max(maxX, xi, tx);
|
|
4221
|
+
minY = Math.min(minY, yi, ty);
|
|
4222
|
+
maxY = Math.max(maxY, yi, ty);
|
|
4223
|
+
}
|
|
4224
|
+
this.xBounds = [minX, maxX];
|
|
4225
|
+
this.yBounds = [minY, maxY];
|
|
4226
|
+
return { arrows, colors };
|
|
4227
|
+
}
|
|
4228
|
+
/** Replace the data and re-upload (for streaming). */
|
|
4229
|
+
setData(x, y, u, v) {
|
|
4230
|
+
const gl = this.gl;
|
|
4231
|
+
const { arrows, colors } = this.build(x, y, u, v);
|
|
4232
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[1]);
|
|
4233
|
+
gl.bufferData(gl.ARRAY_BUFFER, arrows, this.usage);
|
|
4234
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
4235
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
4236
|
+
}
|
|
3696
4237
|
bounds() {
|
|
3697
4238
|
if (this.count === 0) return null;
|
|
3698
4239
|
return { x: this.xBounds, y: this.yBounds };
|
|
@@ -3757,7 +4298,7 @@ void main() {
|
|
|
3757
4298
|
gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
|
|
3758
4299
|
}`
|
|
3759
4300
|
);
|
|
3760
|
-
var
|
|
4301
|
+
var FRAG12 = (
|
|
3761
4302
|
/* glsl */
|
|
3762
4303
|
`#version 300 es
|
|
3763
4304
|
precision highp float;
|
|
@@ -3811,16 +4352,16 @@ void main() {
|
|
|
3811
4352
|
}`
|
|
3812
4353
|
);
|
|
3813
4354
|
var CORNERS3 = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
3814
|
-
var
|
|
3815
|
-
function
|
|
3816
|
-
let p =
|
|
4355
|
+
var programCache13 = /* @__PURE__ */ new WeakMap();
|
|
4356
|
+
function getProgram10(gl) {
|
|
4357
|
+
let p = programCache13.get(gl);
|
|
3817
4358
|
if (!p) {
|
|
3818
|
-
p = createProgram(gl, VERT9,
|
|
3819
|
-
|
|
4359
|
+
p = createProgram(gl, VERT9, FRAG12);
|
|
4360
|
+
programCache13.set(gl, p);
|
|
3820
4361
|
}
|
|
3821
4362
|
return p;
|
|
3822
4363
|
}
|
|
3823
|
-
var
|
|
4364
|
+
var counter16 = 0;
|
|
3824
4365
|
var ScatterLayer = class {
|
|
3825
4366
|
constructor(gl, opts) {
|
|
3826
4367
|
this.buffers = [];
|
|
@@ -3828,9 +4369,9 @@ var ScatterLayer = class {
|
|
|
3828
4369
|
this.yRef = 0;
|
|
3829
4370
|
this.xBounds = [0, 0];
|
|
3830
4371
|
this.yBounds = [0, 0];
|
|
3831
|
-
this.id = `scatter-${
|
|
4372
|
+
this.id = `scatter-${counter16++}`;
|
|
3832
4373
|
this.gl = gl;
|
|
3833
|
-
this.program =
|
|
4374
|
+
this.program = getProgram10(gl);
|
|
3834
4375
|
this.size = opts.size ?? 5;
|
|
3835
4376
|
this.marker = MARKERS[opts.marker ?? "circle"];
|
|
3836
4377
|
const colorInput = opts.color ?? "#3b82f6";
|
|
@@ -3838,6 +4379,7 @@ var ScatterLayer = class {
|
|
|
3838
4379
|
this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
|
|
3839
4380
|
this.name = opts.name ?? this.id;
|
|
3840
4381
|
this.yAxis = opts.yAxis ?? "y";
|
|
4382
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
3841
4383
|
this.useVertexColor = opts.colorBy != null;
|
|
3842
4384
|
this.labels = opts.labels;
|
|
3843
4385
|
const n = Math.min(opts.x.length, opts.y.length);
|
|
@@ -3895,13 +4437,13 @@ var ScatterLayer = class {
|
|
|
3895
4437
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
3896
4438
|
const posBuf = gl.createBuffer();
|
|
3897
4439
|
gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
|
|
3898
|
-
gl.bufferData(gl.ARRAY_BUFFER, pos,
|
|
4440
|
+
gl.bufferData(gl.ARRAY_BUFFER, pos, this.usage);
|
|
3899
4441
|
gl.enableVertexAttribArray(1);
|
|
3900
4442
|
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
|
|
3901
4443
|
gl.vertexAttribDivisor(1, 1);
|
|
3902
4444
|
const colorBuf = gl.createBuffer();
|
|
3903
4445
|
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
|
|
3904
|
-
gl.bufferData(gl.ARRAY_BUFFER, colors,
|
|
4446
|
+
gl.bufferData(gl.ARRAY_BUFFER, colors, this.usage);
|
|
3905
4447
|
gl.enableVertexAttribArray(2);
|
|
3906
4448
|
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
|
|
3907
4449
|
gl.vertexAttribDivisor(2, 1);
|
|
@@ -3954,7 +4496,7 @@ var ScatterLayer = class {
|
|
|
3954
4496
|
this.yBounds = [minY, maxY];
|
|
3955
4497
|
this.useVertexColor = false;
|
|
3956
4498
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers[1]);
|
|
3957
|
-
this.gl.bufferData(this.gl.ARRAY_BUFFER, pos, this.
|
|
4499
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, pos, this.usage);
|
|
3958
4500
|
}
|
|
3959
4501
|
draw(state) {
|
|
3960
4502
|
if (this.count === 0) return;
|
|
@@ -4036,7 +4578,7 @@ void main() {
|
|
|
4036
4578
|
outColor = vec4(uColor.rgb * uColor.a * alpha, uColor.a * alpha);
|
|
4037
4579
|
}`
|
|
4038
4580
|
);
|
|
4039
|
-
var
|
|
4581
|
+
var SEG_CORNERS5 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
|
|
4040
4582
|
var QUAD_CORNERS2 = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
|
|
4041
4583
|
var cache4 = /* @__PURE__ */ new WeakMap();
|
|
4042
4584
|
function programs4(gl) {
|
|
@@ -4047,7 +4589,7 @@ function programs4(gl) {
|
|
|
4047
4589
|
}
|
|
4048
4590
|
return p;
|
|
4049
4591
|
}
|
|
4050
|
-
var
|
|
4592
|
+
var counter17 = 0;
|
|
4051
4593
|
var StemLayer = class {
|
|
4052
4594
|
constructor(gl, opts) {
|
|
4053
4595
|
this.buffers = [];
|
|
@@ -4055,7 +4597,7 @@ var StemLayer = class {
|
|
|
4055
4597
|
this.yRef = 0;
|
|
4056
4598
|
this.xBounds = [0, 0];
|
|
4057
4599
|
this.yBounds = [0, 0];
|
|
4058
|
-
this.id = `stem-${
|
|
4600
|
+
this.id = `stem-${counter17++}`;
|
|
4059
4601
|
this.gl = gl;
|
|
4060
4602
|
this.progs = programs4(gl);
|
|
4061
4603
|
const colorInput = opts.color ?? "#3b82f6";
|
|
@@ -4066,45 +4608,20 @@ var StemLayer = class {
|
|
|
4066
4608
|
this.width = opts.width ?? 1.5;
|
|
4067
4609
|
this.markerSize = opts.markerSize ?? 6;
|
|
4068
4610
|
this.baseline = opts.baseline ?? 0;
|
|
4069
|
-
|
|
4070
|
-
this.
|
|
4071
|
-
this.xs = new Float64Array(n);
|
|
4072
|
-
this.ys = new Float64Array(n);
|
|
4073
|
-
this.xRef = n > 0 ? opts.x[0] : 0;
|
|
4074
|
-
this.yRef = n > 0 ? opts.y[0] : 0;
|
|
4075
|
-
const segs = new Float32Array(n * 4);
|
|
4076
|
-
const tips = new Float32Array(n * 2);
|
|
4077
|
-
let minX = Infinity, maxX = -Infinity;
|
|
4078
|
-
let minY = Math.min(this.baseline, Infinity), maxY = Math.max(this.baseline, -Infinity);
|
|
4079
|
-
for (let i = 0; i < n; i++) {
|
|
4080
|
-
const x = opts.x[i], y = opts.y[i];
|
|
4081
|
-
this.xs[i] = x;
|
|
4082
|
-
this.ys[i] = y;
|
|
4083
|
-
segs[i * 4] = x - this.xRef;
|
|
4084
|
-
segs[i * 4 + 1] = this.baseline - this.yRef;
|
|
4085
|
-
segs[i * 4 + 2] = x - this.xRef;
|
|
4086
|
-
segs[i * 4 + 3] = y - this.yRef;
|
|
4087
|
-
tips[i * 2] = x - this.xRef;
|
|
4088
|
-
tips[i * 2 + 1] = y - this.yRef;
|
|
4089
|
-
if (x < minX) minX = x;
|
|
4090
|
-
if (x > maxX) maxX = x;
|
|
4091
|
-
if (y < minY) minY = y;
|
|
4092
|
-
if (y > maxY) maxY = y;
|
|
4093
|
-
}
|
|
4094
|
-
this.xBounds = [minX, maxX];
|
|
4095
|
-
this.yBounds = [minY, maxY];
|
|
4611
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
4612
|
+
const { segs, tips } = this.build(opts.x, opts.y);
|
|
4096
4613
|
const cornerSeg = gl.createBuffer();
|
|
4097
4614
|
gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
|
|
4098
|
-
gl.bufferData(gl.ARRAY_BUFFER,
|
|
4615
|
+
gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS5, gl.STATIC_DRAW);
|
|
4099
4616
|
const cornerQuad = gl.createBuffer();
|
|
4100
4617
|
gl.bindBuffer(gl.ARRAY_BUFFER, cornerQuad);
|
|
4101
4618
|
gl.bufferData(gl.ARRAY_BUFFER, QUAD_CORNERS2, gl.STATIC_DRAW);
|
|
4102
4619
|
const segBuf = gl.createBuffer();
|
|
4103
4620
|
gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
|
|
4104
|
-
gl.bufferData(gl.ARRAY_BUFFER, segs,
|
|
4621
|
+
gl.bufferData(gl.ARRAY_BUFFER, segs, this.usage);
|
|
4105
4622
|
const tipBuf = gl.createBuffer();
|
|
4106
4623
|
gl.bindBuffer(gl.ARRAY_BUFFER, tipBuf);
|
|
4107
|
-
gl.bufferData(gl.ARRAY_BUFFER, tips,
|
|
4624
|
+
gl.bufferData(gl.ARRAY_BUFFER, tips, this.usage);
|
|
4108
4625
|
this.buffers = [cornerSeg, cornerQuad, segBuf, tipBuf];
|
|
4109
4626
|
this.stemVao = gl.createVertexArray();
|
|
4110
4627
|
gl.bindVertexArray(this.stemVao);
|
|
@@ -4128,6 +4645,46 @@ var StemLayer = class {
|
|
|
4128
4645
|
this.uStem = uniformLocations(gl, this.progs.stem, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uWidth"]);
|
|
4129
4646
|
this.uMarker = uniformLocations(gl, this.progs.marker, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uSize"]);
|
|
4130
4647
|
}
|
|
4648
|
+
/** Recompute count/refs/bounds and the stem+tip vertex arrays from new x/y. */
|
|
4649
|
+
build(x, y) {
|
|
4650
|
+
const n = Math.min(x.length, y.length);
|
|
4651
|
+
this.count = n;
|
|
4652
|
+
this.xs = new Float64Array(n);
|
|
4653
|
+
this.ys = new Float64Array(n);
|
|
4654
|
+
this.xRef = n > 0 ? x[0] : 0;
|
|
4655
|
+
this.yRef = n > 0 ? y[0] : 0;
|
|
4656
|
+
const segs = new Float32Array(n * 4);
|
|
4657
|
+
const tips = new Float32Array(n * 2);
|
|
4658
|
+
let minX = Infinity, maxX = -Infinity;
|
|
4659
|
+
let minY = Math.min(this.baseline, Infinity), maxY = Math.max(this.baseline, -Infinity);
|
|
4660
|
+
for (let i = 0; i < n; i++) {
|
|
4661
|
+
const xi = x[i], yi = y[i];
|
|
4662
|
+
this.xs[i] = xi;
|
|
4663
|
+
this.ys[i] = yi;
|
|
4664
|
+
segs[i * 4] = xi - this.xRef;
|
|
4665
|
+
segs[i * 4 + 1] = this.baseline - this.yRef;
|
|
4666
|
+
segs[i * 4 + 2] = xi - this.xRef;
|
|
4667
|
+
segs[i * 4 + 3] = yi - this.yRef;
|
|
4668
|
+
tips[i * 2] = xi - this.xRef;
|
|
4669
|
+
tips[i * 2 + 1] = yi - this.yRef;
|
|
4670
|
+
if (xi < minX) minX = xi;
|
|
4671
|
+
if (xi > maxX) maxX = xi;
|
|
4672
|
+
if (yi < minY) minY = yi;
|
|
4673
|
+
if (yi > maxY) maxY = yi;
|
|
4674
|
+
}
|
|
4675
|
+
this.xBounds = [minX, maxX];
|
|
4676
|
+
this.yBounds = [minY, maxY];
|
|
4677
|
+
return { segs, tips };
|
|
4678
|
+
}
|
|
4679
|
+
/** Replace the data and re-upload (for streaming). */
|
|
4680
|
+
setData(x, y) {
|
|
4681
|
+
const gl = this.gl;
|
|
4682
|
+
const { segs, tips } = this.build(x, y);
|
|
4683
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[2]);
|
|
4684
|
+
gl.bufferData(gl.ARRAY_BUFFER, segs, this.usage);
|
|
4685
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[3]);
|
|
4686
|
+
gl.bufferData(gl.ARRAY_BUFFER, tips, this.usage);
|
|
4687
|
+
}
|
|
4131
4688
|
bounds() {
|
|
4132
4689
|
if (this.count === 0) return null;
|
|
4133
4690
|
return { x: this.xBounds, y: this.yBounds };
|
|
@@ -4568,7 +5125,92 @@ var CategoricalScale = class {
|
|
|
4568
5125
|
return this.factors[Math.round(value)] ?? "";
|
|
4569
5126
|
}
|
|
4570
5127
|
};
|
|
4571
|
-
|
|
5128
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
5129
|
+
var OrdinalTimeScale = class {
|
|
5130
|
+
constructor(times = []) {
|
|
5131
|
+
this.type = "ordinal-time";
|
|
5132
|
+
this.log = false;
|
|
5133
|
+
this.times = Array.from({ length: times.length }, (_, i) => times[i]);
|
|
5134
|
+
const n = this.times.length;
|
|
5135
|
+
this.domain = n > 0 ? [-0.5, n - 0.5] : [-0.5, 0.5];
|
|
5136
|
+
}
|
|
5137
|
+
norm(value) {
|
|
5138
|
+
const [a, b] = this.domain;
|
|
5139
|
+
return b === a ? 0 : (value - a) / (b - a);
|
|
5140
|
+
}
|
|
5141
|
+
invert(t) {
|
|
5142
|
+
const [a, b] = this.domain;
|
|
5143
|
+
return a + t * (b - a);
|
|
5144
|
+
}
|
|
5145
|
+
/** Timestamp at a (rounded, clamped) index. */
|
|
5146
|
+
timeAt(i) {
|
|
5147
|
+
const n = this.times.length;
|
|
5148
|
+
if (n === 0) return 0;
|
|
5149
|
+
const k = Math.max(0, Math.min(n - 1, Math.round(i)));
|
|
5150
|
+
return this.times[k];
|
|
5151
|
+
}
|
|
5152
|
+
ticks(target = 6) {
|
|
5153
|
+
const n = this.times.length;
|
|
5154
|
+
if (n === 0) return [];
|
|
5155
|
+
const i0 = Math.max(0, Math.ceil(this.domain[0] - 1e-9));
|
|
5156
|
+
const i1 = Math.min(n - 1, Math.floor(this.domain[1] + 1e-9));
|
|
5157
|
+
if (i1 < i0) return [];
|
|
5158
|
+
const dayKey = (d) => d.getFullYear() * 1e4 + d.getMonth() * 100 + d.getDate();
|
|
5159
|
+
const levels = [
|
|
5160
|
+
{ key: (d) => d.getFullYear(), label: (d) => `${d.getFullYear()}` },
|
|
5161
|
+
{
|
|
5162
|
+
key: (d) => d.getFullYear() * 12 + d.getMonth(),
|
|
5163
|
+
label: (d) => d.getMonth() === 0 ? `${d.getFullYear()}` : MONTHS[d.getMonth()]
|
|
5164
|
+
},
|
|
5165
|
+
{ key: dayKey, label: (d) => `${d.getDate()} ${MONTHS[d.getMonth()]}` },
|
|
5166
|
+
{ key: (d) => dayKey(d) * 100 + d.getHours(), label: hhmm },
|
|
5167
|
+
{ key: (d) => (dayKey(d) * 100 + d.getHours()) * 100 + d.getMinutes(), label: hhmm }
|
|
5168
|
+
];
|
|
5169
|
+
const boundaries = (lvl) => {
|
|
5170
|
+
const out = [];
|
|
5171
|
+
let prev = i0 > 0 ? lvl.key(new Date(this.times[i0 - 1])) : NaN;
|
|
5172
|
+
for (let i = i0; i <= i1; i++) {
|
|
5173
|
+
const k = lvl.key(new Date(this.times[i]));
|
|
5174
|
+
if (k !== prev) {
|
|
5175
|
+
out.push(i);
|
|
5176
|
+
prev = k;
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
return out;
|
|
5180
|
+
};
|
|
5181
|
+
let best = null;
|
|
5182
|
+
for (const lvl of levels) {
|
|
5183
|
+
const b = boundaries(lvl);
|
|
5184
|
+
if (b.length === 0) continue;
|
|
5185
|
+
best = { lvl, b };
|
|
5186
|
+
if (b.length >= target) break;
|
|
5187
|
+
}
|
|
5188
|
+
let idxs;
|
|
5189
|
+
let label;
|
|
5190
|
+
if (best) {
|
|
5191
|
+
idxs = best.b;
|
|
5192
|
+
label = best.lvl.label;
|
|
5193
|
+
if (idxs.length > target * 1.5) {
|
|
5194
|
+
const stride = Math.ceil(idxs.length / target);
|
|
5195
|
+
idxs = idxs.filter((_, k) => k % stride === 0);
|
|
5196
|
+
}
|
|
5197
|
+
} else {
|
|
5198
|
+
idxs = [];
|
|
5199
|
+
const step = Math.max(1, Math.floor((i1 - i0) / Math.max(1, target - 1)));
|
|
5200
|
+
for (let i = i0; i <= i1; i += step) idxs.push(i);
|
|
5201
|
+
label = (d) => `${d.getDate()} ${MONTHS[d.getMonth()]} ${hhmm(d)}`;
|
|
5202
|
+
}
|
|
5203
|
+
return idxs.map((i) => ({ value: i, label: label(new Date(this.times[i])) }));
|
|
5204
|
+
}
|
|
5205
|
+
formatTick(value) {
|
|
5206
|
+
const d = new Date(this.timeAt(value));
|
|
5207
|
+
return `${d.getDate()} ${MONTHS[d.getMonth()]} ${hhmm(d)}`;
|
|
5208
|
+
}
|
|
5209
|
+
};
|
|
5210
|
+
function hhmm(d) {
|
|
5211
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
5212
|
+
}
|
|
5213
|
+
function makeScale(type, domain, factors, times) {
|
|
4572
5214
|
switch (type) {
|
|
4573
5215
|
case "linear":
|
|
4574
5216
|
return new LinearScale(domain);
|
|
@@ -4578,6 +5220,8 @@ function makeScale(type, domain, factors) {
|
|
|
4578
5220
|
return new TimeScale(domain);
|
|
4579
5221
|
case "categorical":
|
|
4580
5222
|
return new CategoricalScale(factors ?? []);
|
|
5223
|
+
case "ordinal-time":
|
|
5224
|
+
return new OrdinalTimeScale(times ?? []);
|
|
4581
5225
|
default:
|
|
4582
5226
|
throw new Error(`Unknown scale type: ${type}`);
|
|
4583
5227
|
}
|
|
@@ -4724,6 +5368,12 @@ var Plot = class {
|
|
|
4724
5368
|
this.hoverPx = null;
|
|
4725
5369
|
/** Cursor position while the pointer is pressed, when `crosshair`. */
|
|
4726
5370
|
this.pressPx = null;
|
|
5371
|
+
// Linked-pane plumbing (see linkX). View/cursor changes are emitted from render().
|
|
5372
|
+
this.viewListeners = [];
|
|
5373
|
+
this.cursorListeners = [];
|
|
5374
|
+
this.lastEmittedX = null;
|
|
5375
|
+
this.lastEmittedCursor = NaN;
|
|
5376
|
+
this.linkedCursorX = null;
|
|
4727
5377
|
/** A point clicked to pin its details, until another click clears it. */
|
|
4728
5378
|
this.selected = null;
|
|
4729
5379
|
this.annotations = [];
|
|
@@ -4742,20 +5392,20 @@ var Plot = class {
|
|
|
4742
5392
|
this.sharedCanvas = s.canvas;
|
|
4743
5393
|
const sx = options.scales?.x ?? {};
|
|
4744
5394
|
const sy = options.scales?.y ?? {};
|
|
4745
|
-
const
|
|
4746
|
-
this.scaleX = makeScale(sx.type ?? "linear", sx.domain ?? [0, 1], sx.factors);
|
|
4747
|
-
this.autoX = !
|
|
4748
|
-
this.initialX =
|
|
5395
|
+
const xBand = sx.type === "categorical" || sx.type === "ordinal-time";
|
|
5396
|
+
this.scaleX = makeScale(sx.type ?? "linear", sx.domain ?? [0, 1], sx.factors, sx.times);
|
|
5397
|
+
this.autoX = !xBand && sx.domain == null;
|
|
5398
|
+
this.initialX = xBand ? this.scaleX.domain : sx.domain ?? null;
|
|
4749
5399
|
this.axisX = new Axis(options.axes?.x);
|
|
4750
|
-
const
|
|
4751
|
-
const yScale = makeScale(sy.type ?? "linear", sy.domain ?? [0, 1], sy.factors);
|
|
5400
|
+
const yBand = sy.type === "categorical" || sy.type === "ordinal-time";
|
|
5401
|
+
const yScale = makeScale(sy.type ?? "linear", sy.domain ?? [0, 1], sy.factors, sy.times);
|
|
4752
5402
|
this.yAxes.set("y", {
|
|
4753
5403
|
id: "y",
|
|
4754
5404
|
scale: yScale,
|
|
4755
5405
|
axis: new Axis(options.axes?.y),
|
|
4756
5406
|
side: "left",
|
|
4757
|
-
auto: !
|
|
4758
|
-
initial:
|
|
5407
|
+
auto: !yBand && sy.domain == null,
|
|
5408
|
+
initial: yBand ? yScale.domain : sy.domain ?? null
|
|
4759
5409
|
});
|
|
4760
5410
|
this.isDark = options.theme === "dark";
|
|
4761
5411
|
this.theme = options.theme === "dark" ? darkTheme : options.theme === "light" || options.theme == null ? lightTheme : options.theme;
|
|
@@ -5039,6 +5689,10 @@ var Plot = class {
|
|
|
5039
5689
|
addCandlestick(opts) {
|
|
5040
5690
|
return this.register(new CandlestickLayer(this.gl, opts));
|
|
5041
5691
|
}
|
|
5692
|
+
/** An OHLC bar chart (low→high line with open/close ticks). Streams like candlesticks. */
|
|
5693
|
+
addOhlc(opts) {
|
|
5694
|
+
return this.register(new OhlcLayer(this.gl, opts));
|
|
5695
|
+
}
|
|
5042
5696
|
/** Filled polygons (choropleth-capable). Rings are triangulated with earcut. */
|
|
5043
5697
|
addPatches(opts) {
|
|
5044
5698
|
return this.register(new PatchesLayer(this.gl, opts));
|
|
@@ -5146,21 +5800,31 @@ var Plot = class {
|
|
|
5146
5800
|
/** Register an additional named y axis. Series opt in via `addLine({ yAxis })`. */
|
|
5147
5801
|
addYAxis(id, opts = {}) {
|
|
5148
5802
|
if (this.yAxes.has(id)) throw new Error(`Y axis "${id}" already exists`);
|
|
5149
|
-
const { type, domain, factors, side, color, ...axisConfig } = opts;
|
|
5150
|
-
const
|
|
5151
|
-
const scale = makeScale(type ?? "linear", domain ?? [0, 1], factors);
|
|
5803
|
+
const { type, domain, factors, times, side, color, ...axisConfig } = opts;
|
|
5804
|
+
const band = type === "categorical" || type === "ordinal-time";
|
|
5805
|
+
const scale = makeScale(type ?? "linear", domain ?? [0, 1], factors, times);
|
|
5152
5806
|
this.yAxes.set(id, {
|
|
5153
5807
|
id,
|
|
5154
5808
|
scale,
|
|
5155
5809
|
axis: new Axis(axisConfig),
|
|
5156
5810
|
side: side ?? "right",
|
|
5157
|
-
auto: !
|
|
5158
|
-
initial:
|
|
5811
|
+
auto: !band && domain == null,
|
|
5812
|
+
initial: band ? scale.domain : domain ?? null,
|
|
5159
5813
|
color
|
|
5160
5814
|
});
|
|
5161
5815
|
this.autoscale();
|
|
5162
5816
|
this.requestRender();
|
|
5163
5817
|
}
|
|
5818
|
+
/** Whether a y axis with this id exists (the primary is always `"y"`). */
|
|
5819
|
+
hasYAxis(id) {
|
|
5820
|
+
return this.yAxes.has(id);
|
|
5821
|
+
}
|
|
5822
|
+
/** Remove a secondary y axis. No-op for the primary `"y"` or an unknown id. */
|
|
5823
|
+
removeYAxis(id) {
|
|
5824
|
+
if (id === "y" || !this.yAxes.has(id)) return;
|
|
5825
|
+
this.yAxes.delete(id);
|
|
5826
|
+
this.requestRender();
|
|
5827
|
+
}
|
|
5164
5828
|
removeLayer(layer) {
|
|
5165
5829
|
const i = this.layers.indexOf(layer);
|
|
5166
5830
|
if (i >= 0) {
|
|
@@ -5193,6 +5857,32 @@ var Plot = class {
|
|
|
5193
5857
|
}
|
|
5194
5858
|
this.requestRender();
|
|
5195
5859
|
}
|
|
5860
|
+
/**
|
|
5861
|
+
* Subscribe to x-domain changes (pan / zoom / home / setView). Fires once per
|
|
5862
|
+
* frame after the domain settles. Returns an unsubscribe function. See {@link linkX}.
|
|
5863
|
+
*/
|
|
5864
|
+
onViewChange(cb) {
|
|
5865
|
+
this.viewListeners.push(cb);
|
|
5866
|
+
return () => {
|
|
5867
|
+
this.viewListeners = this.viewListeners.filter((f) => f !== cb);
|
|
5868
|
+
};
|
|
5869
|
+
}
|
|
5870
|
+
/**
|
|
5871
|
+
* Subscribe to the hover cursor's data-space x (or `null` when it leaves the
|
|
5872
|
+
* plot). Returns an unsubscribe function. Used to share a crosshair across panes.
|
|
5873
|
+
*/
|
|
5874
|
+
onCursorMove(cb) {
|
|
5875
|
+
this.cursorListeners.push(cb);
|
|
5876
|
+
return () => {
|
|
5877
|
+
this.cursorListeners = this.cursorListeners.filter((f) => f !== cb);
|
|
5878
|
+
};
|
|
5879
|
+
}
|
|
5880
|
+
/** Draw a linked crosshair at this data-space x (pushed from another pane), or clear it with `null`. */
|
|
5881
|
+
setLinkedCursor(dataX) {
|
|
5882
|
+
if (this.linkedCursorX === dataX) return;
|
|
5883
|
+
this.linkedCursorX = dataX;
|
|
5884
|
+
this.requestRender();
|
|
5885
|
+
}
|
|
5196
5886
|
setYDomain(id, range) {
|
|
5197
5887
|
const ya = this.yAxes.get(id);
|
|
5198
5888
|
if (!ya) throw new Error(`Unknown y axis "${id}"`);
|
|
@@ -5296,11 +5986,27 @@ var Plot = class {
|
|
|
5296
5986
|
primaryY() {
|
|
5297
5987
|
return this.yAxes.get("y");
|
|
5298
5988
|
}
|
|
5989
|
+
/** Fire view/cursor listeners once the frame's x-domain + cursor are settled. */
|
|
5990
|
+
emitLinks(region) {
|
|
5991
|
+
const dx = this.scaleX.domain;
|
|
5992
|
+
if (!this.lastEmittedX || this.lastEmittedX[0] !== dx[0] || this.lastEmittedX[1] !== dx[1]) {
|
|
5993
|
+
this.lastEmittedX = [dx[0], dx[1]];
|
|
5994
|
+
for (const cb of this.viewListeners) cb(this.lastEmittedX);
|
|
5995
|
+
}
|
|
5996
|
+
if (this.cursorListeners.length) {
|
|
5997
|
+
const cx = this.hoverEnabled && this.hoverPx ? this.scaleX.invert(Math.max(0, Math.min(1, (this.hoverPx.x - region.left) / region.width))) : null;
|
|
5998
|
+
if (cx !== this.lastEmittedCursor) {
|
|
5999
|
+
this.lastEmittedCursor = cx;
|
|
6000
|
+
for (const cb of this.cursorListeners) cb(cx);
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
5299
6004
|
render() {
|
|
5300
6005
|
const layout = this.layout();
|
|
5301
6006
|
const region = plotRegion(layout);
|
|
5302
6007
|
if (this.equalAspect) this.applyAspect(region);
|
|
5303
6008
|
if (this.boundedPan) this.clampView();
|
|
6009
|
+
this.emitLinks(region);
|
|
5304
6010
|
const primary = this.primaryY();
|
|
5305
6011
|
const ticksX = this.axisX.resolve(this.scaleX);
|
|
5306
6012
|
const ticksYPrimary = primary.axis.resolve(primary.scale);
|
|
@@ -5371,6 +6077,12 @@ var Plot = class {
|
|
|
5371
6077
|
if (this.crosshair && this.pressPx) {
|
|
5372
6078
|
drawCrosshairXY(this.axisCtx, region, this.pressPx.x, this.pressPx.y, this.theme);
|
|
5373
6079
|
}
|
|
6080
|
+
if (this.linkedCursorX != null) {
|
|
6081
|
+
const nx = this.scaleX.norm(this.linkedCursorX);
|
|
6082
|
+
if (nx >= -1e-3 && nx <= 1.001) {
|
|
6083
|
+
drawCrosshair(this.axisCtx, region, pxX(region, nx), this.theme);
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
5374
6086
|
if (this.hoverEnabled && this.hoverPx) {
|
|
5375
6087
|
this.renderHover(region);
|
|
5376
6088
|
} else {
|
|
@@ -5987,6 +6699,28 @@ var Plot = class {
|
|
|
5987
6699
|
this.requestRender();
|
|
5988
6700
|
}
|
|
5989
6701
|
};
|
|
6702
|
+
function linkX(plots) {
|
|
6703
|
+
let applying = false;
|
|
6704
|
+
const unsubs = [];
|
|
6705
|
+
for (const p of plots) {
|
|
6706
|
+
unsubs.push(
|
|
6707
|
+
p.onViewChange((x) => {
|
|
6708
|
+
if (applying) return;
|
|
6709
|
+
applying = true;
|
|
6710
|
+
for (const q of plots) if (q !== p) q.setView({ x });
|
|
6711
|
+
applying = false;
|
|
6712
|
+
})
|
|
6713
|
+
);
|
|
6714
|
+
unsubs.push(
|
|
6715
|
+
p.onCursorMove((cx) => {
|
|
6716
|
+
for (const q of plots) if (q !== p) q.setLinkedCursor(cx);
|
|
6717
|
+
})
|
|
6718
|
+
);
|
|
6719
|
+
}
|
|
6720
|
+
return () => {
|
|
6721
|
+
for (const u of unsubs) u();
|
|
6722
|
+
};
|
|
6723
|
+
}
|
|
5990
6724
|
|
|
5991
6725
|
// src/polar/polar.ts
|
|
5992
6726
|
var PolarPlot = class {
|
|
@@ -6091,7 +6825,7 @@ var PolarPlot = class {
|
|
|
6091
6825
|
addLine(opts) {
|
|
6092
6826
|
const closed = opts.closed ?? false;
|
|
6093
6827
|
const { x, y, maxR } = this.toXY(opts.theta, opts.r, closed);
|
|
6094
|
-
const layer = new LineLayer(this.gl, { x, y, color: opts.color, width: opts.width ?? 2, decimate: false });
|
|
6828
|
+
const layer = new LineLayer(this.gl, { x, y, color: opts.color, width: opts.width ?? 2, decimate: false, renderType: opts.renderType });
|
|
6095
6829
|
const entry = { layer, closed, maxR, theta: opts.theta, r: opts.r };
|
|
6096
6830
|
this.entries.push(entry);
|
|
6097
6831
|
this.refit();
|
|
@@ -6099,7 +6833,7 @@ var PolarPlot = class {
|
|
|
6099
6833
|
}
|
|
6100
6834
|
addScatter(opts) {
|
|
6101
6835
|
const { x, y, maxR } = this.toXY(opts.theta, opts.r, false);
|
|
6102
|
-
const layer = new ScatterLayer(this.gl, { x, y, color: opts.color, size: opts.size ?? 5 });
|
|
6836
|
+
const layer = new ScatterLayer(this.gl, { x, y, color: opts.color, size: opts.size ?? 5, renderType: opts.renderType });
|
|
6103
6837
|
const entry = { layer, closed: false, maxR, theta: opts.theta, r: opts.r, labels: opts.labels };
|
|
6104
6838
|
this.entries.push(entry);
|
|
6105
6839
|
this.refit();
|
|
@@ -6559,7 +7293,7 @@ void main() {
|
|
|
6559
7293
|
gl_Position = uMVP * vec4(world, 1.0);
|
|
6560
7294
|
}`
|
|
6561
7295
|
);
|
|
6562
|
-
var
|
|
7296
|
+
var FRAG13 = (
|
|
6563
7297
|
/* glsl */
|
|
6564
7298
|
`#version 300 es
|
|
6565
7299
|
precision highp float;
|
|
@@ -6574,16 +7308,16 @@ void main() {
|
|
|
6574
7308
|
outColor = vec4(vColor * shade, 1.0);
|
|
6575
7309
|
}`
|
|
6576
7310
|
);
|
|
6577
|
-
var
|
|
6578
|
-
function
|
|
6579
|
-
let p =
|
|
7311
|
+
var programCache14 = /* @__PURE__ */ new WeakMap();
|
|
7312
|
+
function getProgram11(gl) {
|
|
7313
|
+
let p = programCache14.get(gl);
|
|
6580
7314
|
if (!p) {
|
|
6581
|
-
p = createProgram(gl, VERT10,
|
|
6582
|
-
|
|
7315
|
+
p = createProgram(gl, VERT10, FRAG13);
|
|
7316
|
+
programCache14.set(gl, p);
|
|
6583
7317
|
}
|
|
6584
7318
|
return p;
|
|
6585
7319
|
}
|
|
6586
|
-
function
|
|
7320
|
+
function medianSpacing4(vals, n) {
|
|
6587
7321
|
const s = Array.from({ length: n }, (_, i) => vals[i]).sort((a, b) => a - b);
|
|
6588
7322
|
const diffs = [];
|
|
6589
7323
|
for (let i = 1; i < n; i++) {
|
|
@@ -6594,62 +7328,22 @@ function medianSpacing3(vals, n) {
|
|
|
6594
7328
|
diffs.sort((a, b) => a - b);
|
|
6595
7329
|
return diffs[Math.floor(diffs.length / 2)];
|
|
6596
7330
|
}
|
|
6597
|
-
var
|
|
7331
|
+
var counter18 = 0;
|
|
6598
7332
|
var Bar3DLayer = class {
|
|
6599
7333
|
constructor(gl, opts) {
|
|
6600
7334
|
this.buffers = [];
|
|
6601
7335
|
this.cInfo = null;
|
|
6602
7336
|
this.lightDir = [0.5, 1, 0.35];
|
|
6603
7337
|
this.ambient = 0.35;
|
|
6604
|
-
this.id = `bar3d-${
|
|
7338
|
+
this.id = `bar3d-${counter18++}`;
|
|
6605
7339
|
this.gl = gl;
|
|
6606
|
-
this.program =
|
|
7340
|
+
this.program = getProgram11(gl);
|
|
6607
7341
|
this.name = opts.name;
|
|
6608
|
-
|
|
6609
|
-
this.
|
|
6610
|
-
|
|
6611
|
-
this.
|
|
6612
|
-
|
|
6613
|
-
const cvals = opts.colorBy?.values ?? (opts.colorBy ? opts.y : null);
|
|
6614
|
-
const cmap = opts.colorBy ? colormap(opts.colorBy.colormap ?? "viridis") : null;
|
|
6615
|
-
let lo = opts.colorBy?.domain?.[0] ?? Infinity;
|
|
6616
|
-
let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
|
|
6617
|
-
if (cmap && cvals && !opts.colorBy?.domain) {
|
|
6618
|
-
for (let i = 0; i < n; i++) {
|
|
6619
|
-
const v = cvals[i];
|
|
6620
|
-
if (v < lo) lo = v;
|
|
6621
|
-
if (v > hi) hi = v;
|
|
6622
|
-
}
|
|
6623
|
-
}
|
|
6624
|
-
const span = hi - lo || 1;
|
|
6625
|
-
if (cmap) this.cInfo = { colormap: opts.colorBy.colormap ?? "viridis", domain: [lo, hi], label: opts.name };
|
|
6626
|
-
else this.colorCss = opts.color != null && typeof opts.color === "string" ? opts.color : "#3b82f6";
|
|
6627
|
-
const inst = new Float32Array(n * 6);
|
|
6628
|
-
this.positions = new Float32Array(n * 3);
|
|
6629
|
-
let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity, minY = 0, maxY = 0;
|
|
6630
|
-
const hw = this.width / 2;
|
|
6631
|
-
for (let i = 0; i < n; i++) {
|
|
6632
|
-
const x = opts.x[i], z = opts.z[i], h = opts.y[i];
|
|
6633
|
-
inst[i * 6] = x;
|
|
6634
|
-
inst[i * 6 + 1] = z;
|
|
6635
|
-
inst[i * 6 + 2] = h;
|
|
6636
|
-
this.positions[i * 3] = x;
|
|
6637
|
-
this.positions[i * 3 + 1] = h;
|
|
6638
|
-
this.positions[i * 3 + 2] = z;
|
|
6639
|
-
let c;
|
|
6640
|
-
if (cmap && cvals) c = cmap((cvals[i] - lo) / span);
|
|
6641
|
-
else c = [base[0], base[1], base[2]];
|
|
6642
|
-
inst[i * 6 + 3] = c[0];
|
|
6643
|
-
inst[i * 6 + 4] = c[1];
|
|
6644
|
-
inst[i * 6 + 5] = c[2];
|
|
6645
|
-
if (x - hw < minX) minX = x - hw;
|
|
6646
|
-
if (x + hw > maxX) maxX = x + hw;
|
|
6647
|
-
if (z - hw < minZ) minZ = z - hw;
|
|
6648
|
-
if (z + hw > maxZ) maxZ = z + hw;
|
|
6649
|
-
if (h < minY) minY = h;
|
|
6650
|
-
if (h > maxY) maxY = h;
|
|
6651
|
-
}
|
|
6652
|
-
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7342
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
7343
|
+
this.optWidth = opts.width;
|
|
7344
|
+
this.colorByOpt = opts.colorBy;
|
|
7345
|
+
this.base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.24, 0.55, 0.96, 1];
|
|
7346
|
+
if (!opts.colorBy) this.colorCss = opts.color != null && typeof opts.color === "string" ? opts.color : "#3b82f6";
|
|
6653
7347
|
this.vao = gl.createVertexArray();
|
|
6654
7348
|
gl.bindVertexArray(this.vao);
|
|
6655
7349
|
const cubeBuf = gl.createBuffer();
|
|
@@ -6661,7 +7355,6 @@ var Bar3DLayer = class {
|
|
|
6661
7355
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
6662
7356
|
const instBuf = gl.createBuffer();
|
|
6663
7357
|
gl.bindBuffer(gl.ARRAY_BUFFER, instBuf);
|
|
6664
|
-
gl.bufferData(gl.ARRAY_BUFFER, inst, gl.STATIC_DRAW);
|
|
6665
7358
|
gl.enableVertexAttribArray(2);
|
|
6666
7359
|
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 24, 0);
|
|
6667
7360
|
gl.vertexAttribDivisor(2, 1);
|
|
@@ -6673,8 +7366,64 @@ var Bar3DLayer = class {
|
|
|
6673
7366
|
gl.vertexAttribDivisor(4, 1);
|
|
6674
7367
|
gl.bindVertexArray(null);
|
|
6675
7368
|
this.buffers = [cubeBuf, instBuf];
|
|
7369
|
+
this.instBuf = instBuf;
|
|
7370
|
+
this.build(opts.x, opts.z, opts.y);
|
|
6676
7371
|
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uWidth", "uLightDir", "uAmbient"]);
|
|
6677
7372
|
}
|
|
7373
|
+
/** Build the per-bar instances (base/height/color) and (re)upload the instance buffer. */
|
|
7374
|
+
build(x, z, y) {
|
|
7375
|
+
const gl = this.gl;
|
|
7376
|
+
const n = Math.min(x.length, y.length, z.length);
|
|
7377
|
+
this.count = n;
|
|
7378
|
+
const dx = medianSpacing4(x, n), dz = medianSpacing4(z, n);
|
|
7379
|
+
this.width = this.optWidth ?? Math.min(dx, dz) * 0.7;
|
|
7380
|
+
const base = this.base;
|
|
7381
|
+
const cvals = this.colorByOpt?.values ?? (this.colorByOpt ? y : null);
|
|
7382
|
+
const cmap = this.colorByOpt ? colormap(this.colorByOpt.colormap ?? "viridis") : null;
|
|
7383
|
+
let lo = this.colorByOpt?.domain?.[0] ?? Infinity;
|
|
7384
|
+
let hi = this.colorByOpt?.domain?.[1] ?? -Infinity;
|
|
7385
|
+
if (cmap && cvals && !this.colorByOpt?.domain) {
|
|
7386
|
+
for (let i = 0; i < n; i++) {
|
|
7387
|
+
const v = cvals[i];
|
|
7388
|
+
if (v < lo) lo = v;
|
|
7389
|
+
if (v > hi) hi = v;
|
|
7390
|
+
}
|
|
7391
|
+
}
|
|
7392
|
+
const span = hi - lo || 1;
|
|
7393
|
+
if (cmap) this.cInfo = { colormap: this.colorByOpt.colormap ?? "viridis", domain: [lo, hi], label: this.name };
|
|
7394
|
+
const inst = new Float32Array(n * 6);
|
|
7395
|
+
this.positions = new Float32Array(n * 3);
|
|
7396
|
+
let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity, minY = 0, maxY = 0;
|
|
7397
|
+
const hw = this.width / 2;
|
|
7398
|
+
for (let i = 0; i < n; i++) {
|
|
7399
|
+
const px = x[i], pz = z[i], h = y[i];
|
|
7400
|
+
inst[i * 6] = px;
|
|
7401
|
+
inst[i * 6 + 1] = pz;
|
|
7402
|
+
inst[i * 6 + 2] = h;
|
|
7403
|
+
this.positions[i * 3] = px;
|
|
7404
|
+
this.positions[i * 3 + 1] = h;
|
|
7405
|
+
this.positions[i * 3 + 2] = pz;
|
|
7406
|
+
let c;
|
|
7407
|
+
if (cmap && cvals) c = cmap((cvals[i] - lo) / span);
|
|
7408
|
+
else c = [base[0], base[1], base[2]];
|
|
7409
|
+
inst[i * 6 + 3] = c[0];
|
|
7410
|
+
inst[i * 6 + 4] = c[1];
|
|
7411
|
+
inst[i * 6 + 5] = c[2];
|
|
7412
|
+
if (px - hw < minX) minX = px - hw;
|
|
7413
|
+
if (px + hw > maxX) maxX = px + hw;
|
|
7414
|
+
if (pz - hw < minZ) minZ = pz - hw;
|
|
7415
|
+
if (pz + hw > maxZ) maxZ = pz + hw;
|
|
7416
|
+
if (h < minY) minY = h;
|
|
7417
|
+
if (h > maxY) maxY = h;
|
|
7418
|
+
}
|
|
7419
|
+
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7420
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instBuf);
|
|
7421
|
+
gl.bufferData(gl.ARRAY_BUFFER, inst, this.usage);
|
|
7422
|
+
}
|
|
7423
|
+
/** Stream new bar positions/heights (instances rebuilt). Call `plot.refresh()` after. */
|
|
7424
|
+
setData(x, z, y) {
|
|
7425
|
+
this.build(x, z, y);
|
|
7426
|
+
}
|
|
6678
7427
|
bounds3() {
|
|
6679
7428
|
return this.count ? this.b3 : null;
|
|
6680
7429
|
}
|
|
@@ -6735,7 +7484,7 @@ uniform mat4 uMVP;
|
|
|
6735
7484
|
out vec3 vColor;
|
|
6736
7485
|
void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
6737
7486
|
);
|
|
6738
|
-
var
|
|
7487
|
+
var FRAG14 = (
|
|
6739
7488
|
/* glsl */
|
|
6740
7489
|
`#version 300 es
|
|
6741
7490
|
precision highp float;
|
|
@@ -6743,26 +7492,41 @@ in vec3 vColor;
|
|
|
6743
7492
|
out vec4 outColor;
|
|
6744
7493
|
void main() { outColor = vec4(vColor, 1.0); }`
|
|
6745
7494
|
);
|
|
6746
|
-
var
|
|
6747
|
-
function
|
|
6748
|
-
let p =
|
|
7495
|
+
var programCache15 = /* @__PURE__ */ new WeakMap();
|
|
7496
|
+
function getProgram12(gl) {
|
|
7497
|
+
let p = programCache15.get(gl);
|
|
6749
7498
|
if (!p) {
|
|
6750
|
-
p = createProgram(gl, VERT11,
|
|
6751
|
-
|
|
7499
|
+
p = createProgram(gl, VERT11, FRAG14);
|
|
7500
|
+
programCache15.set(gl, p);
|
|
6752
7501
|
}
|
|
6753
7502
|
return p;
|
|
6754
7503
|
}
|
|
6755
|
-
var
|
|
7504
|
+
var counter19 = 0;
|
|
6756
7505
|
var Contour3DLayer = class {
|
|
6757
7506
|
constructor(gl, opts) {
|
|
6758
7507
|
this.cInfo = null;
|
|
6759
|
-
this.id = `contour3d-${
|
|
7508
|
+
this.id = `contour3d-${counter19++}`;
|
|
6760
7509
|
this.gl = gl;
|
|
6761
|
-
this.program =
|
|
7510
|
+
this.program = getProgram12(gl);
|
|
6762
7511
|
this.name = opts.name;
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
7512
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
7513
|
+
this.cmapName = opts.colormap ?? "viridis";
|
|
7514
|
+
this.levelsOpt = opts.levels;
|
|
7515
|
+
this.fixed = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : null;
|
|
7516
|
+
this.vao = gl.createVertexArray();
|
|
7517
|
+
this.buffer = gl.createBuffer();
|
|
7518
|
+
this.build(opts.values, opts.cols, opts.rows, opts.extentX, opts.extentZ);
|
|
7519
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP"]);
|
|
7520
|
+
}
|
|
7521
|
+
/** Marching-squares the grid at each iso level and (re)upload the line buffer. */
|
|
7522
|
+
build(values, cols, rows, extentX, extentZ) {
|
|
7523
|
+
const gl = this.gl;
|
|
7524
|
+
this.cols = cols;
|
|
7525
|
+
this.rows = rows;
|
|
7526
|
+
const [x0, x1] = extentX ?? [0, cols - 1];
|
|
7527
|
+
const [z0, z1] = extentZ ?? [0, rows - 1];
|
|
7528
|
+
this.extentX = [x0, x1];
|
|
7529
|
+
this.extentZ = [z0, z1];
|
|
6766
7530
|
let vmin = Infinity, vmax = -Infinity;
|
|
6767
7531
|
for (let i = 0; i < values.length; i++) {
|
|
6768
7532
|
const v = values[i];
|
|
@@ -6770,11 +7534,11 @@ var Contour3DLayer = class {
|
|
|
6770
7534
|
if (v > vmax) vmax = v;
|
|
6771
7535
|
}
|
|
6772
7536
|
const lspan = vmax - vmin || 1;
|
|
6773
|
-
const count = typeof
|
|
6774
|
-
const levels = Array.isArray(
|
|
6775
|
-
const cmap = colormap(
|
|
6776
|
-
const fixed =
|
|
6777
|
-
if (!fixed) this.cInfo = { colormap:
|
|
7537
|
+
const count = typeof this.levelsOpt === "number" ? this.levelsOpt : 10;
|
|
7538
|
+
const levels = Array.isArray(this.levelsOpt) ? this.levelsOpt : Array.from({ length: count }, (_, i) => vmin + lspan * (i + 1) / (count + 1));
|
|
7539
|
+
const cmap = colormap(this.cmapName);
|
|
7540
|
+
const fixed = this.fixed;
|
|
7541
|
+
if (!fixed) this.cInfo = { colormap: this.cmapName, domain: [vmin, vmax], label: this.name };
|
|
6778
7542
|
const wx = (c) => x0 + c / (cols - 1) * (x1 - x0);
|
|
6779
7543
|
const wz = (r) => z0 + r / (rows - 1) * (z1 - z0);
|
|
6780
7544
|
const at = (c, r) => values[r * cols + c];
|
|
@@ -6804,17 +7568,24 @@ var Contour3DLayer = class {
|
|
|
6804
7568
|
}
|
|
6805
7569
|
this.vertCount = data.length / 6;
|
|
6806
7570
|
this.b3 = { x: [x0, x1], y: [vmin, vmax], z: [z0, z1] };
|
|
6807
|
-
this.vao = gl.createVertexArray();
|
|
6808
|
-
this.buffer = gl.createBuffer();
|
|
6809
7571
|
gl.bindVertexArray(this.vao);
|
|
6810
7572
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
6811
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data),
|
|
7573
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), this.usage);
|
|
6812
7574
|
gl.enableVertexAttribArray(0);
|
|
6813
7575
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
6814
7576
|
gl.enableVertexAttribArray(1);
|
|
6815
7577
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
6816
7578
|
gl.bindVertexArray(null);
|
|
6817
|
-
|
|
7579
|
+
}
|
|
7580
|
+
/** Stream a new scalar field (contours recomputed). Call `plot.refresh()` after. */
|
|
7581
|
+
setData(values, opts) {
|
|
7582
|
+
this.build(
|
|
7583
|
+
values,
|
|
7584
|
+
opts?.cols ?? this.cols,
|
|
7585
|
+
opts?.rows ?? this.rows,
|
|
7586
|
+
opts?.extentX ?? this.extentX,
|
|
7587
|
+
opts?.extentZ ?? this.extentZ
|
|
7588
|
+
);
|
|
6818
7589
|
}
|
|
6819
7590
|
bounds3() {
|
|
6820
7591
|
return this.vertCount ? this.b3 : null;
|
|
@@ -7456,7 +8227,7 @@ uniform mat4 uMVP;
|
|
|
7456
8227
|
out vec3 vN;
|
|
7457
8228
|
void main() { vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
7458
8229
|
);
|
|
7459
|
-
var
|
|
8230
|
+
var FRAG15 = (
|
|
7460
8231
|
/* glsl */
|
|
7461
8232
|
`#version 300 es
|
|
7462
8233
|
precision highp float;
|
|
@@ -7473,33 +8244,42 @@ void main() {
|
|
|
7473
8244
|
outColor = vec4(uColor * shade * uOpacity, uOpacity);
|
|
7474
8245
|
}`
|
|
7475
8246
|
);
|
|
7476
|
-
var
|
|
7477
|
-
function
|
|
7478
|
-
let p =
|
|
8247
|
+
var programCache16 = /* @__PURE__ */ new WeakMap();
|
|
8248
|
+
function getProgram13(gl) {
|
|
8249
|
+
let p = programCache16.get(gl);
|
|
7479
8250
|
if (!p) {
|
|
7480
|
-
p = createProgram(gl, VERT12,
|
|
7481
|
-
|
|
8251
|
+
p = createProgram(gl, VERT12, FRAG15);
|
|
8252
|
+
programCache16.set(gl, p);
|
|
7482
8253
|
}
|
|
7483
8254
|
return p;
|
|
7484
8255
|
}
|
|
7485
|
-
var
|
|
8256
|
+
var counter20 = 0;
|
|
7486
8257
|
var IsosurfaceLayer = class {
|
|
7487
8258
|
constructor(gl, opts) {
|
|
7488
8259
|
this.lightDir = [0.5, 1, 0.35];
|
|
7489
8260
|
this.ambient = 0.35;
|
|
7490
|
-
this.id = `isosurface-${
|
|
8261
|
+
this.id = `isosurface-${counter20++}`;
|
|
7491
8262
|
this.gl = gl;
|
|
7492
|
-
this.program =
|
|
8263
|
+
this.program = getProgram13(gl);
|
|
7493
8264
|
this.name = opts.name;
|
|
8265
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
7494
8266
|
const ci = opts.color ?? "#38bdf8";
|
|
7495
8267
|
this.color = Array.isArray(ci) ? ci : parseColor(ci);
|
|
7496
8268
|
this.colorCss = typeof ci === "string" ? ci : toColorCss(this.color);
|
|
7497
8269
|
this.opacity = opts.opacity ?? 1;
|
|
7498
|
-
|
|
8270
|
+
this.vao = gl.createVertexArray();
|
|
8271
|
+
this.buffer = gl.createBuffer();
|
|
8272
|
+
this.build(opts.values, opts.dims, opts.isoLevel, opts.extent);
|
|
8273
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uColor", "uLightDir", "uAmbient", "uOpacity"]);
|
|
8274
|
+
}
|
|
8275
|
+
/** Run marching cubes and (re)upload the interleaved pos/normal vertex buffer. */
|
|
8276
|
+
build(values, dims, isoLevel, extent) {
|
|
8277
|
+
const gl = this.gl;
|
|
8278
|
+
const { positions, normals } = marchingCubes(values, dims, isoLevel, extent);
|
|
7499
8279
|
this.vertCount = positions.length / 3;
|
|
7500
|
-
const ex =
|
|
7501
|
-
const ey =
|
|
7502
|
-
const ez =
|
|
8280
|
+
const ex = extent?.x ?? [0, dims[0] - 1];
|
|
8281
|
+
const ey = extent?.y ?? [0, dims[1] - 1];
|
|
8282
|
+
const ez = extent?.z ?? [0, dims[2] - 1];
|
|
7503
8283
|
this.b3 = { x: ex, y: ey, z: ez };
|
|
7504
8284
|
const data = new Float32Array(this.vertCount * 6);
|
|
7505
8285
|
for (let i = 0; i < this.vertCount; i++) {
|
|
@@ -7510,17 +8290,18 @@ var IsosurfaceLayer = class {
|
|
|
7510
8290
|
data[i * 6 + 4] = normals[i * 3 + 1];
|
|
7511
8291
|
data[i * 6 + 5] = normals[i * 3 + 2];
|
|
7512
8292
|
}
|
|
7513
|
-
this.vao = gl.createVertexArray();
|
|
7514
|
-
this.buffer = gl.createBuffer();
|
|
7515
8293
|
gl.bindVertexArray(this.vao);
|
|
7516
8294
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
7517
|
-
gl.bufferData(gl.ARRAY_BUFFER, data,
|
|
8295
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
|
|
7518
8296
|
gl.enableVertexAttribArray(0);
|
|
7519
8297
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
7520
8298
|
gl.enableVertexAttribArray(1);
|
|
7521
8299
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
7522
8300
|
gl.bindVertexArray(null);
|
|
7523
|
-
|
|
8301
|
+
}
|
|
8302
|
+
/** Stream a new volume + iso level (marching-cubes mesh recomputed). Call `plot.refresh()` after. */
|
|
8303
|
+
setData(values, dims, isoLevel, extent) {
|
|
8304
|
+
this.build(values, dims, isoLevel, extent);
|
|
7524
8305
|
}
|
|
7525
8306
|
bounds3() {
|
|
7526
8307
|
return this.vertCount ? this.b3 : null;
|
|
@@ -7557,7 +8338,7 @@ layout(location = 0) in vec3 aPos;
|
|
|
7557
8338
|
uniform mat4 uMVP;
|
|
7558
8339
|
void main() { gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
7559
8340
|
);
|
|
7560
|
-
var
|
|
8341
|
+
var FRAG16 = (
|
|
7561
8342
|
/* glsl */
|
|
7562
8343
|
`#version 300 es
|
|
7563
8344
|
precision highp float;
|
|
@@ -7565,21 +8346,22 @@ uniform vec4 uColor;
|
|
|
7565
8346
|
out vec4 outColor;
|
|
7566
8347
|
void main() { outColor = uColor; }`
|
|
7567
8348
|
);
|
|
7568
|
-
var
|
|
7569
|
-
function
|
|
7570
|
-
let p =
|
|
8349
|
+
var programCache17 = /* @__PURE__ */ new WeakMap();
|
|
8350
|
+
function getProgram14(gl) {
|
|
8351
|
+
let p = programCache17.get(gl);
|
|
7571
8352
|
if (!p) {
|
|
7572
|
-
p = createProgram(gl, VERT13,
|
|
7573
|
-
|
|
8353
|
+
p = createProgram(gl, VERT13, FRAG16);
|
|
8354
|
+
programCache17.set(gl, p);
|
|
7574
8355
|
}
|
|
7575
8356
|
return p;
|
|
7576
8357
|
}
|
|
7577
|
-
var
|
|
8358
|
+
var counter21 = 0;
|
|
7578
8359
|
var Line3DLayer = class {
|
|
7579
8360
|
constructor(gl, opts) {
|
|
7580
|
-
this.id = `line3d-${
|
|
8361
|
+
this.id = `line3d-${counter21++}`;
|
|
7581
8362
|
this.gl = gl;
|
|
7582
|
-
this.program =
|
|
8363
|
+
this.program = getProgram14(gl);
|
|
8364
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
7583
8365
|
this.name = opts.name;
|
|
7584
8366
|
const ci = opts.color ?? "#38bdf8";
|
|
7585
8367
|
this.color = Array.isArray(ci) ? ci : parseColor(ci);
|
|
@@ -7606,7 +8388,7 @@ var Line3DLayer = class {
|
|
|
7606
8388
|
this.buffer = gl.createBuffer();
|
|
7607
8389
|
gl.bindVertexArray(this.vao);
|
|
7608
8390
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
7609
|
-
gl.bufferData(gl.ARRAY_BUFFER, data,
|
|
8391
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
|
|
7610
8392
|
gl.enableVertexAttribArray(0);
|
|
7611
8393
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
7612
8394
|
gl.bindVertexArray(null);
|
|
@@ -7639,7 +8421,7 @@ var Line3DLayer = class {
|
|
|
7639
8421
|
}
|
|
7640
8422
|
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7641
8423
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
7642
|
-
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.
|
|
8424
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.usage);
|
|
7643
8425
|
}
|
|
7644
8426
|
draw(gl, mvp) {
|
|
7645
8427
|
if (this.count < 2) return;
|
|
@@ -7749,7 +8531,7 @@ void main() {
|
|
|
7749
8531
|
gl_PointSize = aSize > 0.0 ? aSize : uSize;
|
|
7750
8532
|
}`
|
|
7751
8533
|
);
|
|
7752
|
-
var
|
|
8534
|
+
var FRAG17 = (
|
|
7753
8535
|
/* glsl */
|
|
7754
8536
|
`#version 300 es
|
|
7755
8537
|
precision highp float;
|
|
@@ -7761,23 +8543,24 @@ void main() {
|
|
|
7761
8543
|
outColor = vec4(vColor, 1.0);
|
|
7762
8544
|
}`
|
|
7763
8545
|
);
|
|
7764
|
-
var
|
|
7765
|
-
function
|
|
7766
|
-
let p =
|
|
8546
|
+
var programCache18 = /* @__PURE__ */ new WeakMap();
|
|
8547
|
+
function getProgram15(gl) {
|
|
8548
|
+
let p = programCache18.get(gl);
|
|
7767
8549
|
if (!p) {
|
|
7768
|
-
p = createProgram(gl, VERT14,
|
|
7769
|
-
|
|
8550
|
+
p = createProgram(gl, VERT14, FRAG17);
|
|
8551
|
+
programCache18.set(gl, p);
|
|
7770
8552
|
}
|
|
7771
8553
|
return p;
|
|
7772
8554
|
}
|
|
7773
|
-
var
|
|
8555
|
+
var counter22 = 0;
|
|
7774
8556
|
var PointCloudLayer = class {
|
|
7775
8557
|
constructor(gl, opts) {
|
|
7776
8558
|
this.b3 = { x: [0, 0], y: [0, 0], z: [0, 0] };
|
|
7777
8559
|
this.cInfo = null;
|
|
7778
|
-
this.id = `points3d-${
|
|
8560
|
+
this.id = `points3d-${counter22++}`;
|
|
7779
8561
|
this.gl = gl;
|
|
7780
|
-
this.program =
|
|
8562
|
+
this.program = getProgram15(gl);
|
|
8563
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
7781
8564
|
this.size = opts.size ?? 4;
|
|
7782
8565
|
this.name = opts.name;
|
|
7783
8566
|
this.labels = opts.labels;
|
|
@@ -7829,7 +8612,7 @@ var PointCloudLayer = class {
|
|
|
7829
8612
|
this.buffer = buffer;
|
|
7830
8613
|
gl.bindVertexArray(vao);
|
|
7831
8614
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
7832
|
-
gl.bufferData(gl.ARRAY_BUFFER, this.data,
|
|
8615
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.data, this.usage);
|
|
7833
8616
|
gl.enableVertexAttribArray(0);
|
|
7834
8617
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 28, 0);
|
|
7835
8618
|
gl.enableVertexAttribArray(1);
|
|
@@ -7866,7 +8649,7 @@ var PointCloudLayer = class {
|
|
|
7866
8649
|
}
|
|
7867
8650
|
this.updateBounds();
|
|
7868
8651
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
7869
|
-
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.data, this.
|
|
8652
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.data, this.usage);
|
|
7870
8653
|
}
|
|
7871
8654
|
bounds3() {
|
|
7872
8655
|
return this.count ? this.b3 : null;
|
|
@@ -7904,7 +8687,7 @@ uniform mat4 uMVP;
|
|
|
7904
8687
|
out vec3 vColor;
|
|
7905
8688
|
void main() { vColor = aColor; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
7906
8689
|
);
|
|
7907
|
-
var
|
|
8690
|
+
var FRAG18 = (
|
|
7908
8691
|
/* glsl */
|
|
7909
8692
|
`#version 300 es
|
|
7910
8693
|
precision highp float;
|
|
@@ -7912,12 +8695,12 @@ in vec3 vColor;
|
|
|
7912
8695
|
out vec4 outColor;
|
|
7913
8696
|
void main() { outColor = vec4(vColor, 1.0); }`
|
|
7914
8697
|
);
|
|
7915
|
-
var
|
|
7916
|
-
function
|
|
7917
|
-
let p =
|
|
8698
|
+
var programCache19 = /* @__PURE__ */ new WeakMap();
|
|
8699
|
+
function getProgram16(gl) {
|
|
8700
|
+
let p = programCache19.get(gl);
|
|
7918
8701
|
if (!p) {
|
|
7919
|
-
p = createProgram(gl, VERT15,
|
|
7920
|
-
|
|
8702
|
+
p = createProgram(gl, VERT15, FRAG18);
|
|
8703
|
+
programCache19.set(gl, p);
|
|
7921
8704
|
}
|
|
7922
8705
|
return p;
|
|
7923
8706
|
}
|
|
@@ -7928,23 +8711,37 @@ function norm(x, y, z) {
|
|
|
7928
8711
|
function cross2(a, b) {
|
|
7929
8712
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
7930
8713
|
}
|
|
7931
|
-
var
|
|
8714
|
+
var counter23 = 0;
|
|
7932
8715
|
var Quiver3DLayer = class {
|
|
7933
8716
|
constructor(gl, opts) {
|
|
7934
8717
|
this.cInfo = null;
|
|
7935
|
-
this.id = `quiver3d-${
|
|
8718
|
+
this.id = `quiver3d-${counter23++}`;
|
|
7936
8719
|
this.gl = gl;
|
|
7937
|
-
this.program =
|
|
8720
|
+
this.program = getProgram16(gl);
|
|
7938
8721
|
this.name = opts.name;
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
8722
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
8723
|
+
this.scale = opts.scale ?? 1;
|
|
8724
|
+
this.headSize = opts.headSize ?? 0.28;
|
|
8725
|
+
this.colorByOpt = opts.colorBy;
|
|
8726
|
+
this.base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.5, 0.8, 1, 1];
|
|
8727
|
+
if (!opts.colorBy) this.colorCss = typeof opts.color === "string" ? opts.color : toColorCss(this.base);
|
|
8728
|
+
this.vao = gl.createVertexArray();
|
|
8729
|
+
this.buffer = gl.createBuffer();
|
|
8730
|
+
this.build(opts.x, opts.y, opts.z, opts.u, opts.v, opts.w);
|
|
8731
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP"]);
|
|
8732
|
+
}
|
|
8733
|
+
/** Build the arrow line geometry (shaft + 4-wing head) and (re)upload the vertex buffer. */
|
|
8734
|
+
build(x, y, z, u, v, w) {
|
|
8735
|
+
const gl = this.gl;
|
|
8736
|
+
const scale = this.scale;
|
|
8737
|
+
const headSize = this.headSize;
|
|
8738
|
+
const n = Math.min(x.length, y.length, z.length, u.length, v.length, w.length);
|
|
8739
|
+
const base = this.base;
|
|
8740
|
+
const cmap = this.colorByOpt ? colormap(this.colorByOpt.colormap ?? "viridis") : null;
|
|
8741
|
+
let lo = this.colorByOpt?.domain?.[0] ?? Infinity;
|
|
8742
|
+
let hi = this.colorByOpt?.domain?.[1] ?? -Infinity;
|
|
8743
|
+
const mag = (i) => Math.hypot(u[i], v[i], w[i]);
|
|
8744
|
+
if (cmap && !this.colorByOpt?.domain) {
|
|
7948
8745
|
for (let i = 0; i < n; i++) {
|
|
7949
8746
|
const m = mag(i);
|
|
7950
8747
|
if (m < lo) lo = m;
|
|
@@ -7952,22 +8749,21 @@ var Quiver3DLayer = class {
|
|
|
7952
8749
|
}
|
|
7953
8750
|
}
|
|
7954
8751
|
const span = hi - lo || 1;
|
|
7955
|
-
if (cmap) this.cInfo = { colormap:
|
|
7956
|
-
else this.colorCss = typeof opts.color === "string" ? opts.color : toColorCss(base);
|
|
8752
|
+
if (cmap) this.cInfo = { colormap: this.colorByOpt.colormap ?? "viridis", domain: [lo, hi], label: this.name };
|
|
7957
8753
|
const data = [];
|
|
7958
8754
|
this.positions = new Float32Array(n * 3);
|
|
7959
8755
|
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, minZ = Infinity, maxZ = -Infinity;
|
|
7960
|
-
const bump = (
|
|
7961
|
-
if (
|
|
7962
|
-
if (
|
|
7963
|
-
if (
|
|
7964
|
-
if (
|
|
7965
|
-
if (
|
|
7966
|
-
if (
|
|
8756
|
+
const bump = (x2, y2, z2) => {
|
|
8757
|
+
if (x2 < minX) minX = x2;
|
|
8758
|
+
if (x2 > maxX) maxX = x2;
|
|
8759
|
+
if (y2 < minY) minY = y2;
|
|
8760
|
+
if (y2 > maxY) maxY = y2;
|
|
8761
|
+
if (z2 < minZ) minZ = z2;
|
|
8762
|
+
if (z2 > maxZ) maxZ = z2;
|
|
7967
8763
|
};
|
|
7968
8764
|
for (let i = 0; i < n; i++) {
|
|
7969
|
-
const bx =
|
|
7970
|
-
const ux =
|
|
8765
|
+
const bx = x[i], by = y[i], bz = z[i];
|
|
8766
|
+
const ux = u[i] * scale, uy = v[i] * scale, uz = w[i] * scale;
|
|
7971
8767
|
const tx = bx + ux, ty = by + uy, tz = bz + uz;
|
|
7972
8768
|
this.positions[i * 3] = bx;
|
|
7973
8769
|
this.positions[i * 3 + 1] = by;
|
|
@@ -7994,17 +8790,18 @@ var Quiver3DLayer = class {
|
|
|
7994
8790
|
}
|
|
7995
8791
|
this.vertCount = data.length / 6;
|
|
7996
8792
|
this.b3 = { x: [minX, maxX], y: [minY, maxY], z: [minZ, maxZ] };
|
|
7997
|
-
this.vao = gl.createVertexArray();
|
|
7998
|
-
this.buffer = gl.createBuffer();
|
|
7999
8793
|
gl.bindVertexArray(this.vao);
|
|
8000
8794
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
8001
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data),
|
|
8795
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), this.usage);
|
|
8002
8796
|
gl.enableVertexAttribArray(0);
|
|
8003
8797
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
8004
8798
|
gl.enableVertexAttribArray(1);
|
|
8005
8799
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
|
|
8006
8800
|
gl.bindVertexArray(null);
|
|
8007
|
-
|
|
8801
|
+
}
|
|
8802
|
+
/** Stream a new vector field (arrows rebuilt). Call `plot.refresh()` after. */
|
|
8803
|
+
setData(x, y, z, u, v, w) {
|
|
8804
|
+
this.build(x, y, z, u, v, w);
|
|
8008
8805
|
}
|
|
8009
8806
|
bounds3() {
|
|
8010
8807
|
return this.vertCount ? this.b3 : null;
|
|
@@ -8042,7 +8839,7 @@ out vec3 vColor;
|
|
|
8042
8839
|
out vec3 vN;
|
|
8043
8840
|
void main() { vColor = aColor; vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
8044
8841
|
);
|
|
8045
|
-
var
|
|
8842
|
+
var FRAG19 = (
|
|
8046
8843
|
/* glsl */
|
|
8047
8844
|
`#version 300 es
|
|
8048
8845
|
precision highp float;
|
|
@@ -8075,12 +8872,12 @@ in vec3 vColor;
|
|
|
8075
8872
|
out vec4 outColor;
|
|
8076
8873
|
void main() { outColor = vec4(vColor, 1.0); }`
|
|
8077
8874
|
);
|
|
8078
|
-
var
|
|
8079
|
-
function
|
|
8080
|
-
let p =
|
|
8875
|
+
var programCache20 = /* @__PURE__ */ new WeakMap();
|
|
8876
|
+
function getProgram17(gl) {
|
|
8877
|
+
let p = programCache20.get(gl);
|
|
8081
8878
|
if (!p) {
|
|
8082
|
-
p = createProgram(gl, VERT16,
|
|
8083
|
-
|
|
8879
|
+
p = createProgram(gl, VERT16, FRAG19);
|
|
8880
|
+
programCache20.set(gl, p);
|
|
8084
8881
|
}
|
|
8085
8882
|
return p;
|
|
8086
8883
|
}
|
|
@@ -8093,21 +8890,33 @@ function getWireProgram(gl) {
|
|
|
8093
8890
|
}
|
|
8094
8891
|
return p;
|
|
8095
8892
|
}
|
|
8096
|
-
var
|
|
8893
|
+
var counter24 = 0;
|
|
8097
8894
|
var SurfaceLayer = class {
|
|
8098
8895
|
constructor(gl, opts) {
|
|
8099
8896
|
this.vDomain = [0, 1];
|
|
8100
8897
|
this.lightDir = [0.5, 1, 0.35];
|
|
8101
8898
|
this.ambient = 0.35;
|
|
8102
|
-
this.id = `surface-${
|
|
8899
|
+
this.id = `surface-${counter24++}`;
|
|
8103
8900
|
this.gl = gl;
|
|
8104
8901
|
this.wireframe = opts.wireframe ?? false;
|
|
8105
|
-
this.program = this.wireframe ? getWireProgram(gl) :
|
|
8902
|
+
this.program = this.wireframe ? getWireProgram(gl) : getProgram17(gl);
|
|
8106
8903
|
this.name = opts.name;
|
|
8107
8904
|
this.cmapName = opts.colormap ?? "viridis";
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8905
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
8906
|
+
this.vao = gl.createVertexArray();
|
|
8907
|
+
this.buffer = gl.createBuffer();
|
|
8908
|
+
this.build(opts.values, opts.cols, opts.rows, opts.extentX, opts.extentZ);
|
|
8909
|
+
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uLightDir", "uAmbient"]);
|
|
8910
|
+
}
|
|
8911
|
+
/** Build the mesh (positions/normals/colors or wireframe) and (re)upload the vertex buffer. */
|
|
8912
|
+
build(values, cols, rows, extentX, extentZ) {
|
|
8913
|
+
const gl = this.gl;
|
|
8914
|
+
this.cols = cols;
|
|
8915
|
+
this.rows = rows;
|
|
8916
|
+
const [x0, x1] = extentX ?? [0, cols - 1];
|
|
8917
|
+
const [z0, z1] = extentZ ?? [0, rows - 1];
|
|
8918
|
+
this.extentX = [x0, x1];
|
|
8919
|
+
this.extentZ = [z0, z1];
|
|
8111
8920
|
const dxWorld = (x1 - x0) / Math.max(1, cols - 1);
|
|
8112
8921
|
const dzWorld = (z1 - z0) / Math.max(1, rows - 1);
|
|
8113
8922
|
let vmin = Infinity, vmax = -Infinity;
|
|
@@ -8117,7 +8926,7 @@ var SurfaceLayer = class {
|
|
|
8117
8926
|
if (v > vmax) vmax = v;
|
|
8118
8927
|
}
|
|
8119
8928
|
const span = vmax - vmin || 1;
|
|
8120
|
-
const cmap = colormap(
|
|
8929
|
+
const cmap = colormap(this.cmapName);
|
|
8121
8930
|
const wx = (c) => x0 + c / (cols - 1) * (x1 - x0);
|
|
8122
8931
|
const wz = (r) => z0 + r / (rows - 1) * (z1 - z0);
|
|
8123
8932
|
const at = (c, r) => values[r * cols + c];
|
|
@@ -8131,12 +8940,8 @@ var SurfaceLayer = class {
|
|
|
8131
8940
|
const data = [];
|
|
8132
8941
|
this.b3 = { x: [x0, x1], y: [vmin, vmax], z: [z0, z1] };
|
|
8133
8942
|
this.vDomain = [vmin, vmax];
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
this.vao = vao;
|
|
8137
|
-
this.buffer = buffer;
|
|
8138
|
-
gl.bindVertexArray(vao);
|
|
8139
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
8943
|
+
gl.bindVertexArray(this.vao);
|
|
8944
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
8140
8945
|
if (this.wireframe) {
|
|
8141
8946
|
const vertL = (c, r) => {
|
|
8142
8947
|
const [cr, cg, cb] = cmap((at(c, r) - vmin) / span);
|
|
@@ -8155,7 +8960,7 @@ var SurfaceLayer = class {
|
|
|
8155
8960
|
}
|
|
8156
8961
|
}
|
|
8157
8962
|
this.vertexCount = data.length / 6;
|
|
8158
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data),
|
|
8963
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), this.usage);
|
|
8159
8964
|
gl.enableVertexAttribArray(0);
|
|
8160
8965
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
8161
8966
|
gl.enableVertexAttribArray(1);
|
|
@@ -8177,7 +8982,7 @@ var SurfaceLayer = class {
|
|
|
8177
8982
|
}
|
|
8178
8983
|
}
|
|
8179
8984
|
this.vertexCount = data.length / 9;
|
|
8180
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data),
|
|
8985
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), this.usage);
|
|
8181
8986
|
gl.enableVertexAttribArray(0);
|
|
8182
8987
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 36, 0);
|
|
8183
8988
|
gl.enableVertexAttribArray(1);
|
|
@@ -8186,7 +8991,16 @@ var SurfaceLayer = class {
|
|
|
8186
8991
|
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 36, 24);
|
|
8187
8992
|
}
|
|
8188
8993
|
gl.bindVertexArray(null);
|
|
8189
|
-
|
|
8994
|
+
}
|
|
8995
|
+
/** Stream a new height grid (mesh/normals/wireframe rebuilt). Call `plot.refresh()` after. */
|
|
8996
|
+
setData(values, opts) {
|
|
8997
|
+
this.build(
|
|
8998
|
+
values,
|
|
8999
|
+
opts?.cols ?? this.cols,
|
|
9000
|
+
opts?.rows ?? this.rows,
|
|
9001
|
+
opts?.extentX ?? this.extentX,
|
|
9002
|
+
opts?.extentZ ?? this.extentZ
|
|
9003
|
+
);
|
|
8190
9004
|
}
|
|
8191
9005
|
bounds3() {
|
|
8192
9006
|
return this.b3;
|
|
@@ -8231,7 +9045,7 @@ uniform mat4 uMVP;
|
|
|
8231
9045
|
out vec3 vLocal;
|
|
8232
9046
|
void main() { vLocal = aLocal; gl_Position = uMVP * vec4(aPos, 1.0); }`
|
|
8233
9047
|
);
|
|
8234
|
-
var
|
|
9048
|
+
var FRAG20 = (
|
|
8235
9049
|
/* glsl */
|
|
8236
9050
|
`#version 300 es
|
|
8237
9051
|
precision highp float;
|
|
@@ -8269,12 +9083,12 @@ void main() {
|
|
|
8269
9083
|
outColor = acc; // premultiplied
|
|
8270
9084
|
}`
|
|
8271
9085
|
);
|
|
8272
|
-
var
|
|
8273
|
-
function
|
|
8274
|
-
let p =
|
|
9086
|
+
var programCache21 = /* @__PURE__ */ new WeakMap();
|
|
9087
|
+
function getProgram18(gl) {
|
|
9088
|
+
let p = programCache21.get(gl);
|
|
8275
9089
|
if (!p) {
|
|
8276
|
-
p = createProgram(gl, VERT17,
|
|
8277
|
-
|
|
9090
|
+
p = createProgram(gl, VERT17, FRAG20);
|
|
9091
|
+
programCache21.set(gl, p);
|
|
8278
9092
|
}
|
|
8279
9093
|
return p;
|
|
8280
9094
|
}
|
|
@@ -8296,45 +9110,35 @@ var CUBE_FACES = [
|
|
|
8296
9110
|
[0, 3, 7, 0, 7, 4],
|
|
8297
9111
|
[1, 5, 6, 1, 6, 2]
|
|
8298
9112
|
];
|
|
8299
|
-
var
|
|
9113
|
+
var counter25 = 0;
|
|
8300
9114
|
var VolumeLayer = class {
|
|
8301
9115
|
constructor(gl, opts) {
|
|
9116
|
+
this.vDomain = [0, 1];
|
|
8302
9117
|
this.camLocal = [0.5, 0.5, 2];
|
|
8303
|
-
this.id = `volume-${
|
|
9118
|
+
this.id = `volume-${counter25++}`;
|
|
8304
9119
|
this.gl = gl;
|
|
8305
|
-
this.program =
|
|
9120
|
+
this.program = getProgram18(gl);
|
|
8306
9121
|
this.name = opts.name;
|
|
9122
|
+
this.usage = bufferUsage(gl, opts.renderType);
|
|
8307
9123
|
this.density = opts.density ?? 1;
|
|
8308
9124
|
this.cmapName = opts.colormap ?? "viridis";
|
|
9125
|
+
this.dims = opts.dims;
|
|
9126
|
+
this.domainOpt = opts.domain;
|
|
8309
9127
|
const [nx, ny, nz] = opts.dims;
|
|
8310
9128
|
const ex = opts.extent?.x ?? [0, nx - 1];
|
|
8311
9129
|
const ey = opts.extent?.y ?? [0, ny - 1];
|
|
8312
9130
|
const ez = opts.extent?.z ?? [0, nz - 1];
|
|
8313
9131
|
this.ext = { x: ex, y: ey, z: ez };
|
|
8314
9132
|
this.b3 = { x: ex, y: ey, z: ez };
|
|
8315
|
-
let vmin = opts.domain?.[0] ?? Infinity;
|
|
8316
|
-
let vmax = opts.domain?.[1] ?? -Infinity;
|
|
8317
|
-
if (!opts.domain) {
|
|
8318
|
-
for (let i = 0; i < opts.values.length; i++) {
|
|
8319
|
-
const v = opts.values[i];
|
|
8320
|
-
if (v < vmin) vmin = v;
|
|
8321
|
-
if (v > vmax) vmax = v;
|
|
8322
|
-
}
|
|
8323
|
-
}
|
|
8324
|
-
this.vDomain = [vmin, vmax];
|
|
8325
|
-
const span = vmax - vmin || 1;
|
|
8326
|
-
const voxels = new Uint8Array(nx * ny * nz);
|
|
8327
|
-
for (let i = 0; i < voxels.length; i++) voxels[i] = Math.max(0, Math.min(255, Math.round((opts.values[i] - vmin) / span * 255)));
|
|
8328
9133
|
this.tex = gl.createTexture();
|
|
8329
9134
|
gl.bindTexture(gl.TEXTURE_3D, this.tex);
|
|
8330
|
-
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
8331
|
-
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, nx, ny, nz, 0, gl.RED, gl.UNSIGNED_BYTE, voxels);
|
|
8332
9135
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
8333
9136
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
8334
9137
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
8335
9138
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
8336
9139
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
|
|
8337
9140
|
gl.bindTexture(gl.TEXTURE_3D, null);
|
|
9141
|
+
this.uploadVolume(opts.values);
|
|
8338
9142
|
const cmap = colormap(this.cmapName);
|
|
8339
9143
|
const lutPix = new Uint8Array(256 * 4);
|
|
8340
9144
|
for (let i = 0; i < 256; i++) {
|
|
@@ -8365,7 +9169,7 @@ var VolumeLayer = class {
|
|
|
8365
9169
|
this.buffer = gl.createBuffer();
|
|
8366
9170
|
gl.bindVertexArray(this.vao);
|
|
8367
9171
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
8368
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data),
|
|
9172
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), this.usage);
|
|
8369
9173
|
gl.enableVertexAttribArray(0);
|
|
8370
9174
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
|
|
8371
9175
|
gl.enableVertexAttribArray(1);
|
|
@@ -8373,6 +9177,32 @@ var VolumeLayer = class {
|
|
|
8373
9177
|
gl.bindVertexArray(null);
|
|
8374
9178
|
this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uVolume", "uLUT", "uCamLocal", "uDensity"]);
|
|
8375
9179
|
}
|
|
9180
|
+
/** Normalize the scalar field and (re)upload it into the R8 3D texture. */
|
|
9181
|
+
uploadVolume(values) {
|
|
9182
|
+
const gl = this.gl;
|
|
9183
|
+
const [nx, ny, nz] = this.dims;
|
|
9184
|
+
let vmin = this.domainOpt?.[0] ?? Infinity;
|
|
9185
|
+
let vmax = this.domainOpt?.[1] ?? -Infinity;
|
|
9186
|
+
if (!this.domainOpt) {
|
|
9187
|
+
for (let i = 0; i < values.length; i++) {
|
|
9188
|
+
const v = values[i];
|
|
9189
|
+
if (v < vmin) vmin = v;
|
|
9190
|
+
if (v > vmax) vmax = v;
|
|
9191
|
+
}
|
|
9192
|
+
}
|
|
9193
|
+
this.vDomain = [vmin, vmax];
|
|
9194
|
+
const span = vmax - vmin || 1;
|
|
9195
|
+
const voxels = new Uint8Array(nx * ny * nz);
|
|
9196
|
+
for (let i = 0; i < voxels.length; i++) voxels[i] = Math.max(0, Math.min(255, Math.round((values[i] - vmin) / span * 255)));
|
|
9197
|
+
gl.bindTexture(gl.TEXTURE_3D, this.tex);
|
|
9198
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
9199
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, nx, ny, nz, 0, gl.RED, gl.UNSIGNED_BYTE, voxels);
|
|
9200
|
+
gl.bindTexture(gl.TEXTURE_3D, null);
|
|
9201
|
+
}
|
|
9202
|
+
/** Stream a new scalar volume (same dims); re-uploads the 3D texture. Call `plot.refresh()` after. */
|
|
9203
|
+
setData(values) {
|
|
9204
|
+
this.uploadVolume(values);
|
|
9205
|
+
}
|
|
8376
9206
|
bounds3() {
|
|
8377
9207
|
return this.b3;
|
|
8378
9208
|
}
|
|
@@ -9212,6 +10042,401 @@ var Plot3D = class {
|
|
|
9212
10042
|
this.controlsEl = panel;
|
|
9213
10043
|
}
|
|
9214
10044
|
};
|
|
10045
|
+
|
|
10046
|
+
// src/finance/indicators.ts
|
|
10047
|
+
function sma(values, period) {
|
|
10048
|
+
const n = values.length;
|
|
10049
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10050
|
+
if (period <= 0 || n < period) return out;
|
|
10051
|
+
let sum = 0;
|
|
10052
|
+
for (let i = 0; i < n; i++) {
|
|
10053
|
+
sum += values[i];
|
|
10054
|
+
if (i >= period) sum -= values[i - period];
|
|
10055
|
+
if (i >= period - 1) out[i] = sum / period;
|
|
10056
|
+
}
|
|
10057
|
+
return out;
|
|
10058
|
+
}
|
|
10059
|
+
function wma(values, period) {
|
|
10060
|
+
const n = values.length;
|
|
10061
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10062
|
+
if (period <= 0 || n < period) return out;
|
|
10063
|
+
const denom = period * (period + 1) / 2;
|
|
10064
|
+
for (let i = period - 1; i < n; i++) {
|
|
10065
|
+
let acc = 0;
|
|
10066
|
+
for (let k = 0; k < period; k++) acc += values[i - period + 1 + k] * (k + 1);
|
|
10067
|
+
out[i] = acc / denom;
|
|
10068
|
+
}
|
|
10069
|
+
return out;
|
|
10070
|
+
}
|
|
10071
|
+
function ema(values, period) {
|
|
10072
|
+
const n = values.length;
|
|
10073
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10074
|
+
if (period <= 0 || n < period) return out;
|
|
10075
|
+
const alpha = 2 / (period + 1);
|
|
10076
|
+
let seed = 0;
|
|
10077
|
+
for (let i = 0; i < period; i++) seed += values[i];
|
|
10078
|
+
let prev = seed / period;
|
|
10079
|
+
out[period - 1] = prev;
|
|
10080
|
+
for (let i = period; i < n; i++) {
|
|
10081
|
+
prev = values[i] * alpha + prev * (1 - alpha);
|
|
10082
|
+
out[i] = prev;
|
|
10083
|
+
}
|
|
10084
|
+
return out;
|
|
10085
|
+
}
|
|
10086
|
+
function rollingStd(values, period) {
|
|
10087
|
+
const n = values.length;
|
|
10088
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10089
|
+
if (period <= 0 || n < period) return out;
|
|
10090
|
+
let sum = 0, sumSq = 0;
|
|
10091
|
+
for (let i = 0; i < n; i++) {
|
|
10092
|
+
const v = values[i];
|
|
10093
|
+
sum += v;
|
|
10094
|
+
sumSq += v * v;
|
|
10095
|
+
if (i >= period) {
|
|
10096
|
+
const old = values[i - period];
|
|
10097
|
+
sum -= old;
|
|
10098
|
+
sumSq -= old * old;
|
|
10099
|
+
}
|
|
10100
|
+
if (i >= period - 1) {
|
|
10101
|
+
const mean = sum / period;
|
|
10102
|
+
const variance = Math.max(0, sumSq / period - mean * mean);
|
|
10103
|
+
out[i] = Math.sqrt(variance);
|
|
10104
|
+
}
|
|
10105
|
+
}
|
|
10106
|
+
return out;
|
|
10107
|
+
}
|
|
10108
|
+
function bollinger(close, period = 20, k = 2) {
|
|
10109
|
+
const middle = sma(close, period);
|
|
10110
|
+
const sd = rollingStd(close, period);
|
|
10111
|
+
const n = close.length;
|
|
10112
|
+
const upper = new Float64Array(n).fill(NaN);
|
|
10113
|
+
const lower = new Float64Array(n).fill(NaN);
|
|
10114
|
+
for (let i = 0; i < n; i++) {
|
|
10115
|
+
if (!Number.isNaN(middle[i])) {
|
|
10116
|
+
upper[i] = middle[i] + k * sd[i];
|
|
10117
|
+
lower[i] = middle[i] - k * sd[i];
|
|
10118
|
+
}
|
|
10119
|
+
}
|
|
10120
|
+
return { middle, upper, lower };
|
|
10121
|
+
}
|
|
10122
|
+
function rsi(close, period = 14) {
|
|
10123
|
+
const n = close.length;
|
|
10124
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10125
|
+
if (period <= 0 || n <= period) return out;
|
|
10126
|
+
let gain = 0, loss = 0;
|
|
10127
|
+
for (let i = 1; i <= period; i++) {
|
|
10128
|
+
const ch = close[i] - close[i - 1];
|
|
10129
|
+
if (ch >= 0) gain += ch;
|
|
10130
|
+
else loss -= ch;
|
|
10131
|
+
}
|
|
10132
|
+
let avgGain = gain / period, avgLoss = loss / period;
|
|
10133
|
+
out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
|
|
10134
|
+
for (let i = period + 1; i < n; i++) {
|
|
10135
|
+
const ch = close[i] - close[i - 1];
|
|
10136
|
+
const g = ch > 0 ? ch : 0, l = ch < 0 ? -ch : 0;
|
|
10137
|
+
avgGain = (avgGain * (period - 1) + g) / period;
|
|
10138
|
+
avgLoss = (avgLoss * (period - 1) + l) / period;
|
|
10139
|
+
out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
|
|
10140
|
+
}
|
|
10141
|
+
return out;
|
|
10142
|
+
}
|
|
10143
|
+
function macd(close, fast = 12, slow = 26, signalPeriod = 9) {
|
|
10144
|
+
const n = close.length;
|
|
10145
|
+
const emaFast = ema(close, fast);
|
|
10146
|
+
const emaSlow = ema(close, slow);
|
|
10147
|
+
const line = new Float64Array(n).fill(NaN);
|
|
10148
|
+
for (let i = 0; i < n; i++) {
|
|
10149
|
+
if (!Number.isNaN(emaFast[i]) && !Number.isNaN(emaSlow[i])) line[i] = emaFast[i] - emaSlow[i];
|
|
10150
|
+
}
|
|
10151
|
+
const firstValid = line.findIndex((v) => !Number.isNaN(v));
|
|
10152
|
+
const signal = new Float64Array(n).fill(NaN);
|
|
10153
|
+
if (firstValid >= 0) {
|
|
10154
|
+
const seg = line.subarray(firstValid);
|
|
10155
|
+
const sig = ema(seg, signalPeriod);
|
|
10156
|
+
for (let i = 0; i < sig.length; i++) signal[firstValid + i] = sig[i];
|
|
10157
|
+
}
|
|
10158
|
+
const histogram2 = new Float64Array(n).fill(NaN);
|
|
10159
|
+
for (let i = 0; i < n; i++) {
|
|
10160
|
+
if (!Number.isNaN(line[i]) && !Number.isNaN(signal[i])) histogram2[i] = line[i] - signal[i];
|
|
10161
|
+
}
|
|
10162
|
+
return { macd: line, signal, histogram: histogram2 };
|
|
10163
|
+
}
|
|
10164
|
+
function vwap(high, low, close, volume) {
|
|
10165
|
+
const n = Math.min(high.length, low.length, close.length, volume.length);
|
|
10166
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10167
|
+
let cumPV = 0, cumV = 0;
|
|
10168
|
+
for (let i = 0; i < n; i++) {
|
|
10169
|
+
const typical = (high[i] + low[i] + close[i]) / 3;
|
|
10170
|
+
cumPV += typical * volume[i];
|
|
10171
|
+
cumV += volume[i];
|
|
10172
|
+
out[i] = cumV === 0 ? NaN : cumPV / cumV;
|
|
10173
|
+
}
|
|
10174
|
+
return out;
|
|
10175
|
+
}
|
|
10176
|
+
function trueRange(high, low, close) {
|
|
10177
|
+
const n = Math.min(high.length, low.length, close.length);
|
|
10178
|
+
const out = new Float64Array(n);
|
|
10179
|
+
if (n > 0) out[0] = high[0] - low[0];
|
|
10180
|
+
for (let i = 1; i < n; i++) {
|
|
10181
|
+
const pc = close[i - 1];
|
|
10182
|
+
out[i] = Math.max(high[i] - low[i], Math.abs(high[i] - pc), Math.abs(low[i] - pc));
|
|
10183
|
+
}
|
|
10184
|
+
return out;
|
|
10185
|
+
}
|
|
10186
|
+
function atr(high, low, close, period = 14) {
|
|
10187
|
+
const tr = trueRange(high, low, close);
|
|
10188
|
+
const n = tr.length;
|
|
10189
|
+
const out = new Float64Array(n).fill(NaN);
|
|
10190
|
+
if (n < period || period <= 0) return out;
|
|
10191
|
+
let sum = 0;
|
|
10192
|
+
for (let i = 0; i < period; i++) sum += tr[i];
|
|
10193
|
+
let prev = sum / period;
|
|
10194
|
+
out[period - 1] = prev;
|
|
10195
|
+
for (let i = period; i < n; i++) {
|
|
10196
|
+
prev = (prev * (period - 1) + tr[i]) / period;
|
|
10197
|
+
out[i] = prev;
|
|
10198
|
+
}
|
|
10199
|
+
return out;
|
|
10200
|
+
}
|
|
10201
|
+
function firstFinite(values) {
|
|
10202
|
+
for (let i = 0; i < values.length; i++) if (!Number.isNaN(values[i])) return i;
|
|
10203
|
+
return -1;
|
|
10204
|
+
}
|
|
10205
|
+
|
|
10206
|
+
// src/finance/transforms.ts
|
|
10207
|
+
function heikinAshi(d) {
|
|
10208
|
+
const n = Math.min(d.open.length, d.high.length, d.low.length, d.close.length);
|
|
10209
|
+
const open = new Float64Array(n), high = new Float64Array(n), low = new Float64Array(n), close = new Float64Array(n);
|
|
10210
|
+
for (let i = 0; i < n; i++) {
|
|
10211
|
+
const o = d.open[i], h = d.high[i], l = d.low[i], c = d.close[i];
|
|
10212
|
+
const haClose = (o + h + l + c) / 4;
|
|
10213
|
+
const haOpen = i === 0 ? (o + c) / 2 : (open[i - 1] + close[i - 1]) / 2;
|
|
10214
|
+
close[i] = haClose;
|
|
10215
|
+
open[i] = haOpen;
|
|
10216
|
+
high[i] = Math.max(h, haOpen, haClose);
|
|
10217
|
+
low[i] = Math.min(l, haOpen, haClose);
|
|
10218
|
+
}
|
|
10219
|
+
return { open, high, low, close };
|
|
10220
|
+
}
|
|
10221
|
+
function renko(close, brickSize) {
|
|
10222
|
+
const bricks = [];
|
|
10223
|
+
const n = close.length;
|
|
10224
|
+
if (n === 0 || brickSize <= 0) return bricks;
|
|
10225
|
+
let base = close[0];
|
|
10226
|
+
let x = 0;
|
|
10227
|
+
for (let i = 1; i < n; i++) {
|
|
10228
|
+
let diff = close[i] - base;
|
|
10229
|
+
while (Math.abs(diff) >= brickSize) {
|
|
10230
|
+
const up = diff > 0;
|
|
10231
|
+
const open = base;
|
|
10232
|
+
const next = base + (up ? brickSize : -brickSize);
|
|
10233
|
+
bricks.push({ x: x++, open, close: next, up });
|
|
10234
|
+
base = next;
|
|
10235
|
+
diff = close[i] - base;
|
|
10236
|
+
}
|
|
10237
|
+
}
|
|
10238
|
+
return bricks;
|
|
10239
|
+
}
|
|
10240
|
+
function lineBreak(close, lines = 3) {
|
|
10241
|
+
const bricks = [];
|
|
10242
|
+
const n = close.length;
|
|
10243
|
+
if (n === 0) return bricks;
|
|
10244
|
+
const ends = [close[0]];
|
|
10245
|
+
let x = 0;
|
|
10246
|
+
for (let i = 1; i < n; i++) {
|
|
10247
|
+
const c = close[i];
|
|
10248
|
+
const recent = ends.slice(-lines);
|
|
10249
|
+
const hi = Math.max(...recent);
|
|
10250
|
+
const lo = Math.min(...recent);
|
|
10251
|
+
if (c > hi) {
|
|
10252
|
+
bricks.push({ x: x++, open: ends[ends.length - 1], close: c, up: true });
|
|
10253
|
+
ends.push(c);
|
|
10254
|
+
} else if (c < lo) {
|
|
10255
|
+
bricks.push({ x: x++, open: ends[ends.length - 1], close: c, up: false });
|
|
10256
|
+
ends.push(c);
|
|
10257
|
+
}
|
|
10258
|
+
}
|
|
10259
|
+
return bricks;
|
|
10260
|
+
}
|
|
10261
|
+
function pointAndFigure(high, low, boxSize, reversal = 3) {
|
|
10262
|
+
const n = Math.min(high.length, low.length);
|
|
10263
|
+
const cols = [];
|
|
10264
|
+
if (n === 0 || boxSize <= 0) return cols;
|
|
10265
|
+
const q = (p) => Math.floor(p / boxSize) * boxSize;
|
|
10266
|
+
let dir = null;
|
|
10267
|
+
let top = q(high[0]), bottom = q(low[0]);
|
|
10268
|
+
let col = 0;
|
|
10269
|
+
const pushCol = (kind, from, to) => {
|
|
10270
|
+
const boxes = [];
|
|
10271
|
+
for (let b = Math.min(from, to); b <= Math.max(from, to) + 1e-9; b += boxSize) boxes.push(b + boxSize / 2);
|
|
10272
|
+
cols.push({ col, kind, from, to, boxes });
|
|
10273
|
+
};
|
|
10274
|
+
for (let i = 1; i < n; i++) {
|
|
10275
|
+
const h = q(high[i]), l = q(low[i]);
|
|
10276
|
+
if (dir === null) {
|
|
10277
|
+
dir = h - bottom >= l - bottom ? "X" : "O";
|
|
10278
|
+
}
|
|
10279
|
+
if (dir === "X") {
|
|
10280
|
+
if (h > top) top = h;
|
|
10281
|
+
else if (bottom - l >= reversal * boxSize || top - l >= reversal * boxSize) {
|
|
10282
|
+
pushCol("X", bottom, top);
|
|
10283
|
+
col++;
|
|
10284
|
+
dir = "O";
|
|
10285
|
+
bottom = l;
|
|
10286
|
+
}
|
|
10287
|
+
} else {
|
|
10288
|
+
if (l < bottom) bottom = l;
|
|
10289
|
+
else if (h - top >= reversal * boxSize || h - bottom >= reversal * boxSize) {
|
|
10290
|
+
pushCol("O", top, bottom);
|
|
10291
|
+
col++;
|
|
10292
|
+
dir = "X";
|
|
10293
|
+
top = h;
|
|
10294
|
+
}
|
|
10295
|
+
}
|
|
10296
|
+
}
|
|
10297
|
+
if (dir === "X") pushCol("X", bottom, top);
|
|
10298
|
+
else if (dir === "O") pushCol("O", top, bottom);
|
|
10299
|
+
return cols;
|
|
10300
|
+
}
|
|
10301
|
+
function volumeProfile(price, volume, bins = 24) {
|
|
10302
|
+
const n = Math.min(price.length, volume.length);
|
|
10303
|
+
let lo = Infinity, hi = -Infinity;
|
|
10304
|
+
for (let i = 0; i < n; i++) {
|
|
10305
|
+
const p = price[i];
|
|
10306
|
+
if (p < lo) lo = p;
|
|
10307
|
+
if (p > hi) hi = p;
|
|
10308
|
+
}
|
|
10309
|
+
if (!isFinite(lo) || !isFinite(hi) || bins <= 0) {
|
|
10310
|
+
return { levels: new Float64Array(0), volume: new Float64Array(0), binSize: 0, priceMin: 0, priceMax: 0, pocIndex: -1 };
|
|
10311
|
+
}
|
|
10312
|
+
if (hi === lo) hi = lo + 1;
|
|
10313
|
+
const binSize = (hi - lo) / bins;
|
|
10314
|
+
const levels = new Float64Array(bins);
|
|
10315
|
+
const vol = new Float64Array(bins);
|
|
10316
|
+
for (let b = 0; b < bins; b++) levels[b] = lo + (b + 0.5) * binSize;
|
|
10317
|
+
for (let i = 0; i < n; i++) {
|
|
10318
|
+
let b = Math.floor((price[i] - lo) / binSize);
|
|
10319
|
+
if (b < 0) b = 0;
|
|
10320
|
+
else if (b >= bins) b = bins - 1;
|
|
10321
|
+
vol[b] += volume[i];
|
|
10322
|
+
}
|
|
10323
|
+
let pocIndex = 0;
|
|
10324
|
+
for (let b = 1; b < bins; b++) if (vol[b] > vol[pocIndex]) pocIndex = b;
|
|
10325
|
+
return { levels, volume: vol, binSize, priceMin: lo, priceMax: hi, pocIndex };
|
|
10326
|
+
}
|
|
10327
|
+
function depth(bids, asks) {
|
|
10328
|
+
const b = Array.from({ length: bids.length }, (_, i) => bids[i]).sort((p, q) => q[0] - p[0]);
|
|
10329
|
+
const a = Array.from({ length: asks.length }, (_, i) => asks[i]).sort((p, q) => p[0] - q[0]);
|
|
10330
|
+
const bidPrice = new Float64Array(b.length), bidCum = new Float64Array(b.length);
|
|
10331
|
+
let cum = 0;
|
|
10332
|
+
for (let i = 0; i < b.length; i++) {
|
|
10333
|
+
cum += b[i][1];
|
|
10334
|
+
bidPrice[i] = b[i][0];
|
|
10335
|
+
bidCum[i] = cum;
|
|
10336
|
+
}
|
|
10337
|
+
bidPrice.reverse();
|
|
10338
|
+
bidCum.reverse();
|
|
10339
|
+
const askPrice = new Float64Array(a.length), askCum = new Float64Array(a.length);
|
|
10340
|
+
cum = 0;
|
|
10341
|
+
for (let i = 0; i < a.length; i++) {
|
|
10342
|
+
cum += a[i][1];
|
|
10343
|
+
askPrice[i] = a[i][0];
|
|
10344
|
+
askCum[i] = cum;
|
|
10345
|
+
}
|
|
10346
|
+
return { bidPrice, bidCum, askPrice, askCum };
|
|
10347
|
+
}
|
|
10348
|
+
|
|
10349
|
+
// src/finance/charts.ts
|
|
10350
|
+
function addHeikinAshi(plot, opts) {
|
|
10351
|
+
const ha = heikinAshi(opts);
|
|
10352
|
+
return plot.addCandlestick({ ...opts, open: ha.open, high: ha.high, low: ha.low, close: ha.close });
|
|
10353
|
+
}
|
|
10354
|
+
function addRenko(plot, opts) {
|
|
10355
|
+
const bricks = renko(opts.close, opts.brickSize);
|
|
10356
|
+
const n = bricks.length;
|
|
10357
|
+
const x = new Float64Array(n), open = new Float64Array(n), high = new Float64Array(n);
|
|
10358
|
+
const low = new Float64Array(n), close = new Float64Array(n);
|
|
10359
|
+
for (let i = 0; i < n; i++) {
|
|
10360
|
+
const b = bricks[i];
|
|
10361
|
+
x[i] = b.x;
|
|
10362
|
+
open[i] = b.open;
|
|
10363
|
+
close[i] = b.close;
|
|
10364
|
+
high[i] = Math.max(b.open, b.close);
|
|
10365
|
+
low[i] = Math.min(b.open, b.close);
|
|
10366
|
+
}
|
|
10367
|
+
return plot.addCandlestick({
|
|
10368
|
+
x,
|
|
10369
|
+
open,
|
|
10370
|
+
high,
|
|
10371
|
+
low,
|
|
10372
|
+
close,
|
|
10373
|
+
width: 1,
|
|
10374
|
+
upColor: opts.upColor,
|
|
10375
|
+
downColor: opts.downColor,
|
|
10376
|
+
wickWidth: 1e-3,
|
|
10377
|
+
name: opts.name,
|
|
10378
|
+
yAxis: opts.yAxis,
|
|
10379
|
+
renderType: opts.renderType
|
|
10380
|
+
});
|
|
10381
|
+
}
|
|
10382
|
+
function addVolumeProfile(plot, opts) {
|
|
10383
|
+
const vp = volumeProfile(opts.price, opts.volume, opts.bins ?? 24);
|
|
10384
|
+
const base = opts.color ?? "#60a5fa";
|
|
10385
|
+
const colors = opts.pocColor ? Array.from(vp.levels, (_, i) => i === vp.pocIndex ? opts.pocColor : base) : void 0;
|
|
10386
|
+
return plot.addBar({
|
|
10387
|
+
x: vp.levels,
|
|
10388
|
+
y: vp.volume,
|
|
10389
|
+
orientation: "h",
|
|
10390
|
+
base: 0,
|
|
10391
|
+
width: vp.binSize * 0.85,
|
|
10392
|
+
color: base,
|
|
10393
|
+
colors,
|
|
10394
|
+
name: opts.name,
|
|
10395
|
+
yAxis: opts.yAxis,
|
|
10396
|
+
renderType: opts.renderType
|
|
10397
|
+
});
|
|
10398
|
+
}
|
|
10399
|
+
function addBollinger(plot, opts) {
|
|
10400
|
+
const { middle, upper, lower } = bollinger(opts.close, opts.period ?? 20, opts.k ?? 2);
|
|
10401
|
+
const start = firstFinite(middle);
|
|
10402
|
+
const slice = (a) => start < 0 ? a.subarray(0, 0) : a.subarray(start);
|
|
10403
|
+
const n = middle.length;
|
|
10404
|
+
const xs = new Float64Array(Math.max(0, n - Math.max(0, start)));
|
|
10405
|
+
for (let i = 0; i < xs.length; i++) xs[i] = opts.x[start + i];
|
|
10406
|
+
const color = opts.color ?? "#a78bfa";
|
|
10407
|
+
const width = opts.width ?? 1;
|
|
10408
|
+
const rt = opts.renderType;
|
|
10409
|
+
const band = opts.bandColor ? plot.addArea({ x: xs, y: slice(upper), base: slice(lower), color: opts.bandColor, yAxis: opts.yAxis, renderType: rt }) : void 0;
|
|
10410
|
+
return {
|
|
10411
|
+
band,
|
|
10412
|
+
upper: plot.addLine({ x: xs, y: slice(upper), color, width, yAxis: opts.yAxis, renderType: rt }),
|
|
10413
|
+
middle: plot.addLine({ x: xs, y: slice(middle), color, width, name: "BB", yAxis: opts.yAxis, renderType: rt }),
|
|
10414
|
+
lower: plot.addLine({ x: xs, y: slice(lower), color, width, yAxis: opts.yAxis, renderType: rt })
|
|
10415
|
+
};
|
|
10416
|
+
}
|
|
10417
|
+
function addDepth(plot, opts) {
|
|
10418
|
+
const d = depth(opts.bids, opts.asks);
|
|
10419
|
+
return {
|
|
10420
|
+
bid: plot.addArea({
|
|
10421
|
+
x: d.bidPrice,
|
|
10422
|
+
y: d.bidCum,
|
|
10423
|
+
base: 0,
|
|
10424
|
+
color: opts.bidColor ?? "rgba(38,166,154,0.4)",
|
|
10425
|
+
name: "bids",
|
|
10426
|
+
yAxis: opts.yAxis,
|
|
10427
|
+
renderType: opts.renderType
|
|
10428
|
+
}),
|
|
10429
|
+
ask: plot.addArea({
|
|
10430
|
+
x: d.askPrice,
|
|
10431
|
+
y: d.askCum,
|
|
10432
|
+
base: 0,
|
|
10433
|
+
color: opts.askColor ?? "rgba(239,83,80,0.4)",
|
|
10434
|
+
name: "asks",
|
|
10435
|
+
yAxis: opts.yAxis,
|
|
10436
|
+
renderType: opts.renderType
|
|
10437
|
+
})
|
|
10438
|
+
};
|
|
10439
|
+
}
|
|
9215
10440
|
export {
|
|
9216
10441
|
AreaLayer,
|
|
9217
10442
|
Axis,
|
|
@@ -9232,6 +10457,8 @@ export {
|
|
|
9232
10457
|
LineLayer,
|
|
9233
10458
|
LinearScale,
|
|
9234
10459
|
LogScale,
|
|
10460
|
+
OhlcLayer,
|
|
10461
|
+
OrdinalTimeScale,
|
|
9235
10462
|
PatchesLayer,
|
|
9236
10463
|
PieLayer,
|
|
9237
10464
|
Plot,
|
|
@@ -9247,30 +10474,54 @@ export {
|
|
|
9247
10474
|
TRANSFORM_UNIFORMS,
|
|
9248
10475
|
TimeScale,
|
|
9249
10476
|
VolumeLayer,
|
|
10477
|
+
addBollinger,
|
|
10478
|
+
addDepth,
|
|
10479
|
+
addHeikinAshi,
|
|
10480
|
+
addRenko,
|
|
10481
|
+
addVolumeProfile,
|
|
10482
|
+
atr,
|
|
9250
10483
|
autoTicks,
|
|
10484
|
+
bollinger,
|
|
9251
10485
|
boxStats,
|
|
10486
|
+
bufferUsage,
|
|
9252
10487
|
colormap,
|
|
9253
10488
|
colormapLUT,
|
|
9254
10489
|
createProgram,
|
|
9255
10490
|
createToolbar,
|
|
9256
10491
|
darkTheme,
|
|
9257
10492
|
defaultFormat,
|
|
10493
|
+
depth,
|
|
9258
10494
|
earcut,
|
|
10495
|
+
ema,
|
|
9259
10496
|
fft,
|
|
10497
|
+
firstFinite,
|
|
9260
10498
|
forceLayout,
|
|
10499
|
+
heikinAshi,
|
|
9261
10500
|
histogram,
|
|
9262
10501
|
kde,
|
|
9263
10502
|
lightTheme,
|
|
10503
|
+
lineBreak,
|
|
10504
|
+
linkX,
|
|
10505
|
+
macd,
|
|
9264
10506
|
makeScale,
|
|
9265
10507
|
marchingCubes,
|
|
9266
10508
|
parseColor,
|
|
10509
|
+
pointAndFigure,
|
|
9267
10510
|
quantileSorted,
|
|
10511
|
+
renko,
|
|
9268
10512
|
resolveAxisStyle,
|
|
9269
10513
|
resolveTicks,
|
|
10514
|
+
rollingStd,
|
|
10515
|
+
rsi,
|
|
9270
10516
|
setTransformUniforms,
|
|
10517
|
+
sma,
|
|
9271
10518
|
spectrogram,
|
|
9272
10519
|
toColorCss,
|
|
10520
|
+
trueRange,
|
|
9273
10521
|
uniformLocations,
|
|
9274
|
-
|
|
10522
|
+
volumeProfile,
|
|
10523
|
+
vwap,
|
|
10524
|
+
withMinorTicks,
|
|
10525
|
+
wma
|
|
9275
10526
|
};
|
|
9276
10527
|
//# sourceMappingURL=index.js.map
|