aliencharts 0.2.0 → 0.3.0

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/react.js CHANGED
@@ -172,6 +172,524 @@ var removeDrawingById = (drawings, drawingId) => (Array.isArray(drawings) ? draw
172
172
  (drawing) => drawing?.id !== drawingId
173
173
  );
174
174
 
175
+ // src/core/chartModel.js
176
+ var LINE_CAPABILITIES = Object.freeze({
177
+ appendAnimation: true,
178
+ drawings: true,
179
+ latestValue: true,
180
+ markers: true,
181
+ movingAverage: true
182
+ });
183
+ var BAR_CAPABILITIES = Object.freeze({
184
+ appendAnimation: false,
185
+ drawings: false,
186
+ latestValue: false,
187
+ markers: false,
188
+ movingAverage: false
189
+ });
190
+ var CHART_DEFINITIONS = Object.freeze({
191
+ line: Object.freeze({
192
+ capabilities: LINE_CAPABILITIES,
193
+ defaultOrientation: "vertical",
194
+ orientationFromSeries: false,
195
+ orientations: Object.freeze(["vertical"]),
196
+ rangeIncludesZero: false
197
+ }),
198
+ bar: Object.freeze({
199
+ capabilities: BAR_CAPABILITIES,
200
+ defaultOrientation: "vertical",
201
+ orientationFromSeries: true,
202
+ orientations: Object.freeze(["vertical", "horizontal"]),
203
+ rangeIncludesZero: true
204
+ })
205
+ });
206
+ var getChartCategories = (chart) => {
207
+ if (chart?.categories == null) return [];
208
+ if (!Array.isArray(chart.categories)) {
209
+ throw new TypeError(`Chart "${chart.id}" categories must be an array`);
210
+ }
211
+ const values = /* @__PURE__ */ new Set();
212
+ return chart.categories.map((category, index) => {
213
+ const normalized = typeof category === "string" ? { value: index, label: category } : { value: Number(category?.value), label: category?.label };
214
+ if (!Number.isFinite(normalized.value) || typeof normalized.label !== "string") {
215
+ throw new TypeError(
216
+ `Chart "${chart.id}" category ${index} must be a string or a { value, label } object`
217
+ );
218
+ }
219
+ if (values.has(normalized.value)) {
220
+ throw new TypeError(
221
+ `Chart "${chart.id}" has duplicate category value ${normalized.value}`
222
+ );
223
+ }
224
+ values.add(normalized.value);
225
+ return normalized;
226
+ });
227
+ };
228
+ var getCategoryLabel = (chart, value) => getChartCategories(chart).find((category) => category.value === value)?.label;
229
+ var getSeriesType = (series) => {
230
+ if (series?.type == null) return "line";
231
+ if (Object.prototype.hasOwnProperty.call(CHART_DEFINITIONS, series.type)) {
232
+ return series.type;
233
+ }
234
+ throw new TypeError(`Unknown AlienCharts series type: "${series.type}"`);
235
+ };
236
+ var resolveChartDescriptor = (chart) => {
237
+ const series = Array.isArray(chart?.series) ? chart.series : [];
238
+ const categories = getChartCategories(chart);
239
+ const type = series.length ? getSeriesType(series[0]) : "line";
240
+ const definition = CHART_DEFINITIONS[type];
241
+ const orientation = definition.orientationFromSeries ? series[0]?.orientation || definition.defaultOrientation : definition.defaultOrientation;
242
+ series.forEach((item) => {
243
+ const itemType = getSeriesType(item);
244
+ if (itemType !== type) {
245
+ const mixedTypes = /* @__PURE__ */ new Set([type, itemType]);
246
+ const typeLabel = mixedTypes.has("line") && mixedTypes.has("bar") ? "line and bar" : `${type} and ${itemType}`;
247
+ throw new TypeError(
248
+ `Chart "${chart.id}" cannot mix ${typeLabel} series`
249
+ );
250
+ }
251
+ const itemOrientation = definition.orientationFromSeries ? item.orientation || definition.defaultOrientation : definition.defaultOrientation;
252
+ if (!definition.orientations.includes(itemOrientation)) {
253
+ throw new TypeError(
254
+ `Chart "${chart.id}" has an invalid ${itemType} orientation`
255
+ );
256
+ }
257
+ if (itemOrientation !== orientation) {
258
+ throw new TypeError(
259
+ `Chart "${chart.id}" cannot mix ${itemType} orientations`
260
+ );
261
+ }
262
+ });
263
+ return Object.freeze({
264
+ capabilities: definition.capabilities,
265
+ categorical: categories.length > 0,
266
+ orientation,
267
+ rangeIncludesZero: definition.rangeIncludesZero,
268
+ rendererType: type,
269
+ type
270
+ });
271
+ };
272
+ var resolveChartDescriptors = (charts) => {
273
+ const descriptors = /* @__PURE__ */ new Map();
274
+ charts.forEach((chart) => {
275
+ descriptors.set(chart.id, resolveChartDescriptor(chart));
276
+ });
277
+ return descriptors;
278
+ };
279
+ var getChartDescriptor = (chart, descriptors) => descriptors?.get(chart?.id) || resolveChartDescriptor(chart);
280
+ var getOrderedSeries = (chart, seriesOrderByChart) => {
281
+ const series = Array.isArray(chart?.series) ? chart.series : [];
282
+ const order = seriesOrderByChart?.[chart?.id];
283
+ if (!Array.isArray(order) || order.length !== series.length) {
284
+ return series;
285
+ }
286
+ const used = /* @__PURE__ */ new Set();
287
+ const ordered = [];
288
+ order.forEach((seriesIndex) => {
289
+ if (!Number.isInteger(seriesIndex)) return;
290
+ if (seriesIndex < 0 || seriesIndex >= series.length) return;
291
+ if (used.has(seriesIndex)) return;
292
+ used.add(seriesIndex);
293
+ ordered.push(series[seriesIndex]);
294
+ });
295
+ return ordered.length === series.length ? ordered : series;
296
+ };
297
+ var CHART_TYPES = Object.freeze(Object.keys(CHART_DEFINITIONS));
298
+
299
+ // src/core/coordinateTransform.js
300
+ var safeSpan = (min, max) => Math.max(1e-12, max - min);
301
+ var createCoordinateTransform = ({
302
+ orientation = "vertical",
303
+ categoryRange,
304
+ valueRange,
305
+ plot
306
+ }) => {
307
+ const horizontal = orientation === "horizontal";
308
+ const categorySpan = safeSpan(categoryRange.min, categoryRange.max);
309
+ const valueSpan = safeSpan(valueRange.min, valueRange.max);
310
+ const categoryPixelLength = horizontal ? plot.height : plot.width;
311
+ const valuePixelLength = horizontal ? plot.width : plot.height;
312
+ const categoryRatio = (point) => horizontal ? (point.y - plot.y) / plot.height : (point.x - plot.x) / plot.width;
313
+ const valueRatio = (point) => horizontal ? (point.x - plot.x) / plot.width : 1 - (point.y - plot.y) / plot.height;
314
+ const dataToScreen = (point) => {
315
+ const category = point.category ?? point.x;
316
+ const value = point.value ?? point.y;
317
+ const categoryPosition = (category - categoryRange.min) / categorySpan;
318
+ const valuePosition = (value - valueRange.min) / valueSpan;
319
+ return horizontal ? {
320
+ x: plot.x + valuePosition * plot.width,
321
+ y: plot.y + categoryPosition * plot.height
322
+ } : {
323
+ x: plot.x + categoryPosition * plot.width,
324
+ y: plot.y + (1 - valuePosition) * plot.height
325
+ };
326
+ };
327
+ const screenToData = (point) => ({
328
+ x: categoryRange.min + categoryRatio(point) * categorySpan,
329
+ y: valueRange.min + valueRatio(point) * valueSpan
330
+ });
331
+ const categoryDragDelta = (start, current) => (horizontal ? current.y - start.y : current.x - start.x) / categoryPixelLength * categorySpan;
332
+ const valueOffsetDelta = (start, current) => horizontal ? -((current.x - start.x) / valuePixelLength) * valueSpan : (current.y - start.y) / valuePixelLength * valueSpan;
333
+ const categoryScaleDelta = (start, current) => horizontal ? current.y - start.y : current.x - start.x;
334
+ const valueScaleDelta = (start, current) => horizontal ? current.x - start.x : current.y - start.y;
335
+ const dataBoundsForRect = (rect) => {
336
+ const first = screenToData({ x: rect.left, y: rect.top });
337
+ const second = screenToData({
338
+ x: rect.left + rect.width,
339
+ y: rect.top + rect.height
340
+ });
341
+ return {
342
+ categoryMin: Math.min(first.x, second.x),
343
+ categoryMax: Math.max(first.x, second.x),
344
+ valueMin: Math.min(first.y, second.y),
345
+ valueMax: Math.max(first.y, second.y)
346
+ };
347
+ };
348
+ return Object.freeze({
349
+ categoryDragDelta,
350
+ categoryPixelLength,
351
+ categoryRatio,
352
+ categoryScaleDelta,
353
+ dataBoundsForRect,
354
+ dataToScreen,
355
+ horizontal,
356
+ orientation,
357
+ screenToData,
358
+ valueOffsetDelta,
359
+ valuePixelLength,
360
+ valueRatio,
361
+ valueScaleDelta
362
+ });
363
+ };
364
+
365
+ // src/core/renderers/webglUtils.js
366
+ var INITIAL_GPU_VERTEX_CAPACITY = 4096;
367
+ var createProgram = (gl, vertexSource, fragmentSource) => {
368
+ const compile = (type, source) => {
369
+ const shader = gl.createShader(type);
370
+ gl.shaderSource(shader, source);
371
+ gl.compileShader(shader);
372
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
373
+ throw new Error(gl.getShaderInfoLog(shader) || "Shader compile failed");
374
+ }
375
+ return shader;
376
+ };
377
+ const program = gl.createProgram();
378
+ const vertexShader = compile(gl.VERTEX_SHADER, vertexSource);
379
+ const fragmentShader = compile(gl.FRAGMENT_SHADER, fragmentSource);
380
+ gl.attachShader(program, vertexShader);
381
+ gl.attachShader(program, fragmentShader);
382
+ gl.linkProgram(program);
383
+ gl.deleteShader(vertexShader);
384
+ gl.deleteShader(fragmentShader);
385
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
386
+ throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
387
+ }
388
+ return program;
389
+ };
390
+ var hexToRgb = (color) => {
391
+ const value = String(color || "#38bdf8").replace("#", "");
392
+ const normalized = value.length === 3 ? value.split("").map((char) => char + char).join("") : value.padEnd(6, "0").slice(0, 6);
393
+ const number = Number.parseInt(normalized, 16);
394
+ return [
395
+ (number >> 16 & 255) / 255,
396
+ (number >> 8 & 255) / 255,
397
+ (number & 255) / 255
398
+ ];
399
+ };
400
+ var getNextVertexCapacity = (currentCapacity, requiredCapacity) => {
401
+ let nextCapacity = Math.max(
402
+ INITIAL_GPU_VERTEX_CAPACITY,
403
+ currentCapacity || 0
404
+ );
405
+ while (nextCapacity < requiredCapacity) nextCapacity *= 2;
406
+ return nextCapacity;
407
+ };
408
+ var getDynamicBuffer = (gl, cache, key, requiredFloats) => {
409
+ let entry = cache.get(key);
410
+ if (!entry) {
411
+ entry = {
412
+ buffer: gl.createBuffer(),
413
+ gpuCapacity: 0,
414
+ vertices: new Float32Array(
415
+ getNextVertexCapacity(0, requiredFloats)
416
+ )
417
+ };
418
+ cache.set(key, entry);
419
+ }
420
+ if (entry.vertices.length < requiredFloats) {
421
+ entry.vertices = new Float32Array(
422
+ getNextVertexCapacity(entry.vertices.length, requiredFloats)
423
+ );
424
+ }
425
+ if (entry.gpuCapacity < entry.vertices.length) {
426
+ entry.gpuCapacity = entry.vertices.length;
427
+ gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
428
+ gl.bufferData(
429
+ gl.ARRAY_BUFFER,
430
+ entry.gpuCapacity * Float32Array.BYTES_PER_ELEMENT,
431
+ gl.DYNAMIC_DRAW
432
+ );
433
+ }
434
+ return entry;
435
+ };
436
+ var deleteBufferCache = (gl, cache) => {
437
+ cache.forEach((entry) => {
438
+ if (entry.buffer) gl.deleteBuffer(entry.buffer);
439
+ });
440
+ cache.clear();
441
+ };
442
+
443
+ // src/core/renderers/barRenderer.js
444
+ var BAR_GROUP_WIDTH_RATIO = 0.8;
445
+ var BAR_SLICE_WIDTH_RATIO = 0.9;
446
+ var BAR_VERTEX_FLOAT_STRIDE = 3;
447
+ var VERTEX_SOURCE = `#version 300 es
448
+ in vec2 a_unit;
449
+ in vec3 a_bar;
450
+ uniform vec2 u_resolution;
451
+ uniform vec4 u_rect;
452
+ uniform vec2 u_categoryRange;
453
+ uniform vec2 u_valueRange;
454
+ uniform float u_baseline;
455
+ uniform float u_seriesIndex;
456
+ uniform float u_seriesCount;
457
+ uniform int u_orientation;
458
+
459
+ void main() {
460
+ float groupWidth = a_bar.z * ${BAR_GROUP_WIDTH_RATIO.toFixed(1)};
461
+ float sliceWidth = groupWidth / max(1.0, u_seriesCount);
462
+ float center = a_bar.x - groupWidth * 0.5
463
+ + sliceWidth * (u_seriesIndex + 0.5);
464
+ float halfWidth = sliceWidth * ${(BAR_SLICE_WIDTH_RATIO / 2).toFixed(2)};
465
+ float category = mix(center - halfWidth, center + halfWidth, a_unit.x);
466
+ float value = mix(u_baseline, a_bar.y, a_unit.y);
467
+ float categoryDenom = max(
468
+ 0.000000000001,
469
+ abs(u_categoryRange.y - u_categoryRange.x)
470
+ );
471
+ float valueDenom = max(
472
+ 0.000000000001,
473
+ abs(u_valueRange.y - u_valueRange.x)
474
+ );
475
+ float categoryRatio = (category - u_categoryRange.x) / categoryDenom;
476
+ float valueRatio = (value - u_valueRange.x) / valueDenom;
477
+ vec2 px = u_orientation == 0
478
+ ? vec2(
479
+ u_rect.x + categoryRatio * u_rect.z,
480
+ u_rect.y + (1.0 - valueRatio) * u_rect.w
481
+ )
482
+ : vec2(
483
+ u_rect.x + valueRatio * u_rect.z,
484
+ u_rect.y + categoryRatio * u_rect.w
485
+ );
486
+ vec2 clip = (px / u_resolution) * 2.0 - 1.0;
487
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
488
+ }
489
+ `;
490
+ var FRAGMENT_SOURCE = `#version 300 es
491
+ precision highp float;
492
+ uniform vec3 u_color;
493
+ out vec4 outColor;
494
+
495
+ void main() {
496
+ outColor = vec4(u_color, 1.0);
497
+ }
498
+ `;
499
+ var getBarCategoryInterval = ({
500
+ visiblePoints,
501
+ index,
502
+ categoryMin,
503
+ categoryMax
504
+ }) => {
505
+ const count = visiblePoints.pointCount;
506
+ const category = visiblePoints.x[index];
507
+ const fallbackInterval = (categoryMax - categoryMin) / Math.max(10, count || 1);
508
+ const previousGap = index > 0 ? category - visiblePoints.x[index - 1] : Infinity;
509
+ const nextGap = index + 1 < count ? visiblePoints.x[index + 1] - category : Infinity;
510
+ let interval = Math.min(
511
+ previousGap > 0 ? previousGap : Infinity,
512
+ nextGap > 0 ? nextGap : Infinity
513
+ );
514
+ if (!Number.isFinite(interval)) {
515
+ interval = Number.isFinite(previousGap) && previousGap > 0 ? previousGap : Number.isFinite(nextGap) && nextGap > 0 ? nextGap : fallbackInterval;
516
+ }
517
+ return Math.max(Number.EPSILON, interval || fallbackInterval || 1);
518
+ };
519
+ var getGroupedBarCategory = ({
520
+ visiblePoints,
521
+ pointIndex,
522
+ seriesIndex,
523
+ seriesCount,
524
+ categoryMin,
525
+ categoryMax
526
+ }) => {
527
+ const category = visiblePoints.x[pointIndex];
528
+ const interval = getBarCategoryInterval({
529
+ visiblePoints,
530
+ index: pointIndex,
531
+ categoryMin,
532
+ categoryMax
533
+ });
534
+ const groupWidth = interval * BAR_GROUP_WIDTH_RATIO;
535
+ const sliceWidth = groupWidth / Math.max(1, seriesCount);
536
+ return category - groupWidth / 2 + sliceWidth * (seriesIndex + 0.5);
537
+ };
538
+ var fillBarInstances = ({
539
+ vertices,
540
+ visiblePoints,
541
+ categoryMin,
542
+ categoryMax
543
+ }) => {
544
+ for (let index = 0; index < visiblePoints.pointCount; index += 1) {
545
+ const offset = index * BAR_VERTEX_FLOAT_STRIDE;
546
+ vertices[offset] = visiblePoints.x[index];
547
+ vertices[offset + 1] = visiblePoints.y[index];
548
+ vertices[offset + 2] = getBarCategoryInterval({
549
+ visiblePoints,
550
+ index,
551
+ categoryMin,
552
+ categoryMax
553
+ });
554
+ }
555
+ return visiblePoints.pointCount;
556
+ };
557
+ var createBarRenderer = (gl) => {
558
+ const program = createProgram(gl, VERTEX_SOURCE, FRAGMENT_SOURCE);
559
+ const quadBuffer = gl.createBuffer();
560
+ const buffers = /* @__PURE__ */ new Map();
561
+ gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
562
+ gl.bufferData(
563
+ gl.ARRAY_BUFFER,
564
+ new Float32Array([
565
+ 0,
566
+ 0,
567
+ 1,
568
+ 0,
569
+ 0,
570
+ 1,
571
+ 0,
572
+ 1,
573
+ 1,
574
+ 0,
575
+ 1,
576
+ 1
577
+ ]),
578
+ gl.STATIC_DRAW
579
+ );
580
+ const locations = Object.freeze({
581
+ aBar: gl.getAttribLocation(program, "a_bar"),
582
+ aUnit: gl.getAttribLocation(program, "a_unit"),
583
+ uBaseline: gl.getUniformLocation(program, "u_baseline"),
584
+ uCategoryRange: gl.getUniformLocation(program, "u_categoryRange"),
585
+ uColor: gl.getUniformLocation(program, "u_color"),
586
+ uOrientation: gl.getUniformLocation(program, "u_orientation"),
587
+ uRect: gl.getUniformLocation(program, "u_rect"),
588
+ uResolution: gl.getUniformLocation(program, "u_resolution"),
589
+ uSeriesCount: gl.getUniformLocation(program, "u_seriesCount"),
590
+ uSeriesIndex: gl.getUniformLocation(program, "u_seriesIndex"),
591
+ uValueRange: gl.getUniformLocation(program, "u_valueRange")
592
+ });
593
+ return {
594
+ draw({
595
+ categoryPixelLength,
596
+ chart,
597
+ descriptor,
598
+ pixelHeight,
599
+ pixelWidth,
600
+ scaledPlot,
601
+ seriesOrderByChart,
602
+ state,
603
+ yRange
604
+ }) {
605
+ gl.disable(gl.BLEND);
606
+ gl.useProgram(program);
607
+ gl.enableVertexAttribArray(locations.aUnit);
608
+ gl.enableVertexAttribArray(locations.aBar);
609
+ gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
610
+ gl.vertexAttribPointer(locations.aUnit, 2, gl.FLOAT, false, 0, 0);
611
+ gl.vertexAttribDivisor(locations.aUnit, 0);
612
+ gl.uniform2f(locations.uResolution, pixelWidth, pixelHeight);
613
+ gl.uniform4f(
614
+ locations.uRect,
615
+ scaledPlot.x,
616
+ scaledPlot.y,
617
+ scaledPlot.width,
618
+ scaledPlot.height
619
+ );
620
+ gl.uniform2f(
621
+ locations.uCategoryRange,
622
+ state.xMin,
623
+ state.xMax
624
+ );
625
+ gl.uniform2f(
626
+ locations.uValueRange,
627
+ yRange.minY,
628
+ yRange.maxY
629
+ );
630
+ gl.uniform1f(locations.uBaseline, 0);
631
+ gl.uniform1i(
632
+ locations.uOrientation,
633
+ descriptor.orientation === "horizontal" ? 1 : 0
634
+ );
635
+ const orderedSeries = getOrderedSeries(chart, seriesOrderByChart);
636
+ gl.uniform1f(locations.uSeriesCount, Math.max(1, orderedSeries.length));
637
+ orderedSeries.forEach((series, seriesIndex) => {
638
+ const visiblePoints = series.getVisiblePoints(
639
+ state.xMin,
640
+ state.xMax,
641
+ categoryPixelLength
642
+ );
643
+ if (!visiblePoints.pointCount) return;
644
+ const requiredFloats = visiblePoints.pointCount * BAR_VERTEX_FLOAT_STRIDE;
645
+ const bufferEntry = getDynamicBuffer(
646
+ gl,
647
+ buffers,
648
+ series.id,
649
+ requiredFloats
650
+ );
651
+ const instanceCount = fillBarInstances({
652
+ vertices: bufferEntry.vertices,
653
+ visiblePoints,
654
+ categoryMin: state.xMin,
655
+ categoryMax: state.xMax
656
+ });
657
+ const [r, g, b] = hexToRgb(series.color);
658
+ gl.uniform3f(locations.uColor, r, g, b);
659
+ gl.uniform1f(locations.uSeriesIndex, seriesIndex);
660
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry.buffer);
661
+ gl.vertexAttribPointer(
662
+ locations.aBar,
663
+ BAR_VERTEX_FLOAT_STRIDE,
664
+ gl.FLOAT,
665
+ false,
666
+ 0,
667
+ 0
668
+ );
669
+ gl.vertexAttribDivisor(locations.aBar, 1);
670
+ gl.bufferSubData(
671
+ gl.ARRAY_BUFFER,
672
+ 0,
673
+ bufferEntry.vertices,
674
+ 0,
675
+ instanceCount * BAR_VERTEX_FLOAT_STRIDE
676
+ );
677
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
678
+ });
679
+ gl.vertexAttribDivisor(locations.aBar, 0);
680
+ return { seriesEndpoints: /* @__PURE__ */ new Map() };
681
+ },
682
+ getTooltipCategory(payload) {
683
+ return getGroupedBarCategory(payload);
684
+ },
685
+ destroy() {
686
+ deleteBufferCache(gl, buffers);
687
+ gl.deleteBuffer(quadBuffer);
688
+ gl.deleteProgram(program);
689
+ }
690
+ };
691
+ };
692
+
175
693
  // src/core/lodSeries.js
