@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.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, gl.STATIC_DRAW);
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.gl.DYNAMIC_DRAW);
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
- uniform vec4 uColor;
377
+ in vec4 vColor;
371
378
  out vec4 outColor;
372
- void main() { outColor = vec4(uColor.rgb * uColor.a, uColor.a); }`
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, gl.STATIC_DRAW);
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, "uColor"]);
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 (for streaming). */
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.gl.DYNAMIC_DRAW);
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
- const groups = opts.groups;
727
- const width = opts.width ?? 0.6;
728
- const hw = width / 2;
729
- const showBox = opts.box !== false;
730
- const showViolin = opts.violin === true;
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
- const vao = gl.createVertexArray();
822
- const buffer = gl.createBuffer();
823
- this.vao = vao;
824
- this.buffer = buffer;
825
- gl.bindVertexArray(vao);
826
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
827
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(all), gl.STATIC_DRAW);
828
- gl.enableVertexAttribArray(0);
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
- const up = toColor(opts.upColor ?? "#26a69a");
945
- const down = toColor(opts.downColor ?? "#ef5350");
946
- this.colorCss = toColorCss(up);
947
- const n = Math.min(opts.x.length, opts.open.length, opts.high.length, opts.low.length, opts.close.length);
948
- this.count = n;
949
- const width = opts.width ?? medianSpacing2(opts.x, n) * 0.7;
950
- this.xRef = n > 0 ? opts.x[0] : 0;
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, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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 toColor(input) {
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 FRAG5 = (
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 programCache4 = /* @__PURE__ */ new WeakMap();
1166
- function getProgram4(gl) {
1167
- let p = programCache4.get(gl);
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, FRAG5);
1170
- programCache4.set(gl, p);
1541
+ p = createProgram(gl, VERT4, FRAG6);
1542
+ programCache5.set(gl, p);
1171
1543
  }
1172
1544
  return p;
1173
1545
  }
1174
- var counter5 = 0;
1546
+ var counter6 = 0;
1175
1547
  var ContourLayer = class {
1176
1548
  constructor(gl, opts) {
1177
- this.id = `contour-${counter5++}`;
1549
+ this.vertexCount = 0;
1550
+ this.id = `contour-${counter6++}`;
1178
1551
  this.gl = gl;
1179
- this.program = getProgram4(gl);
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
- const { cols, rows, values } = opts;
1183
- const [x0, x1] = opts.extent.x, [y0, y1] = opts.extent.y;
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 opts.levels === "number" ? opts.levels : 8;
1193
- const levels = Array.isArray(opts.levels) ? opts.levels : Array.from({ length: count }, (_, i) => vmin + (vmax - vmin) * (i + 1) / (count + 1));
1194
- const cmap = colormap(opts.colormap ?? "viridis");
1195
- const fixed = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : null;
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
- const vao = gl.createVertexArray();
1227
- const buffer = gl.createBuffer();
1228
- this.vao = vao;
1229
- this.buffer = buffer;
1230
- gl.bindVertexArray(vao);
1231
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
1232
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
1233
- gl.enableVertexAttribArray(0);
1234
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 24, 0);
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 SEG_VERT = (
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 SEG_CORNERS2 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
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, SEG_VERT, SOLID_FRAG),
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 counter6 = 0;
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-${counter6++}`;
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
- const n = Math.min(opts.x.length, opts.y.length);
1353
- this.xRef = n > 0 ? opts.x[0] : 0;
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, SEG_CORNERS2, gl.STATIC_DRAW);
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, new Float32Array(segs), gl.STATIC_DRAW);
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, new Float32Array(caps), gl.STATIC_DRAW);
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, new Float32Array(band), gl.STATIC_DRAW);
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 programCache5 = /* @__PURE__ */ new WeakMap();
1992
+ var programCache6 = /* @__PURE__ */ new WeakMap();
1526
1993
  function getPrograms(gl) {
1527
- let p = programCache5.get(gl);
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
- programCache5.set(gl, p);
1997
+ programCache6.set(gl, p);
1531
1998
  }
1532
1999
  return p;
1533
2000
  }
