@wick-charts/vue 0.6.0 → 0.6.2

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.
@@ -1,9 +1,11 @@
1
- function d(r, s, l, x) {
2
- const { context: t, bitmapSize: e, horizontalPixelRatio: o, verticalPixelRatio: c } = r, a = Math.max(1, Math.round(c)), i = Math.max(1, Math.round(o));
3
- t.strokeStyle = x.crosshair.color, t.lineWidth = Math.max(a, i), t.setLineDash([4 * o, 4 * o]);
4
- const h = Math.round(s) + (i % 2 === 1 ? 0.5 : 0), n = Math.round(l) + (a % 2 === 1 ? 0.5 : 0);
5
- t.beginPath(), t.moveTo(h, 0), t.lineTo(h, e.height), t.moveTo(0, n), t.lineTo(e.width, n), t.stroke(), t.setLineDash([]);
1
+ function u(m) {
2
+ const { scope: x, bitmapX: o, bitmapY: i, theme: M, pane: a } = m, { context: t, bitmapSize: h, horizontalPixelRatio: e, verticalPixelRatio: p } = x, n = o <= a.width, r = i <= a.height;
3
+ if (!n && !r) return;
4
+ const s = Math.max(1, Math.round(p)), c = Math.max(1, Math.round(e));
5
+ t.strokeStyle = M.crosshair.color, t.lineWidth = Math.max(s, c), t.setLineDash([4 * e, 4 * e]);
6
+ const l = Math.round(o) + (c % 2 === 1 ? 0.5 : 0), d = Math.round(i) + (s % 2 === 1 ? 0.5 : 0);
7
+ t.beginPath(), n && (t.moveTo(l, 0), t.lineTo(l, h.height)), r && (t.moveTo(0, d), t.lineTo(h.width, d)), t.stroke(), t.setLineDash([]);
6
8
  }
7
9
  export {
8
- d as renderCrosshair
10
+ u as renderCrosshair
9
11
  };
@@ -1,22 +1,26 @@
1
- function T({ scope: c, timeScale: h, yScale: d, theme: a, yTicks: m, timeTicks: p }) {
2
- const { context: t, bitmapSize: s, horizontalPixelRatio: e, verticalPixelRatio: u } = c;
3
- t.save(), t.strokeStyle = a.grid.color, a.grid.style === "dashed" ? t.setLineDash([4 * e, 4 * e]) : a.grid.style === "dotted" && t.setLineDash([1 * e, 3 * e]);
4
- const l = Math.max(1, Math.round(u)), g = l % 2 === 1 ? 0.5 : 0;
5
- t.lineWidth = l;
6
- for (const { value: n, opacity: o } of m.entries) {
7
- if (o <= 0.01) continue;
8
- const i = Math.round(d.valueToBitmapY(n)) + g;
9
- t.globalAlpha = o, t.beginPath(), t.moveTo(0, i), t.lineTo(s.width, i), t.stroke();
1
+ import { crispLineWidth as h, crispCenterOffset as d } from "../utils/pixel-grid.js";
2
+ function x({ scope: p, timeScale: m, yScale: f, theme: a, yTicks: u, timeTicks: T, alpha: n = 1 }) {
3
+ if (n <= 0.01) return;
4
+ const { context: e, bitmapSize: c, horizontalPixelRatio: t, verticalPixelRatio: l } = p;
5
+ e.save(), e.strokeStyle = a.grid.color, a.grid.style === "dashed" ? e.setLineDash([4 * t, 4 * t]) : a.grid.style === "dotted" && e.setLineDash([1 * t, 3 * t]);
6
+ const g = h(l), v = d(l);
7
+ e.lineWidth = g;
8
+ for (const { value: r, opacity: s } of u.entries) {
9
+ const i = s * n;
10
+ if (i <= 0.01) continue;
11
+ const o = Math.round(f.valueToBitmapY(r)) + v;
12
+ e.globalAlpha = i, e.beginPath(), e.moveTo(0, o), e.lineTo(c.width, o), e.stroke();
10
13
  }
11
- const r = Math.max(1, Math.round(e)), y = r % 2 === 1 ? 0.5 : 0;
12
- t.lineWidth = r;
13
- for (const { value: n, opacity: o } of p.entries) {
14
- if (o <= 0.01) continue;
15
- const i = Math.round(h.timeToBitmapX(n)) + y;
16
- t.globalAlpha = o, t.beginPath(), t.moveTo(i, 0), t.lineTo(i, s.height), t.stroke();
14
+ const y = h(t), b = d(t);
15
+ e.lineWidth = y;
16
+ for (const { value: r, opacity: s } of T.entries) {
17
+ const i = s * n;
18
+ if (i <= 0.01) continue;
19
+ const o = Math.round(m.timeToBitmapX(r)) + b;
20
+ e.globalAlpha = i, e.beginPath(), e.moveTo(o, 0), e.lineTo(o, c.height), e.stroke();
17
21
  }
18
- t.restore();
22
+ e.restore();
19
23
  }
20
24
  export {
21
- T as renderGrid
25
+ x as renderGrid
22
26
  };
@@ -6,8 +6,8 @@ function i(o, e) {
6
6
  const t = o.clientX - e.clientX, s = o.clientY - e.clientY;
7
7
  return Math.hypot(t, s);
8
8
  }
9
- const d = 5, v = 500;
10
- class M extends a {
9
+ const d = 5, m = 500;
10
+ class T extends a {
11
11
  zoom;
12
12
  pan;
13
13
  canvas;
@@ -15,13 +15,27 @@ class M extends a {
15
15
  yScale;
16
16
  target;
17
17
  constructor(e, t, s, n) {
18
- super(), this.canvas = e, this.target = t, this.timeScale = s, this.yScale = n, this.zoom = new r(t, s), this.pan = new u(t, s, e), e.style.cursor = "crosshair", e.style.touchAction = "none", e.addEventListener("wheel", this.onWheel, { passive: !1 }), e.addEventListener("mousedown", this.onMouseDown), e.addEventListener("mousemove", this.onMouseMove), e.addEventListener("mouseup", this.onMouseUp), e.addEventListener("mouseleave", this.onMouseLeave), e.addEventListener("click", this.onClick), e.addEventListener("dblclick", this.onDblClick), e.addEventListener("touchstart", this.onTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.onTouchMove, { passive: !1 }), e.addEventListener("touchend", this.onTouchEnd), e.addEventListener("touchcancel", this.onTouchCancel);
18
+ super(), this.canvas = e, this.target = t, this.timeScale = s, this.yScale = n, this.zoom = new r(t, s), this.pan = new u(t, s, e), e.style.cursor = "crosshair", this.syncPanZoomMode(), e.addEventListener("wheel", this.onWheel, { passive: !1 }), e.addEventListener("mousedown", this.onMouseDown), e.addEventListener("mousemove", this.onMouseMove), e.addEventListener("mouseup", this.onMouseUp), e.addEventListener("mouseleave", this.onMouseLeave), e.addEventListener("click", this.onClick), e.addEventListener("dblclick", this.onDblClick), e.addEventListener("touchstart", this.onTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.onTouchMove, { passive: !1 }), e.addEventListener("touchend", this.onTouchEnd), e.addEventListener("touchcancel", this.onTouchCancel);
19
+ }
20
+ /** Live read of the target's capability — series can be added or removed at any time. */
21
+ panZoomEnabled() {
22
+ return this.target.canPanZoom?.() ?? !0;
23
+ }
24
+ /**
25
+ * Reflect the target's pan/zoom capability into the canvas CSS. While
26
+ * pan/zoomable, `touch-action: none` hands every touch gesture to the
27
+ * chart; on a spatial-only target native scrolling stays enabled so the
28
+ * chart doesn't trap the page. The host re-invokes this whenever the
29
+ * series mix changes.
30
+ */
31
+ syncPanZoomMode() {
32
+ this.canvas.style.touchAction = this.panZoomEnabled() ? "none" : "auto";
19
33
  }
20
34
  onWheel = (e) => {
21
- this.zoom.handleWheel(e);
35
+ this.panZoomEnabled() && this.zoom.handleWheel(e);
22
36
  };
23
37
  onMouseDown = (e) => {
24
- this.zoom.cancelPendingRebound(), this.pan.handleMouseDown(e), this.downClient = { x: e.clientX, y: e.clientY };
38
+ this.zoom.cancelPendingRebound(), this.panZoomEnabled() && this.pan.handleMouseDown(e), this.downClient = { x: e.clientX, y: e.clientY };
25
39
  };
26
40
  onMouseMove = (e) => {
27
41
  this.pan.isDragging() && this.pan.handleMouseMove(e), this.emitCrosshair(e.offsetX, e.offsetY);
@@ -54,36 +68,38 @@ class M extends a {
54
68
  // synthesize a phantom 'click'.
55
69
  gestureHadTwoFingers = !1;
56
70
  onTouchStart = (e) => {
57
- e.preventDefault(), this.zoom.cancelPendingRebound(), this.touchCount = e.touches.length, e.touches.length === 1 ? (this.pan.handleMouseDown({
71
+ this.panZoomEnabled() && (e.preventDefault(), this.zoom.cancelPendingRebound(), this.touchCount = e.touches.length, e.touches.length === 1 ? (this.pan.handleMouseDown({
58
72
  button: 0,
59
73
  clientX: e.touches[0].clientX
60
- }), this.downClient = { x: e.touches[0].clientX, y: e.touches[0].clientY }, this.touchDownTime = Date.now(), this.gestureHadTwoFingers = !1) : e.touches.length === 2 && (this.lastTouchDist = i(e.touches[0], e.touches[1]), this.gestureHadTwoFingers = !0);
74
+ }), this.downClient = { x: e.touches[0].clientX, y: e.touches[0].clientY }, this.touchDownTime = Date.now(), this.gestureHadTwoFingers = !1) : e.touches.length === 2 && (this.lastTouchDist = i(e.touches[0], e.touches[1]), this.gestureHadTwoFingers = !0));
61
75
  };
62
76
  onTouchMove = (e) => {
63
- if (e.preventDefault(), e.touches.length === 1 && this.touchCount === 1)
64
- this.pan.handleMouseMove({
65
- clientX: e.touches[0].clientX
66
- });
67
- else if (e.touches.length === 2) {
68
- const t = i(e.touches[0], e.touches[1]), s = (e.touches[0].clientX + e.touches[1].clientX) / 2, n = this.canvas.getBoundingClientRect();
69
- if (t > 0 && this.lastTouchDist > 0) {
70
- const h = l(this.lastTouchDist / t, 0.1, 10), c = this.timeScale.xToTime(s - n.left);
71
- this.target.zoomAt(c, h, this.timeScale.getMediaWidth());
77
+ if (this.panZoomEnabled()) {
78
+ if (e.preventDefault(), e.touches.length === 1 && this.touchCount === 1)
79
+ this.pan.handleMouseMove({
80
+ clientX: e.touches[0].clientX
81
+ });
82
+ else if (e.touches.length === 2) {
83
+ const t = i(e.touches[0], e.touches[1]), s = (e.touches[0].clientX + e.touches[1].clientX) / 2, n = this.canvas.getBoundingClientRect();
84
+ if (t > 0 && this.lastTouchDist > 0) {
85
+ const h = l(this.lastTouchDist / t, 0.1, 10), c = this.timeScale.xToTime(s - n.left);
86
+ this.target.zoomAt(c, h, this.timeScale.getMediaWidth());
87
+ }
88
+ this.lastTouchDist = t;
72
89
  }
73
- this.lastTouchDist = t;
74
90
  }
75
91
  };
76
92
  onTouchEnd = (e) => {
77
93
  if (e.touches.length === 0) {
78
94
  const t = e.changedTouches[0];
79
- if (this.touchCount === 1 && !this.gestureHadTwoFingers && t && !this.movedBeyondClickTolerance(t.clientX, t.clientY) && Date.now() - this.touchDownTime <= v) {
95
+ if (this.touchCount === 1 && !this.gestureHadTwoFingers && t && !this.movedBeyondClickTolerance(t.clientX, t.clientY) && Date.now() - this.touchDownTime <= m) {
80
96
  const s = this.canvas.getBoundingClientRect();
81
97
  this.emit("click", this.posFromOffset(t.clientX - s.left, t.clientY - s.top));
82
98
  }
83
99
  this.pan.handleMouseUp(), this.touchCount = 0, this.lastTouchDist = 0, this.downClient = null, this.gestureHadTwoFingers = !1, this.emit("crosshairMove", null);
84
100
  return;
85
101
  }
86
- e.touches.length === 1 && (this.touchCount = 1, this.lastTouchDist = 0, this.pan.handleMouseDown({ button: 0, clientX: e.touches[0].clientX }), this.downClient = { x: e.touches[0].clientX, y: e.touches[0].clientY }, this.touchDownTime = Date.now());
102
+ e.touches.length === 1 && this.touchCount === 2 && (this.touchCount = 1, this.lastTouchDist = 0, this.pan.handleMouseDown({ button: 0, clientX: e.touches[0].clientX }), this.downClient = { x: e.touches[0].clientX, y: e.touches[0].clientY }, this.touchDownTime = Date.now());
87
103
  };
88
104
  onTouchCancel = () => {
89
105
  this.pan.handleMouseUp(), this.touchCount = 0, this.lastTouchDist = 0, this.downClient = null, this.gestureHadTwoFingers = !1, this.emit("crosshairMove", null);
@@ -104,5 +120,5 @@ class M extends a {
104
120
  }
105
121
  }
106
122
  export {
107
- M as InteractionHandler
123
+ T as InteractionHandler
108
124
  };
@@ -2,8 +2,8 @@ import { DEFAULT_LINE_ENTRY as w } from "../animation/config.js";
2
2
  import { XScale as T } from "../scales/x-scale.js";
3
3
  import { YScale as R } from "../scales/y-scale.js";
4
4
  import { decimateCandles as k, decimateLinear as C } from "./decimate.js";
5
- import { hitTest as y, computeSnapCenter as E, computePan as I, computeResize as M } from "./interactions.js";
6
- import { computeWindowGeometry as $, renderBackground as V, renderMiniCandlestick as L, renderMiniBar as N, renderMiniLine as P } from "./render.js";
5
+ import { hitTest as y, computeSnapCenter as E, computePan as I, computeResize as $ } from "./interactions.js";
6
+ import { computeWindowGeometry as M, renderBackground as V, renderMiniCandlestick as L, renderMiniBar as N, renderMiniLine as P } from "./render.js";
7
7
  import { Animator as S } from "../animation/animator.js";
8
8
  import { easeOutCubic as z } from "../animation/easing.js";
9
9
  class G {
@@ -58,8 +58,8 @@ class G {
58
58
  #g = null;
59
59
  // Bound listeners so we can remove the same references on destroy.
60
60
  #I;
61
- #M;
62
61
  #$;
62
+ #M;
63
63
  #V;
64
64
  #L;
65
65
  #b;
@@ -68,9 +68,9 @@ class G {
68
68
  this.#s = t.container, this.#e = t.chart, this.#x = t.data, this.#v = t.options ?? {}, window.getComputedStyle(this.#s).position === "static" && (this.#s.style.position = "relative"), this.#t = document.createElement("canvas"), this.#t.style.position = "absolute", this.#t.style.left = "0", this.#t.style.top = "0", this.#t.style.height = "100%", this.#t.style.touchAction = "none", this.#s.appendChild(this.#t);
69
69
  const e = this.#t.getContext("2d", { alpha: !0 });
70
70
  if (!e) throw new Error("NavigatorController: failed to acquire 2D context");
71
- this.#z = e, this.#o = document.createElement("div"), this.#o.dataset.chartNavigatorOverlay = "", this.#o.style.cssText = "position:absolute;left:0;top:0;height:100%;pointer-events:none;", this.#d = document.createElement("div"), this.#d.style.cssText = "position:absolute;left:0;top:0;bottom:0;", this.#r = document.createElement("div"), this.#r.style.cssText = "position:absolute;top:0;bottom:0;", this.#l = document.createElement("div"), this.#l.style.cssText = "position:absolute;top:0;bottom:0;box-sizing:border-box;", this.#a = b(), this.#c = b(), this.#o.append(this.#d, this.#r, this.#l, this.#a, this.#c), this.#s.appendChild(this.#o), this.#I = () => this.#B(), this.#M = () => this.#f(), this.#$ = () => this.#B(), this.#e.on("viewportChange", this.#I), this.#e.on("overlayChange", this.#M), this.#e.on("tickFrame", this.#$), this.#V = (s) => this.#H(s), this.#L = (s) => this.#J(s), this.#b = (s) => this.#K(s), this.#N = () => {
71
+ this.#z = e, this.#o = document.createElement("div"), this.#o.dataset.chartNavigatorOverlay = "", this.#o.style.cssText = "position:absolute;left:0;top:0;height:100%;pointer-events:none;", this.#d = document.createElement("div"), this.#d.style.cssText = "position:absolute;left:0;top:0;bottom:0;", this.#r = document.createElement("div"), this.#r.style.cssText = "position:absolute;top:0;bottom:0;", this.#l = document.createElement("div"), this.#l.style.cssText = "position:absolute;top:0;bottom:0;box-sizing:border-box;", this.#a = b(), this.#c = b(), this.#o.append(this.#d, this.#r, this.#l, this.#a, this.#c), this.#s.appendChild(this.#o), this.#I = () => this.#B(), this.#$ = () => this.#f(), this.#M = () => this.#B(), this.#e.on("viewportChange", this.#I), this.#e.on("overlayChange", this.#$), this.#e.on("tickFrame", this.#M), this.#V = (s) => this.#U(s), this.#L = (s) => this.#J(s), this.#b = (s) => this.#K(s), this.#N = () => {
72
72
  this.#g === null && (this.#t.style.cursor = "default");
73
- }, this.#t.addEventListener("pointerdown", this.#V), this.#t.addEventListener("pointermove", this.#L), this.#t.addEventListener("pointerup", this.#b), this.#t.addEventListener("pointercancel", this.#b), this.#t.addEventListener("pointerleave", this.#N), this.#E = new ResizeObserver((s) => this.#U(s[0])), this.#E.observe(this.#s), this.#D();
73
+ }, this.#t.addEventListener("pointerdown", this.#V), this.#t.addEventListener("pointermove", this.#L), this.#t.addEventListener("pointerup", this.#b), this.#t.addEventListener("pointercancel", this.#b), this.#t.addEventListener("pointerleave", this.#N), this.#E = new ResizeObserver((s) => this.#j(s[0])), this.#E.observe(this.#s), this.#D();
74
74
  const i = this.#s.getBoundingClientRect();
75
75
  i.width > 0 && i.height > 0 && this.#W(i.width, i.height), this.#f();
76
76
  }
@@ -100,7 +100,7 @@ class G {
100
100
  t !== void 0 && (this.#s.style.height = `${t}px`);
101
101
  }
102
102
  destroy() {
103
- this.#e.off("viewportChange", this.#I), this.#e.off("overlayChange", this.#M), this.#e.off("tickFrame", this.#$), this.#t.removeEventListener("pointerdown", this.#V), this.#t.removeEventListener("pointermove", this.#L), this.#t.removeEventListener("pointerup", this.#b), this.#t.removeEventListener("pointercancel", this.#b), this.#t.removeEventListener("pointerleave", this.#N), this.#E.disconnect(), this.#p !== null && (cancelAnimationFrame(this.#p), this.#p = null), this.#t.remove(), this.#o.remove();
103
+ this.#e.off("viewportChange", this.#I), this.#e.off("overlayChange", this.#$), this.#e.off("tickFrame", this.#M), this.#t.removeEventListener("pointerdown", this.#V), this.#t.removeEventListener("pointermove", this.#L), this.#t.removeEventListener("pointerup", this.#b), this.#t.removeEventListener("pointercancel", this.#b), this.#t.removeEventListener("pointerleave", this.#N), this.#E.disconnect(), this.#p !== null && (cancelAnimationFrame(this.#p), this.#p = null), this.#t.remove(), this.#o.remove();
104
104
  }
105
105
  /** CSS pixel height of the strip — theme default unless overridden. */
106
106
  get height() {
@@ -112,7 +112,7 @@ class G {
112
112
  get #i() {
113
113
  return Math.max(0, this.#A - this.#e.yAxisWidth);
114
114
  }
115
- #U(t) {
115
+ #j(t) {
116
116
  if (!t) return;
117
117
  const e = t.contentBoxSize?.[0], i = e?.inlineSize ?? t.contentRect.width, s = e?.blockSize ?? t.contentRect.height;
118
118
  i <= 0 || s <= 0 || (this.#W(i, s), this.#f());
@@ -169,17 +169,17 @@ class G {
169
169
  return this.#g !== null ? this.#e.getVisibleRange() : this.#e.getAnimationState().xRange;
170
170
  }
171
171
  #S() {
172
- return this.#P(), $(this.#u, this.#Y(), this.#y());
172
+ return this.#P(), M(this.#u, this.#Y(), this.#y());
173
173
  }
174
- #j() {
174
+ #H() {
175
175
  const t = this.#y(), e = t.to - t.from;
176
176
  return e <= 0 || this.#i <= 0 ? 0 : this.#i / e;
177
177
  }
178
- #H(t) {
178
+ #U(t) {
179
179
  if (t.button !== void 0 && t.button !== 0) return;
180
180
  const e = this.#w(t), i = this.#y();
181
181
  if (i.to <= i.from) return;
182
- const s = this.#S(), n = y(e, s), h = this.#j();
182
+ const s = this.#S(), n = y(e, s), h = this.#H();
183
183
  if (h === 0) return;
184
184
  let l = this.#e.getVisibleRange();
185
185
  if (n.snapToCenter) {
@@ -216,7 +216,7 @@ class G {
216
216
  });
217
217
  else {
218
218
  const h = e.gesture === "resize-left" ? "left" : "right";
219
- n = M({
219
+ n = $({
220
220
  edge: h,
221
221
  startVisible: e.startVisible,
222
222
  deltaPx: i,
@@ -8,7 +8,7 @@ function f(s, t) {
8
8
  if (s[e] !== t[e]) return !1;
9
9
  return !0;
10
10
  }
11
- class m {
11
+ let m = class {
12
12
  #e = [];
13
13
  #r = [];
14
14
  #t = /* @__PURE__ */ new Map();
@@ -104,7 +104,7 @@ class m {
104
104
  reset() {
105
105
  this.#e = [], this.#r = [], this.#t.clear(), this.#s = !1;
106
106
  }
107
- }
107
+ };
108
108
  export {
109
109
  m as AxisTickTracker
110
110
  };
@@ -1,13 +1,14 @@
1
- import { formatTime as m, niceTimeIntervals as c } from "../utils/time.js";
2
- import { AxisTickTracker as f } from "./tick-tracker.js";
3
- const o = 50, v = 80;
4
- class p {
1
+ import { crispCenterOffset as m } from "../utils/pixel-grid.js";
2
+ import { formatTime as c, niceTimeIntervals as f } from "../utils/time.js";
3
+ import { AxisTickTracker as v } from "./tick-tracker.js";
4
+ const o = 50, d = 80;
5
+ class b {
5
6
  /**
6
7
  * Shared fade state for time ticks. Grid lines (canvas) and DOM tick labels
7
8
  * read opacity from the same tracker, so a tick fading in/out looks
8
9
  * identical on both surfaces.
9
10
  */
10
- tickTracker = new f();
11
+ tickTracker = new v();
11
12
  from = 0;
12
13
  to = 0;
13
14
  width = 1;
@@ -37,17 +38,17 @@ class p {
37
38
  return this.labelCountHintValue;
38
39
  }
39
40
  get minLabelSpacing() {
40
- return this.minSpacingValue ?? v;
41
+ return this.minSpacingValue ?? d;
41
42
  }
42
- update(t, e, n, r) {
43
- if (this.from = t.from, this.to = t.to, this.width = e, this.pixelRatio = n, r !== void 0 && r > 0) {
44
- const a = h(r);
45
- this.lastBucketKey !== null && this.lastBucketKey !== a && this.resetHysteresis(), this.lastBucketKey = a, this.dataInterval = r;
43
+ update(t, e, n, l) {
44
+ if (this.from = t.from, this.to = t.to, this.width = e, this.pixelRatio = n, l !== void 0 && l > 0) {
45
+ const a = h(l);
46
+ this.lastBucketKey !== null && this.lastBucketKey !== a && this.resetHysteresis(), this.lastBucketKey = a, this.dataInterval = l;
46
47
  }
47
48
  this.resolveInterval();
48
49
  }
49
50
  setLabelCount(t) {
50
- this.labelCountHintValue = d(t), this.resetHysteresis(), this.resolveInterval();
51
+ this.labelCountHintValue = p(t), this.resetHysteresis(), this.resolveInterval();
51
52
  }
52
53
  setMinSpacing(t) {
53
54
  this.minSpacingValue = I(t), this.resetHysteresis(), this.resolveInterval();
@@ -88,7 +89,7 @@ class p {
88
89
  * `formatTime`, applying the installed locale/timezone.
89
90
  */
90
91
  formatX(t, e) {
91
- return this.customFormat ? this.customFormat(t, e) : m(t, e, { locale: this.localeValue, timeZone: this.timeZoneValue });
92
+ return this.customFormat ? this.customFormat(t, e) : c(t, e, { locale: this.localeValue, timeZone: this.timeZoneValue });
92
93
  }
93
94
  resetHysteresis() {
94
95
  this.lastInterval = null, this.lastWant = null;
@@ -96,8 +97,22 @@ class p {
96
97
  timeToX(t) {
97
98
  return this.to <= this.from ? 0 : (t - this.from) / (this.to - this.from) * this.width;
98
99
  }
100
+ /** X in physical (bitmap) pixels, snapped to the pixel grid — for
101
+ * axis-aligned edges that have to stay crisp. */
99
102
  timeToBitmapX(t) {
100
- return Math.round(this.timeToX(t) * this.pixelRatio);
103
+ return Math.round(this.timeToBitmapXExact(t));
104
+ }
105
+ /** Same in physical pixels, off the grid — see {@link YScale.valueToBitmapYExact}. */
106
+ timeToBitmapXExact(t) {
107
+ return this.timeToX(t) * this.pixelRatio;
108
+ }
109
+ /**
110
+ * X position in CSS pixels of the *stroke center* the canvas grid draws for
111
+ * `time`. DOM axis labels position off this instead of raw {@link timeToX}
112
+ * so text and gridline share one pixel grid.
113
+ */
114
+ timeToSnappedX(t) {
115
+ return (this.timeToBitmapX(t) + m(this.pixelRatio)) / this.pixelRatio;
101
116
  }
102
117
  xToTime(t) {
103
118
  return this.to <= this.from ? this.from : this.from + t / this.width * (this.to - this.from);
@@ -119,16 +134,16 @@ class p {
119
134
  niceTickValues(t) {
120
135
  if (this.to <= this.from) return { ticks: [], tickInterval: 0 };
121
136
  if (this.customTickGenerator) {
122
- const i = this.customTickGenerator({ from: this.from, to: this.to }, t).slice(0, o), l = i.length >= 2 ? i[1] - i[0] : t;
123
- return { ticks: i, tickInterval: l };
137
+ const i = this.customTickGenerator({ from: this.from, to: this.to }, t).slice(0, o), r = i.length >= 2 ? i[1] - i[0] : t;
138
+ return { ticks: i, tickInterval: r };
124
139
  }
125
140
  if (this.dataInterval !== t) {
126
141
  const i = h(t);
127
142
  this.lastBucketKey !== null && this.lastBucketKey !== i && this.resetHysteresis(), this.lastBucketKey = i, this.dataInterval = t, this.resolveInterval();
128
143
  }
129
144
  if (this.resolvedInterval == null) return { ticks: [], tickInterval: 0 };
130
- const e = this.resolvedInterval, n = Math.ceil(this.from / e) * e, r = Math.max(0, Math.min(o, Math.floor((this.to - n) / e) + 1)), a = [];
131
- for (let i = 0; i < r; i++) a.push(n + i * e);
145
+ const e = this.resolvedInterval, n = Math.ceil(this.from / e) * e, l = Math.max(0, Math.min(o, Math.floor((this.to - n) / e) + 1)), a = [];
146
+ for (let i = 0; i < l; i++) a.push(n + i * e);
132
147
  return { ticks: a, tickInterval: e };
133
148
  }
134
149
  getRange() {
@@ -147,31 +162,31 @@ class p {
147
162
  this.resolvedInterval = null;
148
163
  return;
149
164
  }
150
- const t = c(this.dataInterval), e = this.to - this.from, n = e / this.width * this.minLabelSpacing;
151
- let r;
165
+ const t = f(this.dataInterval), e = this.to - this.from, n = e / this.width * this.minLabelSpacing;
166
+ let l;
152
167
  if (this.labelCountHint != null) {
153
- const l = Math.max(1, this.labelCountHint);
154
- r = Math.max(e / l, n);
168
+ const r = Math.max(1, this.labelCountHint);
169
+ l = Math.max(e / r, n);
155
170
  } else
156
- r = n;
171
+ l = n;
157
172
  if (this.lastInterval != null && this.lastWant != null && t.includes(this.lastInterval)) {
158
- const l = r >= this.lastWant * 0.8 && r <= this.lastWant * 1.25, u = this.lastInterval >= n;
159
- if (l && u) {
173
+ const r = l >= this.lastWant * 0.8 && l <= this.lastWant * 1.25, u = this.lastInterval >= n;
174
+ if (r && u) {
160
175
  this.resolvedInterval = this.lastInterval;
161
176
  return;
162
177
  }
163
178
  }
164
179
  let a = t[t.length - 1], i = t.length - 1;
165
- for (let l = 0; l < t.length; l++)
166
- if (t[l] >= r) {
167
- a = t[l], i = l;
180
+ for (let r = 0; r < t.length; r++)
181
+ if (t[r] >= l) {
182
+ a = t[r], i = r;
168
183
  break;
169
184
  }
170
185
  if (this.labelCountHint != null && i > 0 && this.countTicks(a) < this.labelCountHint) {
171
- const l = t[i - 1];
172
- l >= n && (a = l);
186
+ const r = t[i - 1];
187
+ r >= n && (a = r);
173
188
  }
174
- this.resolvedInterval = a, this.lastInterval = a, this.lastWant = r;
189
+ this.resolvedInterval = a, this.lastInterval = a, this.lastWant = l;
175
190
  }
176
191
  countTicks(t) {
177
192
  if (!(t > 0)) return 0;
@@ -182,12 +197,12 @@ class p {
182
197
  function h(s) {
183
198
  return s < 6e4 ? 0 : s < 36e5 ? 1 : s < 864e5 ? 2 : 3;
184
199
  }
185
- function d(s) {
200
+ function p(s) {
186
201
  return typeof s == "number" && Number.isFinite(s) && s >= 2 ? Math.floor(s) : null;
187
202
  }
188
203
  function I(s) {
189
204
  return typeof s == "number" && Number.isFinite(s) && s > 0 ? s : null;
190
205
  }
191
206
  export {
192
- p as XScale
207
+ b as XScale
193
208
  };
@@ -1,19 +1,20 @@
1
1
  import { formatCompact as o } from "../utils/format.js";
2
- import { AxisTickTracker as u } from "./tick-tracker.js";
3
- function m(e) {
2
+ import { crispCenterOffset as u } from "../utils/pixel-grid.js";
3
+ import { AxisTickTracker as m } from "./tick-tracker.js";
4
+ function c(e) {
4
5
  if (!Number.isFinite(e) || e <= 0) return 1;
5
6
  const t = 10 ** Math.floor(Math.log10(e)), i = e / t;
6
7
  return (i <= 1 ? 1 : i <= 2 ? 2 : i <= 5 ? 5 : 10) * t;
7
8
  }
8
- function c(e) {
9
+ function v(e) {
9
10
  if (!Number.isFinite(e) || e <= 0) return 1;
10
11
  const t = 10 ** Math.floor(Math.log10(e)), i = e / t;
11
12
  return (i >= 5 ? 5 : i >= 2 ? 2 : 1) * t;
12
13
  }
13
- const r = 50, v = 50;
14
- class M {
14
+ const l = 50, f = 50;
15
+ class T {
15
16
  /** Shared fade state for Y ticks; see {@link TimeScale.tickTracker}. */
16
- tickTracker = new u();
17
+ tickTracker = new m();
17
18
  min = 0;
18
19
  max = 0;
19
20
  height = 1;
@@ -41,7 +42,7 @@ class M {
41
42
  return this.labelCountHintValue;
42
43
  }
43
44
  get minLabelSpacing() {
44
- return this.minSpacingValue ?? v;
45
+ return this.minSpacingValue ?? f;
45
46
  }
46
47
  /** Recalculate the scale with a new Y range, chart height, and device pixel ratio. */
47
48
  update(t, i, a) {
@@ -49,11 +50,11 @@ class M {
49
50
  }
50
51
  /** Desired label count. Invalid values (NaN, <2, Infinity) clear the hint. */
51
52
  setLabelCount(t) {
52
- this.labelCountHintValue = f(t), this.resetHysteresis(), this.resolveInterval();
53
+ this.labelCountHintValue = x(t), this.resetHysteresis(), this.resolveInterval();
53
54
  }
54
55
  /** Minimum pixel gap between adjacent labels. */
55
56
  setMinSpacing(t) {
56
- this.minSpacingValue = g(t), this.resetHysteresis(), this.resolveInterval();
57
+ this.minSpacingValue = p(t), this.resetHysteresis(), this.resolveInterval();
57
58
  }
58
59
  resetHysteresis() {
59
60
  this.lastInterval = null, this.lastRawInterval = null;
@@ -79,9 +80,26 @@ class M {
79
80
  const i = this.max - this.min;
80
81
  return i === 0 ? this.height / 2 : (1 - (t - this.min) / i) * this.height;
81
82
  }
82
- /** Convert a value to a Y position in physical (bitmap) pixels. */
83
+ /** Convert a value to a Y position in physical (bitmap) pixels, snapped to
84
+ * the pixel grid — for axis-aligned edges that have to stay crisp. */
83
85
  valueToBitmapY(t) {
84
- return Math.round(this.valueToY(t) * this.pixelRatio);
86
+ return Math.round(this.valueToBitmapYExact(t));
87
+ }
88
+ /**
89
+ * Same in physical pixels, off the grid. Anti-aliased geometry reads this
90
+ * one: snapping buys a diagonal stroke no sharpness, and during a slow
91
+ * animation it quantises the motion into whole-pixel jumps.
92
+ */
93
+ valueToBitmapYExact(t) {
94
+ return this.valueToY(t) * this.pixelRatio;
95
+ }
96
+ /**
97
+ * Y position in CSS pixels of the *stroke center* the canvas grid draws for
98
+ * `value`. DOM axis labels position off this instead of raw {@link valueToY}
99
+ * so text and gridline share one pixel grid.
100
+ */
101
+ valueToSnappedY(t) {
102
+ return (this.valueToBitmapY(t) + u(this.pixelRatio)) / this.pixelRatio;
85
103
  }
86
104
  /** Convert a Y position in CSS pixels back to a value. */
87
105
  yToValue(t) {
@@ -93,9 +111,9 @@ class M {
93
111
  * chart height. Pure read — resolution happens in `update()`.
94
112
  */
95
113
  niceTickValues() {
96
- if (this.customTickGenerator) return this.customTickGenerator({ min: this.min, max: this.max }).slice(0, r);
114
+ if (this.customTickGenerator) return this.customTickGenerator({ min: this.min, max: this.max }).slice(0, l);
97
115
  if (this.resolvedInterval == null) return [];
98
- const t = this.resolvedInterval, i = Math.ceil(this.min / t) * t, a = Math.max(0, Math.min(r, Math.floor((this.max - i) / t) + 1)), s = [];
116
+ const t = this.resolvedInterval, i = Math.ceil(this.min / t) * t, a = Math.max(0, Math.min(l, Math.floor((this.max - i) / t) + 1)), s = [];
99
117
  for (let n = 0; n < a; n++) s.push(i + n * t);
100
118
  return s;
101
119
  }
@@ -137,16 +155,16 @@ class M {
137
155
  const t = this.max - this.min, i = t * this.minLabelSpacing / this.height, a = this.labelCountHint != null ? Math.max(1, this.labelCountHint) : Math.max(1, Math.floor(this.height / this.minLabelSpacing));
138
156
  let s = t / a;
139
157
  if (s < i && (s = i), this.lastInterval != null && this.lastRawInterval != null) {
140
- const l = s >= this.lastRawInterval * 0.8 && s <= this.lastRawInterval * 1.25, h = this.lastInterval >= i;
141
- if (l && h) {
158
+ const r = s >= this.lastRawInterval * 0.8 && s <= this.lastRawInterval * 1.25, h = this.lastInterval >= i;
159
+ if (r && h) {
142
160
  this.resolvedInterval = this.lastInterval;
143
161
  return;
144
162
  }
145
163
  }
146
- let n = m(s);
164
+ let n = c(s);
147
165
  if (this.labelCountHint != null && this.countTicks(n) < this.labelCountHint) {
148
- const l = c(s);
149
- l >= i && l > 0 && (n = l);
166
+ const r = v(s);
167
+ r >= i && r > 0 && (n = r);
150
168
  }
151
169
  this.resolvedInterval = n, this.lastInterval = n, this.lastRawInterval = s;
152
170
  }
@@ -156,12 +174,12 @@ class M {
156
174
  return Math.max(0, Math.floor((this.max - i) / t) + 1);
157
175
  }
158
176
  }
159
- function f(e) {
177
+ function x(e) {
160
178
  return typeof e == "number" && Number.isFinite(e) && e >= 2 ? Math.floor(e) : null;
161
179
  }
162
- function g(e) {
180
+ function p(e) {
163
181
  return typeof e == "number" && Number.isFinite(e) && e > 0 ? e : null;
164
182
  }
165
183
  export {
166
- M as YScale
184
+ T as YScale
167
185
  };