@photonviz/core 0.1.0 → 0.1.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
@@ -854,6 +854,196 @@ var BoxLayer = class {
854
854
  }
855
855
  };
856
856
 
857
+ // src/layers/candlestick.ts
858
+ var BODY_VERT = (
859
+ /* glsl */
860
+ `#version 300 es
861
+ precision highp float;
862
+ layout(location = 0) in vec2 aCorner; // unit rect [0,1]^2
863
+ layout(location = 1) in vec4 aRect; // (x0,y0,x1,y1) offset data space
864
+ layout(location = 2) in vec4 aColor;
865
+ ${TRANSFORM_GLSL}
866
+ out vec4 vColor;
867
+ void main() {
868
+ vec2 p = mix(aRect.xy, aRect.zw, aCorner);
869
+ vColor = aColor;
870
+ gl_Position = vec4(dataToClip(p), 0.0, 1.0);
871
+ }`
872
+ );
873
+ var WICK_VERT = (
874
+ /* glsl */
875
+ `#version 300 es
876
+ precision highp float;
877
+ layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
878
+ layout(location = 1) in vec4 aSeg; // (x0,y0,x1,y1) offset data space
879
+ layout(location = 2) in vec4 aColor;
880
+ uniform vec2 uResolution;
881
+ uniform float uWidth;
882
+ ${TRANSFORM_GLSL}
883
+ out vec4 vColor;
884
+ void main() {
885
+ vec2 s0 = (dataToClip(aSeg.xy) * 0.5 + 0.5) * uResolution;
886
+ vec2 s1 = (dataToClip(aSeg.zw) * 0.5 + 0.5) * uResolution;
887
+ vec2 d = s1 - s0;
888
+ float len = length(d);
889
+ vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
890
+ vec2 nrm = vec2(-dir.y, dir.x);
891
+ vec2 pos = mix(s0, s1, aCorner.x) + nrm * (aCorner.y * uWidth * 0.5);
892
+ vColor = aColor;
893
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
894
+ }`
895
+ );
896
+ var FRAG4 = (
897
+ /* glsl */
898
+ `#version 300 es
899
+ precision highp float;
900
+ in vec4 vColor;
901
+ out vec4 outColor;
902
+ void main() { outColor = vec4(vColor.rgb * vColor.a, vColor.a); }`
903
+ );
904
+ var RECT_CORNERS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1]);
905
+ var SEG_CORNERS = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
906
+ var cache = /* @__PURE__ */ new WeakMap();
907
+ function programs(gl) {
908
+ let p = cache.get(gl);
909
+ if (!p) {
910
+ p = { body: createProgram(gl, BODY_VERT, FRAG4), wick: createProgram(gl, WICK_VERT, FRAG4) };
911
+ cache.set(gl, p);
912
+ }
913
+ return p;
914
+ }
915
+ function medianSpacing2(x, n) {
916
+ if (n < 2) return 1;
917
+ const diffs = [];
918
+ for (let i = 1; i < n; i++) diffs.push(Math.abs(x[i] - x[i - 1]));
919
+ diffs.sort((a, b) => a - b);
920
+ return diffs[Math.floor(diffs.length / 2)] || 1;
921
+ }
922
+ var counter4 = 0;
923
+ var CandlestickLayer = class {
924
+ constructor(gl, opts) {
925
+ this.buffers = [];
926
+ this.xRef = 0;
927
+ this.yRef = 0;
928
+ this.xBounds = [0, 0];
929
+ this.yBounds = [0, 0];
930
+ this.id = `candlestick-${counter4++}`;
931
+ this.gl = gl;
932
+ this.progs = programs(gl);
933
+ this.name = opts.name ?? this.id;
934
+ this.yAxis = opts.yAxis ?? "y";
935
+ this.wickWidth = opts.wickWidth ?? 1.5;
936
+ const up = toColor(opts.upColor ?? "#26a69a");
937
+ const down = toColor(opts.downColor ?? "#ef5350");
938
+ this.colorCss = toColorCss(up);
939
+ const n = Math.min(opts.x.length, opts.open.length, opts.high.length, opts.low.length, opts.close.length);
940
+ this.count = n;
941
+ const width = opts.width ?? medianSpacing2(opts.x, n) * 0.7;
942
+ this.xRef = n > 0 ? opts.x[0] : 0;
943
+ this.yRef = n > 0 ? opts.open[0] : 0;
944
+ const rects = new Float32Array(n * 4);
945
+ const wicks = new Float32Array(n * 4);
946
+ const colors = new Float32Array(n * 4);
947
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
948
+ for (let i = 0; i < n; i++) {
949
+ const cx = opts.x[i], o = opts.open[i], h = opts.high[i], l = opts.low[i], c = opts.close[i];
950
+ const x0 = cx - width / 2, x1 = cx + width / 2;
951
+ rects[i * 4] = x0 - this.xRef;
952
+ rects[i * 4 + 1] = o - this.yRef;
953
+ rects[i * 4 + 2] = x1 - this.xRef;
954
+ rects[i * 4 + 3] = c - this.yRef;
955
+ wicks[i * 4] = cx - this.xRef;
956
+ wicks[i * 4 + 1] = l - this.yRef;
957
+ wicks[i * 4 + 2] = cx - this.xRef;
958
+ wicks[i * 4 + 3] = h - this.yRef;
959
+ const col = c >= o ? up : down;
960
+ colors[i * 4] = col[0];
961
+ colors[i * 4 + 1] = col[1];
962
+ colors[i * 4 + 2] = col[2];
963
+ colors[i * 4 + 3] = col[3];
964
+ minX = Math.min(minX, x0);
965
+ maxX = Math.max(maxX, x1);
966
+ minY = Math.min(minY, l);
967
+ maxY = Math.max(maxY, h);
968
+ }
969
+ this.xBounds = [minX, maxX];
970
+ this.yBounds = [minY, maxY];
971
+ const cornerRect = gl.createBuffer();
972
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerRect);
973
+ gl.bufferData(gl.ARRAY_BUFFER, RECT_CORNERS, gl.STATIC_DRAW);
974
+ const cornerSeg = gl.createBuffer();
975
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
976
+ gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS, gl.STATIC_DRAW);
977
+ const rectBuf = gl.createBuffer();
978
+ gl.bindBuffer(gl.ARRAY_BUFFER, rectBuf);
979
+ gl.bufferData(gl.ARRAY_BUFFER, rects, gl.STATIC_DRAW);
980
+ const wickBuf = gl.createBuffer();
981
+ gl.bindBuffer(gl.ARRAY_BUFFER, wickBuf);
982
+ gl.bufferData(gl.ARRAY_BUFFER, wicks, gl.STATIC_DRAW);
983
+ const colorBuf = gl.createBuffer();
984
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
985
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
986
+ this.buffers = [cornerRect, cornerSeg, rectBuf, wickBuf, colorBuf];
987
+ this.bodyVao = gl.createVertexArray();
988
+ gl.bindVertexArray(this.bodyVao);
989
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerRect);
990
+ gl.enableVertexAttribArray(0);
991
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
992
+ gl.bindBuffer(gl.ARRAY_BUFFER, rectBuf);
993
+ gl.enableVertexAttribArray(1);
994
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
995
+ gl.vertexAttribDivisor(1, 1);
996
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
997
+ gl.enableVertexAttribArray(2);
998
+ gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
999
+ gl.vertexAttribDivisor(2, 1);
1000
+ this.wickVao = gl.createVertexArray();
1001
+ gl.bindVertexArray(this.wickVao);
1002
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
1003
+ gl.enableVertexAttribArray(0);
1004
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1005
+ gl.bindBuffer(gl.ARRAY_BUFFER, wickBuf);
1006
+ gl.enableVertexAttribArray(1);
1007
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
1008
+ gl.vertexAttribDivisor(1, 1);
1009
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
1010
+ gl.enableVertexAttribArray(2);
1011
+ gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
1012
+ gl.vertexAttribDivisor(2, 1);
1013
+ gl.bindVertexArray(null);
1014
+ this.uBody = uniformLocations(gl, this.progs.body, [...TRANSFORM_UNIFORMS]);
1015
+ this.uWick = uniformLocations(gl, this.progs.wick, [...TRANSFORM_UNIFORMS, "uResolution", "uWidth"]);
1016
+ }
1017
+ bounds() {
1018
+ if (this.count === 0) return null;
1019
+ return { x: this.xBounds, y: this.yBounds };
1020
+ }
1021
+ draw(state) {
1022
+ if (this.count === 0) return;
1023
+ const gl = state.gl;
1024
+ gl.useProgram(this.progs.wick);
1025
+ setTransformUniforms(gl, this.uWick, state.x, state.y, this.xRef, this.yRef);
1026
+ gl.uniform2f(this.uWick.uResolution, state.pixelWidth, state.pixelHeight);
1027
+ gl.uniform1f(this.uWick.uWidth, this.wickWidth * state.dpr);
1028
+ gl.bindVertexArray(this.wickVao);
1029
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
1030
+ gl.bindVertexArray(null);
1031
+ gl.useProgram(this.progs.body);
1032
+ setTransformUniforms(gl, this.uBody, state.x, state.y, this.xRef, this.yRef);
1033
+ gl.bindVertexArray(this.bodyVao);
1034
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
1035
+ gl.bindVertexArray(null);
1036
+ }
1037
+ dispose() {
1038
+ this.gl.deleteVertexArray(this.bodyVao);
1039
+ this.gl.deleteVertexArray(this.wickVao);
1040
+ for (const b of this.buffers) this.gl.deleteBuffer(b);
1041
+ }
1042
+ };
1043
+ function toColor(input) {
1044
+ return Array.isArray(input) ? input : parseColor(input);
1045
+ }
1046
+
857
1047
  // src/color/colormap.ts