1534
- var counter7 = 0;
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-${counter7++}`;
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 n = Math.min(opts.x.length, opts.y.length);
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 ? opts.x[0] : 0;
1556
- this.yRef = n > 0 ? opts.y[0] : 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 = opts.x[i], py = opts.y[i];
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 opts.edges) {
2063
+ for (const [a, b] of edges) {
1572
2064
  if (a < 0 || b < 0 || a >= n || b >= n) continue;
1573
- edgePos.push(opts.x[a] - this.xRef, opts.y[a] - this.yRef, opts.x[b] - this.xRef, opts.y[b] - this.yRef);
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
- const nodeBuf = gl.createBuffer();
1577
- const edgeBuf = gl.createBuffer();
1578
- this.buffers = [nodeBuf, edgeBuf];
1579
- this.nodeVao = gl.createVertexArray();
1580
- gl.bindVertexArray(this.nodeVao);
1581
- gl.bindBuffer(gl.ARRAY_BUFFER, nodeBuf);
1582
- gl.bufferData(gl.ARRAY_BUFFER, nodePos, gl.STATIC_DRAW);
1583
- gl.enableVertexAttribArray(0);
1584
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1585
- this.edgeVao = gl.createVertexArray();
1586
- gl.bindVertexArray(this.edgeVao);
1587
- gl.bindBuffer(gl.ARRAY_BUFFER, edgeBuf);
1588
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(edgePos), gl.STATIC_DRAW);
1589
- gl.enableVertexAttribArray(0);
1590
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1591
- gl.bindVertexArray(null);
1592
- this.edgeUniforms = uniformLocations(gl, this.progs.edge, [...TRANSFORM_UNIFORMS, "uColor"]);
1593
- this.nodeUniforms = uniformLocations(gl, this.progs.node, [...TRANSFORM_UNIFORMS, "uColor", "uSize"]);
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 FRAG6 = (
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 programCache6 = /* @__PURE__ */ new WeakMap();
1651
- function getProgram5(gl) {
1652
- let p = programCache6.get(gl);
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, FRAG6);
1655
- programCache6.set(gl, p);
2151
+ p = createProgram(gl, VERT5, FRAG7);
2152
+ programCache7.set(gl, p);
1656
2153
  }
1657
2154
  return p;
1658
2155
  }
1659
- var counter8 = 0;
2156
+ var counter9 = 0;
1660
2157
  var HeatmapLayer = class {
1661
2158
  constructor(gl, opts) {
1662
- this.id = `heatmap-${counter8++}`;
2159
+ this.id = `heatmap-${counter9++}`;
1663
2160
  this.gl = gl;
1664
- this.program = getProgram5(gl);
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
- const { cols, rows, values } = opts;
1672
- const lut = colormapLUT(opts.colormap ?? "viridis");
1673
- let lo = opts.domain?.[0] ?? Infinity;
1674
- let hi = opts.domain?.[1] ?? -Infinity;
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 FRAG7 = (
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 programCache7 = /* @__PURE__ */ new WeakMap();
1801
- function getProgram6(gl) {
1802
- let p = programCache7.get(gl);
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, FRAG7);
1805
- programCache7.set(gl, p);
2317
+ p = createProgram(gl, VERT6, FRAG8);
2318
+ programCache8.set(gl, p);
1806
2319
  }
1807
2320
  return p;
1808
2321
  }
1809
- var counter9 = 0;
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-${counter9++}`;
2332
+ this.id = `hexbin-${counter10++}`;
1818
2333
  this.gl = gl;
1819
- this.program = getProgram6(gl);
2334
+ this.program = getProgram7(gl);
2335
+ this.usage = bufferUsage(gl, opts.renderType);
1820
2336
  this.yAxis = opts.yAxis ?? "y";
