aliencharts 0.3.2 → 0.3.3

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
@@ -1742,19 +1742,24 @@ var getScaledYRangeForLayout = ({
1742
1742
  descriptor,
1743
1743
  state,
1744
1744
  plot,
1745
+ yBaseRangeRef,
1745
1746
  yScaleRef,
1746
1747
  yCenterOffsetRef
1747
- }) => applyYScale(
1748
- getYRange(
1748
+ }) => {
1749
+ const automaticRange = getYRange(
1749
1750
  chart,
1750
1751
  state.xMin,
1751
1752
  state.xMax,
1752
1753
  getCategoryPixelLength(chart, plot, descriptor),
1753
1754
  descriptor
1754
- ),
1755
- yScaleRef.current.get(chart.id) ?? 1,
1756
- yCenterOffsetRef.current.get(chart.id) ?? 0
1757
- );
1755
+ );
1756
+ const lockedRange = yBaseRangeRef?.current.get(chart.id);
1757
+ return applyYScale(
1758
+ lockedRange ? { ...automaticRange, ...lockedRange } : automaticRange,
1759
+ yScaleRef.current.get(chart.id) ?? 1,
1760
+ yCenterOffsetRef.current.get(chart.id) ?? 0
1761
+ );
1762
+ };
1758
1763
  var applyRectangleZoom = ({
1759
1764
  chart,
1760
1765
  descriptor: suppliedDescriptor,
@@ -1763,6 +1768,7 @@ var applyRectangleZoom = ({
1763
1768
  end,
1764
1769
  initialVisiblePoints,
1765
1770
  viewStateRef,
1771
+ yBaseRangeRef,
1766
1772
  yScaleRef,
1767
1773
  yCenterOffsetRef,
1768
1774
  yManualScaleRef
@@ -1779,6 +1785,7 @@ var applyRectangleZoom = ({
1779
1785
  descriptor,
1780
1786
  state,
1781
1787
  plot,
1788
+ yBaseRangeRef,
1782
1789
  yScaleRef,
1783
1790
  yCenterOffsetRef
1784
1791
  });
@@ -1831,6 +1838,7 @@ var screenPointToDataPoint = ({
1831
1838
  plot,
1832
1839
  initialVisiblePoints,
1833
1840
  viewStateRef,
1841
+ yBaseRangeRef,
1834
1842
  yScaleRef,
1835
1843
  yCenterOffsetRef
1836
1844
  }) => {
@@ -1841,6 +1849,7 @@ var screenPointToDataPoint = ({
1841
1849
  descriptor,
1842
1850
  state,
1843
1851
  plot,
1852
+ yBaseRangeRef,
1844
1853
  yScaleRef,
1845
1854
  yCenterOffsetRef
1846
1855
  });
@@ -1957,6 +1966,7 @@ var drawChartLayouts = ({
1957
1966
  antialiasLines = false,
1958
1967
  layouts,
1959
1968
  viewStateRef,
1969
+ yBaseRangeRef,
1960
1970
  yScaleRef,
1961
1971
  yCenterOffsetRef,
1962
1972
  initialVisiblePoints,
@@ -1994,17 +2004,15 @@ var drawChartLayouts = ({
1994
2004
  plot,
1995
2005
  descriptor
1996
2006
  );
1997
- const yRange = applyYScale(
1998
- getYRange(
1999
- chart,
2000
- state.xMin,
2001
- state.xMax,
2002
- categoryPixelLength,
2003
- descriptor
2004
- ),
2005
- yScaleRef.current.get(chart.id) ?? 1,
2006
- yCenterOffsetRef.current.get(chart.id) ?? 0
2007
- );
2007
+ const yRange = getScaledYRangeForLayout({
2008
+ chart,
2009
+ descriptor,
2010
+ state,
2011
+ plot,
2012
+ yBaseRangeRef,
2013
+ yScaleRef,
2014
+ yCenterOffsetRef
2015
+ });
2008
2016
  gl.enable(gl.SCISSOR_TEST);
2009
2017
  gl.scissor(
2010
2018
  Math.floor(scaledPlot.x),
@@ -2244,6 +2252,7 @@ var ChartGridController = class {
2244
2252
  this.viewStates = /* @__PURE__ */ new Map();
2245
2253
  this.yScales = /* @__PURE__ */ new Map();
2246
2254
  this.yOffsets = /* @__PURE__ */ new Map();
2255
+ this.yBaseRanges = /* @__PURE__ */ new Map();
2247
2256
  this.latestX = /* @__PURE__ */ new Map();
2248
2257
  this.axisOverlays = {};
2249
2258
  this.layouts = [];
@@ -2294,7 +2303,7 @@ var ChartGridController = class {
2294
2303
  }
2295
2304
  initializeChartState() {
2296
2305
  const ids = new Set(this.options.charts.map((chart) => chart.id));
2297
- [this.viewStates, this.yScales, this.yOffsets, this.latestX].forEach((map) => {
2306
+ [this.viewStates, this.yScales, this.yOffsets, this.yBaseRanges, this.latestX].forEach((map) => {
2298
2307
  [...map.keys()].forEach((id) => {
2299
2308
  if (!ids.has(id)) map.delete(id);
2300
2309
  });
@@ -2481,6 +2490,7 @@ var ChartGridController = class {
2481
2490
  antialiasLines: this.options.antialiasLines,
2482
2491
  layouts,
2483
2492
  viewStateRef: { current: this.viewStates },
2493
+ yBaseRangeRef: { current: this.yBaseRanges },
2484
2494
  yScaleRef: { current: this.yScales },
2485
2495
  yCenterOffsetRef: { current: this.yOffsets },
2486
2496
  initialVisiblePoints: this.options.initialVisiblePoints,
@@ -2626,8 +2636,11 @@ var ChartGridController = class {
2626
2636
  (item) => item.id === crosshair.chartId
2627
2637
  );
2628
2638
  const xAxisLabel = chart?.xAxisLabel ?? this.options.xAxisLabel;
2639
+ const pointLayer = `<div data-crosshair-points class="pointer-events-none absolute z-30 overflow-hidden" style="left:${crosshair.plot.x}px;top:${crosshair.plot.y}px;width:${crosshair.plot.width}px;height:${crosshair.plot.height}px">
2640
+ ${crosshair.points.map((point) => `<div data-crosshair-point class="absolute size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border border-background" style="left:${point.x - crosshair.plot.x}px;top:${point.y - crosshair.plot.y}px;background:${point.color}"></div>`).join("")}
2641
+ </div>`;
2629
2642
  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>
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("")}
2643
+ ${pointLayer}
2631
2644
  <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>`;
2632
2645
  }
2633
2646
  rectangleOverlayHtml() {
@@ -2658,22 +2671,42 @@ var ChartGridController = class {
2658
2671
  return this.getTransform(layout, state, range).dataToScreen(point);
2659
2672
  }
2660
2673
  getRange(layout, state = this.viewStates.get(layout.chart.id)) {
2661
- return applyYScale(
2662
- getYRange(
2674
+ const automaticRange = getYRange(
2675
+ layout.chart,
2676
+ state.xMin,
2677
+ state.xMax,
2678
+ getCategoryPixelLength(
2663
2679
  layout.chart,
2664
- state.xMin,
2665
- state.xMax,
2666
- getCategoryPixelLength(
2667
- layout.chart,
2668
- layout.plot,
2669
- layout.descriptor
2670
- ),
2680
+ layout.plot,
2671
2681
  layout.descriptor
2672
2682
  ),
2683
+ layout.descriptor
2684
+ );
2685
+ const lockedRange = this.yBaseRanges.get(layout.chart.id);
2686
+ return applyYScale(
2687
+ lockedRange ? { ...automaticRange, ...lockedRange } : automaticRange,
2673
2688
  this.yScales.get(layout.chart.id),
2674
2689
  this.yOffsets.get(layout.chart.id)
2675
2690
  );
2676
2691
  }
2692
+ lockYRange(layout, state = this.viewStates.get(layout.chart.id)) {
2693
+ if (this.yBaseRanges.has(layout.chart.id)) return;
2694
+ const range = getYRange(
2695
+ layout.chart,
2696
+ state.xMin,
2697
+ state.xMax,
2698
+ getCategoryPixelLength(
2699
+ layout.chart,
2700
+ layout.plot,
2701
+ layout.descriptor
2702
+ ),
2703
+ layout.descriptor
2704
+ );
2705
+ this.yBaseRanges.set(layout.chart.id, {
2706
+ minY: range.minY,
2707
+ maxY: range.maxY
2708
+ });
2709
+ }
2677
2710
  getTransform(layout, state, range) {
2678
2711
  return createCoordinateTransform({
2679
2712
  orientation: layout.descriptor.orientation,
@@ -2776,7 +2809,7 @@ var ChartGridController = class {
2776
2809
  this.focusedChartId = layout.chart.id;
2777
2810
  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;
2778
2811
  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 } });
2812
+ const dataPoint = screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yBaseRangeRef: { current: this.yBaseRanges }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2780
2813
  if (["hline", "vline", "pin"].includes(this.activeDrawingTool)) {
2781
2814
  this.commitDrawing(layout.chart.id, this.activeDrawingTool, dataPoint, dataPoint);
2782
2815
  } else if (this.drawingSession?.chartId === layout.chart.id) {
@@ -2799,7 +2832,17 @@ var ChartGridController = class {
2799
2832
  } else {
2800
2833
  this.selectedDrawingId = null;
2801
2834
  this.emit("onSelectedDrawingIdChange", null);
2802
- this.drag = { type: "pan", layout, start: point, state: { ...this.viewStates.get(layout.chart.id) }, yOffset: this.yOffsets.get(layout.chart.id) || 0 };
2835
+ const state = { ...this.viewStates.get(layout.chart.id) };
2836
+ const range = this.getRange(layout, state);
2837
+ this.drag = {
2838
+ type: "pan",
2839
+ layout,
2840
+ start: point,
2841
+ state,
2842
+ yOffset: this.yOffsets.get(layout.chart.id) || 0,
2843
+ canPanY: this.yBaseRanges.has(layout.chart.id),
2844
+ valueSpan: range.maxY - range.minY
2845
+ };
2803
2846
  }
2804
2847
  } else if (layout.descriptor.orientation === "vertical" && point.x >= layout.plot.x + layout.plot.width) {
2805
2848
  this.drag = { type: "y-scale", layout, start: point, scale: this.yScales.get(layout.chart.id) || 1 };
@@ -2821,7 +2864,7 @@ var ChartGridController = class {
2821
2864
  const { layout, point } = context;
2822
2865
  if (this.drawingSession) {
2823
2866
  if (layout.chart.id !== this.drawingSession.chartId) return;
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 } });
2867
+ const end = screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yBaseRangeRef: { current: this.yBaseRanges }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2825
2868
  this.draftDrawing = createDraftDrawing({ ...this.drawingSession, end });
2826
2869
  this.requestRender();
2827
2870
  return;
@@ -2829,7 +2872,7 @@ var ChartGridController = class {
2829
2872
  if (this.drag) {
2830
2873
  const { chart, descriptor, plot } = this.drag.layout;
2831
2874
  if (this.drag.type === "drawing-edit") {
2832
- const dataPoint = screenPointToDataPoint({ point, chart, descriptor, plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2875
+ const dataPoint = screenPointToDataPoint({ point, chart, descriptor, plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yBaseRangeRef: { current: this.yBaseRanges }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } });
2833
2876
  const next = updateDrawingById(this.drawings, this.drag.drawingId, (drawing) => {
2834
2877
  if (drawing.type === "hline") {
2835
2878
  const deltaX = (drawing.end?.x ?? drawing.start.x) - drawing.start.x;
@@ -2847,22 +2890,13 @@ var ChartGridController = class {
2847
2890
  }
2848
2891
  if (this.drag.type === "rectangle") this.rectangleZoomRect = { start: this.drag.start, end: point };
2849
2892
  if (this.drag.type === "pan") {
2850
- const span = this.drag.state.xMax - this.drag.state.xMin;
2851
- const baseRange = getYRange(
2852
- chart,
2853
- this.drag.state.xMin,
2854
- this.drag.state.xMax,
2855
- getCategoryPixelLength(chart, plot, descriptor),
2856
- descriptor
2857
- );
2858
- const rangeSpan = (baseRange.maxY - baseRange.minY) * (this.yScales.get(chart.id) || 1);
2859
2893
  const transform = createCoordinateTransform({
2860
2894
  orientation: descriptor.orientation,
2861
2895
  categoryRange: {
2862
2896
  min: this.drag.state.xMin,
2863
2897
  max: this.drag.state.xMax
2864
2898
  },
2865
- valueRange: { min: 0, max: rangeSpan },
2899
+ valueRange: { min: 0, max: this.drag.valueSpan },
2866
2900
  plot
2867
2901
  });
2868
2902
  const categoryDelta = transform.categoryDragDelta(
@@ -2874,7 +2908,9 @@ var ChartGridController = class {
2874
2908
  point
2875
2909
  );
2876
2910
  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);
2911
+ if (this.drag.canPanY) {
2912
+ this.yOffsets.set(chart.id, this.drag.yOffset + valueDelta);
2913
+ }
2878
2914
  }
2879
2915
  if (this.drag.type === "y-scale") {
2880
2916
  const transform = createCoordinateTransform({
@@ -2884,6 +2920,7 @@ var ChartGridController = class {
2884
2920
  plot
2885
2921
  });
2886
2922
  const delta = transform.valueScaleDelta(this.drag.start, point);
2923
+ if (delta !== 0) this.lockYRange(this.drag.layout);
2887
2924
  this.yScales.set(
2888
2925
  chart.id,
2889
2926
  scaleValueRange(
@@ -2926,7 +2963,7 @@ var ChartGridController = class {
2926
2963
  const normalized = normalizeRect(this.rectangleZoomRect.start, this.rectangleZoomRect.end);
2927
2964
  if (normalized?.width >= 4 && normalized?.height >= 4) {
2928
2965
  const layout = this.drag.layout;
2929
- applyRectangleZoom({
2966
+ const zoomed = applyRectangleZoom({
2930
2967
  chart: layout.chart,
2931
2968
  descriptor: layout.descriptor,
2932
2969
  plot: layout.plot,
@@ -2934,10 +2971,12 @@ var ChartGridController = class {
2934
2971
  end: this.rectangleZoomRect.end,
2935
2972
  initialVisiblePoints: this.options.initialVisiblePoints,
2936
2973
  viewStateRef: { current: this.viewStates },
2974
+ yBaseRangeRef: { current: this.yBaseRanges },
2937
2975
  yScaleRef: { current: this.yScales },
2938
2976
  yCenterOffsetRef: { current: this.yOffsets },
2939
2977
  yManualScaleRef: { current: /* @__PURE__ */ new Set() }
2940
2978
  });
2979
+ if (zoomed) this.lockYRange(layout);
2941
2980
  }
2942
2981
  this.rectangleZoomChartId = null;
2943
2982
  this.rectangleZoomRect = null;
@@ -2958,6 +2997,7 @@ var ChartGridController = class {
2958
2997
  if (event.shiftKey) {
2959
2998
  const wheelDelta = event.deltaY || event.deltaX;
2960
2999
  const currentScale = this.yScales.get(layout.chart.id) || 1;
3000
+ if (wheelDelta !== 0) this.lockYRange(layout);
2961
3001
  this.yScales.set(
2962
3002
  layout.chart.id,
2963
3003
  scaleValueRange(
@@ -2992,7 +3032,7 @@ var ChartGridController = class {
2992
3032
  event.preventDefault();
2993
3033
  const { layout, point } = context;
2994
3034
  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;
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;
3035
+ const data = inPlot ? screenPointToDataPoint({ point, chart: layout.chart, descriptor: layout.descriptor, plot: layout.plot, initialVisiblePoints: this.options.initialVisiblePoints, viewStateRef: { current: this.viewStates }, yBaseRangeRef: { current: this.yBaseRanges }, yScaleRef: { current: this.yScales }, yCenterOffsetRef: { current: this.yOffsets } }) : null;
2996
3036
  this.options.onChartContextMenu({ chart: layout.chart, event, point: data });
2997
3037
  }
2998
3038
  updateCrosshair(point, layout) {
@@ -3039,6 +3079,7 @@ var ChartGridController = class {
3039
3079
  xValue: nearest.xValue,
3040
3080
  categoryLabel: getCategoryLabel(layout.chart, nearest.xValue),
3041
3081
  points,
3082
+ plot: { ...layout.plot },
3042
3083
  tooltipX: point.x + 232 > layout.rect.x + layout.rect.width ? point.x - 232 : point.x + 12,
3043
3084
  tooltipY: clamp(point.y + 12, layout.plot.y, layout.rect.y + layout.rect.height - 96)
3044
3085
  };
@@ -3106,6 +3147,7 @@ var ChartGridController = class {
3106
3147
  this.viewStates.set(chartId, getInitialView(chart, this.options.initialVisiblePoints));
3107
3148
  this.yScales.set(chartId, 1);
3108
3149
  this.yOffsets.set(chartId, 0);
3150
+ this.yBaseRanges.delete(chartId);
3109
3151
  this.requestRender();
3110
3152
  }
3111
3153
  openFullscreen(chartId) {