176
694
  var DEFAULT_MAX_LEVELS = 18;
177
695
  var toTypedArray = (value, Type) => {
@@ -250,7 +768,7 @@ var concatTyped = (Type, left, right) => {
250
768
  out.set(right, left.length);
251
769
  return out;
252
770
  };
253
- var LineSeries = class {
771
+ var LodSeries = class {
254
772
  constructor({ id, name, color, x, y, maxLevels = DEFAULT_MAX_LEVELS }) {
255
773
  this.id = id;
256
774
  this.name = name || id;
@@ -365,273 +883,182 @@ var LineSeries = class {
365
883
  };
366
884
  }
367
885
  };
368
- var createSeries = (options) => new LineSeries(options);
369
-
370
- // src/core/chartRenderer.js
371
- var CHART_HEIGHT = 360;
372
- var RIGHT_AXIS_WIDTH = 58;
373
- var PLOT_PADDING = {
374
- left: 38,
375
- right: RIGHT_AXIS_WIDTH,
376
- top: 34,
377
- bottom: 28
886
+ var LineSeries = class extends LodSeries {
887
+ constructor(options) {
888
+ super(options);
889
+ this.type = "line";
890
+ }
378
891
  };
379
- var Y_SCALE_MIN = 0.05;
380
- var Y_SCALE_MAX = 40;
381
- var Y_AXIS_TICK_COUNT = 5;
382
- var X_AXIS_TICK_COUNT = 5;
383
- var X_SCALE_MIN_SPAN = 10;
384
- var X_SCALE_MAX_SPAN = 1e9;
385
- var JUMP_LATEST_RIGHT_PADDING_RATIO = 0.1;
386
- var INITIAL_GPU_VERTEX_CAPACITY = 4096;
892
+ var BarSeries = class extends LodSeries {
893
+ constructor({ orientation = "vertical", ...options }) {
894
+ super(options);
895
+ if (orientation !== "vertical" && orientation !== "horizontal") {
896
+ throw new TypeError(
897
+ 'BarSeries orientation must be "vertical" or "horizontal"'
898
+ );
899
+ }
900
+ this.type = "bar";
901
+ this.orientation = orientation;
902
+ }
903
+ };
904
+ var createLineSeries = (options) => new LineSeries(options);
905
+ var createSeries = createLineSeries;
906
+ var createBarSeries = (options) => new BarSeries(options);
907
+
908
+ // src/core/renderers/lineRenderer.js
387
909
  var AA_VERTEX_FLOAT_STRIDE = 6;
388
910
  var AA_LINE_WIDTH_PX = 1.5;
389
911
  var AA_EDGE_WIDTH_PX = 1;
390
912
  var DASH_LENGTH_PX = 7;
391
913
  var DASH_GAP_PX = 5;
392
- var DEFAULT_CHART_BACKGROUND = "#f5f9ff";
393
- var EMPTY_OBJECT = {};
394
- var hexToRgb = (color) => {
395
- const value = String(color || "#38bdf8").replace("#", "");
396
- const normalized = value.length === 3 ? value.split("").map((char) => char + char).join("") : value.padEnd(6, "0").slice(0, 6);
397
- const number = Number.parseInt(normalized, 16);
398
- return [
399
- (number >> 16 & 255) / 255,
400
- (number >> 8 & 255) / 255,
401
- (number & 255) / 255
402
- ];
403
- };
404
- var getReadableTextColor = (backgroundColor) => {
405
- const [r, g, b] = hexToRgb(backgroundColor);
406
- const toLinear = (value) => value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
407
- const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
408
- return luminance > 0.45 ? "#111827" : "#ffffff";
409
- };
410
- var getSeriesLabelTextColor = (series) => {
411
- if (series.__labelTextColorFor !== series.color) {
412
- series.__labelTextColorFor = series.color;
413
- series.__labelTextColor = getReadableTextColor(series.color);
414
- }
415
- return series.__labelTextColor;
416
- };
417
- var createProgram = (gl) => {
418
- const vertexSource = `#version 300 es
419
- in vec2 a_xy;
420
- uniform vec2 u_resolution;
421
- uniform vec4 u_rect;
422
- uniform vec2 u_xRange;
423
- uniform vec2 u_yRange;
914
+ var LINE_VERTEX_SOURCE = `#version 300 es
915
+ in vec2 a_xy;
916
+ uniform vec2 u_resolution;
917
+ uniform vec4 u_rect;
918
+ uniform vec2 u_xRange;
919
+ uniform vec2 u_yRange;
424
920
 
425
- void main() {
426
- float xDenom = max(0.000000000001, abs(u_xRange.y - u_xRange.x));
427
- float yDenom = max(0.000000000001, abs(u_yRange.y - u_yRange.x));
428
- float nx = (a_xy.x - u_xRange.x) / xDenom;
429
- float ny = (a_xy.y - u_yRange.x) / yDenom;
430
- float px = u_rect.x + nx * u_rect.z;
431
- float py = u_rect.y + (1.0 - ny) * u_rect.w;
432
- vec2 zeroToOne = vec2(px, py) / u_resolution;
433
- vec2 clip = zeroToOne * 2.0 - 1.0;
434
- gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
435
- }
436
- `;
437
- const fragmentSource = `#version 300 es
438
- precision highp float;
439
- uniform vec3 u_color;
440
- out vec4 outColor;
921
+ void main() {
922
+ float xDenom = max(0.000000000001, abs(u_xRange.y - u_xRange.x));
923
+ float yDenom = max(0.000000000001, abs(u_yRange.y - u_yRange.x));
924
+ float nx = (a_xy.x - u_xRange.x) / xDenom;
925
+ float ny = (a_xy.y - u_yRange.x) / yDenom;
926
+ float px = u_rect.x + nx * u_rect.z;
927
+ float py = u_rect.y + (1.0 - ny) * u_rect.w;
928
+ vec2 clip = (vec2(px, py) / u_resolution) * 2.0 - 1.0;
929
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
930
+ }
931
+ `;
932
+ var LINE_FRAGMENT_SOURCE = `#version 300 es
933
+ precision highp float;
934
+ uniform vec3 u_color;
935
+ out vec4 outColor;
441
936
 
442
- void main() {
443
- outColor = vec4(u_color, 1.0);
444
- }
445
- `;
446
- const compile = (type, source) => {
447
- const shader = gl.createShader(type);
448
- gl.shaderSource(shader, source);
449
- gl.compileShader(shader);
450
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
451
- throw new Error(gl.getShaderInfoLog(shader) || "Shader compile failed");
452
- }
453
- return shader;
454
- };
455
- const program = gl.createProgram();
456
- gl.attachShader(program, compile(gl.VERTEX_SHADER, vertexSource));
457
- gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fragmentSource));
458
- gl.linkProgram(program);
459
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
460
- throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
461
- }
462
- return program;
463
- };
464
- var createAntialiasProgram = (gl) => {
465
- const vertexSource = `#version 300 es
466
- in vec2 a_start;
467
- in vec2 a_end;
468
- in float a_side;
469
- in float a_along;
470
- uniform vec2 u_resolution;
471
- uniform vec4 u_rect;
472
- uniform vec2 u_xRange;
473
- uniform vec2 u_yRange;
474
- uniform float u_lineHalfWidth;
475
- uniform float u_edgeWidth;
476
- out float v_side;
937
+ void main() {
938
+ outColor = vec4(u_color, 1.0);
939
+ }
940
+ `;
941
+ var AA_VERTEX_SOURCE = `#version 300 es
942
+ in vec2 a_start;
943
+ in vec2 a_end;
944
+ in float a_side;
945
+ in float a_along;
946
+ uniform vec2 u_resolution;
947
+ uniform vec4 u_rect;
948
+ uniform vec2 u_xRange;
949
+ uniform vec2 u_yRange;
950
+ uniform float u_lineHalfWidth;
951
+ uniform float u_edgeWidth;
952
+ out float v_side;
477
953
 
478
- vec2 toPixel(vec2 value) {
479
- float xDenom = max(0.000000000001, abs(u_xRange.y - u_xRange.x));
480
- float yDenom = max(0.000000000001, abs(u_yRange.y - u_yRange.x));
481
- float nx = (value.x - u_xRange.x) / xDenom;
482
- float ny = (value.y - u_yRange.x) / yDenom;
483
- return vec2(
484
- u_rect.x + nx * u_rect.z,
485
- u_rect.y + (1.0 - ny) * u_rect.w
486
- );
487
- }
954
+ vec2 toPixel(vec2 value) {
955
+ float xDenom = max(0.000000000001, abs(u_xRange.y - u_xRange.x));
956
+ float yDenom = max(0.000000000001, abs(u_yRange.y - u_yRange.x));
957
+ float nx = (value.x - u_xRange.x) / xDenom;
958
+ float ny = (value.y - u_yRange.x) / yDenom;
959
+ return vec2(
960
+ u_rect.x + nx * u_rect.z,
961
+ u_rect.y + (1.0 - ny) * u_rect.w
962
+ );
963
+ }
488
964
 
489
- void main() {
490
- vec2 startPx = toPixel(a_start);
491
- vec2 endPx = toPixel(a_end);
492
- vec2 delta = endPx - startPx;
493
- float segmentLength = max(0.000001, length(delta));
494
- vec2 direction = delta / segmentLength;
495
- vec2 normal = vec2(-direction.y, direction.x);
496
- float expand = u_lineHalfWidth + u_edgeWidth;
497
- float cap = min(expand, segmentLength * 0.5);
498
- vec2 px = mix(startPx, endPx, a_along)
499
- + direction * ((a_along * 2.0 - 1.0) * cap)
500
- + normal * a_side * expand;
501
- vec2 zeroToOne = px / u_resolution;
502
- vec2 clip = zeroToOne * 2.0 - 1.0;
503
- v_side = a_side;
504
- gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
505
- }
506
- `;
507
- const fragmentSource = `#version 300 es
508
- precision highp float;
509
- uniform vec3 u_color;
510
- uniform float u_lineHalfWidth;
511
- uniform float u_edgeWidth;
512
- in float v_side;
513
- out vec4 outColor;
965
+ void main() {
966
+ vec2 startPx = toPixel(a_start);
967
+ vec2 endPx = toPixel(a_end);
968
+ vec2 delta = endPx - startPx;
969
+ float segmentLength = max(0.000001, length(delta));
970
+ vec2 direction = delta / segmentLength;
971
+ vec2 normal = vec2(-direction.y, direction.x);
972
+ float expand = u_lineHalfWidth + u_edgeWidth;
973
+ float cap = min(expand, segmentLength * 0.5);
974
+ vec2 px = mix(startPx, endPx, a_along)
975
+ + direction * ((a_along * 2.0 - 1.0) * cap)
976
+ + normal * a_side * expand;
977
+ vec2 clip = (px / u_resolution) * 2.0 - 1.0;
978
+ v_side = a_side;
979
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
980
+ }
981
+ `;
982
+ var AA_FRAGMENT_SOURCE = `#version 300 es
983
+ precision highp float;
984
+ uniform vec3 u_color;
985
+ uniform float u_lineHalfWidth;
986
+ uniform float u_edgeWidth;
987
+ in float v_side;
988
+ out vec4 outColor;
514
989
 
515
- void main() {
516
- float expand = u_lineHalfWidth + u_edgeWidth;
517
- float distanceFromCenter = abs(v_side) * expand;
518
- float alpha = 1.0 - smoothstep(u_lineHalfWidth, expand, distanceFromCenter);
519
- outColor = vec4(u_color, alpha);
520
- }
521
- `;
522
- const compile = (type, source) => {
523
- const shader = gl.createShader(type);
524
- gl.shaderSource(shader, source);
525
- gl.compileShader(shader);
526
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
527
- throw new Error(gl.getShaderInfoLog(shader) || "Shader compile failed");
528
- }
529
- return shader;
530
- };
531
- const program = gl.createProgram();
532
- gl.attachShader(program, compile(gl.VERTEX_SHADER, vertexSource));
533
- gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fragmentSource));
534
- gl.linkProgram(program);
535
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
536
- throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
537
- }
538
- return program;
539
- };
540
- var createSeriesBufferCache = () => /* @__PURE__ */ new Map();
541
- var getNextVertexCapacity = (currentCapacity, requiredCapacity) => {
542
- let nextCapacity = Math.max(
543
- INITIAL_GPU_VERTEX_CAPACITY,
544
- currentCapacity || 0
545
- );
546
- while (nextCapacity < requiredCapacity) {
547
- nextCapacity *= 2;
548
- }
549
- return nextCapacity;
550
- };
551
- var getSeriesBuffer = (gl, cache, seriesId, pointCount) => {
552
- const requiredVertexFloats = pointCount * 2;
553
- let entry = cache.get(seriesId);
554
- if (!entry) {
555
- entry = {
556
- buffer: gl.createBuffer(),
557
- gpuCapacity: 0,
558
- vertices: new Float32Array(
559
- getNextVertexCapacity(0, requiredVertexFloats)
560
- )
561
- };
562
- cache.set(seriesId, entry);
563
- }
564
- if (entry.vertices.length < requiredVertexFloats) {
565
- entry.vertices = new Float32Array(
566
- getNextVertexCapacity(entry.vertices.length, requiredVertexFloats)
567
- );
568
- }
569
- if (entry.gpuCapacity < entry.vertices.length) {
570
- entry.gpuCapacity = entry.vertices.length;
571
- gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
572
- gl.bufferData(
573
- gl.ARRAY_BUFFER,
574
- entry.gpuCapacity * Float32Array.BYTES_PER_ELEMENT,
575
- gl.DYNAMIC_DRAW
576
- );
990
+ void main() {
991
+ float expand = u_lineHalfWidth + u_edgeWidth;
992
+ float distanceFromCenter = abs(v_side) * expand;
993
+ float alpha = 1.0 - smoothstep(u_lineHalfWidth, expand, distanceFromCenter);
994
+ outColor = vec4(u_color, alpha);
995
+ }
996
+ `;
997
+ var fillSeriesPoints = ({
998
+ vertices,
999
+ visiblePoints,
1000
+ pointCount,
1001
+ animatedPoint
1002
+ }) => {
1003
+ for (let index = 0; index < pointCount; index += 1) {
1004
+ const source = animatedPoint && index === animatedPoint.index ? animatedPoint : { x: visiblePoints.x[index], y: visiblePoints.y[index] };
1005
+ vertices[index * 2] = source.x;
1006
+ vertices[index * 2 + 1] = source.y;
577
1007
  }
578
- return entry;
579
1008
  };
580
- var getAntialiasSeriesBuffer = (gl, cache, seriesId, pointCount) => {
581
- const segmentCount = Math.max(0, pointCount - 1);
582
- const requiredVertexFloats = segmentCount * 6 * AA_VERTEX_FLOAT_STRIDE;
583
- let entry = cache.get(seriesId);
584
- if (!entry) {
585
- entry = {
586
- buffer: gl.createBuffer(),
587
- gpuCapacity: 0,
588
- vertices: new Float32Array(
589
- getNextVertexCapacity(0, requiredVertexFloats)
590
- )
591
- };
592
- cache.set(seriesId, entry);
593
- }
594
- if (entry.vertices.length < requiredVertexFloats) {
595
- entry.vertices = new Float32Array(
596
- getNextVertexCapacity(entry.vertices.length, requiredVertexFloats)
597
- );
598
- }
599
- if (entry.gpuCapacity < entry.vertices.length) {
600
- entry.gpuCapacity = entry.vertices.length;
601
- gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
602
- gl.bufferData(
603
- gl.ARRAY_BUFFER,
604
- entry.gpuCapacity * Float32Array.BYTES_PER_ELEMENT,
605
- gl.DYNAMIC_DRAW
606
- );
1009
+ var getSinglePointSegment = ({ series, state, yRange, plot }) => {
1010
+ if (series.length !== 1) return null;
1011
+ const x = series.rawX[0];
1012
+ const y = series.rawY[0];
1013
+ if (x < state.xMin || x > state.xMax || y < yRange.minY || y > yRange.maxY) {
1014
+ return null;
607
1015
  }
608
- return entry;
1016
+ const halfWidth = (state.xMax - state.xMin) / Math.max(1, plot.width) * 3;
1017
+ return {
1018
+ x: new Float64Array([x - halfWidth, x + halfWidth]),
1019
+ y: new Float32Array([y, y]),
1020
+ bucketSize: 1,
1021
+ pointCount: 2,
1022
+ endpoint: { x, y }
1023
+ };
609
1024
  };
610
- var deleteSeriesBuffers = (gl, cache) => {
611
- cache.forEach((entry) => {
612
- if (entry.buffer) gl.deleteBuffer(entry.buffer);
613
- });
614
- cache.clear();
1025
+ var writeAntialiasVertex = (vertices, offset, start, end, side, along) => {
1026
+ vertices[offset] = start.x;
1027
+ vertices[offset + 1] = start.y;
1028
+ vertices[offset + 2] = end.x;
1029
+ vertices[offset + 3] = end.y;
1030
+ vertices[offset + 4] = side;
1031
+ vertices[offset + 5] = along;
615
1032
  };
616
- var fillSeriesPoints = ({ vertices, visiblePoints, pointCount, animatedPoint }) => {
617
- for (let i = 0; i < pointCount; i += 1) {
618
- if (animatedPoint && i === animatedPoint.index) {
619
- vertices[i * 2] = animatedPoint.x;
620
- vertices[i * 2 + 1] = animatedPoint.y;
621
- continue;
622
- }
623
- vertices[i * 2] = visiblePoints.x[i];
624
- vertices[i * 2 + 1] = visiblePoints.y[i];
1033
+ var getSeriesPoint = (visiblePoints, animatedPoint, index) => animatedPoint && index === animatedPoint.index ? { x: animatedPoint.x, y: animatedPoint.y } : { x: visiblePoints.x[index], y: visiblePoints.y[index] };
1034
+ var fillAntialiasSeriesSegments = ({
1035
+ vertices,
1036
+ visiblePoints,
1037
+ pointCount,
1038
+ animatedPoint
1039
+ }) => {
1040
+ let offset = 0;
1041
+ for (let index = 0; index < pointCount - 1; index += 1) {
1042
+ const start = getSeriesPoint(visiblePoints, animatedPoint, index);
1043
+ const end = getSeriesPoint(visiblePoints, animatedPoint, index + 1);
1044
+ [
1045
+ [-1, 0],
1046
+ [1, 0],
1047
+ [-1, 1],
1048
+ [-1, 1],
1049
+ [1, 0],
1050
+ [1, 1]
1051
+ ].forEach(([side, along]) => {
1052
+ writeAntialiasVertex(vertices, offset, start, end, side, along);
1053
+ offset += AA_VERTEX_FLOAT_STRIDE;
1054
+ });
625
1055
  }
1056
+ return offset / AA_VERTEX_FLOAT_STRIDE;
626
1057
  };
627
1058
  var projectDataToPixel = ({ x, y, state, yRange, plot }) => ({
628
1059
  x: plot.x + (x - state.xMin) / (state.xMax - state.xMin) * plot.width,
629
1060
  y: plot.y + (yRange.maxY - y) / (yRange.maxY - yRange.minY) * plot.height
630
1061
  });
631
- var writeLineVertex = (vertices, offset, x, y) => {
632
- vertices[offset] = x;
633
- vertices[offset + 1] = y;
634
- };
635
1062
  var fillDashedSeriesSegments = ({
636
1063
  vertices,
637
1064
  visiblePoints,
@@ -643,14 +1070,11 @@ var fillDashedSeriesSegments = ({
643
1070
  let offset = 0;
644
1071
  let distance = 0;
645
1072
  const dashTotal = DASH_LENGTH_PX + DASH_GAP_PX;
646
- for (let i = 0; i < pointCount - 1; i += 1) {
647
- const start = {
648
- x: visiblePoints.x[i],
649
- y: visiblePoints.y[i]
650
- };
1073
+ for (let index = 0; index < pointCount - 1; index += 1) {
1074
+ const start = { x: visiblePoints.x[index], y: visiblePoints.y[index] };
651
1075
  const end = {
652
- x: visiblePoints.x[i + 1],
653
- y: visiblePoints.y[i + 1]
1076
+ x: visiblePoints.x[index + 1],
1077
+ y: visiblePoints.y[index + 1]
654
1078
  };
655
1079
  const startPx = projectDataToPixel({ ...start, state, yRange, plot });
656
1080
  const endPx = projectDataToPixel({ ...end, state, yRange, plot });
@@ -662,25 +1086,14 @@ var fillDashedSeriesSegments = ({
662
1086
  const boundary = phase < DASH_LENGTH_PX ? DASH_LENGTH_PX - phase : dashTotal - phase;
663
1087
  const step = Math.min(boundary, pixelLength - consumed);
664
1088
  if (phase < DASH_LENGTH_PX) {
665
- if (offset + 4 > vertices.length) {
666
- return offset / 2;
667
- }
1089
+ if (offset + 4 > vertices.length) return offset / 2;
668
1090
  const t0 = consumed / pixelLength;
669
1091
  const t1 = (consumed + step) / pixelLength;
670
- writeLineVertex(
671
- vertices,
672
- offset,
673
- start.x + (end.x - start.x) * t0,
674
- start.y + (end.y - start.y) * t0
675
- );
676
- offset += 2;
677
- writeLineVertex(
678
- vertices,
679
- offset,
680
- start.x + (end.x - start.x) * t1,
681
- start.y + (end.y - start.y) * t1
682
- );
683
- offset += 2;
1092
+ vertices[offset] = start.x + (end.x - start.x) * t0;
1093
+ vertices[offset + 1] = start.y + (end.y - start.y) * t0;
1094
+ vertices[offset + 2] = start.x + (end.x - start.x) * t1;
1095
+ vertices[offset + 3] = start.y + (end.y - start.y) * t1;
1096
+ offset += 4;
684
1097
  }
685
1098
  consumed += step;
686
1099
  distance += step;
@@ -688,107 +1101,6 @@ var fillDashedSeriesSegments = ({
688
1101
  }
689
1102
  return offset / 2;
690
1103
  };
691
- var getSeriesPoint = (visiblePoints, animatedPoint, index) => {
692
- if (animatedPoint && index === animatedPoint.index) {
693
- return { x: animatedPoint.x, y: animatedPoint.y };
694
- }
695
- return { x: visiblePoints.x[index], y: visiblePoints.y[index] };
696
- };
697
- var getSinglePointSegment = ({ series, state, yRange, plot }) => {
698
- if (series.length !== 1) return null;
699
- const x = series.rawX[0];
700
- const y = series.rawY[0];
701
- if (x < state.xMin || x > state.xMax || y < yRange.minY || y > yRange.maxY) {
702
- return null;
703
- }
704
- const halfWidth = (state.xMax - state.xMin) / Math.max(1, plot.width) * 3;
705
- return {
706
- x: new Float64Array([x - halfWidth, x + halfWidth]),
707
- y: new Float32Array([y, y]),
708
- bucketSize: 1,
709
- pointCount: 2,
710
- endpoint: { x, y }
711
- };
712
- };
713
- var writeAntialiasVertex = (vertices, offset, start, end, side, along) => {
714
- vertices[offset] = start.x;
715
- vertices[offset + 1] = start.y;
716
- vertices[offset + 2] = end.x;
717
- vertices[offset + 3] = end.y;
718
- vertices[offset + 4] = side;
719
- vertices[offset + 5] = along;
720
- };
721
- var fillAntialiasSeriesSegments = ({
722
- vertices,
723
- visiblePoints,
724
- pointCount,
725
- animatedPoint
726
- }) => {
727
- let offset = 0;
728
- for (let i = 0; i < pointCount - 1; i += 1) {
729
- const start = getSeriesPoint(visiblePoints, animatedPoint, i);
730
- const end = getSeriesPoint(visiblePoints, animatedPoint, i + 1);
731
- writeAntialiasVertex(vertices, offset, start, end, -1, 0);
732
- offset += AA_VERTEX_FLOAT_STRIDE;
733
- writeAntialiasVertex(vertices, offset, start, end, 1, 0);
734
- offset += AA_VERTEX_FLOAT_STRIDE;
735
- writeAntialiasVertex(vertices, offset, start, end, -1, 1);
736
- offset += AA_VERTEX_FLOAT_STRIDE;
737
- writeAntialiasVertex(vertices, offset, start, end, -1, 1);
738
- offset += AA_VERTEX_FLOAT_STRIDE;
739
- writeAntialiasVertex(vertices, offset, start, end, 1, 0);
740
- offset += AA_VERTEX_FLOAT_STRIDE;
741
- writeAntialiasVertex(vertices, offset, start, end, 1, 1);
742
- offset += AA_VERTEX_FLOAT_STRIDE;
743
- }
744
- return offset / AA_VERTEX_FLOAT_STRIDE;
745
- };
746
- var lowerBound2 = (array, value) => {
747
- let lo = 0;
748
- let hi = array.length;
749
- while (lo < hi) {
750
- const mid = lo + hi >> 1;
751
- if (array[mid] < value) lo = mid + 1;
752
- else hi = mid;
753
- }
754
- return lo;
755
- };
756
- var getNearestPointIndex = (xValues, xValue) => {
757
- if (!xValues.length) return -1;
758
- const nextIndex = lowerBound2(xValues, xValue);
759
- if (nextIndex <= 0) return 0;
760
- if (nextIndex >= xValues.length) return xValues.length - 1;
761
- const previousIndex = nextIndex - 1;
762
- return Math.abs(xValues[nextIndex] - xValue) < Math.abs(xValue - xValues[previousIndex]) ? nextIndex : previousIndex;
763
- };
764
- var getChartXBounds = (chart) => {
765
- let minX = Infinity;
766
- let maxX = -Infinity;
767
- chart.series.forEach((series) => {
768
- if (series.length === 0) return;
769
- minX = Math.min(minX, series.rawX[0]);
770
- maxX = Math.max(maxX, series.rawX[series.length - 1]);
771
- });
772
- return { minX, maxX };
773
- };
774
- var getOrderedSeries = (chart, seriesOrderByChart) => {
775
- const series = Array.isArray(chart?.series) ? chart.series : [];
776
- const order = seriesOrderByChart?.[chart?.id];
777
- if (!Array.isArray(order) || order.length !== series.length) {
778
- return series;
779
- }
780
- const used = /* @__PURE__ */ new Set();
781
- const ordered = [];
782
- order.forEach((seriesIndex) => {
783
- if (!Number.isInteger(seriesIndex)) return;
784
- if (seriesIndex < 0 || seriesIndex >= series.length) return;
785
- if (used.has(seriesIndex)) return;
786
- used.add(seriesIndex);
787
- ordered.push(series[seriesIndex]);
788
- });
789
- if (ordered.length !== series.length) return series;
790
- return ordered;
791
- };
792
1104
  var normalizeMovingAverage = (movingAverage) => {
793
1105
  if (!movingAverage?.enabled) return null;
794
1106
  return {
@@ -797,7 +1109,6 @@ var normalizeMovingAverage = (movingAverage) => {
797
1109
  type: movingAverage.type === "sma" ? "sma" : "ema"
798
1110
  };
799
1111
  };
800
- var getMovingAverageCacheKey = (series, movingAverage) => `${series.id}::ma:${movingAverage.type}:${movingAverage.period}`;
801
1112
  var calculateMovingAverageChunk = ({
802
1113
  sourceY,
803
1114
  startIndex,
@@ -806,106 +1117,524 @@ var calculateMovingAverageChunk = ({
806
1117
  previousAverage
807
1118
  }) => {
808
1119
  const out = new Float32Array(Math.max(0, sourceY.length - startIndex));
809
- if (out.length === 0) return out;
1120
+ if (!out.length) return out;
810
1121
  if (type === "sma") {
811
1122
  let sum = 0;
812
1123
  const firstWindowStart = Math.max(0, startIndex - period + 1);
813
- for (let i = firstWindowStart; i < startIndex; i += 1) {
814
- sum += sourceY[i];
1124
+ for (let index = firstWindowStart; index < startIndex; index += 1) {
1125
+ sum += sourceY[index];
815
1126
  }
816
- for (let sourceIndex = startIndex; sourceIndex < sourceY.length; sourceIndex += 1) {
817
- sum += sourceY[sourceIndex];
818
- const removeIndex = sourceIndex - period;
819
- if (removeIndex >= firstWindowStart) {
820
- sum -= sourceY[removeIndex];
821
- }
822
- const safePeriod = Math.min(period, sourceIndex + 1);
823
- out[sourceIndex - startIndex] = sum / safePeriod;
1127
+ for (let index = startIndex; index < sourceY.length; index += 1) {
1128
+ sum += sourceY[index];
1129
+ const removeIndex = index - period;
1130
+ if (removeIndex >= firstWindowStart) sum -= sourceY[removeIndex];
1131
+ out[index - startIndex] = sum / Math.min(period, index + 1);
824
1132
  }
825
1133
  return out;
826
1134
  }
827
1135
  const multiplier = 2 / (period + 1);
828
- let prev = Number.isFinite(previousAverage) ? previousAverage : sourceY[startIndex];
829
- for (let sourceIndex = startIndex; sourceIndex < sourceY.length; sourceIndex += 1) {
830
- prev = sourceY[sourceIndex] * multiplier + prev * (1 - multiplier);
831
- out[sourceIndex - startIndex] = prev;
1136
+ let previous = Number.isFinite(previousAverage) ? previousAverage : sourceY[startIndex];
1137
+ for (let index = startIndex; index < sourceY.length; index += 1) {
1138
+ previous = sourceY[index] * multiplier + previous * (1 - multiplier);
1139
+ out[index - startIndex] = previous;
832
1140
  }
833
1141
  return out;
834
1142
  };
835
- var getMovingAverageSeriesForChart = ({
836
- chart,
837
- movingAverage,
838
- cache
839
- }) => {
840
- const normalized = normalizeMovingAverage(movingAverage);
841
- if (!normalized || !cache) return [];
842
- return chart.series.map((series) => {
843
- if (series.length === 0) return null;
844
- const cacheKey = getMovingAverageCacheKey(series, normalized);
845
- const cached = cache.get(cacheKey);
846
- if (cached && cached.sourceSeries === series && cached.sourceLength <= series.length) {
847
- const appendStart = cached.sourceLength;
848
- if (appendStart < series.length) {
849
- const appendedY = calculateMovingAverageChunk({
850
- sourceY: series.rawY.subarray(0, series.length),
851
- startIndex: appendStart,
852
- period: normalized.period,
853
- type: normalized.type,
854
- previousAverage: appendStart > 0 ? cached.series.rawY[appendStart - 1] : void 0
855
- });
856
- cached.series.append(
857
- series.rawX.subarray(appendStart, series.length),
858
- appendedY
859
- );
860
- cached.sourceLength = series.length;
861
- }
862
- cached.series.color = series.color;
863
- cached.series.name = `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`;
864
- return cached.series;
865
- }
866
- const y = calculateMovingAverageChunk({
867
- sourceY: series.rawY.subarray(0, series.length),
868
- startIndex: 0,
869
- period: normalized.period,
870
- type: normalized.type
871
- });
872
- const maSeries = createSeries({
873
- id: cacheKey,
874
- name: `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`,
875
- color: series.color,
876
- x: series.rawX.subarray(0, series.length),
877
- y
878
- });
879
- cache.set(cacheKey, {
880
- sourceSeries: series,
881
- sourceLength: series.length,
882
- series: maSeries
883
- });
884
- return maSeries;
885
- }).filter(Boolean);
1143
+ var getMovingAverageSeriesForChart = ({
1144
+ chart,
1145
+ movingAverage,
1146
+ cache
1147
+ }) => {
1148
+ const normalized = normalizeMovingAverage(movingAverage);
1149
+ if (!normalized) return [];
1150
+ return chart.series.map((series) => {
1151
+ if (!series.length) return null;
1152
+ const key = `${series.id}::ma:${normalized.type}:${normalized.period}`;
1153
+ const cached = cache.get(key);
1154
+ if (cached && cached.sourceSeries === series && cached.sourceLength <= series.length) {
1155
+ const appendStart = cached.sourceLength;
1156
+ if (appendStart < series.length) {
1157
+ const appendedY = calculateMovingAverageChunk({
1158
+ sourceY: series.rawY.subarray(0, series.length),
1159
+ startIndex: appendStart,
1160
+ period: normalized.period,
1161
+ type: normalized.type,
1162
+ previousAverage: appendStart > 0 ? cached.series.rawY[appendStart - 1] : void 0
1163
+ });
1164
+ cached.series.append(
1165
+ series.rawX.subarray(appendStart, series.length),
1166
+ appendedY
1167
+ );
1168
+ cached.sourceLength = series.length;
1169
+ }
1170
+ cached.series.color = series.color;
1171
+ cached.series.name = `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`;
1172
+ return cached.series;
1173
+ }
1174
+ const movingY = calculateMovingAverageChunk({
1175
+ sourceY: series.rawY.subarray(0, series.length),
1176
+ startIndex: 0,
1177
+ period: normalized.period,
1178
+ type: normalized.type
1179
+ });
1180
+ const movingSeries = createLineSeries({
1181
+ id: key,
1182
+ name: `${series.name} ${normalized.type.toUpperCase()} ${normalized.period}`,
1183
+ color: series.color,
1184
+ x: series.rawX.subarray(0, series.length),
1185
+ y: movingY
1186
+ });
1187
+ cache.set(key, {
1188
+ sourceSeries: series,
1189
+ sourceLength: series.length,
1190
+ series: movingSeries
1191
+ });
1192
+ return movingSeries;
1193
+ }).filter(Boolean);
1194
+ };
1195
+ var getLineLocations = (gl, program) => Object.freeze({
1196
+ aXy: gl.getAttribLocation(program, "a_xy"),
1197
+ uColor: gl.getUniformLocation(program, "u_color"),
1198
+ uRect: gl.getUniformLocation(program, "u_rect"),
1199
+ uResolution: gl.getUniformLocation(program, "u_resolution"),
1200
+ uXRange: gl.getUniformLocation(program, "u_xRange"),
1201
+ uYRange: gl.getUniformLocation(program, "u_yRange")
1202
+ });
1203
+ var createLineRenderer = (gl) => {
1204
+ const program = createProgram(
1205
+ gl,
1206
+ LINE_VERTEX_SOURCE,
1207
+ LINE_FRAGMENT_SOURCE
1208
+ );
1209
+ const antialiasProgram = createProgram(
1210
+ gl,
1211
+ AA_VERTEX_SOURCE,
1212
+ AA_FRAGMENT_SOURCE
1213
+ );
1214
+ const locations = getLineLocations(gl, program);
1215
+ const aaLocations = Object.freeze({
1216
+ aAlong: gl.getAttribLocation(antialiasProgram, "a_along"),
1217
+ aEnd: gl.getAttribLocation(antialiasProgram, "a_end"),
1218
+ aSide: gl.getAttribLocation(antialiasProgram, "a_side"),
1219
+ aStart: gl.getAttribLocation(antialiasProgram, "a_start"),
1220
+ uColor: gl.getUniformLocation(antialiasProgram, "u_color"),
1221
+ uEdgeWidth: gl.getUniformLocation(antialiasProgram, "u_edgeWidth"),
1222
+ uLineHalfWidth: gl.getUniformLocation(antialiasProgram, "u_lineHalfWidth"),
1223
+ uRect: gl.getUniformLocation(antialiasProgram, "u_rect"),
1224
+ uResolution: gl.getUniformLocation(antialiasProgram, "u_resolution"),
1225
+ uXRange: gl.getUniformLocation(antialiasProgram, "u_xRange"),
1226
+ uYRange: gl.getUniformLocation(antialiasProgram, "u_yRange")
1227
+ });
1228
+ const buffers = /* @__PURE__ */ new Map();
1229
+ const antialiasBuffers = /* @__PURE__ */ new Map();
1230
+ const movingAverageCache = /* @__PURE__ */ new Map();
1231
+ const setCommonUniforms = (activeLocations, pixelWidth, pixelHeight, scaledPlot, state, yRange) => {
1232
+ gl.uniform2f(activeLocations.uResolution, pixelWidth, pixelHeight);
1233
+ gl.uniform4f(
1234
+ activeLocations.uRect,
1235
+ scaledPlot.x,
1236
+ scaledPlot.y,
1237
+ scaledPlot.width,
1238
+ scaledPlot.height
1239
+ );
1240
+ gl.uniform2f(activeLocations.uXRange, state.xMin, state.xMax);
1241
+ gl.uniform2f(activeLocations.uYRange, yRange.minY, yRange.maxY);
1242
+ };
1243
+ return {
1244
+ draw({
1245
+ antialiasLines,
1246
+ chart,
1247
+ descriptor,
1248
+ dpr,
1249
+ getAppendAnimatedPoint,
1250
+ movingAverage,
1251
+ now,
1252
+ pixelHeight,
1253
+ pixelWidth,
1254
+ plot,
1255
+ scaledPlot,
1256
+ seriesOrderByChart,
1257
+ state,
1258
+ yRange
1259
+ }) {
1260
+ const useAntialias = Boolean(antialiasLines);
1261
+ const activeProgram = useAntialias ? antialiasProgram : program;
1262
+ const activeLocations = useAntialias ? aaLocations : locations;
1263
+ gl.useProgram(activeProgram);
1264
+ setCommonUniforms(
1265
+ activeLocations,
1266
+ pixelWidth,
1267
+ pixelHeight,
1268
+ scaledPlot,
1269
+ state,
1270
+ yRange
1271
+ );
1272
+ if (useAntialias) {
1273
+ gl.enable(gl.BLEND);
1274
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
1275
+ gl.enableVertexAttribArray(aaLocations.aStart);
1276
+ gl.enableVertexAttribArray(aaLocations.aEnd);
1277
+ gl.enableVertexAttribArray(aaLocations.aSide);
1278
+ gl.enableVertexAttribArray(aaLocations.aAlong);
1279
+ gl.uniform1f(
1280
+ aaLocations.uLineHalfWidth,
1281
+ AA_LINE_WIDTH_PX * dpr / 2
1282
+ );
1283
+ gl.uniform1f(aaLocations.uEdgeWidth, AA_EDGE_WIDTH_PX * dpr);
1284
+ } else {
1285
+ gl.disable(gl.BLEND);
1286
+ gl.enableVertexAttribArray(locations.aXy);
1287
+ }
1288
+ const seriesEndpoints = /* @__PURE__ */ new Map();
1289
+ const hideBaseSeries = descriptor.capabilities.movingAverage && Boolean(movingAverage?.enabled) && Boolean(movingAverage?.hideBase);
1290
+ if (!hideBaseSeries) {
1291
+ getOrderedSeries(chart, seriesOrderByChart).forEach((series) => {
1292
+ let visiblePoints = series.getVisiblePoints(
1293
+ state.xMin,
1294
+ state.xMax,
1295
+ plot.width
1296
+ );
1297
+ let singlePointEndpoint = null;
1298
+ if (visiblePoints.pointCount < 2) {
1299
+ const singlePointSegment = getSinglePointSegment({
1300
+ series,
1301
+ state,
1302
+ yRange,
1303
+ plot
1304
+ });
1305
+ if (!singlePointSegment) return;
1306
+ visiblePoints = singlePointSegment;
1307
+ singlePointEndpoint = singlePointSegment.endpoint;
1308
+ }
1309
+ const animatedPoint = singlePointEndpoint ? null : getAppendAnimatedPoint({
1310
+ seriesId: series.id,
1311
+ visiblePoints,
1312
+ now
1313
+ });
1314
+ const pointCount = animatedPoint?.pointCount ?? visiblePoints.pointCount;
1315
+ if (animatedPoint) {
1316
+ seriesEndpoints.set(series.id, {
1317
+ x: animatedPoint.x,
1318
+ y: animatedPoint.y
1319
+ });
1320
+ } else if (singlePointEndpoint) {
1321
+ seriesEndpoints.set(series.id, singlePointEndpoint);
1322
+ }
1323
+ const [r, g, b] = hexToRgb(series.color);
1324
+ gl.uniform3f(activeLocations.uColor, r, g, b);
1325
+ if (useAntialias) {
1326
+ const requiredFloats = Math.max(0, pointCount - 1) * 6 * AA_VERTEX_FLOAT_STRIDE;
1327
+ const entry2 = getDynamicBuffer(
1328
+ gl,
1329
+ antialiasBuffers,
1330
+ series.id,
1331
+ requiredFloats
1332
+ );
1333
+ const drawVertexCount = fillAntialiasSeriesSegments({
1334
+ vertices: entry2.vertices,
1335
+ visiblePoints,
1336
+ pointCount,
1337
+ animatedPoint
1338
+ });
1339
+ const stride = AA_VERTEX_FLOAT_STRIDE * Float32Array.BYTES_PER_ELEMENT;
1340
+ gl.bindBuffer(gl.ARRAY_BUFFER, entry2.buffer);
1341
+ gl.vertexAttribPointer(
1342
+ aaLocations.aStart,
1343
+ 2,
1344
+ gl.FLOAT,
1345
+ false,
1346
+ stride,
1347
+ 0
1348
+ );
1349
+ gl.vertexAttribPointer(
1350
+ aaLocations.aEnd,
1351
+ 2,
1352
+ gl.FLOAT,
1353
+ false,
1354
+ stride,
1355
+ 2 * Float32Array.BYTES_PER_ELEMENT
1356
+ );
1357
+ gl.vertexAttribPointer(
1358
+ aaLocations.aSide,
1359
+ 1,
1360
+ gl.FLOAT,
1361
+ false,
1362
+ stride,
1363
+ 4 * Float32Array.BYTES_PER_ELEMENT
1364
+ );
1365
+ gl.vertexAttribPointer(
1366
+ aaLocations.aAlong,
1367
+ 1,
1368
+ gl.FLOAT,
1369
+ false,
1370
+ stride,
1371
+ 5 * Float32Array.BYTES_PER_ELEMENT
1372
+ );
1373
+ gl.bufferSubData(
1374
+ gl.ARRAY_BUFFER,
1375
+ 0,
1376
+ entry2.vertices,
1377
+ 0,
1378
+ drawVertexCount * AA_VERTEX_FLOAT_STRIDE
1379
+ );
1380
+ gl.drawArrays(gl.TRIANGLES, 0, drawVertexCount);
1381
+ return;
1382
+ }
1383
+ const entry = getDynamicBuffer(
1384
+ gl,
1385
+ buffers,
1386
+ series.id,
1387
+ pointCount * 2
1388
+ );
1389
+ fillSeriesPoints({
1390
+ vertices: entry.vertices,
1391
+ visiblePoints,
1392
+ pointCount,
1393
+ animatedPoint
1394
+ });
1395
+ gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
1396
+ gl.vertexAttribPointer(locations.aXy, 2, gl.FLOAT, false, 0, 0);
1397
+ gl.bufferSubData(
1398
+ gl.ARRAY_BUFFER,
1399
+ 0,
1400
+ entry.vertices,
1401
+ 0,
1402
+ pointCount * 2
1403
+ );
1404
+ gl.drawArrays(gl.LINE_STRIP, 0, pointCount);
1405
+ });
1406
+ }
1407
+ const movingSeries = getMovingAverageSeriesForChart({
1408
+ chart,
1409
+ movingAverage: descriptor.capabilities.movingAverage ? movingAverage : null,
1410
+ cache: movingAverageCache
1411
+ });
1412
+ if (movingSeries.length) {
1413
+ gl.disable(gl.BLEND);
1414
+ gl.useProgram(program);
1415
+ gl.enableVertexAttribArray(locations.aXy);
1416
+ setCommonUniforms(
1417
+ locations,
1418
+ pixelWidth,
1419
+ pixelHeight,
1420
+ scaledPlot,
1421
+ state,
1422
+ yRange
1423
+ );
1424
+ getOrderedSeries(
1425
+ { ...chart, series: movingSeries },
1426
+ seriesOrderByChart
1427
+ ).forEach((series) => {
1428
+ const visiblePoints = series.getVisiblePoints(
1429
+ state.xMin,
1430
+ state.xMax,
1431
+ plot.width
1432
+ );
1433
+ if (visiblePoints.pointCount < 2) return;
1434
+ const entry = getDynamicBuffer(
1435
+ gl,
1436
+ buffers,
1437
+ series.id,
1438
+ visiblePoints.pointCount * 8 * 2
1439
+ );
1440
+ const drawVertexCount = fillDashedSeriesSegments({
1441
+ vertices: entry.vertices,
1442
+ visiblePoints,
1443
+ pointCount: visiblePoints.pointCount,
1444
+ state,
1445
+ yRange,
1446
+ plot
1447
+ });
1448
+ if (drawVertexCount < 2) return;
1449
+ const [r, g, b] = hexToRgb(series.color);
1450
+ gl.uniform3f(locations.uColor, r, g, b);
1451
+ gl.bindBuffer(gl.ARRAY_BUFFER, entry.buffer);
1452
+ gl.vertexAttribPointer(locations.aXy, 2, gl.FLOAT, false, 0, 0);
1453
+ gl.bufferSubData(
1454
+ gl.ARRAY_BUFFER,
1455
+ 0,
1456
+ entry.vertices,
1457
+ 0,
1458
+ drawVertexCount * 2
1459
+ );
1460
+ gl.drawArrays(gl.LINES, 0, drawVertexCount);
1461
+ });
1462
+ }
1463
+ gl.disable(gl.BLEND);
1464
+ return { seriesEndpoints };
1465
+ },
1466
+ destroy() {
1467
+ deleteBufferCache(gl, buffers);
1468
+ deleteBufferCache(gl, antialiasBuffers);
1469
+ movingAverageCache.clear();
1470
+ gl.deleteProgram(program);
1471
+ gl.deleteProgram(antialiasProgram);
1472
+ }
1473
+ };
1474
+ };
1475
+
1476
+ // src/core/renderers/index.js
1477
+ var RENDERER_FACTORIES = Object.freeze({
1478
+ bar: createBarRenderer,
1479
+ line: createLineRenderer
1480
+ });
1481
+ var createRendererRegistry = (gl) => {
1482
+ const renderers = new Map(
1483
+ Object.entries(RENDERER_FACTORIES).map(([type, createRenderer]) => [
1484
+ type,
1485
+ createRenderer(gl)
1486
+ ])
1487
+ );
1488
+ return {
1489
+ draw(type, payload) {
1490
+ const renderer = renderers.get(type);
1491
+ if (!renderer) {
1492
+ throw new TypeError(`No AlienCharts renderer registered for "${type}"`);
1493
+ }
1494
+ return renderer.draw(payload);
1495
+ },
1496
+ getTooltipCategory(type, payload) {
1497
+ const renderer = renderers.get(type);
1498
+ if (!renderer) {
1499
+ throw new TypeError(`No AlienCharts renderer registered for "${type}"`);
1500
+ }
1501
+ return renderer.getTooltipCategory?.(payload) ?? payload.visiblePoints.x[payload.pointIndex];
1502
+ },
1503
+ destroy() {
1504
+ renderers.forEach((renderer) => renderer.destroy());
1505
+ renderers.clear();
1506
+ }
1507
+ };
1508
+ };
1509
+
1510
+ // src/core/chartRenderer.js
1511
+ var CHART_HEIGHT = 360;
1512
+ var RIGHT_AXIS_WIDTH = 58;
1513
+ var PLOT_PADDING = Object.freeze({
1514
+ left: 38,
1515
+ right: RIGHT_AXIS_WIDTH,
1516
+ top: 34,
1517
+ bottom: 28
1518
+ });
1519
+ var HORIZONTAL_PLOT_PADDING = Object.freeze({
1520
+ left: RIGHT_AXIS_WIDTH,
1521
+ right: 20,
1522
+ top: 34,
1523
+ bottom: 28
1524
+ });
1525
+ var CATEGORICAL_PLOT_PADDING = Object.freeze({
1526
+ ...PLOT_PADDING,
1527
+ bottom: 42
1528
+ });
1529
+ var CATEGORICAL_HORIZONTAL_PLOT_PADDING = Object.freeze({
1530
+ ...HORIZONTAL_PLOT_PADDING,
1531
+ left: 128
1532
+ });
1533
+ var Y_SCALE_MIN = 0.05;
1534
+ var Y_SCALE_MAX = 40;
1535
+ var Y_AXIS_TICK_COUNT = 5;
1536
+ var X_AXIS_TICK_COUNT = 5;
1537
+ var X_SCALE_MIN_SPAN = 10;
1538
+ var X_SCALE_MAX_SPAN = 1e9;
1539
+ var JUMP_LATEST_RIGHT_PADDING_RATIO = 0.1;
1540
+ var DEFAULT_CHART_BACKGROUND = "#f5f9ff";
1541
+ var getChartOrientation = (chart, descriptor) => (descriptor || resolveChartDescriptor(chart)).orientation;
1542
+ var getPlotPadding = (chart, descriptor) => {
1543
+ const categorical = getChartCategories(chart).length > 0;
1544
+ if (getChartOrientation(chart, descriptor) === "horizontal") {
1545
+ return categorical ? CATEGORICAL_HORIZONTAL_PLOT_PADDING : HORIZONTAL_PLOT_PADDING;
1546
+ }
1547
+ return categorical ? CATEGORICAL_PLOT_PADDING : PLOT_PADDING;
1548
+ };
1549
+ var getCategoryPixelLength = (chart, plot, descriptor) => getChartOrientation(chart, descriptor) === "horizontal" ? plot.height : plot.width;
1550
+ var getReadableTextColor = (backgroundColor) => {
1551
+ const [r, g, b] = hexToRgb(backgroundColor);
1552
+ const toLinear = (value) => value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
1553
+ const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
1554
+ return luminance > 0.45 ? "#111827" : "#ffffff";
1555
+ };
1556
+ var getSeriesLabelTextColor = (series) => {
1557
+ if (series.__labelTextColorFor !== series.color) {
1558
+ series.__labelTextColorFor = series.color;
1559
+ series.__labelTextColor = getReadableTextColor(series.color);
1560
+ }
1561
+ return series.__labelTextColor;
1562
+ };
1563
+ var lowerBound2 = (array, value) => {
1564
+ let low = 0;
1565
+ let high = array.length;
1566
+ while (low < high) {
1567
+ const middle = low + high >> 1;
1568
+ if (array[middle] < value) low = middle + 1;
1569
+ else high = middle;
1570
+ }
1571
+ return low;
1572
+ };
1573
+ var getNearestPointIndex = (xValues, xValue) => {
1574
+ if (!xValues.length) return -1;
1575
+ const nextIndex = lowerBound2(xValues, xValue);
1576
+ if (nextIndex <= 0) return 0;
1577
+ if (nextIndex >= xValues.length) return xValues.length - 1;
1578
+ const previousIndex = nextIndex - 1;
1579
+ return Math.abs(xValues[nextIndex] - xValue) < Math.abs(xValue - xValues[previousIndex]) ? nextIndex : previousIndex;
1580
+ };
1581
+ var getChartXBounds = (chart) => {
1582
+ let minX = Infinity;
1583
+ let maxX = -Infinity;
1584
+ chart.series.forEach((series) => {
1585
+ if (!series.length) return;
1586
+ minX = Math.min(minX, series.rawX[0]);
1587
+ maxX = Math.max(maxX, series.rawX[series.length - 1]);
1588
+ });
1589
+ return { minX, maxX };
1590
+ };
1591
+ var getMinimumCategorySpan = (chart) => {
1592
+ const categories = getChartCategories(chart).map((category) => category.value).sort((left, right) => left - right);
1593
+ if (!categories.length) return X_SCALE_MIN_SPAN;
1594
+ let minimumGap = Infinity;
1595
+ for (let index = 1; index < categories.length; index += 1) {
1596
+ minimumGap = Math.min(minimumGap, categories[index] - categories[index - 1]);
1597
+ }
1598
+ return Number.isFinite(minimumGap) ? minimumGap : 1;
886
1599
  };
887
1600
  var getInitialView = (chart, initialVisiblePoints = null) => {
1601
+ const categories = getChartCategories(chart).map((category) => category.value).sort((left, right) => left - right);
1602
+ if (categories.length) {
1603
+ const requestedCount = Number.isFinite(initialVisiblePoints) && initialVisiblePoints > 0 ? Math.max(1, Math.floor(initialVisiblePoints)) : categories.length;
1604
+ const visible = categories.slice(-requestedCount);
1605
+ const first = visible[0];
1606
+ const last = visible[visible.length - 1];
1607
+ const firstIndex = categories.indexOf(first);
1608
+ const lastIndex = categories.lastIndexOf(last);
1609
+ const leftGap = firstIndex > 0 ? first - categories[firstIndex - 1] : visible[1] - first || 1;
1610
+ const rightGap = lastIndex + 1 < categories.length ? categories[lastIndex + 1] - last : last - visible[visible.length - 2] || leftGap;
1611
+ return {
1612
+ xMin: first - leftGap / 2,
1613
+ xMax: last + rightGap / 2
1614
+ };
1615
+ }
888
1616
  const { minX, maxX } = getChartXBounds(chart);
889
1617
  if (!Number.isFinite(minX) || !Number.isFinite(maxX)) {
890
1618
  return { xMin: 0, xMax: 1 };
891
1619
  }
892
1620
  if (minX === maxX) {
893
1621
  const span2 = Number.isFinite(initialVisiblePoints) && initialVisiblePoints > 0 ? Math.max(10, initialVisiblePoints) : 10;
894
- const rightPadding2 = span2 * JUMP_LATEST_RIGHT_PADDING_RATIO;
895
- const nextMax = maxX + rightPadding2;
1622
+ const nextMax = maxX + span2 * JUMP_LATEST_RIGHT_PADDING_RATIO;
896
1623
  return { xMin: nextMax - span2, xMax: nextMax };
897
1624
  }
898
1625
  let span = maxX - minX;
899
1626
  if (Number.isFinite(initialVisiblePoints) && initialVisiblePoints > 0) {
900
1627
  span = Math.min(span, initialVisiblePoints);
901
- const rightPadding2 = span * JUMP_LATEST_RIGHT_PADDING_RATIO;
902
- const nextMax = maxX + rightPadding2;
1628
+ const nextMax = maxX + span * JUMP_LATEST_RIGHT_PADDING_RATIO;
903
1629
  return { xMin: nextMax - span, xMax: nextMax };
904
1630
  }
905
- const rightPadding = span * JUMP_LATEST_RIGHT_PADDING_RATIO;
906
- return { xMin: minX, xMax: maxX + rightPadding };
1631
+ return {
1632
+ xMin: minX,
1633
+ xMax: maxX + span * JUMP_LATEST_RIGHT_PADDING_RATIO
1634
+ };
907
1635
  };
908
- var getYRange = (chart, xMin, xMax, width) => {
1636
+ var getYRange = (chart, xMin, xMax, width, suppliedDescriptor) => {
1637
+ const descriptor = suppliedDescriptor || resolveChartDescriptor(chart);
909
1638
  let minY = Infinity;
910
1639
  let maxY = -Infinity;
911
1640
  let renderedPoints = 0;
@@ -914,26 +1643,53 @@ var getYRange = (chart, xMin, xMax, width) => {
914
1643
  const visible = series.getVisiblePoints(xMin, xMax, width);
915
1644
  renderedPoints += visible.pointCount;
916
1645
  bucketSize = Math.max(bucketSize, visible.bucketSize);
917
- for (let i = 0; i < visible.y.length; i += 1) {
918
- const value = visible.y[i];
919
- if (value < minY) minY = value;
920
- if (value > maxY) maxY = value;
1646
+ for (let index = 0; index < visible.y.length; index += 1) {
1647
+ minY = Math.min(minY, visible.y[index]);
1648
+ maxY = Math.max(maxY, visible.y[index]);
921
1649
  }
922
1650
  });
923
- if (!Number.isFinite(minY) || !Number.isFinite(maxY)) {
924
- return { minY: 0, maxY: 1, renderedPoints, bucketSize };
925
- }
926
1651
  const fixedMinY = Number(chart.yRange?.min);
927
1652
  const fixedMaxY = Number(chart.yRange?.max);
928
1653
  if (Number.isFinite(fixedMinY) && Number.isFinite(fixedMaxY) && fixedMinY < fixedMaxY) {
929
- return { minY: fixedMinY, maxY: fixedMaxY, renderedPoints, bucketSize };
1654
+ return {
1655
+ minY: fixedMinY,
1656
+ maxY: fixedMaxY,
1657
+ renderedPoints,
1658
+ bucketSize
1659
+ };
1660
+ }
1661
+ if (!Number.isFinite(minY) || !Number.isFinite(maxY)) {
1662
+ return { minY: 0, maxY: 1, renderedPoints, bucketSize };
1663
+ }
1664
+ if (descriptor.rangeIncludesZero) {
1665
+ if (minY >= 0) {
1666
+ return {
1667
+ minY: 0,
1668
+ maxY: maxY === 0 ? 1 : maxY * 1.08,
1669
+ renderedPoints,
1670
+ bucketSize
1671
+ };
1672
+ }
1673
+ if (maxY <= 0) {
1674
+ return {
1675
+ minY: minY === 0 ? -1 : minY * 1.08,
1676
+ maxY: 0,
1677
+ renderedPoints,
1678
+ bucketSize
1679
+ };
1680
+ }
930
1681
  }
931
1682
  if (minY === maxY) {
932
1683
  minY -= 1;
933
1684
  maxY += 1;
934
1685
  }
935
- const pad = (maxY - minY) * 0.08;
936
- return { minY: minY - pad, maxY: maxY + pad, renderedPoints, bucketSize };
1686
+ const padding = (maxY - minY) * 0.08;
1687
+ return {
1688
+ minY: minY - padding,
1689
+ maxY: maxY + padding,
1690
+ renderedPoints,
1691
+ bucketSize
1692
+ };
937
1693
  };
938
1694
  var applyYScale = (yRange, scale = 1, centerOffset = 0) => {
939
1695
  const clampedScale = Math.min(Y_SCALE_MAX, Math.max(Y_SCALE_MIN, scale));
@@ -948,20 +1704,30 @@ var applyYScale = (yRange, scale = 1, centerOffset = 0) => {
948
1704
  var trimFormattedNumber = (value) => value.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
949
1705
  var formatNumber = (value) => {
950
1706
  if (!Number.isFinite(value)) return "";
951
- const abs = Math.abs(value);
952
- if (abs !== 0 && abs < 1e-4) return value.toExponential(2);
953
- if (abs < 1) return trimFormattedNumber(value.toPrecision(5));
954
- if (abs >= 1e5) return value.toFixed(0);
955
- if (abs >= 1e3) return trimFormattedNumber(value.toFixed(1));
956
- if (abs >= 10) return trimFormattedNumber(value.toFixed(2));
1707
+ const absolute = Math.abs(value);
1708
+ if (absolute !== 0 && absolute < 1e-4) return value.toExponential(2);
1709
+ if (absolute < 1) return trimFormattedNumber(value.toPrecision(5));
1710
+ if (absolute >= 1e5) return value.toFixed(0);
1711
+ if (absolute >= 1e3) return trimFormattedNumber(value.toFixed(1));
1712
+ if (absolute >= 10) return trimFormattedNumber(value.toFixed(2));
957
1713
  return trimFormattedNumber(value.toFixed(4));
958
1714
  };
959
1715
  var formatCompactNumber = (value) => {
960
1716
  if (!Number.isFinite(value)) return "";
961
- const abs = Math.abs(value);
962
- if (abs >= 1e9) return `${(value / 1e9).toFixed(abs >= 1e10 ? 0 : 1)}B`;
963
- if (abs >= 1e6) return `${(value / 1e6).toFixed(abs >= 1e7 ? 0 : 1)}M`;
964
- if (abs >= 1e3) return `${(value / 1e3).toFixed(abs >= 1e4 ? 0 : 1)}k`;
1717
+ const absolute = Math.abs(value);
1718
+ if (absolute >= 1e9) {
1719
+ return `${(value / 1e9).toFixed(
1720
+ absolute >= 1e10 ? 0 : 1
1721
+ )}B`;
1722
+ }
1723
+ if (absolute >= 1e6) {
1724
+ return `${(value / 1e6).toFixed(
1725
+ absolute >= 1e7 ? 0 : 1
1726
+ )}M`;
1727
+ }
1728
+ if (absolute >= 1e3) {
1729
+ return `${(value / 1e3).toFixed(absolute >= 1e4 ? 0 : 1)}k`;
1730
+ }
965
1731
  return formatNumber(value);
966
1732
  };
967
1733
  var normalizeRect = (start, end) => {
@@ -979,8 +1745,27 @@ var clampPointToPlot = (point, plot) => ({
979
1745
  x: Math.min(plot.x + plot.width, Math.max(plot.x, point.x)),
980
1746
  y: Math.min(plot.y + plot.height, Math.max(plot.y, point.y))
981
1747
  });
1748
+ var getScaledYRangeForLayout = ({
1749
+ chart,
1750
+ descriptor,
1751
+ state,
1752
+ plot,
1753
+ yScaleRef,
1754
+ yCenterOffsetRef
1755
+ }) => applyYScale(
1756
+ getYRange(
1757
+ chart,
1758
+ state.xMin,
1759
+ state.xMax,
1760
+ getCategoryPixelLength(chart, plot, descriptor),
1761
+ descriptor
1762
+ ),
1763
+ yScaleRef.current.get(chart.id) ?? 1,
1764
+ yCenterOffsetRef.current.get(chart.id) ?? 0
1765
+ );
982
1766
  var applyRectangleZoom = ({
983
1767
  chart,
1768
+ descriptor: suppliedDescriptor,
984
1769
  plot,
985
1770
  start,
986
1771
  end,
@@ -990,120 +1775,164 @@ var applyRectangleZoom = ({
990
1775
  yCenterOffsetRef,
991
1776
  yManualScaleRef
992
1777
  }) => {
993
- const clampedStart = clampPointToPlot(start, plot);
994
- const clampedEnd = clampPointToPlot(end, plot);
995
- const rect = normalizeRect(clampedStart, clampedEnd);
1778
+ const rect = normalizeRect(
1779
+ clampPointToPlot(start, plot),
1780
+ clampPointToPlot(end, plot)
1781
+ );
996
1782
  if (!rect || rect.width < 6 || rect.height < 6) return false;
1783
+ const descriptor = suppliedDescriptor || resolveChartDescriptor(chart);
997
1784
  const state = viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
998
- const currentYRange = applyYScale(
999
- getYRange(chart, state.xMin, state.xMax, plot.width),
1000
- yScaleRef.current.get(chart.id) ?? 1,
1001
- yCenterOffsetRef.current.get(chart.id) ?? 0
1002
- );
1003
- const leftRatio = (rect.left - plot.x) / plot.width;
1004
- const rightRatio = (rect.left + rect.width - plot.x) / plot.width;
1005
- const topRatio = (rect.top - plot.y) / plot.height;
1006
- const bottomRatio = (rect.top + rect.height - plot.y) / plot.height;
1007
- const nextMin = state.xMin + leftRatio * (state.xMax - state.xMin);
1008
- const nextMax = state.xMin + rightRatio * (state.xMax - state.xMin);
1009
- if (nextMax - nextMin < X_SCALE_MIN_SPAN) return false;
1010
- const selectedMaxY = currentYRange.maxY - topRatio * (currentYRange.maxY - currentYRange.minY);
1011
- const selectedMinY = currentYRange.maxY - bottomRatio * (currentYRange.maxY - currentYRange.minY);
1012
- const selectedSpan = selectedMaxY - selectedMinY;
1785
+ const currentYRange = getScaledYRangeForLayout({
1786
+ chart,
1787
+ descriptor,
1788
+ state,
1789
+ plot,
1790
+ yScaleRef,
1791
+ yCenterOffsetRef
1792
+ });
1793
+ const transform = createCoordinateTransform({
1794
+ orientation: descriptor.orientation,
1795
+ categoryRange: { min: state.xMin, max: state.xMax },
1796
+ valueRange: {
1797
+ min: currentYRange.minY,
1798
+ max: currentYRange.maxY
1799
+ },
1800
+ plot
1801
+ });
1802
+ const selected = transform.dataBoundsForRect(rect);
1803
+ if (selected.categoryMax - selected.categoryMin < getMinimumCategorySpan(chart)) {
1804
+ return false;
1805
+ }
1806
+ const selectedSpan = selected.valueMax - selected.valueMin;
1013
1807
  if (!Number.isFinite(selectedSpan) || selectedSpan <= 0) return false;
1014
- viewStateRef.current.set(chart.id, { xMin: nextMin, xMax: nextMax });
1015
- const baseYRange = getYRange(chart, nextMin, nextMax, plot.width);
1808
+ viewStateRef.current.set(chart.id, {
1809
+ xMin: selected.categoryMin,
1810
+ xMax: selected.categoryMax
1811
+ });
1812
+ const baseYRange = getYRange(
1813
+ chart,
1814
+ selected.categoryMin,
1815
+ selected.categoryMax,
1816
+ getCategoryPixelLength(chart, plot, descriptor),
1817
+ descriptor
1818
+ );
1016
1819
  const baseSpan = baseYRange.maxY - baseYRange.minY;
1017
1820
  if (Number.isFinite(baseSpan) && baseSpan > 0) {
1018
- const selectedCenter = (selectedMinY + selectedMaxY) / 2;
1821
+ const selectedCenter = (selected.valueMin + selected.valueMax) / 2;
1019
1822
  const baseCenter = (baseYRange.minY + baseYRange.maxY) / 2;
1020
- const nextScale = Math.min(
1021
- Y_SCALE_MAX,
1022
- Math.max(Y_SCALE_MIN, selectedSpan / baseSpan)
1823
+ yScaleRef.current.set(
1824
+ chart.id,
1825
+ Math.min(
1826
+ Y_SCALE_MAX,
1827
+ Math.max(Y_SCALE_MIN, selectedSpan / baseSpan)
1828
+ )
1023
1829
  );
1024
- yScaleRef.current.set(chart.id, nextScale);
1025
1830
  yCenterOffsetRef.current.set(chart.id, selectedCenter - baseCenter);
1026
1831
  yManualScaleRef.current.add(chart.id);
1027
1832
  }
1028
1833
  return true;
1029
1834
  };
1030
- var getChartState = (chart, initialVisiblePoints, viewStateRef) => viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
1031
- var getScaledYRangeForLayout = ({
1032
- chart,
1033
- state,
1034
- plot,
1035
- yScaleRef,
1036
- yCenterOffsetRef
1037
- }) => applyYScale(
1038
- getYRange(chart, state.xMin, state.xMax, plot.width),
1039
- yScaleRef.current.get(chart.id) ?? 1,
1040
- yCenterOffsetRef.current.get(chart.id) ?? 0
1041
- );
1042
1835
  var screenPointToDataPoint = ({
1043
1836
  point,
1044
1837
  chart,
1838
+ descriptor: suppliedDescriptor,
1045
1839
  plot,
1046
1840
  initialVisiblePoints,
1047
1841
  viewStateRef,
1048
1842
  yScaleRef,
1049
1843
  yCenterOffsetRef
1050
1844
  }) => {
1051
- const state = getChartState(chart, initialVisiblePoints, viewStateRef);
1845
+ const descriptor = suppliedDescriptor || resolveChartDescriptor(chart);
1846
+ const state = viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
1052
1847
  const yRange = getScaledYRangeForLayout({
1053
1848
  chart,
1849
+ descriptor,
1054
1850
  state,
1055
1851
  plot,
1056
1852
  yScaleRef,
1057
1853
  yCenterOffsetRef
1058
1854
  });
1059
- const xRatio = (point.x - plot.x) / plot.width;
1060
- const yRatio = (point.y - plot.y) / plot.height;
1061
- return {
1062
- x: state.xMin + xRatio * (state.xMax - state.xMin),
1063
- y: yRange.maxY - yRatio * (yRange.maxY - yRange.minY)
1064
- };
1065
- };
1066
- var dataPointToScreenPoint = ({ point, state, yRange, plot }) => {
1067
- const xRatio = (point.x - state.xMin) / (state.xMax - state.xMin);
1068
- const yRatio = (yRange.maxY - point.y) / (yRange.maxY - yRange.minY);
1069
- return {
1070
- x: plot.x + xRatio * plot.width,
1071
- y: plot.y + yRatio * plot.height
1072
- };
1855
+ return createCoordinateTransform({
1856
+ orientation: descriptor.orientation,
1857
+ categoryRange: { min: state.xMin, max: state.xMax },
1858
+ valueRange: { min: yRange.minY, max: yRange.maxY },
1859
+ plot
1860
+ }).screenToData(point);
1073
1861
  };
1074
1862
  var createAxisOverlay = ({
1075
1863
  chart,
1864
+ descriptor,
1076
1865
  plot,
1077
1866
  state,
1078
1867
  yRange,
1079
1868
  seriesEndpoints,
1080
- seriesOrderByChart = {}
1869
+ seriesOrderByChart
1081
1870
  }) => {
1082
- const { maxX } = getChartXBounds(chart);
1871
+ const horizontal = descriptor.orientation === "horizontal";
1872
+ const transform = createCoordinateTransform({
1873
+ orientation: descriptor.orientation,
1874
+ categoryRange: { min: state.xMin, max: state.xMax },
1875
+ valueRange: { min: yRange.minY, max: yRange.maxY },
1876
+ plot: { x: 0, y: 0, width: plot.width, height: plot.height }
1877
+ });
1083
1878
  const ticks = Array.from({ length: Y_AXIS_TICK_COUNT }, (_, index) => {
1084
- const ratio = Y_AXIS_TICK_COUNT === 1 ? 0 : index / (Y_AXIS_TICK_COUNT - 1);
1879
+ const ratio = index / (Y_AXIS_TICK_COUNT - 1);
1085
1880
  const value = yRange.maxY - ratio * (yRange.maxY - yRange.minY);
1881
+ const screen = transform.dataToScreen({
1882
+ x: state.xMin,
1883
+ y: value
1884
+ });
1086
1885
  return {
1087
1886
  id: `${chart.id}-tick-${index}`,
1088
1887
  value,
1089
- top: ratio * plot.height
1888
+ top: horizontal ? void 0 : screen.y,
1889
+ left: horizontal ? screen.x : void 0
1090
1890
  };
1091
1891
  });
1092
- const xTicks = Array.from({ length: X_AXIS_TICK_COUNT }, (_, index) => {
1093
- const ratio = X_AXIS_TICK_COUNT === 1 ? 0 : index / (X_AXIS_TICK_COUNT - 1);
1892
+ const categories = getChartCategories(chart);
1893
+ const visibleCategories = categories.filter(
1894
+ (category) => category.value >= state.xMin && category.value <= state.xMax
1895
+ ).sort((left, right) => left.value - right.value);
1896
+ const categoryPixelLength = horizontal ? plot.height : plot.width;
1897
+ const maximumCategoryLabels = Math.max(
1898
+ 1,
1899
+ Math.floor(categoryPixelLength / (horizontal ? 22 : 72))
1900
+ );
1901
+ const categoryLabelStride = Math.max(
1902
+ 1,
1903
+ Math.ceil(visibleCategories.length / maximumCategoryLabels)
1904
+ );
1905
+ const displayedCategories = visibleCategories.filter(
1906
+ (_, index) => index % categoryLabelStride === 0 || index === visibleCategories.length - 1
1907
+ );
1908
+ const xTicks = categories.length ? displayedCategories.map((category, index) => {
1909
+ const screen = transform.dataToScreen({
1910
+ x: category.value,
1911
+ y: yRange.minY
1912
+ });
1913
+ return {
1914
+ id: `${chart.id}-category-${index}-${category.value}`,
1915
+ categorical: true,
1916
+ label: category.label,
1917
+ value: category.value,
1918
+ left: horizontal ? void 0 : screen.x,
1919
+ top: horizontal ? screen.y : void 0
1920
+ };
1921
+ }) : Array.from({ length: X_AXIS_TICK_COUNT }, (_, index) => {
1922
+ const ratio = index / (X_AXIS_TICK_COUNT - 1);
1094
1923
  return {
1095
1924
  id: `${chart.id}-x-tick-${index}`,
1096
1925
  value: state.xMin + ratio * (state.xMax - state.xMin),
1097
- left: 18 + ratio * Math.max(1, plot.width - 36)
1926
+ left: horizontal ? void 0 : 18 + ratio * Math.max(1, plot.width - 36),
1927
+ top: horizontal ? 10 + ratio * Math.max(1, plot.height - 20) : void 0
1098
1928
  };
1099
1929
  });
1100
- const latestValues = getOrderedSeries(chart, seriesOrderByChart).map((series) => {
1101
- if (series.length === 0) return null;
1930
+ const latestValues = descriptor.capabilities.latestValue ? getOrderedSeries(chart, seriesOrderByChart).map((series) => {
1931
+ if (!series.length) return null;
1102
1932
  const value = series.rawY[series.length - 1];
1103
1933
  const x = series.rawX[series.length - 1];
1104
1934
  const endpoint = seriesEndpoints.get(series.id) || { x, y: value };
1105
- const ratio = (yRange.maxY - endpoint.y) / (yRange.maxY - yRange.minY);
1106
- const xRatio = (endpoint.x - state.xMin) / (state.xMax - state.xMin);
1935
+ const screen = transform.dataToScreen(endpoint);
1107
1936
  return {
1108
1937
  id: series.id,
1109
1938
  color: series.color,
@@ -1111,13 +1940,15 @@ var createAxisOverlay = ({
1111
1940
  value: endpoint.y,
1112
1941
  rawValue: value,
1113
1942
  x,
1114
- left: xRatio * plot.width,
1115
- top: ratio * plot.height
1943
+ left: screen.x,
1944
+ top: screen.y
1116
1945
  };
1117
1946
  }).filter(
1118
- (value) => value && Number.isFinite(value.top) && value.top >= -10 && value.top <= plot.height + 10
1119
- );
1947
+ (item) => item && Number.isFinite(item.top) && item.top >= -10 && item.top <= plot.height + 10
1948
+ ) : [];
1949
+ const { maxX } = getChartXBounds(chart);
1120
1950
  return {
1951
+ orientation: descriptor.orientation,
1121
1952
  ticks,
1122
1953
  xTicks,
1123
1954
  latestValues,
@@ -1130,20 +1961,16 @@ var drawChartLayouts = ({
1130
1961
  width,
1131
1962
  height,
1132
1963
  gl,
1133
- program,
1134
- antialiasProgram,
1964
+ rendererRegistry,
1135
1965
  antialiasLines = false,
1136
1966
  layouts,
1137
1967
  viewStateRef,
1138
1968
  yScaleRef,
1139
1969
  yCenterOffsetRef,
1140
- seriesBufferCache,
1141
- antialiasSeriesBufferCache,
1142
1970
  initialVisiblePoints,
1143
1971
  getAppendAnimatedPoint,
1144
- movingAverageByChart = EMPTY_OBJECT,
1145
- movingAverageCache,
1146
- seriesOrderByChart = EMPTY_OBJECT
1972
+ movingAverageByChart,
1973
+ seriesOrderByChart
1147
1974
  }) => {
1148
1975
  const dpr = Math.min(2, window.devicePixelRatio || 1);
1149
1976
  const pixelWidth = Math.max(1, Math.floor(width * dpr));
@@ -1157,50 +1984,12 @@ var drawChartLayouts = ({
1157
1984
  gl.viewport(0, 0, pixelWidth, pixelHeight);
1158
1985
  gl.clearColor(0, 0, 0, 0);
1159
1986
  gl.clear(gl.COLOR_BUFFER_BIT);
1160
- const activeAntialiasLines = antialiasLines && antialiasProgram;
1161
- const activeProgram = activeAntialiasLines ? antialiasProgram : program;
1162
- gl.useProgram(activeProgram);
1163
- const uResolution = gl.getUniformLocation(activeProgram, "u_resolution");
1164
- const uRect = gl.getUniformLocation(activeProgram, "u_rect");
1165
- const uXRange = gl.getUniformLocation(activeProgram, "u_xRange");
1166
- const uYRange = gl.getUniformLocation(activeProgram, "u_yRange");
1167
- const uColor = gl.getUniformLocation(activeProgram, "u_color");
1168
- let aXy = -1;
1169
- let aStart = -1;
1170
- let aEnd = -1;
1171
- let aSide = -1;
1172
- let aAlong = -1;
1173
- let uLineHalfWidth = null;
1174
- let uEdgeWidth = null;
1175
- if (activeAntialiasLines) {
1176
- aStart = gl.getAttribLocation(activeProgram, "a_start");
1177
- aEnd = gl.getAttribLocation(activeProgram, "a_end");
1178
- aSide = gl.getAttribLocation(activeProgram, "a_side");
1179
- aAlong = gl.getAttribLocation(activeProgram, "a_along");
1180
- uLineHalfWidth = gl.getUniformLocation(activeProgram, "u_lineHalfWidth");
1181
- uEdgeWidth = gl.getUniformLocation(activeProgram, "u_edgeWidth");
1182
- gl.enableVertexAttribArray(aStart);
1183
- gl.enableVertexAttribArray(aEnd);
1184
- gl.enableVertexAttribArray(aSide);
1185
- gl.enableVertexAttribArray(aAlong);
1186
- gl.uniform1f(uLineHalfWidth, AA_LINE_WIDTH_PX * dpr / 2);
1187
- gl.uniform1f(uEdgeWidth, AA_EDGE_WIDTH_PX * dpr);
1188
- gl.enable(gl.BLEND);
1189
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
1190
- } else {
1191
- aXy = gl.getAttribLocation(activeProgram, "a_xy");
1192
- gl.enableVertexAttribArray(aXy);
1193
- gl.disable(gl.BLEND);
1194
- }
1195
- gl.uniform2f(uResolution, pixelWidth, pixelHeight);
1196
1987
  const now = performance.now();
1197
1988
  const nextAxisOverlays = {};
1198
- layouts.forEach(({ chart, plot, visible }) => {
1199
- if (!visible) return;
1200
- if (activeAntialiasLines) {
1201
- gl.useProgram(activeProgram);
1202
- gl.uniform2f(uResolution, pixelWidth, pixelHeight);
1203
- }
1989
+ layouts.forEach((layout) => {
1990
+ if (!layout.visible) return;
1991
+ const { chart, plot } = layout;
1992
+ const descriptor = layout.descriptor || getChartDescriptor(chart);
1204
1993
  const state = viewStateRef.current.get(chart.id) || getInitialView(chart, initialVisiblePoints);
1205
1994
  const scaledPlot = {
1206
1995
  x: plot.x * dpr,
@@ -1208,14 +1997,22 @@ var drawChartLayouts = ({
1208
1997
  width: plot.width * dpr,
1209
1998
  height: plot.height * dpr
1210
1999
  };
2000
+ const categoryPixelLength = getCategoryPixelLength(
2001
+ chart,
2002
+ plot,
2003
+ descriptor
2004
+ );
1211
2005
  const yRange = applyYScale(
1212
- getYRange(chart, state.xMin, state.xMax, plot.width),
2006
+ getYRange(
2007
+ chart,
2008
+ state.xMin,
2009
+ state.xMax,
2010
+ categoryPixelLength,
2011
+ descriptor
2012
+ ),
1213
2013
  yScaleRef.current.get(chart.id) ?? 1,
1214
2014
  yCenterOffsetRef.current.get(chart.id) ?? 0
1215
2015
  );
1216
- const seriesEndpoints = /* @__PURE__ */ new Map();
1217
- const chartMovingAverage = movingAverageByChart?.[chart.id];
1218
- const hideBaseSeries = Boolean(chartMovingAverage?.enabled) && Boolean(chartMovingAverage?.hideBase);
1219
2016
  gl.enable(gl.SCISSOR_TEST);
1220
2017
  gl.scissor(
1221
2018
  Math.floor(scaledPlot.x),
@@ -1223,208 +2020,42 @@ var drawChartLayouts = ({
1223
2020
  Math.ceil(scaledPlot.width),
1224
2021
  Math.ceil(scaledPlot.height)
1225
2022
  );
1226
- gl.uniform4f(
1227
- uRect,
1228
- scaledPlot.x,
1229
- scaledPlot.y,
1230
- scaledPlot.width,
1231
- scaledPlot.height
1232
- );
1233
- gl.uniform2f(uXRange, state.xMin, state.xMax);
1234
- gl.uniform2f(uYRange, yRange.minY, yRange.maxY);
1235
- if (!hideBaseSeries) getOrderedSeries(chart, seriesOrderByChart).forEach((series) => {
1236
- let visiblePoints = series.getVisiblePoints(
1237
- state.xMin,
1238
- state.xMax,
1239
- plot.width
1240
- );
1241
- let singlePointEndpoint = null;
1242
- if (visiblePoints.pointCount < 2) {
1243
- const singlePointSegment = getSinglePointSegment({
1244
- series,
1245
- state,
1246
- yRange,
1247
- plot
1248
- });
1249
- if (!singlePointSegment) return;
1250
- visiblePoints = singlePointSegment;
1251
- singlePointEndpoint = singlePointSegment.endpoint;
1252
- }
1253
- const animatedPoint = singlePointEndpoint ? null : getAppendAnimatedPoint({
1254
- seriesId: series.id,
1255
- visiblePoints,
1256
- now
1257
- });
1258
- const pointCount = animatedPoint?.pointCount ?? visiblePoints.pointCount;
1259
- if (animatedPoint) {
1260
- seriesEndpoints.set(series.id, {
1261
- x: animatedPoint.x,
1262
- y: animatedPoint.y
1263
- });
1264
- } else if (singlePointEndpoint) {
1265
- seriesEndpoints.set(series.id, singlePointEndpoint);
1266
- }
1267
- const [r, g, b] = hexToRgb(series.color);
1268
- gl.uniform3f(uColor, r, g, b);
1269
- if (activeAntialiasLines) {
1270
- const bufferEntry2 = getAntialiasSeriesBuffer(
1271
- gl,
1272
- antialiasSeriesBufferCache,
1273
- series.id,
1274
- pointCount
1275
- );
1276
- const drawVertexCount = fillAntialiasSeriesSegments({
1277
- vertices: bufferEntry2.vertices,
1278
- visiblePoints,
1279
- pointCount,
1280
- animatedPoint
1281
- });
1282
- const stride = AA_VERTEX_FLOAT_STRIDE * Float32Array.BYTES_PER_ELEMENT;
1283
- gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry2.buffer);
1284
- gl.vertexAttribPointer(aStart, 2, gl.FLOAT, false, stride, 0);
1285
- gl.vertexAttribPointer(
1286
- aEnd,
1287
- 2,
1288
- gl.FLOAT,
1289
- false,
1290
- stride,
1291
- 2 * Float32Array.BYTES_PER_ELEMENT
1292
- );
1293
- gl.vertexAttribPointer(
1294
- aSide,
1295
- 1,
1296
- gl.FLOAT,
1297
- false,
1298
- stride,
1299
- 4 * Float32Array.BYTES_PER_ELEMENT
1300
- );
1301
- gl.vertexAttribPointer(
1302
- aAlong,
1303
- 1,
1304
- gl.FLOAT,
1305
- false,
1306
- stride,
1307
- 5 * Float32Array.BYTES_PER_ELEMENT
1308
- );
1309
- gl.bufferSubData(
1310
- gl.ARRAY_BUFFER,
1311
- 0,
1312
- bufferEntry2.vertices,
1313
- 0,
1314
- drawVertexCount * AA_VERTEX_FLOAT_STRIDE
1315
- );
1316
- gl.drawArrays(gl.TRIANGLES, 0, drawVertexCount);
1317
- return;
1318
- }
1319
- const bufferEntry = getSeriesBuffer(
1320
- gl,
1321
- seriesBufferCache,
1322
- series.id,
1323
- pointCount
1324
- );
1325
- fillSeriesPoints({
1326
- vertices: bufferEntry.vertices,
1327
- visiblePoints,
1328
- pointCount,
1329
- animatedPoint
1330
- });
1331
- gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry.buffer);
1332
- gl.vertexAttribPointer(aXy, 2, gl.FLOAT, false, 0, 0);
1333
- gl.bufferSubData(
1334
- gl.ARRAY_BUFFER,
1335
- 0,
1336
- bufferEntry.vertices,
1337
- 0,
1338
- pointCount * 2
1339
- );
1340
- gl.drawArrays(gl.LINE_STRIP, 0, pointCount);
1341
- });
1342
- const movingAverageSeries = getMovingAverageSeriesForChart({
2023
+ const result = rendererRegistry.draw(descriptor.rendererType, {
2024
+ antialiasLines,
2025
+ categoryPixelLength,
1343
2026
  chart,
1344
- movingAverage: chartMovingAverage,
1345
- cache: movingAverageCache
1346
- });
1347
- const orderedMovingAverageSeries = getOrderedSeries(
1348
- { ...chart, series: movingAverageSeries },
1349
- seriesOrderByChart
1350
- );
1351
- let maAXy = aXy;
1352
- let maUColor = uColor;
1353
- if (movingAverageSeries.length > 0 && activeAntialiasLines) {
1354
- gl.useProgram(program);
1355
- const maUResolution = gl.getUniformLocation(program, "u_resolution");
1356
- const maURect = gl.getUniformLocation(program, "u_rect");
1357
- const maUXRange = gl.getUniformLocation(program, "u_xRange");
1358
- const maUYRange = gl.getUniformLocation(program, "u_yRange");
1359
- maUColor = gl.getUniformLocation(program, "u_color");
1360
- maAXy = gl.getAttribLocation(program, "a_xy");
1361
- gl.enableVertexAttribArray(maAXy);
1362
- gl.uniform2f(maUResolution, pixelWidth, pixelHeight);
1363
- gl.uniform4f(
1364
- maURect,
1365
- scaledPlot.x,
1366
- scaledPlot.y,
1367
- scaledPlot.width,
1368
- scaledPlot.height
1369
- );
1370
- gl.uniform2f(maUXRange, state.xMin, state.xMax);
1371
- gl.uniform2f(maUYRange, yRange.minY, yRange.maxY);
1372
- }
1373
- orderedMovingAverageSeries.forEach((series) => {
1374
- const visiblePoints = series.getVisiblePoints(
1375
- state.xMin,
1376
- state.xMax,
1377
- plot.width
1378
- );
1379
- if (visiblePoints.pointCount < 2) return;
1380
- const pointCount = visiblePoints.pointCount;
1381
- const [r, g, b] = hexToRgb(series.color);
1382
- gl.uniform3f(maUColor, r, g, b);
1383
- const bufferEntry = getSeriesBuffer(
1384
- gl,
1385
- seriesBufferCache,
1386
- series.id,
1387
- pointCount * 8
1388
- );
1389
- const drawVertexCount = fillDashedSeriesSegments({
1390
- vertices: bufferEntry.vertices,
1391
- visiblePoints,
1392
- pointCount,
1393
- state,
1394
- yRange,
1395
- plot
1396
- });
1397
- if (drawVertexCount < 2) return;
1398
- gl.bindBuffer(gl.ARRAY_BUFFER, bufferEntry.buffer);
1399
- gl.vertexAttribPointer(maAXy, 2, gl.FLOAT, false, 0, 0);
1400
- gl.bufferSubData(
1401
- gl.ARRAY_BUFFER,
1402
- 0,
1403
- bufferEntry.vertices,
1404
- 0,
1405
- drawVertexCount * 2
1406
- );
1407
- gl.drawArrays(gl.LINES, 0, drawVertexCount);
2027
+ descriptor,
2028
+ dpr,
2029
+ getAppendAnimatedPoint,
2030
+ movingAverage: movingAverageByChart?.[chart.id],
2031
+ now,
2032
+ pixelHeight,
2033
+ pixelWidth,
2034
+ plot,
2035
+ scaledPlot,
2036
+ seriesOrderByChart,
2037
+ state,
2038
+ yRange
1408
2039
  });
1409
2040
  nextAxisOverlays[chart.id] = createAxisOverlay({
1410
2041
  chart,
2042
+ descriptor,
1411
2043
  plot,
1412
2044
  state,
1413
2045
  yRange,
1414
- seriesEndpoints,
2046
+ seriesEndpoints: result?.seriesEndpoints || /* @__PURE__ */ new Map(),
1415
2047
  seriesOrderByChart
1416
2048
  });
1417
2049
  gl.disable(gl.SCISSOR_TEST);
1418
2050
  });
1419
- if (activeAntialiasLines) {
1420
- gl.disable(gl.BLEND);
1421
- }
2051
+ gl.disable(gl.BLEND);
1422
2052
  return nextAxisOverlays;
1423
2053
  };
1424
2054
 
1425
2055
  // src/vanilla/createChartGrid.js
1426
2056
  var BASE_ROOT_CLASSES = ["aliencharts-root", "relative", "h-full", "overflow-y-auto"];
1427
- var EMPTY_OBJECT2 = Object.freeze({});
2057
+ var EMPTY_OBJECT = Object.freeze({});
2058
+ var controllerIdSequence = 0;
1428
2059
  var DEFAULT_OPTIONS = Object.freeze({
1429
2060
  charts: [],
1430
2061
  columns: 2,
@@ -1439,7 +2070,7 @@ var DEFAULT_OPTIONS = Object.freeze({
1439
2070
  followVisibleLatest: true,
1440
2071
  xAxisLabel: "STEP",
1441
2072
  disableDrawings: false,
1442
- seriesOrderByChart: EMPTY_OBJECT2,
2073
+ seriesOrderByChart: EMPTY_OBJECT,
1443
2074
  topMarkers: [],
1444
2075
  formatXTick: formatCompactNumber,
1445
2076
  formatXValue: formatNumber,
@@ -1450,6 +2081,13 @@ var APPEND_ANIMATION = Object.freeze({
1450
2081
  maxBucketSize: 64,
1451
2082
  maxRevealPoints: 100
1452
2083
  });
2084
+ var POINTER_SCALE_SENSITIVITY = 0.01;
2085
+ var WHEEL_SCALE_SENSITIVITY = 1e-3;
2086
+ var scaleValueRange = (scale, delta, sensitivity) => clamp(
2087
+ scale * Math.exp(delta * sensitivity),
2088
+ Y_SCALE_MIN,
2089
+ Y_SCALE_MAX
2090
+ );
1453
2091
  var escapeHtml = (value) => String(value ?? "").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#039;");
1454
2092
  var withAlpha = (color, alpha) => {
1455
2093
  const value = String(color || DEFAULT_CHART_BACKGROUND).trim();
@@ -1473,7 +2111,12 @@ var toolbarButton = ({ action, title, icon, hotkey = "", active = false, classNa
1473
2111
  ${icon}
1474
2112
  ${hotkey ? `<span class="pointer-events-none absolute left-full top-1/2 -translate-y-1/2 text-[10px] font-semibold leading-none text-foreground/50">${hotkey}</span>` : ""}
1475
2113
  </button>`;
1476
- var chartCellHtml = (chart, index, backgroundColor) => `
2114
+ var chartCellHtml = (chart, descriptor, index, backgroundColor) => {
2115
+ const padding = getPlotPadding(chart, descriptor);
2116
+ const horizontal = descriptor.orientation === "horizontal";
2117
+ const valueAxisStyle = horizontal ? `left:${padding.left}px;right:${padding.right}px;bottom:0;height:${padding.bottom}px;cursor:ew-resize` : `right:0;top:${padding.top}px;bottom:${padding.bottom}px;width:${padding.right}px;cursor:ns-resize`;
2118
+ const categoryAxisStyle = horizontal ? `left:0;top:${padding.top}px;bottom:${padding.bottom}px;width:${padding.left}px;cursor:ns-resize` : `left:${padding.left}px;right:${padding.right}px;bottom:0;height:${padding.bottom}px;cursor:ew-resize`;
2119
+ return `
1477
2120
  <div data-chart-index="${index}" class="relative select-none overflow-hidden rounded-sm" style="height:${CHART_HEIGHT}px;background-color:${escapeHtml(backgroundColor)}">
1478
2121
  <div class="pointer-events-none absolute inset-x-0 top-0 z-10 flex h-8 items-center px-2 backdrop-blur" data-header>
1479
2122
  <div class="flex min-w-0 items-center gap-1 rounded-sm px-1 text-sm font-semibold text-foreground" data-title-wrap>
@@ -1483,25 +2126,19 @@ var chartCellHtml = (chart, index, backgroundColor) => `
1483
2126
  </div>
1484
2127
  <div data-grid-lines class="pointer-events-none absolute text-border/40"></div>
1485
2128
  <div data-latest-lines></div>
1486
- <div data-y-axis class="absolute right-0 z-20" style="top:${PLOT_PADDING.top}px;bottom:${PLOT_PADDING.bottom}px;width:${RIGHT_AXIS_WIDTH}px;cursor:ns-resize"></div>
1487
- <div data-x-axis class="absolute bottom-0 z-20" style="left:${PLOT_PADDING.left}px;right:${RIGHT_AXIS_WIDTH}px;height:${PLOT_PADDING.bottom}px;cursor:ew-resize"></div>
2129
+ <div data-y-axis class="absolute z-20" style="${valueAxisStyle}"></div>
2130
+ <div data-x-axis class="absolute z-20" style="${categoryAxisStyle}"></div>
1488
2131
  <div data-toolbar></div>
1489
2132
  </div>`;
2133
+ };
1490
2134
  var makeSurface = (canvas, gl) => ({
1491
2135
  canvas,
1492
2136
  gl,
1493
- program: createProgram(gl),
1494
- antialiasProgram: createAntialiasProgram(gl),
1495
- seriesBuffers: createSeriesBufferCache(),
1496
- antialiasBuffers: createSeriesBufferCache(),
1497
- movingAverageCache: /* @__PURE__ */ new Map()
2137
+ renderers: createRendererRegistry(gl)
1498
2138
  });
1499
2139
  var destroySurface = (surface) => {
1500
2140
  if (!surface) return;
1501
- deleteSeriesBuffers(surface.gl, surface.seriesBuffers);
1502
- deleteSeriesBuffers(surface.gl, surface.antialiasBuffers);
1503
- surface.gl.deleteProgram(surface.program);
1504
- surface.gl.deleteProgram(surface.antialiasProgram);
2141
+ surface.renderers.destroy();
1505
2142
  surface.gl.getExtension("WEBGL_lose_context")?.loseContext();
1506
2143
  };
1507
2144
  var AppendAnimator = class {
@@ -1511,29 +2148,33 @@ var AppendAnimator = class {
1511
2148
  this.animations = /* @__PURE__ */ new Map();
1512
2149
  this.frame = null;
1513
2150
  }
1514
- scan(charts) {
2151
+ scan(charts, descriptors) {
1515
2152
  const now = performance.now();
1516
- charts.forEach((chart) => chart.series.forEach((series) => {
1517
- if (!series.length) return;
1518
- const next = {
1519
- x: series.rawX[series.length - 1],
1520
- y: series.rawY[series.length - 1],
1521
- length: series.length
1522
- };
1523
- const previous = this.latest.get(series.id);
1524
- if (previous && next.x > previous.x) {
1525
- this.animations.set(series.id, {
1526
- fromX: previous.x,
1527
- fromY: previous.y,
1528
- toX: next.x,
1529
- toY: next.y,
1530
- appendedCount: next.length - previous.length,
1531
- startedAt: now,
1532
- duration: APPEND_ANIMATION.durationMs
1533
- });
1534
- }
1535
- this.latest.set(series.id, next);
1536
- }));
2153
+ charts.forEach((chart) => {
2154
+ const descriptor = descriptors.get(chart.id);
2155
+ if (!descriptor?.capabilities.appendAnimation) return;
2156
+ chart.series.forEach((series) => {
2157
+ if (!series.length) return;
2158
+ const next = {
2159
+ x: series.rawX[series.length - 1],
2160
+ y: series.rawY[series.length - 1],
2161
+ length: series.length
2162
+ };
2163
+ const previous = this.latest.get(series.id);
2164
+ if (previous && next.x > previous.x) {
2165
+ this.animations.set(series.id, {
2166
+ fromX: previous.x,
2167
+ fromY: previous.y,
2168
+ toX: next.x,
2169
+ toY: next.y,
2170
+ appendedCount: next.length - previous.length,
2171
+ startedAt: now,
2172
+ duration: APPEND_ANIMATION.durationMs
2173
+ });
2174
+ }
2175
+ this.latest.set(series.id, next);
2176
+ });
2177
+ });
1537
2178
  if (this.animations.size) this.start();
1538
2179
  }
1539
2180
  start() {
@@ -1589,8 +2230,10 @@ var ChartGridController = class {
1589
2230
  constructor(container, options) {
1590
2231
  if (!(container instanceof HTMLElement)) throw new TypeError("createChartGrid requires an HTMLElement target");
1591
2232
  this.container = container;
2233
+ this.controllerId = `aliencharts-${controllerIdSequence += 1}`;
1592
2234
  this.options = { ...DEFAULT_OPTIONS, ...options };
1593
2235
  if (!Array.isArray(this.options.charts)) throw new TypeError("charts must be an array");
2236
+ this.chartDescriptors = resolveChartDescriptors(this.options.charts);
1594
2237
  this.destroyed = false;
1595
2238
  this.frame = null;
1596
2239
  this.structureDirty = true;
@@ -1631,7 +2274,7 @@ var ChartGridController = class {
1631
2274
  this.surface = makeSurface(this.canvas, gl);
1632
2275
  this.fullscreenSurface = null;
1633
2276
  this.animator = new AppendAnimator(() => this.requestRender());
1634
- this.animator.scan(this.options.charts);
2277
+ this.animator.scan(this.options.charts, this.chartDescriptors);
1635
2278
  this.bound = {
1636
2279
  click: (event) => this.handleClick(event),
1637
2280
  input: (event) => this.handleInput(event),
@@ -1685,8 +2328,16 @@ var ChartGridController = class {
1685
2328
  if (!next || typeof next !== "object") return;
1686
2329
  const previousCharts = this.options.charts;
1687
2330
  const previousActiveDrawingTool = this.activeDrawingTool;
1688
- this.options = { ...this.options, ...next };
1689
- if (!Array.isArray(this.options.charts)) throw new TypeError("charts must be an array");
2331
+ const nextOptions = { ...this.options, ...next };
2332
+ if (!Array.isArray(nextOptions.charts)) throw new TypeError("charts must be an array");
2333
+ const nextDescriptors = resolveChartDescriptors(nextOptions.charts);
2334
+ const descriptorLayoutChanged = nextOptions.charts.some((chart) => {
2335
+ const previous = this.chartDescriptors.get(chart.id);
2336
+ const current = nextDescriptors.get(chart.id);
2337
+ return previous?.rendererType !== current?.rendererType || previous?.orientation !== current?.orientation || previous?.categorical !== current?.categorical;
2338
+ });
2339
+ this.options = nextOptions;
2340
+ this.chartDescriptors = nextDescriptors;
1690
2341
  if (hasOwn(next, "drawings")) this.drawings = Array.isArray(next.drawings) ? [...next.drawings] : [];
1691
2342
  if (hasOwn(next, "activeDrawingTool")) {
1692
2343
  this.activeDrawingTool = next.activeDrawingTool ?? null;
@@ -1697,7 +2348,7 @@ var ChartGridController = class {
1697
2348
  }
1698
2349
  if (hasOwn(next, "selectedDrawingId")) this.selectedDrawingId = next.selectedDrawingId ?? null;
1699
2350
  if (hasOwn(next, "movingAverageByChart")) this.movingAverageByChart = { ...next.movingAverageByChart || {} };
1700
- if (previousCharts !== this.options.charts || hasOwn(next, "columns") || hasOwn(next, "backgroundColor") || hasOwn(next, "gridLines")) this.structureDirty = true;
2351
+ if (previousCharts !== this.options.charts || descriptorLayoutChanged || hasOwn(next, "columns") || hasOwn(next, "backgroundColor") || hasOwn(next, "gridLines")) this.structureDirty = true;
1701
2352
  this.initializeChartState();
1702
2353
  this.requestRender();
1703
2354
  }
@@ -1707,7 +2358,7 @@ var ChartGridController = class {
1707
2358
  invalidate() {
1708
2359
  this.assertAlive();
1709
2360
  this.followAppendedData();
1710
- this.animator.scan(this.options.charts);
2361
+ this.animator.scan(this.options.charts, this.chartDescriptors);
1711
2362
  this.requestRender();
1712
2363
  }
1713
2364
  followAppendedData() {
@@ -1733,7 +2384,10 @@ var ChartGridController = class {
1733
2384
  const maxX = getChartXBounds(chart).maxX;
1734
2385
  if (!Number.isFinite(maxX)) return;
1735
2386
  const state = this.viewStates.get(chart.id) || getInitialView(chart, this.options.initialVisiblePoints);
1736
- const span = Math.max(10, state.xMax - state.xMin);
2387
+ const span = Math.max(
2388
+ getMinimumCategorySpan(chart),
2389
+ state.xMax - state.xMin
2390
+ );
1737
2391
  const nextMax = maxX + span * 0.1;
1738
2392
  this.viewStates.set(chart.id, { xMin: nextMax - span, xMax: nextMax });
1739
2393
  });
@@ -1745,7 +2399,12 @@ var ChartGridController = class {
1745
2399
  }
1746
2400
  renderStructure() {
1747
2401
  this.grid.style.gridTemplateColumns = `repeat(${Math.max(1, Number(this.options.columns) || 1)}, minmax(18rem, 1fr))`;
1748
- this.grid.innerHTML = this.options.charts.map((chart, index) => chartCellHtml(chart, index, this.options.backgroundColor)).join("");
2402
+ this.grid.innerHTML = this.options.charts.map((chart, index) => chartCellHtml(
2403
+ chart,
2404
+ this.chartDescriptors.get(chart.id),
2405
+ index,
2406
+ this.options.backgroundColor
2407
+ )).join("");
1749
2408
  this.chartNodes = [...this.grid.querySelectorAll("[data-chart-index]")];
1750
2409
  this.chartNodes.forEach((node) => {
1751
2410
  node.querySelector("[data-header]").style.backgroundColor = withAlpha(this.options.backgroundColor, 0.82);
@@ -1762,14 +2421,17 @@ var ChartGridController = class {
1762
2421
  const x = rect.left - hostRect.left + host.scrollLeft;
1763
2422
  const y = rect.top - hostRect.top + host.scrollTop;
1764
2423
  const chart = charts[index];
2424
+ const descriptor = this.chartDescriptors.get(chart.id);
2425
+ const padding = getPlotPadding(chart, descriptor);
1765
2426
  const layout = {
1766
2427
  chart,
2428
+ descriptor,
1767
2429
  rect: { x, y, width: rect.width, height: rect.height },
1768
2430
  plot: {
1769
- x: x + PLOT_PADDING.left,
1770
- y: y + PLOT_PADDING.top,
1771
- width: Math.max(1, rect.width - PLOT_PADDING.left - PLOT_PADDING.right),
1772
- height: Math.max(1, rect.height - PLOT_PADDING.top - PLOT_PADDING.bottom)
2431
+ x: x + padding.left,
2432
+ y: y + padding.top,
2433
+ width: Math.max(1, rect.width - padding.left - padding.right),
2434
+ height: Math.max(1, rect.height - padding.top - padding.bottom)
1773
2435
  },
1774
2436
  visible: fullscreen || rect.bottom >= hostRect.top && rect.top <= hostRect.bottom,
1775
2437
  node,
@@ -1823,72 +2485,85 @@ var ChartGridController = class {
1823
2485
  width: Math.max(1, width),
1824
2486
  height: Math.max(1, height),
1825
2487
  gl: surface.gl,
1826
- program: surface.program,
1827
- antialiasProgram: surface.antialiasProgram,
2488
+ rendererRegistry: surface.renderers,
1828
2489
  antialiasLines: this.options.antialiasLines,
1829
2490
  layouts,
1830
2491
  viewStateRef: { current: this.viewStates },
1831
2492
  yScaleRef: { current: this.yScales },
1832
2493
  yCenterOffsetRef: { current: this.yOffsets },
1833
- seriesBufferCache: surface.seriesBuffers,
1834
- antialiasSeriesBufferCache: surface.antialiasBuffers,
1835
2494
  initialVisiblePoints: this.options.initialVisiblePoints,
1836
2495
  getAppendAnimatedPoint: (payload) => this.animator.getPoint(payload),
1837
2496
  movingAverageByChart: this.movingAverageByChart,
1838
- movingAverageCache: surface.movingAverageCache,
1839
2497
  seriesOrderByChart: this.options.seriesOrderByChart
1840
2498
  });
1841
2499
  }
1842
2500
  updateCells(layouts, axes, fullscreen) {
1843
- layouts.forEach(({ chart, node }) => {
2501
+ layouts.forEach(({ chart, descriptor, node }) => {
1844
2502
  const axis = axes[chart.id];
2503
+ const padding = getPlotPadding(chart, descriptor);
2504
+ const horizontal = axis?.orientation === "horizontal";
1845
2505
  node.querySelector("[data-title-wrap]").classList.toggle("ring-1", this.focusedChartId === chart.id);
1846
2506
  node.querySelector("[data-title-wrap]").classList.toggle("ring-primary/70", this.focusedChartId === chart.id);
1847
2507
  const grid = node.querySelector("[data-grid-lines]");
1848
2508
  const gridOptions = this.options.gridLines === true ? {} : this.options.gridLines || {};
1849
2509
  grid.style.display = this.options.gridLines ? "block" : "none";
1850
2510
  Object.assign(grid.style, {
1851
- left: `${PLOT_PADDING.left}px`,
1852
- right: `${PLOT_PADDING.right}px`,
1853
- top: `${PLOT_PADDING.top}px`,
1854
- bottom: `${PLOT_PADDING.bottom}px`,
2511
+ left: `${padding.left}px`,
2512
+ right: `${padding.right}px`,
2513
+ top: `${padding.top}px`,
2514
+ bottom: `${padding.bottom}px`,
1855
2515
  backgroundImage: "linear-gradient(to right, transparent calc(100% - 1px), currentColor calc(100% - 1px)), linear-gradient(to bottom, transparent calc(100% - 1px), currentColor calc(100% - 1px))",
1856
2516
  backgroundSize: `${Math.max(8, Number(gridOptions.xSpacing) || 80)}px 100%, 100% ${Math.max(8, Number(gridOptions.ySpacing) || 48)}px`
1857
2517
  });
1858
2518
  const yAxis = node.querySelector("[data-y-axis]");
1859
2519
  yAxis.style.backgroundColor = withAlpha(this.options.backgroundColor, 0.78);
1860
- yAxis.innerHTML = (axis?.ticks || []).map((tick) => `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="top:${tick.top}px">${escapeHtml(this.options.formatYValue(tick.value))}</div>`).join("") + (axis?.latestValues || []).map((latest) => `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-sm px-1.5 py-0.5 text-center text-[10px] font-bold tabular-nums shadow-sm" style="top:${latest.top}px;background:${latest.color};color:${getTextColor(latest.color)}">${escapeHtml(this.options.formatYValue(latest.value))}</div>`).join("");
2520
+ yAxis.innerHTML = (axis?.ticks || []).map((tick) => horizontal ? `<div class="pointer-events-none absolute top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="left:${tick.left}px">${escapeHtml(this.options.formatYValue(tick.value))}</div>` : `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="top:${tick.top}px">${escapeHtml(this.options.formatYValue(tick.value))}</div>`).join("") + (axis?.latestValues || []).map((latest) => `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-sm px-1.5 py-0.5 text-center text-[10px] font-bold tabular-nums shadow-sm" style="top:${latest.top}px;background:${latest.color};color:${getTextColor(latest.color)}">${escapeHtml(this.options.formatYValue(latest.value))}</div>`).join("");
1861
2521
  const xAxis = node.querySelector("[data-x-axis]");
1862
2522
  xAxis.style.backgroundColor = withAlpha(this.options.backgroundColor, 0.7);
1863
- xAxis.innerHTML = (axis?.xTicks || []).map((tick) => `<div class="pointer-events-none absolute top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="left:${tick.left}px">${escapeHtml(this.options.formatXTick(tick.value))}</div>`).join("");
2523
+ xAxis.innerHTML = (axis?.xTicks || []).map((tick) => {
2524
+ const label = tick.categorical ? tick.label : this.options.formatXTick(tick.value);
2525
+ if (horizontal && tick.categorical) {
2526
+ return `<div data-category-label title="${escapeHtml(label)}" class="pointer-events-none absolute inset-x-2 -translate-y-1/2 truncate text-right text-[11px] font-medium text-foreground/70" style="top:${tick.top}px">${escapeHtml(label)}</div>`;
2527
+ }
2528
+ if (tick.categorical) {
2529
+ return `<div data-category-label title="${escapeHtml(label)}" class="pointer-events-none absolute top-1/2 w-24 -translate-x-1/2 -translate-y-1/2 truncate text-center text-[11px] font-medium text-foreground/70" style="left:${tick.left}px">${escapeHtml(label)}</div>`;
2530
+ }
2531
+ return horizontal ? `<div class="pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="top:${tick.top}px">${escapeHtml(label)}</div>` : `<div class="pointer-events-none absolute top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[11px] font-medium tabular-nums text-foreground/70" style="left:${tick.left}px">${escapeHtml(label)}</div>`;
2532
+ }).join("");
1864
2533
  const latestLayer = node.querySelector("[data-latest-lines]");
1865
- latestLayer.innerHTML = this.options.showLatestValueLine ? (axis?.latestValues || []).filter((item) => item.left >= 0 && item.left <= axis.plotWidth).map((item) => `<div class="pointer-events-none absolute z-10 opacity-70" style="left:${PLOT_PADDING.left + item.left}px;top:${PLOT_PADDING.top + item.top}px;height:1px;width:${Math.max(0, axis.plotWidth - item.left)}px;background-image:repeating-linear-gradient(to right,${item.color} 0 4px,transparent 4px 8px)"></div>`).join("") : "";
2534
+ latestLayer.innerHTML = this.options.showLatestValueLine ? (axis?.latestValues || []).filter((item) => item.left >= 0 && item.left <= axis.plotWidth).map((item) => `<div class="pointer-events-none absolute z-10 opacity-70" style="left:${padding.left + item.left}px;top:${padding.top + item.top}px;height:1px;width:${Math.max(0, axis.plotWidth - item.left)}px;background-image:repeating-linear-gradient(to right,${item.color} 0 4px,transparent 4px 8px)"></div>`).join("") : "";
1866
2535
  this.updateToolbar(node, chart, axis, fullscreen);
1867
2536
  });
1868
2537
  }
1869
2538
  updateToolbar(node, chart, axis, fullscreen) {
1870
2539
  const target = node.querySelector("[data-toolbar]");
1871
2540
  const focused = fullscreen || this.focusedChartId === chart.id;
2541
+ const descriptor = this.chartDescriptors.get(chart.id);
2542
+ const drawingsAvailable = descriptor.capabilities.drawings;
2543
+ const movingAverageAvailable = descriptor.capabilities.movingAverage;
2544
+ const padding = getPlotPadding(chart, descriptor);
1872
2545
  let html = "";
1873
- if (axis?.showJumpLatest) html += `<button type="button" data-action="jump-latest" aria-label="Jump to latest" class="absolute z-30 inline-flex size-7 items-center justify-center rounded-sm text-foreground hover:bg-accent" style="right:${RIGHT_AXIS_WIDTH + 6}px;bottom:${PLOT_PADDING.bottom + 6}px;background:${withAlpha(this.options.backgroundColor, 0.7)}">${iconSvg("arrowLineRight")}</button>`;
2546
+ if (axis?.showJumpLatest) html += `<button type="button" data-action="jump-latest" aria-label="Jump to latest" class="absolute z-30 inline-flex size-7 items-center justify-center rounded-sm text-foreground hover:bg-accent" style="right:${padding.right + 6}px;bottom:${padding.bottom + 6}px;background:${withAlpha(this.options.backgroundColor, 0.7)}">${iconSvg("arrowLineRight", { className: axis.orientation === "horizontal" ? "rotate-90" : "" })}</button>`;
1874
2547
  if (focused && this.options.showToolbar) {
1875
- const moving = Boolean(this.movingAverageByChart[chart.id]?.enabled);
2548
+ const moving = movingAverageAvailable && Boolean(this.movingAverageByChart[chart.id]?.enabled);
1876
2549
  const hasDrawings = getDrawingsForChart(this.drawings, chart.id).length > 0;
1877
2550
  html += `<div class="absolute left-2 top-12 z-30 flex flex-col gap-0.5 rounded-sm bg-background/80 pr-3 text-foreground shadow-sm backdrop-blur">`;
1878
2551
  html += toolbarButton({ action: "fullscreen", title: fullscreen ? "Exit fullscreen" : "Maximize chart", hotkey: "F", icon: iconSvg(fullscreen ? "arrowsIn" : "arrowsOut") });
1879
2552
  html += toolbarButton({ action: "reset", title: "Reset chart", hotkey: "R", icon: iconSvg("arrowCounterClockwise") });
1880
2553
  html += toolbarButton({ action: "rectangle-zoom", title: "Rectangle zoom", hotkey: "Z", active: this.rectangleZoomChartId === chart.id, icon: iconSvg("magnifyingGlassPlus") });
1881
- if (!this.options.disableDrawings && hasDrawings) html += toolbarButton({ action: "clear-drawings", title: "Clear drawings", className: "text-orange-500", icon: iconSvg("broom") });
1882
- if (!this.options.disableDrawings) {
2554
+ if (drawingsAvailable && !this.options.disableDrawings && hasDrawings) html += toolbarButton({ action: "clear-drawings", title: "Clear drawings", className: "text-orange-500", icon: iconSvg("broom") });
2555
+ if ((drawingsAvailable || movingAverageAvailable) && !this.options.disableDrawings) {
1883
2556
  html += '<div class="my-1 h-px w-full"></div>';
1884
- html += toolbarButton({ action: "tool-trendline", title: "Draw trendline", hotkey: "T", active: this.activeDrawingTool === "trendline", icon: iconSvg("minus", { className: "-rotate-45" }) });
1885
- html += toolbarButton({ action: "tool-hline", title: "Draw horizontal line", hotkey: "H", active: this.activeDrawingTool === "hline", icon: iconSvg("minus") });
1886
- html += toolbarButton({ action: "tool-vline", title: "Draw vertical line", hotkey: "V", active: this.activeDrawingTool === "vline", icon: iconSvg("minus", { className: "rotate-90" }) });
1887
- html += toolbarButton({ action: "tool-pin", title: "Place pin", hotkey: "P", active: this.activeDrawingTool === "pin", icon: iconSvg("mapPin") });
1888
- html += toolbarButton({ action: "moving-average", title: "Toggle moving average", hotkey: "M", active: moving, icon: iconSvg("waveSine") });
2557
+ if (drawingsAvailable) {
2558
+ html += toolbarButton({ action: "tool-trendline", title: "Draw trendline", hotkey: "T", active: this.activeDrawingTool === "trendline", icon: iconSvg("minus", { className: "-rotate-45" }) });
2559
+ html += toolbarButton({ action: "tool-hline", title: "Draw horizontal line", hotkey: "H", active: this.activeDrawingTool === "hline", icon: iconSvg("minus") });
2560
+ html += toolbarButton({ action: "tool-vline", title: "Draw vertical line", hotkey: "V", active: this.activeDrawingTool === "vline", icon: iconSvg("minus", { className: "rotate-90" }) });
2561
+ html += toolbarButton({ action: "tool-pin", title: "Place pin", hotkey: "P", active: this.activeDrawingTool === "pin", icon: iconSvg("mapPin") });
2562
+ }
2563
+ if (movingAverageAvailable) html += toolbarButton({ action: "moving-average", title: "Toggle moving average", hotkey: "M", active: moving, icon: iconSvg("waveSine") });
1889
2564
  }
1890
2565
  html += "</div>";
1891
- if (!this.options.disableDrawings && moving) html += this.movingAverageOptionsHtml(chart);
2566
+ if (movingAverageAvailable && !this.options.disableDrawings && moving) html += this.movingAverageOptionsHtml(chart);
1892
2567
  }
1893
2568
  target.innerHTML = html;
1894
2569
  }
@@ -1916,21 +2591,35 @@ var ChartGridController = class {
1916
2591
  drawingOverlayHtml(layouts) {
1917
2592
  const drawings = this.draftDrawing ? [...this.drawings, this.draftDrawing] : this.drawings;
1918
2593
  if (!drawings.length) return "";
1919
- const body = layouts.flatMap((layout) => getDrawingsForChart(drawings, layout.chart.id).map((drawing) => {
1920
- const geometry = getDrawingGeometry({ drawing, layout, projectPoint: (point) => this.projectPoint(point, layout) });
1921
- if (!geometry) return "";
1922
- const selected = drawing.id === this.selectedDrawingId || drawing.id === "__draft__";
1923
- if (drawing.type === "pin") {
1924
- const text = escapeHtml(geometry.style.text?.trim() || "");
1925
- return `<g opacity="${drawing.id === "__draft__" ? 0.72 : 1}"><g transform="translate(${geometry.start.x} ${geometry.start.y})"><path d="M0 -11 C-6 -11 -10 -6 -10 -1 C-10 6 -2 12 0 15 C2 12 10 6 10 -1 C10 -6 6 -11 0 -11 Z" fill="${geometry.style.color}" stroke="var(--background,#fff)" stroke-width="1.5"/><circle cy="-2" r="3" fill="var(--background,#fff)"/></g>${text ? `<text x="${geometry.start.x + 14}" y="${geometry.start.y}" fill="${geometry.style.color}" stroke="var(--background,#fff)" stroke-width="3" paint-order="stroke" font-size="12" font-weight="600">${text}</text>` : ""}${selected ? `<circle cx="${geometry.start.x}" cy="${geometry.start.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>` : ""}</g>`;
1926
- }
1927
- return `<g><line data-drawing-line x1="${geometry.lineStart.x}" y1="${geometry.lineStart.y}" x2="${geometry.lineEnd.x}" y2="${geometry.lineEnd.y}" stroke="${geometry.style.color}" stroke-width="${geometry.style.lineWidth}" stroke-dasharray="${geometry.style.dashPattern.join(" ")}" opacity="${drawing.id === "__draft__" ? 0.72 : 1}"/>${selected ? `<circle data-drawing-anchor="start" cx="${geometry.start.x}" cy="${geometry.start.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>${drawing.type === "trendline" ? `<circle data-drawing-anchor="end" cx="${geometry.end.x}" cy="${geometry.end.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>` : ""}` : ""}</g>`;
1928
- })).join("");
1929
- return `<svg class="pointer-events-none absolute left-0 top-0 z-20 block overflow-visible" style="width:100%;height:100%">${body}</svg>`;
2594
+ const drawableLayouts = layouts.map((layout, index) => ({
2595
+ layout,
2596
+ index,
2597
+ drawings: layout.descriptor.capabilities.drawings ? getDrawingsForChart(drawings, layout.chart.id) : []
2598
+ })).filter((entry) => entry.drawings.length > 0);
2599
+ if (!drawableLayouts.length) return "";
2600
+ const definitions = drawableLayouts.map(({ layout, index }) => {
2601
+ const clipId = `${this.controllerId}-drawing-clip-${layout.fullscreen ? "fullscreen" : "grid"}-${index}`;
2602
+ return `<clipPath id="${clipId}"><rect data-drawing-clip="${escapeHtml(layout.chart.id)}" x="${layout.plot.x}" y="${layout.plot.y}" width="${layout.plot.width}" height="${layout.plot.height}"/></clipPath>`;
2603
+ }).join("");
2604
+ const body = drawableLayouts.map(({ layout, index, drawings: chartDrawings }) => {
2605
+ const clipId = `${this.controllerId}-drawing-clip-${layout.fullscreen ? "fullscreen" : "grid"}-${index}`;
2606
+ const chartBody = chartDrawings.map((drawing) => {
2607
+ const geometry = getDrawingGeometry({ drawing, layout, projectPoint: (point) => this.projectPoint(point, layout) });
2608
+ if (!geometry) return "";
2609
+ const selected = drawing.id === this.selectedDrawingId || drawing.id === "__draft__";
2610
+ if (drawing.type === "pin") {
2611
+ const text = escapeHtml(geometry.style.text?.trim() || "");
2612
+ return `<g opacity="${drawing.id === "__draft__" ? 0.72 : 1}"><g transform="translate(${geometry.start.x} ${geometry.start.y})"><path d="M0 -11 C-6 -11 -10 -6 -10 -1 C-10 6 -2 12 0 15 C2 12 10 6 10 -1 C10 -6 6 -11 0 -11 Z" fill="${geometry.style.color}" stroke="var(--background,#fff)" stroke-width="1.5"/><circle cy="-2" r="3" fill="var(--background,#fff)"/></g>${text ? `<text x="${geometry.start.x + 14}" y="${geometry.start.y}" fill="${geometry.style.color}" stroke="var(--background,#fff)" stroke-width="3" paint-order="stroke" font-size="12" font-weight="600">${text}</text>` : ""}${selected ? `<circle cx="${geometry.start.x}" cy="${geometry.start.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>` : ""}</g>`;
2613
+ }
2614
+ return `<g><line data-drawing-line x1="${geometry.lineStart.x}" y1="${geometry.lineStart.y}" x2="${geometry.lineEnd.x}" y2="${geometry.lineEnd.y}" stroke="${geometry.style.color}" stroke-width="${geometry.style.lineWidth}" stroke-dasharray="${geometry.style.dashPattern.join(" ")}" opacity="${drawing.id === "__draft__" ? 0.72 : 1}"/>${selected ? `<circle data-drawing-anchor="start" cx="${geometry.start.x}" cy="${geometry.start.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>${drawing.type === "trendline" ? `<circle data-drawing-anchor="end" cx="${geometry.end.x}" cy="${geometry.end.y}" r="4" fill="${geometry.style.color}" stroke="var(--background,#fff)"/>` : ""}` : ""}</g>`;
2615
+ }).join("");
2616
+ return `<g data-drawing-chart="${escapeHtml(layout.chart.id)}" clip-path="url(#${clipId})">${chartBody}</g>`;
2617
+ }).join("");
2618
+ return `<svg class="pointer-events-none absolute left-0 top-0 z-20 block overflow-visible" style="width:100%;height:100%"><defs>${definitions}</defs>${body}</svg>`;
1930
2619
  }
1931
2620
  topMarkersHtml(layouts) {
1932
2621
  if (!this.options.topMarkers?.length) return "";
1933
- return layouts.flatMap((layout) => this.options.topMarkers.map((marker, markerIndex) => {
2622
+ return layouts.flatMap((layout) => !layout.descriptor.capabilities.markers ? [] : this.options.topMarkers.map((marker, markerIndex) => {
1934
2623
  const value = Number(marker.x ?? marker.step);
1935
2624
  const state = this.viewStates.get(layout.chart.id);
1936
2625
  const ratio = (value - state.xMin) / (state.xMax - state.xMin);
@@ -1941,9 +2630,13 @@ var ChartGridController = class {
1941
2630
  crosshairHtml() {
1942
2631
  const crosshair = this.options.showTooltips ? this.crosshair : null;
1943
2632
  if (!crosshair) return "";
2633
+ const chart = this.options.charts.find(
2634
+ (item) => item.id === crosshair.chartId
2635
+ );
2636
+ const xAxisLabel = chart?.xAxisLabel ?? this.options.xAxisLabel;
1944
2637
  return `<div class="pointer-events-none absolute z-20 border-l border-foreground/40" style="left:${crosshair.x}px;top:0;height:100%"></div><div class="pointer-events-none absolute z-20 border-t border-foreground/40" style="top:${crosshair.y}px;left:0;width:100%"></div>
1945
2638
  ${crosshair.points.map((point) => `<div class="pointer-events-none absolute z-30 size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border border-background" style="left:${point.x}px;top:${point.y}px;background:${point.color}"></div>`).join("")}
1946
- <div data-crosshair-tooltip class="pointer-events-none absolute z-30 w-[220px] rounded-sm border border-border/70 bg-popover/80 px-2 py-1.5 text-xs text-popover-foreground shadow-sm backdrop-blur-sm" style="left:${crosshair.tooltipX}px;top:${crosshair.tooltipY}px"><div class="mb-1 font-medium">${escapeHtml(this.options.xAxisLabel)}: ${escapeHtml(this.options.formatXValue(crosshair.xValue))}</div>${crosshair.points.map((point) => `<div class="grid grid-cols-[auto_1fr] gap-x-2"><span class="mt-1 size-2 rounded-full" style="background:${point.color}"></span><div class="min-w-0"><div class="truncate text-muted-foreground">${escapeHtml(point.name)}</div><div class="tabular-nums">${escapeHtml(this.options.formatYValue(point.yValue))}</div></div></div>`).join("")}</div>`;
2639
+ <div data-crosshair-tooltip class="pointer-events-none absolute z-30 w-[220px] rounded-sm border border-border/70 bg-popover/80 px-2 py-1.5 text-xs text-popover-foreground shadow-sm backdrop-blur-sm" style="left:${crosshair.tooltipX}px;top:${crosshair.tooltipY}px"><div class="mb-1 font-medium">${escapeHtml(xAxisLabel)}: ${escapeHtml(crosshair.categoryLabel ?? this.options.formatXValue(crosshair.xValue))}</div>${crosshair.points.map((point) => `<div class="grid grid-cols-[auto_1fr] gap-x-2"><span class="mt-1 size-2 rounded-full" style="background:${point.color}"></span><div class="min-w-0"><div class="truncate text-muted-foreground">${escapeHtml(point.name)}</div><div class="tabular-nums">${escapeHtml(this.options.formatYValue(point.yValue))}</div></div></div>`).join("")}</div>`;
1947
2640
  }
1948
2641
  rectangleOverlayHtml() {
1949
2642
  const rect = this.rectangleZoomRect && normalizeRect(this.rectangleZoomRect.start, this.rectangleZoomRect.end);
@@ -1953,7 +2646,7 @@ var ChartGridController = class {
1953
2646
  const drawing = this.drawings.find((item) => item.id === this.selectedDrawingId);
1954
2647
  if (!drawing) return "";
1955
2648
  const layout = layouts.find((item) => item.chart.id === drawing.chartId);
1956
- if (!layout) return "";
2649
+ if (!layout || !layout.descriptor.capabilities.drawings) return "";
1957
2650
  const style = drawing.style || {};
1958
2651
  const canExtend = drawing.type === "trendline" || drawing.type === "hline";
1959
2652
  const canText = drawing.type === "pin";
@@ -1969,8 +2662,33 @@ var ChartGridController = class {
1969
2662
  }
1970
2663
  projectPoint(point, layout) {
1971
2664
  const state = this.viewStates.get(layout.chart.id);
1972
- const range = applyYScale(getYRange(layout.chart, state.xMin, state.xMax, layout.plot.width), this.yScales.get(layout.chart.id), this.yOffsets.get(layout.chart.id));
1973
- return dataPointToScreenPoint({ point, state, yRange: range, plot: layout.plot });
2665
+ const range = this.getRange(layout, state);
2666
+ return this.getTransform(layout, state, range).dataToScreen(point);
2667
+ }
2668
+ getRange(layout, state = this.viewStates.get(layout.chart.id)) {
2669
+ return applyYScale(
2670
+ getYRange(
2671
+ layout.chart,
2672
+ state.xMin,
2673
+ state.xMax,
2674
+ getCategoryPixelLength(
2675
+ layout.chart,
2676
+ layout.plot,
2677
+ layout.descriptor
2678
+ ),
2679
+ layout.descriptor
2680
+ ),
2681
+ this.yScales.get(layout.chart.id),
2682
+ this.yOffsets.get(layout.chart.id)
2683
+ );
2684
+ }
2685
+ getTransform(layout, state, range) {
2686
+ return createCoordinateTransform({
2687
+ orientation: layout.descriptor.orientation,
2688
+ categoryRange: { min: state.xMin, max: state.xMax },
2689
+ valueRange: { min: range.minY, max: range.maxY },
2690
+ plot: layout.plot
2691
+ });
1974
2692
  }
1975
2693
  pointForEvent(event, host) {
1976
2694
  const rect = host.getBoundingClientRect();
@@ -2065,8 +2783,8 @@ var ChartGridController = class {
2065
2783
  this.activePointerContext = context;
2066
2784
  this.focusedChartId = layout.chart.id;
2067
2785
  const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
2068
- if (inPlot && this.activeDrawingTool && !this.options.disableDrawings) {
2069
- const dataPoint = screenPointToDataPoint({ point, chart: layout.chart, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2786
+ if (inPlot && this.activeDrawingTool && !this.options.disableDrawings && layout.descriptor.capabilities.drawings) {
2787
+ const dataPoint = screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2070
2788
  if (["hline", "vline", "pin"].includes(this.activeDrawingTool)) {
2071
2789
  this.commitDrawing(layout.chart.id, this.activeDrawingTool, dataPoint, dataPoint);
2072
2790
  } else if (this.drawingSession?.chartId === layout.chart.id) {
@@ -2079,7 +2797,7 @@ var ChartGridController = class {
2079
2797
  this.drag = { type: "rectangle", layout, start: point };
2080
2798
  this.rectangleZoomRect = { start: point, end: point };
2081
2799
  } else if (inPlot) {
2082
- const hit = this.hitDrawing(point, layout);
2800
+ const hit = layout.descriptor.capabilities.drawings ? this.hitDrawing(point, layout) : null;
2083
2801
  if (hit) {
2084
2802
  this.selectedDrawingId = hit.drawing.id;
2085
2803
  this.emit("onSelectedDrawingIdChange", hit.drawing.id);
@@ -2091,9 +2809,13 @@ var ChartGridController = class {
2091
2809
  this.emit("onSelectedDrawingIdChange", null);
2092
2810
  this.drag = { type: "pan", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) }, yOffset: this.yOffsets.get(layout.chart.id) || 0 };
2093
2811
  }
2094
- } else if (point.x >= layout.plot.x + layout.plot.width) {
2812
+ } else if (layout.descriptor.orientation === "vertical" && point.x >= layout.plot.x + layout.plot.width) {
2813
+ this.drag = { type: "y-scale", layout, start: point, scale: this.yScales.get(layout.chart.id) || 1 };
2814
+ } else if (layout.descriptor.orientation === "horizontal" && point.y >= layout.plot.y + layout.plot.height) {
2095
2815
  this.drag = { type: "y-scale", layout, start: point, scale: this.yScales.get(layout.chart.id) || 1 };
2096
- } else if (point.y >= layout.plot.y + layout.plot.height) {
2816
+ } else if (layout.descriptor.orientation === "vertical" && point.y >= layout.plot.y + layout.plot.height) {
2817
+ this.drag = { type: "x-scale", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) } };
2818
+ } else if (layout.descriptor.orientation === "horizontal" && point.x <= layout.plot.x) {
2097
2819
  this.drag = { type: "x-scale", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) } };
2098
2820
  }
2099
2821
  context.host.setPointerCapture?.(event.pointerId);
@@ -2107,15 +2829,15 @@ var ChartGridController = class {
2107
2829
  const { layout, point } = context;
2108
2830
  if (this.drawingSession) {
2109
2831
  if (layout.chart.id !== this.drawingSession.chartId) return;
2110
- const end = screenPointToDataPoint({ point, chart: layout.chart, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2832
+ const end = screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2111
2833
  this.draftDrawing = createDraftDrawing({ ...this.drawingSession, end });
2112
2834
  this.requestRender();
2113
2835
  return;
2114
2836
  }
2115
2837
  if (this.drag) {
2116
- const { chart, plot } = this.drag.layout;
2838
+ const { chart, descriptor, plot } = this.drag.layout;
2117
2839
  if (this.drag.type === "drawing-edit") {
2118
- const dataPoint = screenPointToDataPoint({ point, chart, plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2840
+ const dataPoint = screenPointToDataPoint({ point, chart, descriptor, plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2119
2841
  const next = updateDrawingById(this.drawings, this.drag.drawingId, (drawing) => {
2120
2842
  if (drawing.type === "hline") {
2121
2843
  const deltaX = (drawing.end?.x ?? drawing.start.x) - drawing.start.x;
@@ -2134,21 +2856,73 @@ var ChartGridController = class {
2134
2856
  if (this.drag.type === "rectangle") this.rectangleZoomRect = { start: this.drag.start, end: point };
2135
2857
  if (this.drag.type === "pan") {
2136
2858
  const span = this.drag.state.xMax - this.drag.state.xMin;
2137
- const deltaX = (point.x - this.drag.start.x) / plot.width * span;
2138
- const baseRange = getYRange(chart, this.drag.state.xMin, this.drag.state.xMax, plot.width);
2859
+ const baseRange = getYRange(
2860
+ chart,
2861
+ this.drag.state.xMin,
2862
+ this.drag.state.xMax,
2863
+ getCategoryPixelLength(chart, plot, descriptor),
2864
+ descriptor
2865
+ );
2139
2866
  const rangeSpan = (baseRange.maxY - baseRange.minY) * (this.yScales.get(chart.id) || 1);
2140
- const deltaY = (point.y - this.drag.start.y) / plot.height * rangeSpan;
2141
- this.viewStates.set(chart.id, { xMin: this.drag.state.xMin - deltaX, xMax: this.drag.state.xMax - deltaX });
2142
- this.yOffsets.set(chart.id, this.drag.yOffset + deltaY);
2867
+ const transform = createCoordinateTransform({
2868
+ orientation: descriptor.orientation,
2869
+ categoryRange: {
2870
+ min: this.drag.state.xMin,
2871
+ max: this.drag.state.xMax
2872
+ },
2873
+ valueRange: { min: 0, max: rangeSpan },
2874
+ plot
2875
+ });
2876
+ const categoryDelta = transform.categoryDragDelta(
2877
+ this.drag.start,
2878
+ point
2879
+ );
2880
+ const valueDelta = transform.valueOffsetDelta(
2881
+ this.drag.start,
2882
+ point
2883
+ );
2884
+ this.viewStates.set(chart.id, { xMin: this.drag.state.xMin - categoryDelta, xMax: this.drag.state.xMax - categoryDelta });
2885
+ this.yOffsets.set(chart.id, this.drag.yOffset + valueDelta);
2886
+ }
2887
+ if (this.drag.type === "y-scale") {
2888
+ const transform = createCoordinateTransform({
2889
+ orientation: descriptor.orientation,
2890
+ categoryRange: { min: 0, max: 1 },
2891
+ valueRange: { min: 0, max: 1 },
2892
+ plot
2893
+ });
2894
+ const delta = transform.valueScaleDelta(this.drag.start, point);
2895
+ this.yScales.set(
2896
+ chart.id,
2897
+ scaleValueRange(
2898
+ this.drag.scale,
2899
+ delta,
2900
+ POINTER_SCALE_SENSITIVITY
2901
+ )
2902
+ );
2143
2903
  }
2144
- if (this.drag.type === "y-scale") this.yScales.set(chart.id, clamp(this.drag.scale * Math.exp((point.y - this.drag.start.y) * 0.01), Y_SCALE_MIN, Y_SCALE_MAX));
2145
2904
  if (this.drag.type === "x-scale") {
2146
2905
  const center = (this.drag.state.xMin + this.drag.state.xMax) / 2;
2147
- const span = clamp((this.drag.state.xMax - this.drag.state.xMin) * Math.exp((point.x - this.drag.start.x) * 0.01), X_SCALE_MIN_SPAN, X_SCALE_MAX_SPAN);
2906
+ const transform = createCoordinateTransform({
2907
+ orientation: descriptor.orientation,
2908
+ categoryRange: { min: 0, max: 1 },
2909
+ valueRange: { min: 0, max: 1 },
2910
+ plot
2911
+ });
2912
+ const delta = transform.categoryScaleDelta(this.drag.start, point);
2913
+ const span = clamp(
2914
+ (this.drag.state.xMax - this.drag.state.xMin) * Math.exp(delta * 0.01),
2915
+ getMinimumCategorySpan(chart),
2916
+ X_SCALE_MAX_SPAN
2917
+ );
2148
2918
  this.viewStates.set(chart.id, { xMin: center - span / 2, xMax: center + span / 2 });
2149
2919
  }
2150
- if (this.drag.type === "pan") this.updateCrosshair(point, layout);
2151
- else this.requestRender();
2920
+ if (this.drag.type === "pan") {
2921
+ this.updateCrosshair(point, layout);
2922
+ this.requestRender();
2923
+ } else {
2924
+ this.requestRender();
2925
+ }
2152
2926
  return;
2153
2927
  }
2154
2928
  this.updateCrosshair(point, layout, context.fullscreen);
@@ -2162,6 +2936,7 @@ var ChartGridController = class {
2162
2936
  const layout = this.drag.layout;
2163
2937
  applyRectangleZoom({
2164
2938
  chart: layout.chart,
2939
+ descriptor: layout.descriptor,
2165
2940
  plot: layout.plot,
2166
2941
  start: this.rectangleZoomRect.start,
2167
2942
  end: this.rectangleZoomRect.end,
@@ -2188,11 +2963,34 @@ var ChartGridController = class {
2188
2963
  const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
2189
2964
  if (!inPlot) return;
2190
2965
  event.preventDefault();
2966
+ if (event.shiftKey) {
2967
+ const wheelDelta = event.deltaY || event.deltaX;
2968
+ const currentScale = this.yScales.get(layout.chart.id) || 1;
2969
+ this.yScales.set(
2970
+ layout.chart.id,
2971
+ scaleValueRange(
2972
+ currentScale,
2973
+ wheelDelta,
2974
+ WHEEL_SCALE_SENSITIVITY
2975
+ )
2976
+ );
2977
+ this.requestRender();
2978
+ return;
2979
+ }
2191
2980
  const state = this.viewStates.get(layout.chart.id);
2192
- const ratio = clamp((point.x - layout.plot.x) / layout.plot.width, 0, 1);
2981
+ const range = this.getRange(layout, state);
2982
+ const ratio = clamp(
2983
+ this.getTransform(layout, state, range).categoryRatio(point),
2984
+ 0,
2985
+ 1
2986
+ );
2193
2987
  const anchor = state.xMin + ratio * (state.xMax - state.xMin);
2194
2988
  const zoom = Math.exp(event.deltaY * 1e-3);
2195
- const span = clamp((state.xMax - state.xMin) * zoom, X_SCALE_MIN_SPAN, X_SCALE_MAX_SPAN);
2989
+ const span = clamp(
2990
+ (state.xMax - state.xMin) * zoom,
2991
+ getMinimumCategorySpan(layout.chart),
2992
+ X_SCALE_MAX_SPAN
2993
+ );
2196
2994
  this.viewStates.set(layout.chart.id, { xMin: anchor - ratio * span, xMax: anchor + (1 - ratio) * span });
2197
2995
  this.requestRender();
2198
2996
  }
@@ -2202,32 +3000,55 @@ var ChartGridController = class {
2202
3000
  event.preventDefault();
2203
3001
  const { layout, point } = context;
2204
3002
  const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
2205
- const data = inPlot ? screenPointToDataPoint({ point, chart: layout.chart, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } }) : null;
3003
+ const data = inPlot ? screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } }) : null;
2206
3004
  this.options.onChartContextMenu({ chart: layout.chart, event, point: data });
2207
3005
  }
2208
3006
  updateCrosshair(point, layout) {
2209
3007
  const inPlot = point.x >= layout.plot.x && point.x <= layout.plot.x + layout.plot.width && point.y >= layout.plot.y && point.y <= layout.plot.y + layout.plot.height;
2210
3008
  if (!inPlot || !this.options.showTooltips) return this.clearCrosshair();
2211
3009
  const state = this.viewStates.get(layout.chart.id);
2212
- const xValue = state.xMin + (point.x - layout.plot.x) / layout.plot.width * (state.xMax - state.xMin);
2213
- const range = applyYScale(getYRange(layout.chart, state.xMin, state.xMax, layout.plot.width), this.yScales.get(layout.chart.id), this.yOffsets.get(layout.chart.id));
2214
- const points = layout.chart.series.map((series) => {
2215
- const visible = series.getVisiblePoints(state.xMin, state.xMax, layout.plot.width);
3010
+ const orientation = layout.descriptor.orientation;
3011
+ const range = this.getRange(layout, state);
3012
+ const transform = this.getTransform(layout, state, range);
3013
+ const xValue = transform.screenToData(point).x;
3014
+ const tooltipSeries = getOrderedSeries(
3015
+ layout.chart,
3016
+ this.options.seriesOrderByChart
3017
+ );
3018
+ const points = tooltipSeries.map((series, seriesIndex) => {
3019
+ const visible = series.getVisiblePoints(state.xMin, state.xMax, transform.categoryPixelLength);
2216
3020
  if (!visible.pointCount) return null;
2217
3021
  const index = getNearestPointIndex(visible.x, xValue);
2218
- const data = { x: visible.x[index], y: visible.y[index] };
2219
- const screen = dataPointToScreenPoint({ point: data, state, yRange: range, plot: layout.plot });
2220
- return { id: series.id, name: series.name, color: series.color, x: screen.x, y: screen.y, xValue: data.x, yValue: data.y };
3022
+ const category = this.surface.renderers.getTooltipCategory(
3023
+ layout.descriptor.rendererType,
3024
+ {
3025
+ visiblePoints: visible,
3026
+ pointIndex: index,
3027
+ seriesIndex,
3028
+ seriesCount: tooltipSeries.length,
3029
+ categoryMin: state.xMin,
3030
+ categoryMax: state.xMax
3031
+ }
3032
+ );
3033
+ const data = { x: category, y: visible.y[index] };
3034
+ const screen = transform.dataToScreen(data);
3035
+ return { id: series.id, name: series.name, color: series.color, x: screen.x, y: screen.y, xValue: visible.x[index], yValue: data.y };
2221
3036
  }).filter(Boolean);
2222
3037
  if (!points.length) return this.clearCrosshair();
2223
- const nearest = points.reduce((best, item) => Math.abs(item.x - point.x) < Math.abs(best.x - point.x) ? item : best);
3038
+ const nearest = points.reduce((best, item) => {
3039
+ const itemDistance = orientation === "horizontal" ? Math.abs(item.y - point.y) : Math.abs(item.x - point.x);
3040
+ const bestDistance = orientation === "horizontal" ? Math.abs(best.y - point.y) : Math.abs(best.x - point.x);
3041
+ return itemDistance < bestDistance ? item : best;
3042
+ });
2224
3043
  this.crosshair = {
3044
+ chartId: layout.chart.id,
2225
3045
  x: point.x,
2226
3046
  y: point.y,
2227
3047
  xValue: nearest.xValue,
3048
+ categoryLabel: getCategoryLabel(layout.chart, nearest.xValue),
2228
3049
  points,
2229
3050
  tooltipX: point.x + 232 > layout.rect.x + layout.rect.width ? point.x - 232 : point.x + 12,
2230
- tooltipY: clamp(point.y + 12, layout.rect.y + PLOT_PADDING.top, layout.rect.y + layout.rect.height - 96)
3051
+ tooltipY: clamp(point.y + 12, layout.plot.y, layout.rect.y + layout.rect.height - 96)
2231
3052
  };
2232
3053
  this.requestRender();
2233
3054
  }
@@ -2273,6 +3094,8 @@ var ChartGridController = class {
2273
3094
  }
2274
3095
  toggleMovingAverage(chartId) {
2275
3096
  if (!chartId || this.options.disableDrawings) return;
3097
+ const chart = this.options.charts.find((item) => item.id === chartId);
3098
+ if (!chart || !this.chartDescriptors.get(chartId)?.capabilities.movingAverage) return;
2276
3099
  const current = this.movingAverageByChart[chartId] || { period: 21, type: "ema", hideBase: false };
2277
3100
  this.movingAverageByChart = { ...this.movingAverageByChart, [chartId]: { ...current, enabled: !current.enabled } };
2278
3101
  this.emit("onMovingAverageToggle", chartId);
@@ -2300,7 +3123,7 @@ var ChartGridController = class {
2300
3123
  const overlay = document.createElement("div");
2301
3124
  overlay.dataset.alienchartsFullscreen = "";
2302
3125
  overlay.className = "aliencharts-root fixed inset-0 z-[60] bg-background p-2";
2303
- overlay.innerHTML = `<canvas class="pointer-events-none absolute left-0 top-0 z-10 block"></canvas><div data-fullscreen-cell>${chartCellHtml(chart, 0, this.options.backgroundColor)}</div><div data-fullscreen-overlays class="pointer-events-none absolute left-0 top-0 z-20" style="width:100%;height:100%"></div>`;
3126
+ overlay.innerHTML = `<canvas class="pointer-events-none absolute left-0 top-0 z-10 block"></canvas><div data-fullscreen-cell>${chartCellHtml(chart, this.chartDescriptors.get(chart.id), 0, this.options.backgroundColor)}</div><div data-fullscreen-overlays class="pointer-events-none absolute left-0 top-0 z-20" style="width:100%;height:100%"></div>`;
2304
3127
  document.body.append(overlay);
2305
3128
  this.fullscreenOverlay = overlay;
2306
3129
  this.fullscreenNode = overlay.querySelector("[data-chart-index]");
@@ -2338,6 +3161,8 @@ var ChartGridController = class {
2338
3161
  const editable = event.target instanceof HTMLElement && (event.target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(event.target.tagName));
2339
3162
  if (editable) return;
2340
3163
  const chartId = this.fullscreenChartId || this.focusedChartId;
3164
+ const chart = this.options.charts.find((item) => item.id === chartId);
3165
+ const capabilities = chart && this.chartDescriptors.get(chartId)?.capabilities;
2341
3166
  const key = event.key.toLowerCase();
2342
3167
  if (["arrowleft", "arrowright", "arrowup", "arrowdown"].includes(key) && !this.fullscreenChartId) {
2343
3168
  this.moveFocus(key.replace("arrow", ""));
@@ -2351,10 +3176,10 @@ var ChartGridController = class {
2351
3176
  this.rectangleZoomChartId = this.rectangleZoomChartId === chartId ? null : chartId;
2352
3177
  event.preventDefault();
2353
3178
  this.requestRender();
2354
- } else if (key === "m") {
3179
+ } else if (key === "m" && capabilities?.movingAverage) {
2355
3180
  this.toggleMovingAverage(chartId);
2356
3181
  event.preventDefault();
2357
- } else if (["t", "h", "v", "p"].includes(key)) {
3182
+ } else if (["t", "h", "v", "p"].includes(key) && capabilities?.drawings) {
2358
3183
  this.toggleDrawingTool({ t: "trendline", h: "hline", v: "vline", p: "pin" }[key]);
2359
3184
  event.preventDefault();
2360
3185
  } else if ((key === "delete" || key === "backspace") && this.selectedDrawingId) {
@@ -2502,7 +3327,7 @@ var makeSeries = ({ chartIndex, seriesIndex, pointCount }) => {
2502
3327
  value += Math.sin(i * waveA + chartIndex) * amplitudeA + Math.cos(i * waveB + seriesIndex) * amplitudeB + Math.sin(i * waveC) * amplitudeA * 0.35 + trend + (random() - 0.5) * noise + spike;
2503
3328
  y[i] = value;
2504
3329
  }
2505
- return createSeries({
3330
+ return createLineSeries({
2506
3331
  id: `chart-${chartIndex}-series-${seriesIndex}`,
2507
3332
  name: `Run ${seriesIndex + 1}`,
2508
3333
  color: palette[(chartIndex + seriesIndex) % palette.length],
@@ -2525,8 +3350,11 @@ var createMockCharts = ({
2525
3350
  }));
2526
3351
  };
2527
3352
  export {
3353
+ BarSeries,
2528
3354
  ChartGrid,
2529
3355
  LineSeries,
3356
+ createBarSeries,
3357
+ createLineSeries,
2530
3358
  createMockCharts,
2531
3359
  createSeries
2532
3360
  };