1821
- const n = Math.min(opts.x.length, opts.y.length);
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 x = opts.x[i], y = opts.y[i];
1825
- if (x < minX) minX = x;
1826
- if (x > maxX) maxX = x;
1827
- if (y < minY) minY = y;
1828
- if (y > maxY) maxY = y;
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 = opts.radius ?? ((maxX - minX) / 30 || 1);
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 = opts.x[i], py = opts.y[i];
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 cmap = colormap(opts.colormap ?? "viridis");
1857
- const lo = opts.domain?.[0] ?? 1;
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
- const vao = gl.createVertexArray();
1872
- this.vao = vao;
1873
- gl.bindVertexArray(vao);
1874
- const hexBuf = gl.createBuffer();
1875
- gl.bindBuffer(gl.ARRAY_BUFFER, hexBuf);
1876
- gl.bufferData(gl.ARRAY_BUFFER, HEX, gl.STATIC_DRAW);
1877
- gl.enableVertexAttribArray(0);
1878
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1879
- const centerBuf = gl.createBuffer();
1880
- gl.bindBuffer(gl.ARRAY_BUFFER, centerBuf);
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 FRAG8 = (
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 programCache8 = /* @__PURE__ */ new WeakMap();
1944
- function getProgram7(gl) {
1945
- let p = programCache8.get(gl);
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, FRAG8);
1948
- programCache8.set(gl, p);
2479
+ p = createProgram(gl, VERT7, FRAG9);
2480
+ programCache9.set(gl, p);
1949
2481
  }
1950
2482
  return p;
1951
2483
  }
1952
- var counter10 = 0;
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-${counter10++}`;
2490
+ this.id = `image-${counter11++}`;
1959
2491
  this.gl = gl;
1960
- this.program = getProgram7(gl);
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
- if (typeof opts.source === "string") {
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 programCache9 = /* @__PURE__ */ new WeakMap();
2699
+ var programCache10 = /* @__PURE__ */ new WeakMap();
2158
2700
  function getDecProgram(gl) {
2159
- if (programCache9.has(gl)) return programCache9.get(gl);
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
- programCache9.set(gl, result);
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 FRAG9 = (
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 programCache10 = /* @__PURE__ */ new WeakMap();
2451
- function getProgram8(gl) {
2452
- let p = programCache10.get(gl);
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, FRAG9);
2455
- programCache10.set(gl, p);
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 counter11 = 0;
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-${counter11++}`;
3049
+ this.id = `line-${counter12++}`;
2508
3050
  this.gl = gl;
2509
- this.program = getProgram8(gl);
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, gl.STATIC_DRAW);
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.gl.DYNAMIC_DRAW);
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 programCache11 = /* @__PURE__ */ new WeakMap();
3744
+ var programCache12 = /* @__PURE__ */ new WeakMap();
3260
3745
  function getFillProgram(gl) {
3261
- let p = programCache11.get(gl);
3746
+ let p = programCache12.get(gl);
3262
3747
  if (!p) {
3263
3748
  p = createProgram(gl, FILL_VERT, FILL_FRAG);
3264
- programCache11.set(gl, p);
3749
+ programCache12.set(gl, p);
3265
3750
  }
3266
3751
  return p;
3267
3752
  }
3268
- var counter12 = 0;
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-${counter12++}`;
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
- const defRgba = Array.isArray(defColor) ? defColor : parseColor(defColor);
3768
+ this.defRgba = Array.isArray(defColor) ? defColor : parseColor(defColor);
3284
3769
  this.colorCss = typeof defColor === "string" ? defColor : "#3b82f6";
3285
- const opacity = opts.opacity ?? 1;
3286
- const cmap = opts.colormap ? colormap(opts.colormap) : null;
3287
- let lo = opts.domain?.[0] ?? Infinity;
3288
- let hi = opts.domain?.[1] ?? -Infinity;
3289
- if (cmap && !opts.domain) {
3290
- for (const patch of opts.patches) {
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
- for (const patch of opts.patches) {
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 opts.patches) {
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
- const vao = gl.createVertexArray();
3345
- this.vao = vao;
3346
- gl.bindVertexArray(vao);
3347
- const posBuf = gl.createBuffer();
3348
- gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
3349
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
3350
- gl.enableVertexAttribArray(0);
3351
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
3352
- const colBuf = gl.createBuffer();
3353
- gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
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 counter13 = 0;
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-${counter13++}`;
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
- const R = opts.radius ?? 1;
3405
- const rIn = opts.innerRadius ?? 0;
3406
- const start = opts.startAngle ?? Math.PI / 2;
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
- const n = opts.values.length;
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, opts.values[i]);
3944
+ for (let i = 0; i < n; i++) total += Math.max(0, values[i]);
3415
3945
  if (total <= 0) total = 1;