858
1048
  var ANCHORS = {
859
1049
  viridis: [
@@ -917,7 +1107,7 @@ ${TRANSFORM_GLSL}
917
1107
  out vec4 vColor;
918
1108
  void main() { vColor = aColor; gl_Position = vec4(dataToClip(aPos), 0.0, 1.0); }`
919
1109
  );
920
- var FRAG4 = (
1110
+ var FRAG5 = (
921
1111
  /* glsl */
922
1112
  `#version 300 es
923
1113
  precision highp float;
@@ -947,15 +1137,15 @@ var programCache4 = /* @__PURE__ */ new WeakMap();
947
1137
  function getProgram4(gl) {
948
1138
  let p = programCache4.get(gl);
949
1139
  if (!p) {
950
- p = createProgram(gl, VERT4, FRAG4);
1140
+ p = createProgram(gl, VERT4, FRAG5);
951
1141
  programCache4.set(gl, p);
952
1142
  }
953
1143
  return p;
954
1144
  }
955
- var counter4 = 0;
1145
+ var counter5 = 0;
956
1146
  var ContourLayer = class {
957
1147
  constructor(gl, opts) {
958
- this.id = `contour-${counter4++}`;
1148
+ this.id = `contour-${counter5++}`;
959
1149
  this.gl = gl;
960
1150
  this.program = getProgram4(gl);
961
1151
  this.yAxis = opts.yAxis ?? "y";
@@ -1036,6 +1226,230 @@ var ContourLayer = class {
1036
1226
  }
1037
1227
  };
1038
1228
 
1229
+ // src/layers/errorbar.ts
1230
+ var SEG_VERT = (
1231
+ /* glsl */
1232
+ `#version 300 es
1233
+ precision highp float;
1234
+ layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
1235
+ layout(location = 1) in vec4 aSeg; // (x0,y0,x1,y1) offset data space
1236
+ uniform vec2 uResolution;
1237
+ uniform float uWidth;
1238
+ ${TRANSFORM_GLSL}
1239
+ void main() {
1240
+ vec2 s0 = (dataToClip(aSeg.xy) * 0.5 + 0.5) * uResolution;
1241
+ vec2 s1 = (dataToClip(aSeg.zw) * 0.5 + 0.5) * uResolution;
1242
+ vec2 d = s1 - s0;
1243
+ float len = length(d);
1244
+ vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
1245
+ vec2 nrm = vec2(-dir.y, dir.x);
1246
+ vec2 pos = mix(s0, s1, aCorner.x) + nrm * (aCorner.y * uWidth * 0.5);
1247
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
1248
+ }`
1249
+ );
1250
+ var CAP_VERT = (
1251
+ /* glsl */
1252
+ `#version 300 es
1253
+ precision highp float;
1254
+ layout(location = 0) in vec2 aCorner; // unit quad [-1,1]^2
1255
+ layout(location = 1) in vec3 aCap; // (cx, cy, orient) offset data space
1256
+ uniform vec2 uResolution;
1257
+ uniform float uCapSize;
1258
+ uniform float uWidth;
1259
+ ${TRANSFORM_GLSL}
1260
+ void main() {
1261
+ vec2 c = (dataToClip(aCap.xy) * 0.5 + 0.5) * uResolution;
1262
+ vec2 h = aCap.z < 0.5 ? vec2(uCapSize * 0.5, uWidth * 0.5) : vec2(uWidth * 0.5, uCapSize * 0.5);
1263
+ vec2 pos = c + aCorner * h;
1264
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
1265
+ }`
1266
+ );
1267
+ var BAND_VERT = (
1268
+ /* glsl */
1269
+ `#version 300 es
1270
+ precision highp float;
1271
+ layout(location = 0) in vec2 aPos; // offset data space
1272
+ ${TRANSFORM_GLSL}
1273
+ void main() { gl_Position = vec4(dataToClip(aPos), 0.0, 1.0); }`
1274
+ );
1275
+ var SOLID_FRAG = (
1276
+ /* glsl */
1277
+ `#version 300 es
1278
+ precision highp float;
1279
+ uniform vec4 uColor;
1280
+ out vec4 outColor;
1281
+ void main() { outColor = vec4(uColor.rgb * uColor.a, uColor.a); }`
1282
+ );
1283
+ var SEG_CORNERS2 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
1284
+ var QUAD_CORNERS = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
1285
+ var cache2 = /* @__PURE__ */ new WeakMap();
1286
+ function programs2(gl) {
1287
+ let p = cache2.get(gl);
1288
+ if (!p) {
1289
+ p = {
1290
+ seg: createProgram(gl, SEG_VERT, SOLID_FRAG),
1291
+ cap: createProgram(gl, CAP_VERT, SOLID_FRAG),
1292
+ band: createProgram(gl, BAND_VERT, SOLID_FRAG)
1293
+ };
1294
+ cache2.set(gl, p);
1295
+ }
1296
+ return p;
1297
+ }
1298
+ var errAt = (e, i) => e == null ? 0 : typeof e === "number" ? e : e[i] ?? 0;
1299
+ var counter6 = 0;
1300
+ var ErrorBarLayer = class {
1301
+ constructor(gl, opts) {
1302
+ this.buffers = [];
1303
+ this.segCount = 0;
1304
+ this.capCount = 0;
1305
+ this.bandVerts = 0;
1306
+ this.xRef = 0;
1307
+ this.yRef = 0;
1308
+ this.xBounds = [0, 0];
1309
+ this.yBounds = [0, 0];
1310
+ this.id = `errorbar-${counter6++}`;
1311
+ this.gl = gl;
1312
+ this.progs = programs2(gl);
1313
+ const colorInput = opts.color ?? "#3b82f6";
1314
+ this.color = Array.isArray(colorInput) ? colorInput : parseColor(colorInput);
1315
+ this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
1316
+ this.name = opts.name ?? this.id;
1317
+ this.yAxis = opts.yAxis ?? "y";
1318
+ this.width = opts.width ?? 1.5;
1319
+ this.capSize = opts.capSize ?? 6;
1320
+ this.bandOpacity = opts.bandOpacity ?? 0.2;
1321
+ this.showBand = opts.band === true;
1322
+ this.showWhiskers = opts.whiskers ?? true;
1323
+ const n = Math.min(opts.x.length, opts.y.length);
1324
+ this.xRef = n > 0 ? opts.x[0] : 0;
1325
+ this.yRef = n > 0 ? opts.y[0] : 0;
1326
+ const segs = [];
1327
+ const caps = [];
1328
+ const lowPts = [];
1329
+ const highPts = [];
1330
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
1331
+ const hasY = opts.yerr != null || opts.yerrLow != null || opts.yerrHigh != null;
1332
+ const hasX = opts.xerr != null;
1333
+ for (let i = 0; i < n; i++) {
1334
+ const x = opts.x[i], y = opts.y[i];
1335
+ const eyLo = opts.yerrLow != null ? errAt(opts.yerrLow, i) : errAt(opts.yerr, i);
1336
+ const eyHi = opts.yerrHigh != null ? errAt(opts.yerrHigh, i) : errAt(opts.yerr, i);
1337
+ const ex = errAt(opts.xerr, i);
1338
+ const yLo = y - eyLo, yHi = y + eyHi;
1339
+ const xLo = x - ex, xHi = x + ex;
1340
+ if (hasY) {
1341
+ segs.push(x - this.xRef, yLo - this.yRef, x - this.xRef, yHi - this.yRef);
1342
+ caps.push(x - this.xRef, yLo - this.yRef, 0, x - this.xRef, yHi - this.yRef, 0);
1343
+ }
1344
+ if (hasX) {
1345
+ segs.push(xLo - this.xRef, y - this.yRef, xHi - this.xRef, y - this.yRef);
1346
+ caps.push(xLo - this.xRef, y - this.yRef, 1, xHi - this.xRef, y - this.yRef, 1);
1347
+ }
1348
+ lowPts.push([x - this.xRef, yLo - this.yRef]);
1349
+ highPts.push([x - this.xRef, yHi - this.yRef]);
1350
+ minX = Math.min(minX, xLo);
1351
+ maxX = Math.max(maxX, xHi);
1352
+ minY = Math.min(minY, yLo);
1353
+ maxY = Math.max(maxY, yHi);
1354
+ }
1355
+ this.xBounds = [minX, maxX];
1356
+ this.yBounds = [minY, maxY];
1357
+ this.segCount = segs.length / 4;
1358
+ this.capCount = this.capSize > 0 ? caps.length / 3 : 0;
1359
+ const band = [];
1360
+ for (let i = 0; i < n; i++) {
1361
+ band.push(highPts[i][0], highPts[i][1], lowPts[i][0], lowPts[i][1]);
1362
+ }
1363
+ this.bandVerts = n * 2;
1364
+ const cornerSeg = gl.createBuffer();
1365
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
1366
+ gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS2, gl.STATIC_DRAW);
1367
+ const cornerQuad = gl.createBuffer();
1368
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerQuad);
1369
+ gl.bufferData(gl.ARRAY_BUFFER, QUAD_CORNERS, gl.STATIC_DRAW);
1370
+ const segBuf = gl.createBuffer();
1371
+ gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
1372
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(segs), gl.STATIC_DRAW);
1373
+ const capBuf = gl.createBuffer();
1374
+ gl.bindBuffer(gl.ARRAY_BUFFER, capBuf);
1375
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(caps), gl.STATIC_DRAW);
1376
+ const bandBuf = gl.createBuffer();
1377
+ gl.bindBuffer(gl.ARRAY_BUFFER, bandBuf);
1378
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(band), gl.STATIC_DRAW);
1379
+ this.buffers = [cornerSeg, cornerQuad, segBuf, capBuf, bandBuf];
1380
+ this.segVao = gl.createVertexArray();
1381
+ gl.bindVertexArray(this.segVao);
1382
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
1383
+ gl.enableVertexAttribArray(0);
1384
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1385
+ gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
1386
+ gl.enableVertexAttribArray(1);
1387
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
1388
+ gl.vertexAttribDivisor(1, 1);
1389
+ this.capVao = gl.createVertexArray();
1390
+ gl.bindVertexArray(this.capVao);
1391
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerQuad);
1392
+ gl.enableVertexAttribArray(0);
1393
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1394
+ gl.bindBuffer(gl.ARRAY_BUFFER, capBuf);
1395
+ gl.enableVertexAttribArray(1);
1396
+ gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
1397
+ gl.vertexAttribDivisor(1, 1);
1398
+ this.bandVao = gl.createVertexArray();
1399
+ gl.bindVertexArray(this.bandVao);
1400
+ gl.bindBuffer(gl.ARRAY_BUFFER, bandBuf);
1401
+ gl.enableVertexAttribArray(0);
1402
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1403
+ gl.bindVertexArray(null);
1404
+ this.uSeg = uniformLocations(gl, this.progs.seg, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uWidth"]);
1405
+ this.uCap = uniformLocations(gl, this.progs.cap, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uCapSize", "uWidth"]);
1406
+ this.uBand = uniformLocations(gl, this.progs.band, [...TRANSFORM_UNIFORMS, "uColor"]);
1407
+ }
1408
+ bounds() {
1409
+ if (this.segCount === 0 && this.bandVerts === 0) return null;
1410
+ return { x: this.xBounds, y: this.yBounds };
1411
+ }
1412
+ draw(state) {
1413
+ const gl = state.gl;
1414
+ const [r, g, b, a] = this.color;
1415
+ if (this.showBand && this.bandVerts >= 4) {
1416
+ gl.useProgram(this.progs.band);
1417
+ setTransformUniforms(gl, this.uBand, state.x, state.y, this.xRef, this.yRef);
1418
+ gl.uniform4f(this.uBand.uColor, r, g, b, a * this.bandOpacity);
1419
+ gl.bindVertexArray(this.bandVao);
1420
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.bandVerts);
1421
+ gl.bindVertexArray(null);
1422
+ }
1423
+ if (this.showWhiskers && this.segCount > 0) {
1424
+ gl.useProgram(this.progs.seg);
1425
+ setTransformUniforms(gl, this.uSeg, state.x, state.y, this.xRef, this.yRef);
1426
+ gl.uniform4f(this.uSeg.uColor, r, g, b, a);
1427
+ gl.uniform2f(this.uSeg.uResolution, state.pixelWidth, state.pixelHeight);
1428
+ gl.uniform1f(this.uSeg.uWidth, this.width * state.dpr);
1429
+ gl.bindVertexArray(this.segVao);
1430
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.segCount);
1431
+ gl.bindVertexArray(null);
1432
+ if (this.capCount > 0) {
1433
+ gl.useProgram(this.progs.cap);
1434
+ setTransformUniforms(gl, this.uCap, state.x, state.y, this.xRef, this.yRef);
1435
+ gl.uniform4f(this.uCap.uColor, r, g, b, a);
1436
+ gl.uniform2f(this.uCap.uResolution, state.pixelWidth, state.pixelHeight);
1437
+ gl.uniform1f(this.uCap.uCapSize, this.capSize * state.dpr);
1438
+ gl.uniform1f(this.uCap.uWidth, this.width * state.dpr);
1439
+ gl.bindVertexArray(this.capVao);
1440
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.capCount);
1441
+ gl.bindVertexArray(null);
1442
+ }
1443
+ }
1444
+ }
1445
+ dispose() {
1446
+ this.gl.deleteVertexArray(this.segVao);
1447
+ this.gl.deleteVertexArray(this.capVao);
1448
+ this.gl.deleteVertexArray(this.bandVao);
1449
+ for (const b of this.buffers) this.gl.deleteBuffer(b);
1450
+ }
1451
+ };
1452
+
1039
1453
  // src/layers/heatmap.ts
