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