3416
- const cmap = opts.colormap ? colormap(opts.colormap) : null;
3946
+ const cmap = this.cmap;
3417
3947
  const colorAt = (i) => {
3418
- if (opts.colors?.[i] != null) {
3419
- const c = opts.colors[i];
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, opts.values[i]) / total;
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
- const vao = gl.createVertexArray();
3466
- this.vao = vao;
3467
- gl.bindVertexArray(vao);
3468
- const posBuf = gl.createBuffer();
3469
- gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
3470
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
3471
- gl.enableVertexAttribArray(0);
3472
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
3473
- const colBuf = gl.createBuffer();
3474
- gl.bindBuffer(gl.ARRAY_BUFFER, colBuf);
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 FRAG10 = (
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 SEG_CORNERS3 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
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, FRAG10), head: createProgram(gl, HEAD_VERT, FRAG10) };
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 counter14 = 0;
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-${counter14++}`;
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
- const n = Math.min(opts.x.length, opts.y.length, opts.u.length, opts.v.length);
3594
- this.count = n;
3595
- this.xRef = n > 0 ? opts.x[0] : 0;
3596
- this.yRef = n > 0 ? opts.y[0] : 0;
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, SEG_CORNERS3, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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 FRAG11 = (
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 programCache12 = /* @__PURE__ */ new WeakMap();
3815
- function getProgram9(gl) {
3816
- let p = programCache12.get(gl);
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, FRAG11);
3819
- programCache12.set(gl, p);
4359
+ p = createProgram(gl, VERT9, FRAG12);
4360
+ programCache13.set(gl, p);
3820
4361
  }
3821
4362
  return p;
3822
4363
  }
3823
- var counter15 = 0;
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-${counter15++}`;
4372
+ this.id = `scatter-${counter16++}`;
3832
4373
  this.gl = gl;
3833
- this.program = getProgram9(gl);
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, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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.gl.DYNAMIC_DRAW);
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 SEG_CORNERS4 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
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 counter16 = 0;
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-${counter16++}`;
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
- const n = Math.min(opts.x.length, opts.y.length);
4070
- this.count = n;
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, SEG_CORNERS4, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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, gl.STATIC_DRAW);
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
- function makeScale(type, domain, factors) {
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 xCat = sx.type === "categorical";
4746
- this.scaleX = makeScale(sx.type ?? "linear", sx.domain ?? [0, 1], sx.factors);
4747
- this.autoX = !xCat && sx.domain == null;
4748
- this.initialX = xCat ? this.scaleX.domain : sx.domain ?? null;
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 yCat = sy.type === "categorical";
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: !yCat && sy.domain == null,
4758
- initial: yCat ? yScale.domain : sy.domain ?? null
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 cat = type === "categorical";
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: !cat && domain == null,
5158
- initial: cat ? scale.domain : domain ?? null,
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 FRAG12 = (
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 programCache13 = /* @__PURE__ */ new WeakMap();
6578
- function getProgram10(gl) {
6579
- let p = programCache13.get(gl);
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, FRAG12);
6582
- programCache13.set(gl, p);
7315
+ p = createProgram(gl, VERT10, FRAG13);
7316
+ programCache14.set(gl, p);
6583
7317
  }
6584
7318
  return p;
6585
7319
  }
6586
- function medianSpacing3(vals, n) {
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 counter17 = 0;
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-${counter17++}`;
7338
+ this.id = `bar3d-${counter18++}`;
6605
7339
  this.gl = gl;
6606
- this.program = getProgram10(gl);
7340
+ this.program = getProgram11(gl);
6607
7341
  this.name = opts.name;
6608
- const n = Math.min(opts.x.length, opts.y.length, opts.z.length);
6609
- this.count = n;
6610
- const dx = medianSpacing3(opts.x, n), dz = medianSpacing3(opts.z, n);
6611
- this.width = opts.width ?? Math.min(dx, dz) * 0.7;
6612
- const base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.24, 0.55, 0.96, 1];
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 FRAG13 = (
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 programCache14 = /* @__PURE__ */ new WeakMap();
6747
- function getProgram11(gl) {
6748
- let p = programCache14.get(gl);
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, FRAG13);
6751
- programCache14.set(gl, p);
7499
+ p = createProgram(gl, VERT11, FRAG14);
7500
+ programCache15.set(gl, p);
6752
7501
  }