1040
1454
  var VERT5 = (
1041
1455
  /* glsl */
@@ -1050,7 +1464,7 @@ void main() {
1050
1464
  gl_Position = vec4(dataToClip(aPos), 0.0, 1.0);
1051
1465
  }`
1052
1466
  );
1053
- var FRAG5 = (
1467
+ var FRAG6 = (
1054
1468
  /* glsl */
1055
1469
  `#version 300 es
1056
1470
  precision highp float;
@@ -1066,15 +1480,15 @@ var programCache5 = /* @__PURE__ */ new WeakMap();
1066
1480
  function getProgram5(gl) {
1067
1481
  let p = programCache5.get(gl);
1068
1482
  if (!p) {
1069
- p = createProgram(gl, VERT5, FRAG5);
1483
+ p = createProgram(gl, VERT5, FRAG6);
1070
1484
  programCache5.set(gl, p);
1071
1485
  }
1072
1486
  return p;
1073
1487
  }
1074
- var counter5 = 0;
1488
+ var counter7 = 0;
1075
1489
  var HeatmapLayer = class {
1076
1490
  constructor(gl, opts) {
1077
- this.id = `heatmap-${counter5++}`;
1491
+ this.id = `heatmap-${counter7++}`;
1078
1492
  this.gl = gl;
1079
1493
  this.program = getProgram5(gl);
1080
1494
  this.yAxis = opts.yAxis ?? "y";
@@ -1191,7 +1605,7 @@ void main() {
1191
1605
  gl_Position = vec4(dataToClip(aCenter + aCorner * uRadius), 0.0, 1.0);
1192
1606
  }`
1193
1607
  );
1194
- var FRAG6 = (
1608
+ var FRAG7 = (
1195
1609
  /* glsl */
1196
1610
  `#version 300 es
1197
1611
  precision highp float;
@@ -1214,12 +1628,12 @@ var programCache6 = /* @__PURE__ */ new WeakMap();
1214
1628
  function getProgram6(gl) {
1215
1629
  let p = programCache6.get(gl);
1216
1630
  if (!p) {
1217
- p = createProgram(gl, VERT6, FRAG6);
1631
+ p = createProgram(gl, VERT6, FRAG7);
1218
1632
  programCache6.set(gl, p);
1219
1633
  }
1220
1634
  return p;
1221
1635
  }
1222
- var counter6 = 0;
1636
+ var counter8 = 0;
1223
1637
  var HexbinLayer = class {
1224
1638
  constructor(gl, opts) {
1225
1639
  this.buffers = [];
@@ -1227,7 +1641,7 @@ var HexbinLayer = class {
1227
1641
  this.yRef = 0;
1228
1642
  this.xBounds = [0, 0];
1229
1643
  this.yBounds = [0, 0];
1230
- this.id = `hexbin-${counter6++}`;
1644
+ this.id = `hexbin-${counter8++}`;
1231
1645
  this.gl = gl;
1232
1646
  this.program = getProgram6(gl);
1233
1647
  this.yAxis = opts.yAxis ?? "y";
@@ -1325,29 +1739,245 @@ var HexbinLayer = class {
1325
1739
  }
1326
1740
  };
1327
1741
 
1328
- // src/layers/line.ts
1329
- var VERT7 = (
1742
+ // src/layers/line-util.ts
1743
+ function decimateIndices(ys, i0, i1, cols) {
1744
+ const out = [i0];
1745
+ const visN = i1 - i0;
1746
+ for (let b = 0; b < cols; b++) {
1747
+ const lo = i0 + Math.floor(visN * b / cols);
1748
+ const hi = i0 + Math.floor(visN * (b + 1) / cols);
1749
+ if (hi <= lo) continue;
1750
+ let iMin = lo, iMax = lo;
1751
+ for (let i = lo; i < hi; i++) {
1752
+ if (ys[i] < ys[iMin]) iMin = i;
1753
+ if (ys[i] > ys[iMax]) iMax = i;
1754
+ }
1755
+ if (iMin < iMax) out.push(iMin, iMax);
1756
+ else out.push(iMax, iMin);
1757
+ }
1758
+ out.push(i1);
1759
+ return out;
1760
+ }
1761
+
1762
+ // src/layers/gpu-decimate.ts
1763
+ var DEC_VERT = (
1330
1764
  /* glsl */
1331
1765
  `#version 300 es
1332
1766
  precision highp float;
1333
- layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
1334
- layout(location = 1) in vec2 aP0;
1335
- layout(location = 2) in vec2 aP1;
1336
- uniform vec2 uResolution;
1337
- uniform float uWidth;
1338
- uniform float uRound;
1339
- ${TRANSFORM_GLSL}
1340
- out vec2 vPix;
1341
- out vec2 vS0;
1342
- out vec2 vS1;
1767
+ uniform sampler2D uPoints;
1768
+ uniform int uTexW;
1769
+ uniform int uI0;
1770
+ uniform int uI1;
1771
+ uniform int uCols;
1772
+ uniform int uMaxIter; // per-column iteration cap; stride-samples beyond it
1773
+ out vec2 vOut;
1774
+
1775
+ vec2 fetchPoint(int i) {
1776
+ return texelFetch(uPoints, ivec2(i % uTexW, i / uTexW), 0).xy;
1777
+ }
1778
+
1343
1779
  void main() {
1344
- vec2 s0 = (dataToClip(aP0) * 0.5 + 0.5) * uResolution;
1345
- vec2 s1 = (dataToClip(aP1) * 0.5 + 0.5) * uResolution;
1346
- vec2 d = s1 - s0;
1347
- float len = length(d);
1348
- vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
1349
- vec2 nrm = vec2(-dir.y, dir.x);
1350
- float hw = uWidth * 0.5 + 1.5; // half width + AA margin
1780
+ int v = gl_VertexID;
1781
+ int last = 2 * uCols + 1;
1782
+ int idx;
1783
+ if (v == 0) {
1784
+ idx = uI0; // left endpoint, so the line reaches the edge
1785
+ } else if (v >= last) {
1786
+ idx = uI1; // right endpoint
1787
+ } else {
1788
+ int col = (v - 1) / 2;
1789
+ int which = (v - 1) - col * 2; // 0 => min-index extreme, 1 => max-index extreme
1790
+ // Float division avoids int32 overflow of visN*col for huge series.
1791
+ float fvisN = float(uI1 - uI0);
1792
+ int lo = uI0 + int(floor(fvisN * float(col) / float(uCols)));
1793
+ int hi = uI0 + int(floor(fvisN * float(col + 1) / float(uCols)));
1794
+ if (hi <= lo) {
1795
+ idx = lo;
1796
+ } else {
1797
+ int stride = 1;
1798
+ int span = hi - lo;
1799
+ if (span > uMaxIter) stride = (span + uMaxIter - 1) / uMaxIter;
1800
+ int iMin = lo, iMax = lo;
1801
+ float yMin = fetchPoint(lo).y, yMax = yMin;
1802
+ for (int i = lo; i < hi; i += stride) {
1803
+ float y = fetchPoint(i).y;
1804
+ if (y < yMin) { yMin = y; iMin = i; }
1805
+ if (y > yMax) { yMax = y; iMax = i; }
1806
+ }
1807
+ // Emit in index order (matches decimateIndices) to preserve envelope shape.
1808
+ idx = (which == 0) ? min(iMin, iMax) : max(iMin, iMax);
1809
+ }
1810
+ }
1811
+ vOut = fetchPoint(idx);
1812
+ gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // rasterizer-discarded
1813
+ }`
1814
+ );
1815
+ var DEC_FRAG = (
1816
+ /* glsl */
1817
+ `#version 300 es
1818
+ precision highp float;
1819
+ out vec4 o;
1820
+ void main() { o = vec4(0.0); }`
1821
+ );
1822
+ var MAX_ITER = 4096;
1823
+ function compile(gl, type, src) {
1824
+ const sh = gl.createShader(type);
1825
+ gl.shaderSource(sh, src);
1826
+ gl.compileShader(sh);
1827
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
1828
+ const log = gl.getShaderInfoLog(sh);
1829
+ gl.deleteShader(sh);
1830
+ throw new Error(`Decimation shader compile error:
1831
+ ${log}`);
1832
+ }
1833
+ return sh;
1834
+ }
1835
+ var programCache7 = /* @__PURE__ */ new WeakMap();
1836
+ function getDecProgram(gl) {
1837
+ if (programCache7.has(gl)) return programCache7.get(gl);
1838
+ let result = null;
1839
+ try {
1840
+ const vs = compile(gl, gl.VERTEX_SHADER, DEC_VERT);
1841
+ const fs = compile(gl, gl.FRAGMENT_SHADER, DEC_FRAG);
1842
+ const program = gl.createProgram();
1843
+ gl.attachShader(program, vs);
1844
+ gl.attachShader(program, fs);
1845
+ gl.transformFeedbackVaryings(program, ["vOut"], gl.INTERLEAVED_ATTRIBS);
1846
+ gl.linkProgram(program);
1847
+ gl.deleteShader(vs);
1848
+ gl.deleteShader(fs);
1849
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
1850
+ gl.deleteProgram(program);
1851
+ } else {
1852
+ const u = {};
1853
+ for (const name of ["uPoints", "uTexW", "uI0", "uI1", "uCols", "uMaxIter"]) {
1854
+ u[name] = gl.getUniformLocation(program, name);
1855
+ }
1856
+ result = { program, u };
1857
+ }
1858
+ } catch {
1859
+ result = null;
1860
+ }
1861
+ programCache7.set(gl, result);
1862
+ return result;
1863
+ }
1864
+ var GpuDecimator = class {
1865
+ // point capacity currently allocated in the draw buffer
1866
+ constructor(gl) {
1867
+ this.tex = null;
1868
+ this.tf = null;
1869
+ this.emptyVao = null;
1870
+ this.texW = 0;
1871
+ this.decCapacity = -1;
1872
+ this.gl = gl;
1873
+ this.prog = getDecProgram(gl);
1874
+ this.supported = this.prog != null;
1875
+ }
1876
+ /**
1877
+ * (Re)upload the series into the point texture. `data` is interleaved
1878
+ * (x-xRef, y-yRef) float32, exactly as the draw buffer stores it. Returns
1879
+ * false and disables the GPU path if the texture can't be allocated.
1880
+ */
1881
+ setPoints(data, n) {
1882
+ if (!this.supported || n === 0) return this.supported;
1883
+ const gl = this.gl;
1884
+ const maxTex = gl.getParameter(gl.MAX_TEXTURE_SIZE);
1885
+ const texW = Math.min(maxTex, Math.max(1, Math.ceil(Math.sqrt(n))));
1886
+ const texH = Math.ceil(n / texW);
1887
+ if (texH > maxTex) return false;
1888
+ this.texW = texW;
1889
+ const packed = data.length >= texW * texH * 2 ? data : new Float32Array(texW * texH * 2);
1890
+ if (packed !== data) packed.set(data.subarray(0, n * 2));
1891
+ try {
1892
+ if (!this.tex) this.tex = gl.createTexture();
1893
+ gl.bindTexture(gl.TEXTURE_2D, this.tex);
1894
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, texW, texH, 0, gl.RG, gl.FLOAT, packed);
1895
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
1896
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
1897
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1898
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1899
+ gl.bindTexture(gl.TEXTURE_2D, null);
1900
+ } catch {
1901
+ return false;
1902
+ }
1903
+ if (!this.tf) this.tf = gl.createTransformFeedback();
1904
+ if (!this.emptyVao) this.emptyVao = gl.createVertexArray();
1905
+ return true;
1906
+ }
1907
+ /**
1908
+ * Run the reduction for the visible window `[i0, i1]` into `decBuf`, matching
1909
+ * the CPU envelope layout: left endpoint, `cols` min/max pairs, right endpoint.
1910
+ * Returns the emitted point count, or null if the GPU path can't run this call.
1911
+ */
1912
+ run(i0, i1, cols, decBuf) {
1913
+ if (!this.supported || !this.tex || !this.prog) return null;
1914
+ const gl = this.gl;
1915
+ const outCount = 2 * cols + 2;
1916
+ if (this.decCapacity < outCount) {
1917
+ gl.bindBuffer(gl.ARRAY_BUFFER, decBuf);
1918
+ gl.bufferData(gl.ARRAY_BUFFER, outCount * 2 * 4, gl.DYNAMIC_COPY);
1919
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
1920
+ this.decCapacity = outCount;
1921
+ }
1922
+ const { program, u } = this.prog;
1923
+ gl.useProgram(program);
1924
+ gl.activeTexture(gl.TEXTURE0);
1925
+ gl.bindTexture(gl.TEXTURE_2D, this.tex);
1926
+ gl.uniform1i(u.uPoints, 0);
1927
+ gl.uniform1i(u.uTexW, this.texW);
1928
+ gl.uniform1i(u.uI0, i0);
1929
+ gl.uniform1i(u.uI1, i1);
1930
+ gl.uniform1i(u.uCols, cols);
1931
+ gl.uniform1i(u.uMaxIter, MAX_ITER);
1932
+ gl.bindVertexArray(this.emptyVao);
1933
+ gl.enable(gl.RASTERIZER_DISCARD);
1934
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.tf);
1935
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, decBuf);
1936
+ gl.beginTransformFeedback(gl.POINTS);
1937
+ gl.drawArrays(gl.POINTS, 0, outCount);
1938
+ gl.endTransformFeedback();
1939
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
1940
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
1941
+ gl.disable(gl.RASTERIZER_DISCARD);
1942
+ gl.bindVertexArray(null);
1943
+ gl.bindTexture(gl.TEXTURE_2D, null);
1944
+ return outCount;
1945
+ }
1946
+ dispose() {
1947
+ const gl = this.gl;
1948
+ if (this.tex) gl.deleteTexture(this.tex);
1949
+ if (this.tf) gl.deleteTransformFeedback(this.tf);
1950
+ if (this.emptyVao) gl.deleteVertexArray(this.emptyVao);
1951
+ this.tex = null;
1952
+ this.tf = null;
1953
+ this.emptyVao = null;
1954
+ }
1955
+ };
1956
+
1957
+ // src/layers/line.ts
1958
+ var GPU_DECIMATE_MIN = 2e5;
1959
+ var VERT7 = (
1960
+ /* glsl */
1961
+ `#version 300 es
1962
+ precision highp float;
1963
+ layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
1964
+ layout(location = 1) in vec2 aP0;
1965
+ layout(location = 2) in vec2 aP1;
1966
+ uniform vec2 uResolution;
1967
+ uniform float uWidth;
1968
+ uniform float uRound;
1969
+ ${TRANSFORM_GLSL}
1970
+ out vec2 vPix;
1971
+ out vec2 vS0;
1972
+ out vec2 vS1;
1973
+ void main() {
1974
+ vec2 s0 = (dataToClip(aP0) * 0.5 + 0.5) * uResolution;
1975
+ vec2 s1 = (dataToClip(aP1) * 0.5 + 0.5) * uResolution;
1976
+ vec2 d = s1 - s0;
1977
+ float len = length(d);
1978
+ vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
1979
+ vec2 nrm = vec2(-dir.y, dir.x);
1980
+ float hw = uWidth * 0.5 + 1.5; // half width + AA margin
1351
1981
  float ext = uRound > 0.5 ? hw : 0.0; // extend for round caps
1352
1982
  vec2 endpoint = mix(s0, s1, aCorner.x);
1353
1983
  vec2 outward = (aCorner.x < 0.5 ? -dir : dir) * ext;
@@ -1356,7 +1986,7 @@ void main() {
1356
1986
  gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
1357
1987
  }`
1358
1988
  );
1359
- var FRAG7 = (
1989
+ var FRAG8 = (
1360
1990
  /* glsl */
1361
1991
  `#version 300 es
1362
1992
  precision highp float;
@@ -1385,13 +2015,112 @@ void main() {
1385
2015
  outColor = vec4(uColor.rgb * uColor.a, uColor.a) * alpha;
1386
2016
  }`
1387
2017
  );
2018
+ var JOIN_VERT = (
2019
+ /* glsl */
2020
+ `#version 300 es
2021
+ precision highp float;
2022
+ layout(location = 0) in vec2 aPrev;
2023
+ layout(location = 1) in vec2 aP0;
2024
+ layout(location = 2) in vec2 aNext;
2025
+ uniform vec2 uResolution;
2026
+ uniform float uWidth;
2027
+ uniform float uMiter; // >0.5 => miter, else bevel
2028
+ uniform float uMiterLimit;
2029
+ ${TRANSFORM_GLSL}
2030
+ out vec2 vPix;
2031
+ flat out vec2 vE0;
2032
+ flat out vec2 vE1;
2033
+ flat out vec2 vInner;
2034
+ void main() {
2035
+ vec2 sp = (dataToClip(aPrev) * 0.5 + 0.5) * uResolution;
2036
+ vec2 s0 = (dataToClip(aP0) * 0.5 + 0.5) * uResolution;
2037
+ vec2 sn = (dataToClip(aNext) * 0.5 + 0.5) * uResolution;
2038
+ vec2 din = s0 - sp;
2039
+ vec2 dout = sn - s0;
2040
+ float inl = length(din), outl = length(dout);
2041
+ vec2 inN = vec2(0.0), outN = vec2(0.0);
2042
+ float crs = 0.0;
2043
+ bool ok = inl > 1e-6 && outl > 1e-6;
2044
+ if (ok) {
2045
+ din /= inl; dout /= outl;
2046
+ inN = vec2(-din.y, din.x);
2047
+ outN = vec2(-dout.y, dout.x);
2048
+ crs = din.x * dout.y - din.y * dout.x;
2049
+ ok = abs(crs) > 1e-6; // collinear => no notch
2050
+ }
2051
+ if (!ok) { // degenerate: cull the wedge
2052
+ vE0 = vec2(0.0); vE1 = vec2(0.0); vInner = vec2(0.0); vPix = vec2(0.0);
2053
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
2054
+ return;
2055
+ }
2056
+ float hw = uWidth * 0.5;
2057
+ float outerSign = crs > 0.0 ? -1.0 : 1.0;
2058
+ vec2 A = s0 + inN * hw * outerSign;
2059
+ vec2 B = s0 + outN * hw * outerSign;
2060
+ vec2 apex = 0.5 * (A + B); // bevel midpoint
2061
+ if (uMiter > 0.5) {
2062
+ vec2 mN = inN + outN;
2063
+ float ml = length(mN);
2064
+ if (ml > 1e-6) {
2065
+ mN /= ml;
2066
+ float denom = dot(mN, outN);
2067
+ if (denom > 1e-3) {
2068
+ float miterLen = 1.0 / denom;
2069
+ if (miterLen <= uMiterLimit) apex = s0 + mN * outerSign * hw * miterLen;
2070
+ }
2071
+ }
2072
+ }
2073
+ int vid = gl_VertexID;
2074
+ vec2 pos;
2075
+ if (vid == 0 || vid == 3) pos = s0;
2076
+ else if (vid == 1) pos = A;
2077
+ else if (vid == 2 || vid == 4) pos = apex;
2078
+ else pos = B; // vid == 5
2079
+ if (vid < 3) { vE0 = A; vE1 = apex; } // triangle [s0, A, apex]
2080
+ else { vE0 = apex; vE1 = B; } // triangle [s0, apex, B]
2081
+ vInner = s0;
2082
+ vPix = pos;
2083
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
2084
+ }`
2085
+ );
2086
+ var JOIN_FRAG = (
2087
+ /* glsl */
2088
+ `#version 300 es
2089
+ precision highp float;
2090
+ in vec2 vPix;
2091
+ flat in vec2 vE0;
2092
+ flat in vec2 vE1;
2093
+ flat in vec2 vInner;
2094
+ uniform vec4 uColor;
2095
+ out vec4 outColor;
2096
+ void main() {
2097
+ vec2 e = vE1 - vE0;
2098
+ float el = length(e);
2099
+ if (el < 1e-6) discard;
2100
+ vec2 n = vec2(-e.y, e.x) / el;
2101
+ float sideInner = dot(vInner - vE0, n) >= 0.0 ? 1.0 : -1.0;
2102
+ float d = dot(vPix - vE0, n) * sideInner; // >0 inside the outer edge
2103
+ float alpha = clamp(d + 0.5, 0.0, 1.0);
2104
+ if (alpha <= 0.0) discard;
2105
+ outColor = vec4(uColor.rgb * uColor.a, uColor.a) * alpha;
2106
+ }`
2107
+ );
1388
2108
  var CORNERS2 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
1389
- var programCache7 = /* @__PURE__ */ new WeakMap();
2109
+ var programCache8 = /* @__PURE__ */ new WeakMap();
1390
2110
  function getProgram7(gl) {
1391
- let p = programCache7.get(gl);
2111
+ let p = programCache8.get(gl);
2112
+ if (!p) {
2113
+ p = createProgram(gl, VERT7, FRAG8);
2114
+ programCache8.set(gl, p);
2115
+ }
2116
+ return p;
2117
+ }
2118
+ var joinProgramCache = /* @__PURE__ */ new WeakMap();
2119
+ function getJoinProgram(gl) {
2120
+ let p = joinProgramCache.get(gl);
1392
2121
  if (!p) {
1393
- p = createProgram(gl, VERT7, FRAG7);
1394
- programCache7.set(gl, p);
2122
+ p = createProgram(gl, JOIN_VERT, JOIN_FRAG);
2123
+ joinProgramCache.set(gl, p);
1395
2124
  }
1396
2125
  return p;
1397
2126
  }
@@ -1424,20 +2153,24 @@ function upperBound(a, v) {
1424
2153
  }
1425
2154
  return lo;
1426
2155
  }
1427
- var counter7 = 0;
2156
+ var counter9 = 0;
1428
2157
  var LineLayer = class {
1429
2158
  constructor(gl, opts) {
2159
+ this.gpuDec = null;
1430
2160
  this.xRef = 0;
1431
2161
  this.yRef = 0;
1432
2162
  this.xBounds = [0, 0];
1433
2163
  this.yBounds = [0, 0];
1434
2164
  this.decKey = "";
1435
2165
  this.decSegments = 0;
1436
- this.id = `line-${counter7++}`;
2166
+ this.id = `line-${counter9++}`;
1437
2167
  this.gl = gl;
1438
2168
  this.program = getProgram7(gl);
2169
+ this.joinProgram = getJoinProgram(gl);
1439
2170
  this.width = opts.width ?? 1.5;
1440
- this.round = (opts.join ?? "round") === "round";
2171
+ this.joinStyle = opts.join ?? "round";
2172
+ this.round = this.joinStyle === "round";
2173
+ this.miterLimit = opts.miterLimit ?? 4;
1441
2174
  this.decimateOn = opts.decimate !== false;
1442
2175
  this.step = opts.step;
1443
2176
  const colorInput = opts.color ?? "#3b82f6";
@@ -1480,154 +2213,443 @@ var LineLayer = class {
1480
2213
  this.decBuf = gl.createBuffer();
1481
2214
  this.fullVao = gl.createVertexArray();
1482
2215
  this.decVao = gl.createVertexArray();
2216
+ this.joinFullVao = gl.createVertexArray();
2217
+ this.joinDecVao = gl.createVertexArray();
1483
2218
  gl.bindBuffer(gl.ARRAY_BUFFER, this.cornerBuf);
1484
2219
  gl.bufferData(gl.ARRAY_BUFFER, CORNERS2, gl.STATIC_DRAW);
1485
2220
  gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuf);
1486
2221
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
1487
2222
  this.configureVao(this.fullVao, this.posBuf);
1488
2223
  this.configureVao(this.decVao, this.decBuf);
2224
+ this.configureJoinVao(this.joinFullVao, this.posBuf);
2225
+ this.configureJoinVao(this.joinDecVao, this.decBuf);
1489
2226
  this.uniforms = uniformLocations(gl, this.program, [
1490
2227
  ...TRANSFORM_UNIFORMS,
1491
2228
  "uColor",
1492
2229
  "uResolution",
1493
- "uWidth",
1494
- "uRound"
2230
+ "uWidth",
2231
+ "uRound"
2232
+ ]);
2233
+ this.joinUniforms = uniformLocations(gl, this.joinProgram, [
2234
+ ...TRANSFORM_UNIFORMS,
2235
+ "uColor",
2236
+ "uResolution",
2237
+ "uWidth",
2238
+ "uMiter",
2239
+ "uMiterLimit"
2240
+ ]);
2241
+ this.syncGpu(data, n);
2242
+ }
2243
+ // Keep the GPU decimation texture in sync for large series; disable it (fall
2244
+ // back to CPU decimation) if the context can't support the path.
2245
+ syncGpu(data, n) {
2246
+ if (!this.decimateOn || n < GPU_DECIMATE_MIN) {
2247
+ this.disposeGpu();
2248
+ return;
2249
+ }
2250
+ if (!this.gpuDec) {
2251
+ const dec = new GpuDecimator(this.gl);
2252
+ if (!dec.supported) return;
2253
+ this.gpuDec = dec;
2254
+ }
2255
+ if (!this.gpuDec.setPoints(data, n)) this.disposeGpu();
2256
+ }
2257
+ disposeGpu() {
2258
+ if (this.gpuDec) {
2259
+ this.gpuDec.dispose();
2260
+ this.gpuDec = null;
2261
+ }
2262
+ }
2263
+ configureVao(vao, pointBuf) {
2264
+ const gl = this.gl;
2265
+ gl.bindVertexArray(vao);
2266
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.cornerBuf);
2267
+ gl.enableVertexAttribArray(0);
2268
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
2269
+ gl.bindBuffer(gl.ARRAY_BUFFER, pointBuf);
2270
+ gl.enableVertexAttribArray(1);
2271
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 8, 0);
2272
+ gl.vertexAttribDivisor(1, 1);
2273
+ gl.enableVertexAttribArray(2);
2274
+ gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8, 8);
2275
+ gl.vertexAttribDivisor(2, 1);
2276
+ gl.bindVertexArray(null);
2277
+ }
2278
+ // Join instances read three consecutive points (prev, p0, next) from the same
2279
+ // buffer via overlapping attribute offsets; instance i covers vertex i+1.
2280
+ configureJoinVao(vao, pointBuf) {
2281
+ const gl = this.gl;
2282
+ gl.bindVertexArray(vao);
2283
+ gl.bindBuffer(gl.ARRAY_BUFFER, pointBuf);
2284
+ for (let loc = 0; loc < 3; loc++) {
2285
+ gl.enableVertexAttribArray(loc);
2286
+ gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 8, loc * 8);
2287
+ gl.vertexAttribDivisor(loc, 1);
2288
+ }
2289
+ gl.bindVertexArray(null);
2290
+ }
2291
+ bounds() {
2292
+ if (this.count === 0) return null;
2293
+ return { x: this.xBounds, y: this.yBounds };
2294
+ }
2295
+ nearestByX(x) {
2296
+ if (this.count === 0) return null;
2297
+ let best = 0, bestDist = Infinity;
2298
+ for (let i = 0; i < this.count; i++) {
2299
+ const d = Math.abs(this.xs[i] - x);
2300
+ if (d < bestDist) {
2301
+ bestDist = d;
2302
+ best = i;
2303
+ }
2304
+ }
2305
+ return { x: this.xs[best], y: this.ys[best], index: best };
2306
+ }
2307
+ /** Replace the series data and re-upload the GPU buffer (for streaming). */
2308
+ setData(x, y) {
2309
+ let n = Math.min(x.length, y.length);
2310
+ let xs = x, ys = y;
2311
+ if (this.step && n >= 2) {
2312
+ const e = stepExpand(x, y, n, this.step);
2313
+ xs = e.xs;
2314
+ ys = e.ys;
2315
+ n = e.xs.length;
2316
+ }
2317
+ this.xs = new Float64Array(n);
2318
+ this.ys = new Float64Array(n);
2319
+ this.xRef = n > 0 ? xs[0] : 0;
2320
+ this.yRef = n > 0 ? ys[0] : 0;
2321
+ const data = new Float32Array(n * 2);
2322
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, mono = true;
2323
+ for (let i = 0; i < n; i++) {
2324
+ const vx = xs[i], vy = ys[i];
2325
+ this.xs[i] = vx;
2326
+ this.ys[i] = vy;
2327
+ data[i * 2] = vx - this.xRef;
2328
+ data[i * 2 + 1] = vy - this.yRef;
2329
+ if (i > 0 && vx < xs[i - 1]) mono = false;
2330
+ if (vx < minX) minX = vx;
2331
+ if (vx > maxX) maxX = vx;
2332
+ if (vy < minY) minY = vy;
2333
+ if (vy > maxY) maxY = vy;
2334
+ }
2335
+ this.xBounds = [minX, maxX];
2336
+ this.yBounds = [minY, maxY];
2337
+ this.count = n;
2338
+ this.monotonic = mono;
2339
+ this.decKey = "";
2340
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.posBuf);
2341
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.gl.DYNAMIC_DRAW);
2342
+ this.syncGpu(data, n);
2343
+ }
2344
+ /**
2345
+ * Rebuild the min/max-decimated buffer for the visible x-window if it changed.
2346
+ * Returns the segment count to draw from `decVao`, or null to draw everything.
2347
+ */
2348
+ decimate(x, cols) {
2349
+ if (!this.decimateOn || !this.monotonic || this.count < 4 * cols) return null;
2350
+ const target = Math.max(2, cols * 2);
2351
+ let i0 = upperBound(this.xs, x.lo) - 1;
2352
+ let i1 = upperBound(this.xs, x.hi);
2353
+ i0 = Math.max(0, i0);
2354
+ i1 = Math.min(this.count - 1, i1);
2355
+ const visN = i1 - i0;
2356
+ if (visN <= target * 1.5) return null;
2357
+ const key = `${i0}:${i1}:${target}`;
2358
+ if (key === this.decKey) return this.decSegments;
2359
+ if (this.gpuDec) {
2360
+ const outCount = this.gpuDec.run(i0, i1, cols, this.decBuf);
2361
+ if (outCount != null) {
2362
+ this.decKey = key;
2363
+ this.decSegments = outCount - 1;
2364
+ return this.decSegments;
2365
+ }
2366
+ this.disposeGpu();
2367
+ }
2368
+ this.decKey = key;
2369
+ const indices = decimateIndices(this.ys, i0, i1, cols);
2370
+ const out = new Float32Array(indices.length * 2);
2371
+ for (let k = 0; k < indices.length; k++) {
2372
+ const i = indices[k];
2373
+ out[k * 2] = this.xs[i] - this.xRef;
2374
+ out[k * 2 + 1] = this.ys[i] - this.yRef;
2375
+ }
2376
+ const gl = this.gl;
2377
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.decBuf);
2378
+ gl.bufferData(gl.ARRAY_BUFFER, out, gl.DYNAMIC_DRAW);
2379
+ this.decSegments = indices.length - 1;
2380
+ return this.decSegments;
2381
+ }
2382
+ draw(state) {
2383
+ if (this.count < 2) return;
2384
+ const gl = state.gl;
2385
+ const decSegs = this.decimate(state.x, Math.max(1, Math.round(state.pixelWidth)));
2386
+ const decimated = decSegs != null;
2387
+ const vao = decimated ? this.decVao : this.fullVao;
2388
+ const segments = decimated ? decSegs : this.count - 1;
2389
+ if (segments < 1) return;
2390
+ const points = segments + 1;
2391
+ gl.useProgram(this.program);
2392
+ setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
2393
+ gl.uniform4f(this.uniforms.uColor, this.color[0], this.color[1], this.color[2], this.color[3]);
2394
+ gl.uniform2f(this.uniforms.uResolution, state.pixelWidth, state.pixelHeight);
2395
+ gl.uniform1f(this.uniforms.uWidth, this.width * state.dpr);
2396
+ gl.uniform1f(this.uniforms.uRound, this.round ? 1 : 0);
2397
+ gl.bindVertexArray(vao);
2398
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segments);
2399
+ gl.bindVertexArray(null);
2400
+ if ((this.joinStyle === "miter" || this.joinStyle === "bevel") && points >= 3) {
2401
+ gl.useProgram(this.joinProgram);
2402
+ setTransformUniforms(gl, this.joinUniforms, state.x, state.y, this.xRef, this.yRef);
2403
+ gl.uniform4f(this.joinUniforms.uColor, this.color[0], this.color[1], this.color[2], this.color[3]);
2404
+ gl.uniform2f(this.joinUniforms.uResolution, state.pixelWidth, state.pixelHeight);
2405
+ gl.uniform1f(this.joinUniforms.uWidth, this.width * state.dpr);
2406
+ gl.uniform1f(this.joinUniforms.uMiter, this.joinStyle === "miter" ? 1 : 0);
2407
+ gl.uniform1f(this.joinUniforms.uMiterLimit, this.miterLimit);
2408
+ gl.bindVertexArray(decimated ? this.joinDecVao : this.joinFullVao);
2409
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, points - 2);
2410
+ gl.bindVertexArray(null);
2411
+ }
2412
+ }
2413
+ dispose() {
2414
+ const gl = this.gl;
2415
+ this.disposeGpu();
2416
+ gl.deleteVertexArray(this.fullVao);
2417
+ gl.deleteVertexArray(this.decVao);
2418
+ gl.deleteVertexArray(this.joinFullVao);
2419
+ gl.deleteVertexArray(this.joinDecVao);
2420
+ gl.deleteBuffer(this.cornerBuf);
2421
+ gl.deleteBuffer(this.posBuf);
2422
+ gl.deleteBuffer(this.decBuf);
2423
+ }
2424
+ };
2425
+
2426
+ // src/layers/quiver.ts
2427
+ var SHAFT_VERT = (
2428
+ /* glsl */
2429
+ `#version 300 es
2430
+ precision highp float;
2431
+ layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
2432
+ layout(location = 1) in vec4 aArrow; // (bx,by,tx,ty) offset data space
2433
+ layout(location = 2) in vec4 aColor;
2434
+ uniform vec2 uResolution;
2435
+ uniform float uWidth;
2436
+ ${TRANSFORM_GLSL}
2437
+ out vec4 vColor;
2438
+ void main() {
2439
+ vec2 s0 = (dataToClip(aArrow.xy) * 0.5 + 0.5) * uResolution;
2440
+ vec2 s1 = (dataToClip(aArrow.zw) * 0.5 + 0.5) * uResolution;
2441
+ vec2 d = s1 - s0;
2442
+ float len = length(d);
2443
+ vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
2444
+ vec2 nrm = vec2(-dir.y, dir.x);
2445
+ vec2 pos = mix(s0, s1, aCorner.x) + nrm * (aCorner.y * uWidth * 0.5);
2446
+ vColor = aColor;
2447
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
2448
+ }`
2449
+ );
2450
+ var HEAD_VERT = (
2451
+ /* glsl */
2452
+ `#version 300 es
2453
+ precision highp float;
2454
+ layout(location = 1) in vec4 aArrow; // (bx,by,tx,ty) offset data space
2455
+ layout(location = 2) in vec4 aColor;
2456
+ uniform vec2 uResolution;
2457
+ uniform float uHeadSize;
2458
+ ${TRANSFORM_GLSL}
2459
+ out vec4 vColor;
2460
+ void main() {
2461
+ vec2 s0 = (dataToClip(aArrow.xy) * 0.5 + 0.5) * uResolution;
2462
+ vec2 s1 = (dataToClip(aArrow.zw) * 0.5 + 0.5) * uResolution;
2463
+ vec2 d = s1 - s0;
2464
+ float len = length(d);
2465
+ vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
2466
+ vec2 nrm = vec2(-dir.y, dir.x);
2467
+ float w = uHeadSize * 0.6;
2468
+ vec2 pos;
2469
+ if (gl_VertexID == 0) pos = s1; // tip
2470
+ else if (gl_VertexID == 1) pos = s1 - dir * uHeadSize + nrm * w; // back-left
2471
+ else pos = s1 - dir * uHeadSize - nrm * w; // back-right
2472
+ vColor = aColor;
2473
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
2474
+ }`
2475
+ );
2476
+ var FRAG9 = (
2477
+ /* glsl */
2478
+ `#version 300 es
2479
+ precision highp float;
2480
+ in vec4 vColor;
2481
+ uniform vec4 uColor;
2482
+ uniform float uUseVertexColor;
2483
+ out vec4 outColor;
2484
+ void main() {
2485
+ vec4 c = uUseVertexColor > 0.5 ? vColor : uColor;
2486
+ outColor = vec4(c.rgb * c.a, c.a);
2487
+ }`
2488
+ );
2489
+ var SEG_CORNERS3 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
2490
+ var cache3 = /* @__PURE__ */ new WeakMap();
2491
+ function programs3(gl) {
2492
+ let p = cache3.get(gl);
2493
+ if (!p) {
2494
+ p = { shaft: createProgram(gl, SHAFT_VERT, FRAG9), head: createProgram(gl, HEAD_VERT, FRAG9) };
2495
+ cache3.set(gl, p);
2496
+ }
2497
+ return p;
2498
+ }
2499
+ var counter10 = 0;
2500
+ var QuiverLayer = class {
2501
+ constructor(gl, opts) {
2502
+ this.buffers = [];
2503
+ this.xRef = 0;
2504
+ this.yRef = 0;
2505
+ this.xBounds = [0, 0];
2506
+ this.yBounds = [0, 0];
2507
+ this.id = `quiver-${counter10++}`;
2508
+ this.gl = gl;
2509
+ this.progs = programs3(gl);
2510
+ const colorInput = opts.color ?? "#3b82f6";
2511
+ this.color = Array.isArray(colorInput) ? colorInput : parseColor(colorInput);
2512
+ this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
2513
+ this.name = opts.name ?? this.id;
2514
+ this.yAxis = opts.yAxis ?? "y";
2515
+ this.width = opts.width ?? 1.5;
2516
+ this.headSize = opts.headSize ?? 9;
2517
+ this.useVertexColor = opts.colorBy != null;
2518
+ const n = Math.min(opts.x.length, opts.y.length, opts.u.length, opts.v.length);
2519
+ this.count = n;
2520
+ this.xRef = n > 0 ? opts.x[0] : 0;
2521
+ this.yRef = n > 0 ? opts.y[0] : 0;
2522
+ let maxMag = 0;
2523
+ for (let i = 0; i < n; i++) maxMag = Math.max(maxMag, Math.hypot(opts.u[i], opts.v[i]));
2524
+ let scale = opts.scale;
2525
+ if (scale == null) {
2526
+ let minX2 = Infinity, maxX2 = -Infinity, minY2 = Infinity, maxY2 = -Infinity;
2527
+ for (let i = 0; i < n; i++) {
2528
+ minX2 = Math.min(minX2, opts.x[i]);
2529
+ maxX2 = Math.max(maxX2, opts.x[i]);
2530
+ minY2 = Math.min(minY2, opts.y[i]);
2531
+ maxY2 = Math.max(maxY2, opts.y[i]);
2532
+ }
2533
+ const diag = Math.hypot(maxX2 - minX2, maxY2 - minY2) || 1;
2534
+ const cell = diag / Math.max(1, Math.sqrt(n));
2535
+ scale = maxMag > 0 ? 0.9 * cell / maxMag : 1;
2536
+ }
2537
+ const arrows = new Float32Array(n * 4);
2538
+ const colors = new Float32Array(n * 4);
2539
+ const cmap = colormap(opts.colorBy?.colormap ?? "viridis");
2540
+ const vals = opts.colorBy?.values;
2541
+ let lo = opts.colorBy?.domain?.[0] ?? Infinity;
2542
+ let hi = opts.colorBy?.domain?.[1] ?? -Infinity;
2543
+ if (this.useVertexColor && !opts.colorBy?.domain) {
2544
+ for (let i = 0; i < n; i++) {
2545
+ const v = vals ? vals[i] : Math.hypot(opts.u[i], opts.v[i]);
2546
+ lo = Math.min(lo, v);
2547
+ hi = Math.max(hi, v);
2548
+ }
2549
+ }
2550
+ const span = hi - lo || 1;
2551
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
2552
+ for (let i = 0; i < n; i++) {
2553
+ const x = opts.x[i], y = opts.y[i];
2554
+ const tx = x + opts.u[i] * scale, ty = y + opts.v[i] * scale;
2555
+ arrows[i * 4] = x - this.xRef;
2556
+ arrows[i * 4 + 1] = y - this.yRef;
2557
+ arrows[i * 4 + 2] = tx - this.xRef;
2558
+ arrows[i * 4 + 3] = ty - this.yRef;
2559
+ if (this.useVertexColor) {
2560
+ const v = vals ? vals[i] : Math.hypot(opts.u[i], opts.v[i]);
2561
+ const [r, g, b] = cmap((v - lo) / span);
2562
+ colors[i * 4] = r;
2563
+ colors[i * 4 + 1] = g;
2564
+ colors[i * 4 + 2] = b;
2565
+ colors[i * 4 + 3] = 1;
2566
+ }
2567
+ minX = Math.min(minX, x, tx);
2568
+ maxX = Math.max(maxX, x, tx);
2569
+ minY = Math.min(minY, y, ty);
2570
+ maxY = Math.max(maxY, y, ty);
2571
+ }
2572
+ this.xBounds = [minX, maxX];
2573
+ this.yBounds = [minY, maxY];
2574
+ const cornerBuf = gl.createBuffer();
2575
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerBuf);
2576
+ gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS3, gl.STATIC_DRAW);
2577
+ const arrowBuf = gl.createBuffer();
2578
+ gl.bindBuffer(gl.ARRAY_BUFFER, arrowBuf);
2579
+ gl.bufferData(gl.ARRAY_BUFFER, arrows, gl.STATIC_DRAW);
2580
+ const colorBuf = gl.createBuffer();
2581
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
2582
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
2583
+ this.buffers = [cornerBuf, arrowBuf, colorBuf];
2584
+ this.shaftVao = gl.createVertexArray();
2585
+ gl.bindVertexArray(this.shaftVao);
2586
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerBuf);
2587
+ gl.enableVertexAttribArray(0);
2588
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
2589
+ this.bindInstanceAttribs(arrowBuf, colorBuf);
2590
+ gl.bindVertexArray(null);
2591
+ this.headVao = gl.createVertexArray();
2592
+ gl.bindVertexArray(this.headVao);
2593
+ this.bindInstanceAttribs(arrowBuf, colorBuf);
2594
+ gl.bindVertexArray(null);
2595
+ this.uShaft = uniformLocations(gl, this.progs.shaft, [
2596
+ ...TRANSFORM_UNIFORMS,
2597
+ "uColor",
2598
+ "uResolution",
2599
+ "uWidth",
2600
+ "uUseVertexColor"
2601
+ ]);
2602
+ this.uHead = uniformLocations(gl, this.progs.head, [
2603
+ ...TRANSFORM_UNIFORMS,
2604
+ "uColor",
2605
+ "uResolution",
2606
+ "uHeadSize",
2607
+ "uUseVertexColor"
1495
2608
  ]);