6753
7502
  return p;
6754
7503
  }
6755
- var counter18 = 0;
7504
+ var counter19 = 0;
6756
7505
  var Contour3DLayer = class {
6757
7506
  constructor(gl, opts) {
6758
7507
  this.cInfo = null;
6759
- this.id = `contour3d-${counter18++}`;
7508
+ this.id = `contour3d-${counter19++}`;
6760
7509
  this.gl = gl;
6761
- this.program = getProgram11(gl);
7510
+ this.program = getProgram12(gl);
6762
7511
  this.name = opts.name;
6763
- const { cols, rows, values } = opts;
6764
- const [x0, x1] = opts.extentX ?? [0, cols - 1];
6765
- const [z0, z1] = opts.extentZ ?? [0, rows - 1];
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 opts.levels === "number" ? opts.levels : 10;
6774
- const levels = Array.isArray(opts.levels) ? opts.levels : Array.from({ length: count }, (_, i) => vmin + lspan * (i + 1) / (count + 1));
6775
- const cmap = colormap(opts.colormap ?? "viridis");
6776
- const fixed = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : null;
6777
- if (!fixed) this.cInfo = { colormap: opts.colormap ?? "viridis", domain: [vmin, vmax], label: opts.name };
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), gl.STATIC_DRAW);
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
- this.uniforms = uniformLocations(gl, this.program, ["uMVP"]);
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 FRAG14 = (
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 programCache15 = /* @__PURE__ */ new WeakMap();
7477
- function getProgram12(gl) {
7478
- let p = programCache15.get(gl);
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, FRAG14);
7481
- programCache15.set(gl, p);
8251
+ p = createProgram(gl, VERT12, FRAG15);
8252
+ programCache16.set(gl, p);
7482
8253
  }
7483
8254
  return p;
7484
8255
  }
7485
- var counter19 = 0;
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-${counter19++}`;
8261
+ this.id = `isosurface-${counter20++}`;
7491
8262
  this.gl = gl;
7492
- this.program = getProgram12(gl);
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
- const { positions, normals } = marchingCubes(opts.values, opts.dims, opts.isoLevel, opts.extent);
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 = opts.extent?.x ?? [0, opts.dims[0] - 1];
7501
- const ey = opts.extent?.y ?? [0, opts.dims[1] - 1];
7502
- const ez = opts.extent?.z ?? [0, opts.dims[2] - 1];
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, gl.STATIC_DRAW);
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
- this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uColor", "uLightDir", "uAmbient", "uOpacity"]);
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 FRAG15 = (
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 programCache16 = /* @__PURE__ */ new WeakMap();
7569
- function getProgram13(gl) {
7570
- let p = programCache16.get(gl);
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, FRAG15);
7573
- programCache16.set(gl, p);
8353
+ p = createProgram(gl, VERT13, FRAG16);
8354
+ programCache17.set(gl, p);
7574
8355
  }
7575
8356
  return p;
7576
8357
  }
7577
- var counter20 = 0;
8358
+ var counter21 = 0;
7578
8359
  var Line3DLayer = class {
7579
8360
  constructor(gl, opts) {
7580
- this.id = `line3d-${counter20++}`;
8361
+ this.id = `line3d-${counter21++}`;
7581
8362
  this.gl = gl;
7582
- this.program = getProgram13(gl);
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, gl.STATIC_DRAW);
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.gl.DYNAMIC_DRAW);
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 FRAG16 = (
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 programCache17 = /* @__PURE__ */ new WeakMap();
7765
- function getProgram14(gl) {
7766
- let p = programCache17.get(gl);
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, FRAG16);
7769
- programCache17.set(gl, p);
8550
+ p = createProgram(gl, VERT14, FRAG17);
8551
+ programCache18.set(gl, p);
7770
8552
  }
7771
8553
  return p;
7772
8554
  }
7773
- var counter21 = 0;
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-${counter21++}`;
8560
+ this.id = `points3d-${counter22++}`;
7779
8561
  this.gl = gl;