1496
2609
  }
1497
- configureVao(vao, pointBuf) {
2610
+ bindInstanceAttribs(arrowBuf, colorBuf) {
1498
2611
  const gl = this.gl;
1499
- gl.bindVertexArray(vao);
1500
- gl.bindBuffer(gl.ARRAY_BUFFER, this.cornerBuf);
1501
- gl.enableVertexAttribArray(0);
1502
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1503
- gl.bindBuffer(gl.ARRAY_BUFFER, pointBuf);
2612
+ gl.bindBuffer(gl.ARRAY_BUFFER, arrowBuf);
1504
2613
  gl.enableVertexAttribArray(1);
1505
- gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 8, 0);
2614
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
1506
2615
  gl.vertexAttribDivisor(1, 1);
2616
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
1507
2617
  gl.enableVertexAttribArray(2);
1508
- gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8, 8);
2618
+ gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
1509
2619
  gl.vertexAttribDivisor(2, 1);
1510
- gl.bindVertexArray(null);
1511
2620
  }
1512
2621
  bounds() {
1513
2622
  if (this.count === 0) return null;
1514
2623
  return { x: this.xBounds, y: this.yBounds };
1515
2624
  }
1516
- nearestByX(x) {
1517
- if (this.count === 0) return null;
1518
- let best = 0, bestDist = Infinity;
1519
- for (let i = 0; i < this.count; i++) {
1520
- const d = Math.abs(this.xs[i] - x);
1521
- if (d < bestDist) {
1522
- bestDist = d;
1523
- best = i;
1524
- }
1525
- }
1526
- return { x: this.xs[best], y: this.ys[best], index: best };
1527
- }
1528
- /** Replace the series data and re-upload the GPU buffer (for streaming). */
1529
- setData(x, y) {
1530
- let n = Math.min(x.length, y.length);
1531
- let xs = x, ys = y;
1532
- if (this.step && n >= 2) {
1533
- const e = stepExpand(x, y, n, this.step);
1534
- xs = e.xs;
1535
- ys = e.ys;
1536
- n = e.xs.length;
1537
- }
1538
- this.xs = new Float64Array(n);
1539
- this.ys = new Float64Array(n);
1540
- this.xRef = n > 0 ? xs[0] : 0;
1541
- this.yRef = n > 0 ? ys[0] : 0;
1542
- const data = new Float32Array(n * 2);
1543
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, mono = true;
1544
- for (let i = 0; i < n; i++) {
1545
- const vx = xs[i], vy = ys[i];
1546
- this.xs[i] = vx;
1547
- this.ys[i] = vy;
1548
- data[i * 2] = vx - this.xRef;
1549
- data[i * 2 + 1] = vy - this.yRef;
1550
- if (i > 0 && vx < xs[i - 1]) mono = false;
1551
- if (vx < minX) minX = vx;
1552
- if (vx > maxX) maxX = vx;
1553
- if (vy < minY) minY = vy;
1554
- if (vy > maxY) maxY = vy;
1555
- }
1556
- this.xBounds = [minX, maxX];
1557
- this.yBounds = [minY, maxY];
1558
- this.count = n;
1559
- this.monotonic = mono;
1560
- this.decKey = "";
1561
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.posBuf);
1562
- this.gl.bufferData(this.gl.ARRAY_BUFFER, data, this.gl.DYNAMIC_DRAW);
1563
- }
1564
- /**
1565
- * Rebuild the min/max-decimated buffer for the visible x-window if it changed.
1566
- * Returns the segment count to draw from `decVao`, or null to draw everything.
1567
- */
1568
- decimate(x, cols) {
1569
- if (!this.decimateOn || !this.monotonic || this.count < 4 * cols) return null;
1570
- const target = Math.max(2, cols * 2);
1571
- let i0 = upperBound(this.xs, x.lo) - 1;
1572
- let i1 = upperBound(this.xs, x.hi);
1573
- i0 = Math.max(0, i0);
1574
- i1 = Math.min(this.count - 1, i1);
1575
- const visN = i1 - i0;
1576
- if (visN <= target * 1.5) return null;
1577
- const key = `${i0}:${i1}:${target}`;
1578
- if (key === this.decKey) return this.decSegments;
1579
- this.decKey = key;
1580
- const out = [];
1581
- const push = (i) => out.push(this.xs[i] - this.xRef, this.ys[i] - this.yRef);
1582
- push(i0);
1583
- for (let b = 0; b < cols; b++) {
1584
- const lo = i0 + Math.floor(visN * b / cols);
1585
- const hi = i0 + Math.floor(visN * (b + 1) / cols);
1586
- if (hi <= lo) continue;
1587
- let iMin = lo, iMax = lo;
1588
- for (let i = lo; i < hi; i++) {
1589
- if (this.ys[i] < this.ys[iMin]) iMin = i;
1590
- if (this.ys[i] > this.ys[iMax]) iMax = i;
1591
- }
1592
- if (iMin < iMax) {
1593
- push(iMin);
1594
- push(iMax);
1595
- } else {
1596
- push(iMax);
1597
- push(iMin);
1598
- }
1599
- }
1600
- push(i1);
1601
- const gl = this.gl;
1602
- gl.bindBuffer(gl.ARRAY_BUFFER, this.decBuf);
1603
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(out), gl.DYNAMIC_DRAW);
1604
- this.decSegments = out.length / 2 - 1;
1605
- return this.decSegments;
1606
- }
1607
2625
  draw(state) {
1608
- if (this.count < 2) return;
2626
+ if (this.count === 0) return;
1609
2627
  const gl = state.gl;
1610
- const decSegs = this.decimate(state.x, Math.max(1, Math.round(state.pixelWidth)));
1611
- const vao = decSegs != null ? this.decVao : this.fullVao;
1612
- const segments = decSegs != null ? decSegs : this.count - 1;
1613
- if (segments < 1) return;
1614
- gl.useProgram(this.program);
1615
- setTransformUniforms(gl, this.uniforms, state.x, state.y, this.xRef, this.yRef);
1616
- gl.uniform4f(this.uniforms.uColor, this.color[0], this.color[1], this.color[2], this.color[3]);
1617
- gl.uniform2f(this.uniforms.uResolution, state.pixelWidth, state.pixelHeight);
1618
- gl.uniform1f(this.uniforms.uWidth, this.width * state.dpr);
1619
- gl.uniform1f(this.uniforms.uRound, this.round ? 1 : 0);
1620
- gl.bindVertexArray(vao);
1621
- gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segments);
2628
+ const [r, g, b, a] = this.color;
2629
+ const uvc = this.useVertexColor ? 1 : 0;
2630
+ gl.useProgram(this.progs.shaft);
2631
+ setTransformUniforms(gl, this.uShaft, state.x, state.y, this.xRef, this.yRef);
2632
+ gl.uniform4f(this.uShaft.uColor, r, g, b, a);
2633
+ gl.uniform2f(this.uShaft.uResolution, state.pixelWidth, state.pixelHeight);
2634
+ gl.uniform1f(this.uShaft.uWidth, this.width * state.dpr);
2635
+ gl.uniform1f(this.uShaft.uUseVertexColor, uvc);
2636
+ gl.bindVertexArray(this.shaftVao);
2637
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
2638
+ gl.bindVertexArray(null);
2639
+ gl.useProgram(this.progs.head);
2640
+ setTransformUniforms(gl, this.uHead, state.x, state.y, this.xRef, this.yRef);
2641
+ gl.uniform4f(this.uHead.uColor, r, g, b, a);
2642
+ gl.uniform2f(this.uHead.uResolution, state.pixelWidth, state.pixelHeight);
2643
+ gl.uniform1f(this.uHead.uHeadSize, this.headSize * state.dpr);
2644
+ gl.uniform1f(this.uHead.uUseVertexColor, uvc);
2645
+ gl.bindVertexArray(this.headVao);
2646
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 3, this.count);
1622
2647
  gl.bindVertexArray(null);