7780
- this.program = getProgram14(gl);
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, gl.STATIC_DRAW);
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.gl.DYNAMIC_DRAW);
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 FRAG17 = (
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 programCache18 = /* @__PURE__ */ new WeakMap();
7916
- function getProgram15(gl) {
7917
- let p = programCache18.get(gl);
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, FRAG17);
7920
- programCache18.set(gl, p);
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 counter22 = 0;
8714
+ var counter23 = 0;
7932
8715
  var Quiver3DLayer = class {
7933
8716
  constructor(gl, opts) {
7934
8717
  this.cInfo = null;
7935
- this.id = `quiver3d-${counter22++}`;
8718
+ this.id = `quiver3d-${counter23++}`;
7936
8719
  this.gl = gl;
7937
- this.program = getProgram15(gl);
8720
+ this.program = getProgram16(gl);
7938
8721
  this.name = opts.name;
7939
- const scale = opts.scale ?? 1;
7940
- const headSize = opts.headSize ?? 0.28;
7941
- const n = Math.min(opts.x.length, opts.y.length, opts.z.length, opts.u.length, opts.v.length, opts.w.length);
7942
- const base = opts.color != null ? Array.isArray(opts.color) ? opts.color : parseColor(opts.color) : [0.5, 0.8, 1, 1];
7943
- const cmap = opts.colorBy ? colormap(opts.colorBy.colormap ?? "viridis") : null;
7944
- let lo = opts.colorBy?.domain?.[0] ?? Infinity;
7945
- let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
7946
- const mag = (i) => Math.hypot(opts.u[i], opts.v[i], opts.w[i]);
7947
- if (cmap && !opts.colorBy?.domain) {
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: opts.colorBy.colormap ?? "viridis", domain: [lo, hi], label: opts.name };
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 = (x, y, z) => {
7961
- if (x < minX) minX = x;
7962
- if (x > maxX) maxX = x;
7963
- if (y < minY) minY = y;
7964
- if (y > maxY) maxY = y;
7965
- if (z < minZ) minZ = z;
7966
- if (z > maxZ) maxZ = z;
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 = opts.x[i], by = opts.y[i], bz = opts.z[i];
7970
- const ux = opts.u[i] * scale, uy = opts.v[i] * scale, uz = opts.w[i] * scale;
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), gl.STATIC_DRAW);
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
- this.uniforms = uniformLocations(gl, this.program, ["uMVP"]);
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 FRAG18 = (
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 programCache19 = /* @__PURE__ */ new WeakMap();
8079
- function getProgram16(gl) {
8080
- let p = programCache19.get(gl);
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, FRAG18);
8083
- programCache19.set(gl, p);
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 counter23 = 0;
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-${counter23++}`;
8899
+ this.id = `surface-${counter24++}`;
8103
8900
  this.gl = gl;
8104
8901
  this.wireframe = opts.wireframe ?? false;
8105
- this.program = this.wireframe ? getWireProgram(gl) : getProgram16(gl);
8902
+ this.program = this.wireframe ? getWireProgram(gl) : getProgram17(gl);
8106
8903
  this.name = opts.name;
8107
8904
  this.cmapName = opts.colormap ?? "viridis";
8108
- const { cols, rows, values } = opts;
8109
- const [x0, x1] = opts.extentX ?? [0, cols - 1];
8110
- const [z0, z1] = opts.extentZ ?? [0, rows - 1];
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(opts.colormap ?? "viridis");
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
- const vao = gl.createVertexArray();
8135
- const buffer = gl.createBuffer();
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), gl.STATIC_DRAW);
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), gl.STATIC_DRAW);
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
- this.uniforms = uniformLocations(gl, this.program, ["uMVP", "uLightDir", "uAmbient"]);
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 FRAG19 = (
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 programCache20 = /* @__PURE__ */ new WeakMap();
8273
- function getProgram17(gl) {
8274
- let p = programCache20.get(gl);
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, FRAG19);
8277
- programCache20.set(gl, p);
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 counter24 = 0;
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-${counter24++}`;
9118
+ this.id = `volume-${counter25++}`;
8304
9119
  this.gl = gl;
8305
- this.program = getProgram17(gl);
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), gl.STATIC_DRAW);
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
- withMinorTicks
10522
+ volumeProfile,
10523
+ vwap,
10524
+ withMinorTicks,
10525
+ wma
9275
10526
  };
9276
10527
  //# sourceMappingURL=index.js.map