1623
2648
  }
1624
2649
  dispose() {
1625
- const gl = this.gl;
1626
- gl.deleteVertexArray(this.fullVao);
1627
- gl.deleteVertexArray(this.decVao);
1628
- gl.deleteBuffer(this.cornerBuf);
1629
- gl.deleteBuffer(this.posBuf);
1630
- gl.deleteBuffer(this.decBuf);
2650
+ this.gl.deleteVertexArray(this.shaftVao);
2651
+ this.gl.deleteVertexArray(this.headVao);
2652
+ for (const b of this.buffers) this.gl.deleteBuffer(b);
1631
2653
  }
1632
2654
  };
1633
2655
 
@@ -1652,7 +2674,7 @@ void main() {
1652
2674
  gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
1653
2675
  }`
1654
2676
  );
1655
- var FRAG8 = (
2677
+ var FRAG10 = (
1656
2678
  /* glsl */
1657
2679
  `#version 300 es
1658
2680
  precision highp float;
@@ -1670,16 +2692,16 @@ void main() {
1670
2692
  }`
1671
2693
  );
1672
2694
  var CORNERS3 = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
1673
- var programCache8 = /* @__PURE__ */ new WeakMap();
2695
+ var programCache9 = /* @__PURE__ */ new WeakMap();
1674
2696
  function getProgram8(gl) {
1675
- let p = programCache8.get(gl);
2697
+ let p = programCache9.get(gl);
1676
2698
  if (!p) {
1677
- p = createProgram(gl, VERT8, FRAG8);
1678
- programCache8.set(gl, p);
2699
+ p = createProgram(gl, VERT8, FRAG10);
2700
+ programCache9.set(gl, p);
1679
2701
  }
1680
2702
  return p;
1681
2703
  }
1682
- var counter8 = 0;
2704
+ var counter11 = 0;
1683
2705
  var ScatterLayer = class {
1684
2706
  constructor(gl, opts) {
1685
2707
  this.buffers = [];
@@ -1687,7 +2709,7 @@ var ScatterLayer = class {
1687
2709
  this.yRef = 0;
1688
2710
  this.xBounds = [0, 0];
1689
2711
  this.yBounds = [0, 0];
1690
- this.id = `scatter-${counter8++}`;
2712
+ this.id = `scatter-${counter11++}`;
1691
2713
  this.gl = gl;
1692
2714
  this.program = getProgram8(gl);
1693
2715
  this.size = opts.size ?? 5;
@@ -1832,6 +2854,204 @@ var ScatterLayer = class {
1832
2854
  }
1833
2855
  };
1834
2856
 
2857
+ // src/layers/stem.ts
2858
+ var STEM_VERT = (
2859
+ /* glsl */
2860
+ `#version 300 es
2861
+ precision highp float;
2862
+ layout(location = 0) in vec2 aCorner; // (along 0..1, side -1..1)
2863
+ layout(location = 1) in vec4 aSeg; // (x0,y0,x1,y1) offset data space
2864
+ uniform vec2 uResolution;
2865
+ uniform float uWidth;
2866
+ ${TRANSFORM_GLSL}
2867
+ void main() {
2868
+ vec2 s0 = (dataToClip(aSeg.xy) * 0.5 + 0.5) * uResolution;
2869
+ vec2 s1 = (dataToClip(aSeg.zw) * 0.5 + 0.5) * uResolution;
2870
+ vec2 d = s1 - s0;
2871
+ float len = length(d);
2872
+ vec2 dir = len > 1e-6 ? d / len : vec2(1.0, 0.0);
2873
+ vec2 nrm = vec2(-dir.y, dir.x);
2874
+ vec2 pos = mix(s0, s1, aCorner.x) + nrm * (aCorner.y * uWidth * 0.5);
2875
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
2876
+ }`
2877
+ );
2878
+ var MARKER_VERT = (
2879
+ /* glsl */
2880
+ `#version 300 es
2881
+ precision highp float;
2882
+ layout(location = 0) in vec2 aCorner; // unit quad [-1,1]^2
2883
+ layout(location = 1) in vec2 aPos; // tip point (offset data space)
2884
+ uniform vec2 uResolution;
2885
+ uniform float uSize; // radius in device px
2886
+ ${TRANSFORM_GLSL}
2887
+ out vec2 vLocal;
2888
+ void main() {
2889
+ vec2 c = (dataToClip(aPos) * 0.5 + 0.5) * uResolution;
2890
+ vLocal = aCorner;
2891
+ vec2 pos = c + aCorner * uSize;
2892
+ gl_Position = vec4((pos / uResolution) * 2.0 - 1.0, 0.0, 1.0);
2893
+ }`
2894
+ );
2895
+ var SOLID_FRAG2 = (
2896
+ /* glsl */
2897
+ `#version 300 es
2898
+ precision highp float;
2899
+ uniform vec4 uColor;
2900
+ out vec4 outColor;
2901
+ void main() { outColor = vec4(uColor.rgb * uColor.a, uColor.a); }`
2902
+ );
2903
+ var DISC_FRAG = (
2904
+ /* glsl */
2905
+ `#version 300 es
2906
+ precision highp float;
2907
+ in vec2 vLocal;
2908
+ uniform vec4 uColor;
2909
+ out vec4 outColor;
2910
+ void main() {
2911
+ float r = length(vLocal);
2912
+ if (r > 1.0) discard;
2913
+ float alpha = smoothstep(1.0, 1.0 - 0.15, r);
2914
+ outColor = vec4(uColor.rgb * uColor.a * alpha, uColor.a * alpha);
2915
+ }`
2916
+ );
2917
+ var SEG_CORNERS4 = new Float32Array([0, -1, 1, -1, 1, 1, 0, -1, 1, 1, 0, 1]);
2918
+ var QUAD_CORNERS2 = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);
2919
+ var cache4 = /* @__PURE__ */ new WeakMap();
2920
+ function programs4(gl) {
2921
+ let p = cache4.get(gl);
2922
+ if (!p) {
2923
+ p = { stem: createProgram(gl, STEM_VERT, SOLID_FRAG2), marker: createProgram(gl, MARKER_VERT, DISC_FRAG) };
2924
+ cache4.set(gl, p);
2925
+ }
2926
+ return p;
2927
+ }
2928
+ var counter12 = 0;
2929
+ var StemLayer = class {
2930
+ constructor(gl, opts) {
2931
+ this.buffers = [];
2932
+ this.xRef = 0;
2933
+ this.yRef = 0;
2934
+ this.xBounds = [0, 0];
2935
+ this.yBounds = [0, 0];
2936
+ this.id = `stem-${counter12++}`;
2937
+ this.gl = gl;
2938
+ this.progs = programs4(gl);
2939
+ const colorInput = opts.color ?? "#3b82f6";
2940
+ this.color = Array.isArray(colorInput) ? colorInput : parseColor(colorInput);
2941
+ this.colorCss = typeof colorInput === "string" ? colorInput : toColorCss(this.color);
2942
+ this.name = opts.name ?? this.id;
2943
+ this.yAxis = opts.yAxis ?? "y";
2944
+ this.width = opts.width ?? 1.5;
2945
+ this.markerSize = opts.markerSize ?? 6;
2946
+ this.baseline = opts.baseline ?? 0;
2947
+ const n = Math.min(opts.x.length, opts.y.length);
2948
+ this.count = n;
2949
+ this.xs = new Float64Array(n);
2950
+ this.ys = new Float64Array(n);
2951
+ this.xRef = n > 0 ? opts.x[0] : 0;
2952
+ this.yRef = n > 0 ? opts.y[0] : 0;
2953
+ const segs = new Float32Array(n * 4);
2954
+ const tips = new Float32Array(n * 2);
2955
+ let minX = Infinity, maxX = -Infinity;
2956
+ let minY = Math.min(this.baseline, Infinity), maxY = Math.max(this.baseline, -Infinity);
2957
+ for (let i = 0; i < n; i++) {
2958
+ const x = opts.x[i], y = opts.y[i];
2959
+ this.xs[i] = x;
2960
+ this.ys[i] = y;
2961
+ segs[i * 4] = x - this.xRef;
2962
+ segs[i * 4 + 1] = this.baseline - this.yRef;
2963
+ segs[i * 4 + 2] = x - this.xRef;
2964
+ segs[i * 4 + 3] = y - this.yRef;
2965
+ tips[i * 2] = x - this.xRef;
2966
+ tips[i * 2 + 1] = y - this.yRef;
2967
+ if (x < minX) minX = x;
2968
+ if (x > maxX) maxX = x;
2969
+ if (y < minY) minY = y;
2970
+ if (y > maxY) maxY = y;
2971
+ }
2972
+ this.xBounds = [minX, maxX];
2973
+ this.yBounds = [minY, maxY];
2974
+ const cornerSeg = gl.createBuffer();
2975
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
2976
+ gl.bufferData(gl.ARRAY_BUFFER, SEG_CORNERS4, gl.STATIC_DRAW);
2977
+ const cornerQuad = gl.createBuffer();
2978
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerQuad);
2979
+ gl.bufferData(gl.ARRAY_BUFFER, QUAD_CORNERS2, gl.STATIC_DRAW);
2980
+ const segBuf = gl.createBuffer();
2981
+ gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
2982
+ gl.bufferData(gl.ARRAY_BUFFER, segs, gl.STATIC_DRAW);
2983
+ const tipBuf = gl.createBuffer();
2984
+ gl.bindBuffer(gl.ARRAY_BUFFER, tipBuf);
2985
+ gl.bufferData(gl.ARRAY_BUFFER, tips, gl.STATIC_DRAW);
2986
+ this.buffers = [cornerSeg, cornerQuad, segBuf, tipBuf];
2987
+ this.stemVao = gl.createVertexArray();
2988
+ gl.bindVertexArray(this.stemVao);
2989
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerSeg);
2990
+ gl.enableVertexAttribArray(0);
2991
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
2992
+ gl.bindBuffer(gl.ARRAY_BUFFER, segBuf);
2993
+ gl.enableVertexAttribArray(1);
2994
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
2995
+ gl.vertexAttribDivisor(1, 1);
2996
+ this.markerVao = gl.createVertexArray();
2997
+ gl.bindVertexArray(this.markerVao);
2998
+ gl.bindBuffer(gl.ARRAY_BUFFER, cornerQuad);
2999
+ gl.enableVertexAttribArray(0);
3000
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
3001
+ gl.bindBuffer(gl.ARRAY_BUFFER, tipBuf);
3002
+ gl.enableVertexAttribArray(1);
3003
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
3004
+ gl.vertexAttribDivisor(1, 1);
3005
+ gl.bindVertexArray(null);
3006
+ this.uStem = uniformLocations(gl, this.progs.stem, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uWidth"]);
3007
+ this.uMarker = uniformLocations(gl, this.progs.marker, [...TRANSFORM_UNIFORMS, "uColor", "uResolution", "uSize"]);
3008
+ }
3009
+ bounds() {
3010
+ if (this.count === 0) return null;
3011
+ return { x: this.xBounds, y: this.yBounds };
3012
+ }
3013
+ nearestByX(x) {
3014
+ if (this.count === 0) return null;
3015
+ let best = 0, bestDist = Infinity;
3016
+ for (let i = 0; i < this.count; i++) {
3017
+ const d = Math.abs(this.xs[i] - x);
3018
+ if (d < bestDist) {
3019
+ bestDist = d;
3020
+ best = i;
3021
+ }
3022
+ }
3023
+ return { x: this.xs[best], y: this.ys[best], index: best };
3024
+ }
3025
+ draw(state) {
3026
+ if (this.count === 0) return;
3027
+ const gl = state.gl;
3028
+ const [r, g, b, a] = this.color;
3029
+ gl.useProgram(this.progs.stem);
3030
+ setTransformUniforms(gl, this.uStem, state.x, state.y, this.xRef, this.yRef);
3031
+ gl.uniform4f(this.uStem.uColor, r, g, b, a);
3032
+ gl.uniform2f(this.uStem.uResolution, state.pixelWidth, state.pixelHeight);
3033
+ gl.uniform1f(this.uStem.uWidth, this.width * state.dpr);
3034
+ gl.bindVertexArray(this.stemVao);
3035
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
3036
+ gl.bindVertexArray(null);
3037
+ if (this.markerSize > 0) {
3038
+ gl.useProgram(this.progs.marker);
3039
+ setTransformUniforms(gl, this.uMarker, state.x, state.y, this.xRef, this.yRef);
3040
+ gl.uniform4f(this.uMarker.uColor, r, g, b, a);
3041
+ gl.uniform2f(this.uMarker.uResolution, state.pixelWidth, state.pixelHeight);
3042
+ gl.uniform1f(this.uMarker.uSize, this.markerSize / 2 * state.dpr);
3043
+ gl.bindVertexArray(this.markerVao);
3044
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.count);
3045
+ gl.bindVertexArray(null);
3046
+ }
3047
+ }
3048
+ dispose() {
3049
+ this.gl.deleteVertexArray(this.stemVao);
3050
+ this.gl.deleteVertexArray(this.markerVao);
3051
+ for (const b of this.buffers) this.gl.deleteBuffer(b);
3052
+ }
3053
+ };
3054
+
1835
3055
  // src/render/overlay.ts
1836
3056
  function plotRegion(layout) {
1837
3057
  const { margin } = layout;
@@ -2409,6 +3629,18 @@ var Plot = class {
2409
3629
  addContour(opts) {
2410
3630
  return this.register(new ContourLayer(this.gl, opts));
2411
3631
  }
3632
+ addErrorBar(opts) {
3633
+ return this.register(new ErrorBarLayer(this.gl, opts));
3634
+ }
3635
+ addStem(opts) {
3636
+ return this.register(new StemLayer(this.gl, opts));
3637
+ }
3638
+ addQuiver(opts) {
3639
+ return this.register(new QuiverLayer(this.gl, opts));
3640
+ }
3641
+ addCandlestick(opts) {
3642
+ return this.register(new CandlestickLayer(this.gl, opts));
3643
+ }
2412
3644
  /** Compute an STFT of `signal` and render it as a heatmap (time × frequency). */
2413
3645
  addHeatmapSpectrogram(signal, opts = {}) {
2414
3646
  const s = spectrogram(signal, opts);
@@ -2746,18 +3978,19 @@ var Plot = class {
2746
3978
  }
2747
3979
  return { type: "plot" };
2748
3980
  }
3981
+ // Pan/zoom shift the domain in the scale's *transformed* space (via invert),
3982
+ // not raw data space — so a log axis stays positive instead of crossing zero
3983
+ // into NaN. For linear/time scales this is identical to the old arithmetic.
2749
3984
  panX(dxPx, region) {
2750
- const dxData = dxPx / region.width * (this.scaleX.domain[1] - this.scaleX.domain[0]);
2751
- this.scaleX.domain = [this.scaleX.domain[0] - dxData, this.scaleX.domain[1] - dxData];
3985
+ const f = dxPx / region.width;
3986
+ this.scaleX.domain = [this.scaleX.invert(-f), this.scaleX.invert(1 - f)];
2752
3987
  this.autoX = false;
2753
3988
  }
2754
3989
  panY(id, dyPx, region) {
2755
- const dyFrac = dyPx / region.height;
3990
+ const f = dyPx / region.height;
2756
3991
  for (const ya of this.yAxes.values()) {
2757
3992
  if (id && ya.id !== id) continue;
2758
- const span = ya.scale.domain[1] - ya.scale.domain[0];
2759
- const d = dyFrac * span;
2760
- ya.scale.domain = [ya.scale.domain[0] + d, ya.scale.domain[1] + d];
3993
+ ya.scale.domain = [ya.scale.invert(f), ya.scale.invert(1 + f)];
2761
3994
  ya.auto = false;
2762
3995
  }
2763
3996
  }
@@ -2931,16 +4164,14 @@ var Plot = class {
2931
4164
  zoomAround(nx, ny, factor) {
2932
4165
  const lock = this.axisLock();
2933
4166
  if (lock.x) {
2934
- const [x0, x1] = this.scaleX.domain;
2935
- const cx = x0 + nx * (x1 - x0);
2936
- this.scaleX.domain = [cx + (x0 - cx) * factor, cx + (x1 - cx) * factor];
4167
+ const t = nx * (1 - factor);
4168
+ this.scaleX.domain = [this.scaleX.invert(t), this.scaleX.invert(t + factor)];
2937
4169
  this.autoX = false;
2938
4170
  }
2939
4171
  if (lock.y) {
4172
+ const t = ny * (1 - factor);
2940
4173
  for (const ya of this.yAxes.values()) {
2941
- const [y0, y1] = ya.scale.domain;
2942
- const cy = y0 + ny * (y1 - y0);
2943
- ya.scale.domain = [cy + (y0 - cy) * factor, cy + (y1 - cy) * factor];
4174
+ ya.scale.domain = [ya.scale.invert(t), ya.scale.invert(t + factor)];
2944
4175
  ya.auto = false;
2945
4176
  }
2946
4177
  }
@@ -3217,7 +4448,7 @@ void main() {
3217
4448
  gl_PointSize = uSize;
3218
4449
  }`
3219
4450
  );
3220
- var FRAG9 = (
4451
+ var FRAG11 = (
3221
4452
  /* glsl */
3222
4453
  `#version 300 es
3223
4454
  precision highp float;
@@ -3229,19 +4460,19 @@ void main() {
3229
4460
  outColor = vec4(vColor, 1.0);
3230
4461
  }`
3231
4462
  );
3232
- var programCache9 = /* @__PURE__ */ new WeakMap();
4463
+ var programCache10 = /* @__PURE__ */ new WeakMap();
3233
4464
  function getProgram9(gl) {
3234
- let p = programCache9.get(gl);
4465
+ let p = programCache10.get(gl);
3235
4466
  if (!p) {
3236
- p = createProgram(gl, VERT9, FRAG9);
3237
- programCache9.set(gl, p);
4467
+ p = createProgram(gl, VERT9, FRAG11);
4468
+ programCache10.set(gl, p);
3238
4469
  }
3239
4470
  return p;
3240
4471
  }
3241
- var counter9 = 0;
4472
+ var counter13 = 0;
3242
4473
  var PointCloudLayer = class {
3243
4474
  constructor(gl, opts) {
3244
- this.id = `points3d-${counter9++}`;
4475
+ this.id = `points3d-${counter13++}`;
3245
4476
  this.gl = gl;
3246
4477
  this.program = getProgram9(gl);
3247
4478
  this.size = opts.size ?? 4;
@@ -3326,7 +4557,7 @@ out vec3 vColor;
3326
4557
  out vec3 vN;
3327
4558
  void main() { vColor = aColor; vN = aNormal; gl_Position = uMVP * vec4(aPos, 1.0); }`
3328
4559
  );
3329
- var FRAG10 = (
4560
+ var FRAG12 = (
3330
4561
  /* glsl */
3331
4562
  `#version 300 es
3332
4563
  precision highp float;
@@ -3341,21 +4572,21 @@ void main() {
3341
4572
  outColor = vec4(vColor * shade, 1.0);
3342
4573
  }`
3343
4574
  );
3344
- var programCache10 = /* @__PURE__ */ new WeakMap();
4575
+ var programCache11 = /* @__PURE__ */ new WeakMap();
3345
4576
  function getProgram10(gl) {
3346
- let p = programCache10.get(gl);
4577
+ let p = programCache11.get(gl);
3347
4578
  if (!p) {
3348
- p = createProgram(gl, VERT10, FRAG10);
3349
- programCache10.set(gl, p);
4579
+ p = createProgram(gl, VERT10, FRAG12);
4580
+ programCache11.set(gl, p);
3350
4581
  }
3351
4582
  return p;
3352
4583
  }
3353
- var counter10 = 0;
4584
+ var counter14 = 0;
3354
4585
  var SurfaceLayer = class {
3355
4586
  constructor(gl, opts) {
3356
4587
  this.lightDir = [0.5, 1, 0.35];
3357
4588
  this.ambient = 0.35;
3358
- this.id = `surface-${counter10++}`;
4589
+ this.id = `surface-${counter14++}`;
3359
4590
  this.gl = gl;
3360
4591
  this.program = getProgram10(gl);
3361
4592
  const { cols, rows, values } = opts;
@@ -3825,7 +5056,9 @@ export {
3825
5056
  Axis,
3826
5057
  BarLayer,
3827
5058
  BoxLayer,
5059
+ CandlestickLayer,
3828
5060
  ContourLayer,
5061
+ ErrorBarLayer,
3829
5062
  HeatmapLayer,
3830
5063
  HexbinLayer,
3831
5064
  LineLayer,
@@ -3835,7 +5068,9 @@ export {
3835
5068
  Plot3D,
3836
5069
  PointCloudLayer,
3837
5070
  PolarPlot,
5071
+ QuiverLayer,
3838
5072
  ScatterLayer,
5073
+ StemLayer,
3839
5074
  SurfaceLayer,
3840
5075
  TimeScale,
3841
5076
  autoTicks,