@wick-charts/vue 0.6.2 → 0.6.4

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.
Files changed (61) hide show
  1. package/README.md +2 -2
  2. package/dist/BandSeries.vue.js +50 -0
  3. package/dist/BandSeries.vue2.js +4 -0
  4. package/dist/ChartContainer.vue.js +45 -39
  5. package/dist/composables.js +39 -26
  6. package/dist/core/dist/animation/config.js +92 -75
  7. package/dist/core/dist/axis/dom-labels.js +56 -43
  8. package/dist/core/dist/chart/badge-animator.js +30 -22
  9. package/dist/core/dist/chart/edge-indicators.js +22 -21
  10. package/dist/core/dist/chart/last-value.js +33 -26
  11. package/dist/core/dist/chart/options.js +5 -5
  12. package/dist/core/dist/chart/scale-guards.js +26 -0
  13. package/dist/core/dist/chart/y-target.js +46 -19
  14. package/dist/core/dist/chart.js +670 -518
  15. package/dist/core/dist/components/crosshair.js +8 -8
  16. package/dist/core/dist/components/grid.js +26 -17
  17. package/dist/core/dist/components/marker.js +28 -28
  18. package/dist/core/dist/components/reference-line.js +16 -16
  19. package/dist/core/dist/data/decimation.js +58 -39
  20. package/dist/core/dist/interactions/handler.js +3 -2
  21. package/dist/core/dist/interactions/zoom.js +6 -5
  22. package/dist/core/dist/navigator/controller.js +47 -44
  23. package/dist/core/dist/perf/perf-hud.js +3 -3
  24. package/dist/core/dist/scales/log-scale.js +76 -0
  25. package/dist/core/dist/scales/y-scale.js +134 -35
  26. package/dist/core/dist/series/band.js +670 -0
  27. package/dist/core/dist/series/bar-intro.js +11 -11
  28. package/dist/core/dist/series/bar.js +301 -251
  29. package/dist/core/dist/series/base-multi-layer.js +100 -76
  30. package/dist/core/dist/series/candlestick.js +238 -235
  31. package/dist/core/dist/series/heatmap.js +5 -5
  32. package/dist/core/dist/series/line-intro.js +50 -40
  33. package/dist/core/dist/series/line.js +561 -483
  34. package/dist/core/dist/series/painters/builtins.js +4 -4
  35. package/dist/core/dist/series/painters/canvas-path.js +64 -52
  36. package/dist/core/dist/series/register-builtins.js +9 -8
  37. package/dist/core/dist/series/scale-stack.js +25 -0
  38. package/dist/core/dist/snapshots.js +36 -35
  39. package/dist/core/dist/tooltip-position.js +5 -5
  40. package/dist/core/dist/utils/point.js +10 -0
  41. package/dist/index.d.ts +501 -128
  42. package/dist/index.js +195 -189
  43. package/dist/ui/Crosshair.vue.js +14 -14
  44. package/dist/ui/HeatmapTooltip.vue.js +6 -6
  45. package/dist/ui/InfoBar.vue.js +100 -81
  46. package/dist/ui/PieTooltip.vue.js +4 -4
  47. package/dist/ui/TimeAxis.vue.js +23 -22
  48. package/dist/ui/Tooltip.vue.js +140 -111
  49. package/dist/ui/YAxis.vue.js +25 -24
  50. package/dist/ui/YLabel.vue.js +63 -70
  51. package/package.json +2 -2
  52. package/src/BandSeries.vue +87 -0
  53. package/src/ChartContainer.vue +20 -8
  54. package/src/composables.ts +35 -1
  55. package/src/index.ts +13 -0
  56. package/src/ui/Crosshair.vue +2 -2
  57. package/src/ui/InfoBar.vue +20 -1
  58. package/src/ui/TimeAxis.vue +7 -4
  59. package/src/ui/Tooltip.vue +40 -3
  60. package/src/ui/YAxis.vue +5 -3
  61. package/src/ui/YLabel.vue +5 -16
@@ -10,7 +10,7 @@ function N(i) {
10
10
  ownsMonitor: !e
11
11
  };
12
12
  }
13
- const P = 100, z = 600, f = 250, F = 1e3 / 60, H = 1e3 / 120, m = 60, b = 4, j = 34, S = 64, L = 24;
13
+ const P = 100, z = 600, f = 250, F = 1e3 / 60, H = 1e3 / 120, m = 60, b = 4, L = 34, S = 64, j = 24;
14
14
  function I(i) {
15
15
  let e = 0;
16
16
  for (const t of i)
@@ -233,7 +233,7 @@ class D {
233
233
  }), this.element.insertBefore(this.sparkHost, this.element.firstChild)) : (Object.assign(this.sparkHost.style, {
234
234
  position: "relative",
235
235
  inset: "auto",
236
- height: `${j}px`,
236
+ height: `${L}px`,
237
237
  marginTop: "7px",
238
238
  borderRadius: "4px",
239
239
  opacity: "1",
@@ -308,7 +308,7 @@ class D {
308
308
  }
309
309
  /** Keeps the plateau below the FPS number (mini) or the Y tick (strip). */
310
310
  sparkPaddingTop() {
311
- return this.mode === "mini" ? L : 12;
311
+ return this.mode === "mini" ? j : 12;
312
312
  }
313
313
  /** Append one FPS sample to the sparkline chart and roll its window. */
314
314
  appendSpark(e) {
@@ -0,0 +1,76 @@
1
+ import { formatCompact as x } from "../utils/format.js";
2
+ const b = 1e-9, p = /* @__PURE__ */ new Map();
3
+ function K(n = {}) {
4
+ const t = n.base ?? 10;
5
+ if (!Number.isFinite(t) || t <= 0 || t === 1)
6
+ throw new Error(`[wick-charts] logScale: base must be a positive number other than 1, got ${t}.`);
7
+ const o = p.get(t);
8
+ if (o) return o;
9
+ const a = S(t);
10
+ return p.set(t, a), a;
11
+ }
12
+ function S(n) {
13
+ const t = Math.log(n), o = n === 10 ? Math.log10 : n === 2 ? Math.log2 : (r) => Math.log(r) / t, a = n === 10 ? [1, 2, 5] : [1];
14
+ return {
15
+ forward: o,
16
+ inverse: (r) => n ** r,
17
+ isPlottable: (r) => r > 0,
18
+ ticks: (r) => C(r, P({ ...r, forward: o, base: n, mantissas: a })),
19
+ format: (r) => B(x(r))
20
+ };
21
+ }
22
+ function C(n, t) {
23
+ const { linear: o, labelCount: a, height: r, minSpacing: l } = n;
24
+ if (o.length < 2) return t;
25
+ if (t.length < 2) return o;
26
+ const s = a ?? Math.max(2, Math.floor(r / l)), e = Math.abs(t.length - s);
27
+ return Math.abs(o.length - s) + 1 < e ? o : t;
28
+ }
29
+ function P(n) {
30
+ const { min: t, max: o, height: a, minSpacing: r, labelCount: l, forward: s, base: e, mantissas: i } = n, c = s(t), h = s(o), u = a / (h - c), f = k({ lo: c, hi: h, pxPerPower: u, minSpacing: r, labelCount: l, base: e });
31
+ if (f > 1) return w({ lo: c, hi: h, step: f, base: e });
32
+ const g = M({ lo: c, hi: h, base: e, mantissas: [1], forward: s });
33
+ if (i.length === 1) return g;
34
+ const $ = s(i[1]) - s(i[0]);
35
+ if (u * $ < r) return g;
36
+ const m = M({ lo: c, hi: h, base: e, mantissas: i, forward: s });
37
+ return l !== null && m.length > l ? g : m;
38
+ }
39
+ function k(n) {
40
+ const { lo: t, hi: o, pxPerPower: a, minSpacing: r, labelCount: l, base: s } = n;
41
+ let e = 1;
42
+ for (; e < 1e6; ) {
43
+ const i = e * a >= r, c = l === null || w({ lo: t, hi: o, step: e, base: s }).length <= l;
44
+ if (i && c) return e;
45
+ e = v(e);
46
+ }
47
+ return e;
48
+ }
49
+ function v(n) {
50
+ const t = n / 10 ** Math.floor(Math.log10(n));
51
+ return t < 2 ? n * 2 : t < 5 ? n * 2.5 : n * 2;
52
+ }
53
+ function w(n) {
54
+ const { lo: t, hi: o, step: a, base: r } = n, l = Math.ceil(t / a - b), s = Math.floor(o / a + b), e = [];
55
+ for (let i = l; i <= s; i++)
56
+ e.push(d(1, r, i * a));
57
+ return e;
58
+ }
59
+ function M(n) {
60
+ const { lo: t, hi: o, base: a, mantissas: r, forward: l } = n, s = Math.floor(t), e = Math.floor(o + b), i = [];
61
+ for (let c = s; c <= e; c++)
62
+ for (const h of r) {
63
+ const u = d(h, a, c), f = l(u);
64
+ f < t - b || f > o + b || i.push(u);
65
+ }
66
+ return i;
67
+ }
68
+ function d(n, t, o) {
69
+ return o >= 0 ? n * t ** o : n / t ** -o;
70
+ }
71
+ function B(n) {
72
+ return n.replace(/(\.\d*?)0+([KMBT]?)$/, "$1$2").replace(/\.([KMBT]?)$/, "$1");
73
+ }
74
+ export {
75
+ K as logScale
76
+ };
@@ -6,17 +6,25 @@ function c(e) {
6
6
  const t = 10 ** Math.floor(Math.log10(e)), i = e / t;
7
7
  return (i <= 1 ? 1 : i <= 2 ? 2 : i <= 5 ? 5 : 10) * t;
8
8
  }
9
- function v(e) {
9
+ function f(e) {
10
10
  if (!Number.isFinite(e) || e <= 0) return 1;
11
11
  const t = 10 ** Math.floor(Math.log10(e)), i = e / t;
12
12
  return (i >= 5 ? 5 : i >= 2 ? 2 : 1) * t;
13
13
  }
14
- const l = 50, f = 50;
15
- class T {
14
+ const a = 50, v = 50;
15
+ function g(e, t) {
16
+ return Number.isFinite(t) ? e?.isPlottable?.(t) ?? !0 : !1;
17
+ }
18
+ class d {
16
19
  /** Shared fade state for Y ticks; see {@link TimeScale.tickTracker}. */
17
20
  tickTracker = new m();
21
+ /** Non-linear value mapping (`axis.y.scale`); `null` is linear. */
22
+ transform = null;
18
23
  min = 0;
19
24
  max = 0;
25
+ /** Domain bounds in scale space — `transform.forward` of `min` / `max`. */
26
+ lo = 0;
27
+ hi = 0;
20
28
  height = 1;
21
29
  pixelRatio = 1;
22
30
  // Label density knobs — written by <YAxis labelCount=… minLabelSpacing=…>.
@@ -34,6 +42,8 @@ class T {
34
42
  * the boundary doesn't instantly flip back.
35
43
  */
36
44
  lastRawInterval = null;
45
+ /** The transform's own ticks (e.g. powers of ten); `null` while linear ticks are in use. */
46
+ ownTicks = null;
37
47
  /** Custom formatter (driven by `<YAxis format=…>`). */
38
48
  customFormat = null;
39
49
  /** Custom tick generator — bypasses {1,2,5}×10^k resolution in `niceTickValues` when set. */
@@ -42,23 +52,49 @@ class T {
42
52
  return this.labelCountHintValue;
43
53
  }
44
54
  get minLabelSpacing() {
45
- return this.minSpacingValue ?? f;
55
+ return this.minSpacingValue ?? v;
46
56
  }
47
57
  /** Recalculate the scale with a new Y range, chart height, and device pixel ratio. */
48
- update(t, i, a) {
49
- this.min = t.min, this.max = t.max, this.height = i, this.pixelRatio = a, this.resolveInterval();
58
+ update(t, i, s) {
59
+ this.min = t.min, this.max = t.max, this.height = i, this.pixelRatio = s, this.updateDomain(), this.resolveTicks();
60
+ }
61
+ /** Install a non-linear value mapping, or `null` for linear. The range is kept as-is — the chart refits it. */
62
+ setTransform(t) {
63
+ t !== this.transform && (this.transform = t, this.resetHysteresis(), this.updateDomain(), this.resolveTicks());
64
+ }
65
+ getTransform() {
66
+ return this.transform;
67
+ }
68
+ /**
69
+ * Whether `value` has a position on this scale: finite, and inside the
70
+ * transform's domain (positive on log). Renderers skip values that fail —
71
+ * {@link valueToY} still maps them (to the plot floor) so nothing
72
+ * downstream sees NaN.
73
+ */
74
+ isPlottable(t) {
75
+ return g(this.transform, t);
50
76
  }
51
77
  /** Desired label count. Invalid values (NaN, <2, Infinity) clear the hint. */
52
78
  setLabelCount(t) {
53
- this.labelCountHintValue = x(t), this.resetHysteresis(), this.resolveInterval();
79
+ this.labelCountHintValue = p(t), this.resetHysteresis(), this.resolveTicks();
54
80
  }
55
81
  /** Minimum pixel gap between adjacent labels. */
56
82
  setMinSpacing(t) {
57
- this.minSpacingValue = p(t), this.resetHysteresis(), this.resolveInterval();
83
+ this.minSpacingValue = x(t), this.resetHysteresis(), this.resolveTicks();
58
84
  }
59
85
  resetHysteresis() {
60
86
  this.lastInterval = null, this.lastRawInterval = null;
61
87
  }
88
+ /** A transformed domain needs both bounds plottable; anything else collapses to a flat one. */
89
+ updateDomain() {
90
+ const t = this.transform;
91
+ if (t === null) {
92
+ this.lo = this.min, this.hi = this.max;
93
+ return;
94
+ }
95
+ const i = this.isPlottable(this.min) && this.isPlottable(this.max);
96
+ this.lo = i ? t.forward(this.min) : 0, this.hi = i ? t.forward(this.max) : 0;
97
+ }
62
98
  /** Install (or clear) a custom formatter. */
63
99
  setFormat(t) {
64
100
  this.customFormat = t;
@@ -75,10 +111,15 @@ class T {
75
111
  setTickGenerator(t) {
76
112
  this.customTickGenerator = t, this.resetHysteresis();
77
113
  }
78
- /** Convert a value to a Y position in CSS (media) pixels. */
114
+ /**
115
+ * Convert a value to a Y position in CSS (media) pixels. A value the
116
+ * transform can't place (≤ 0 on log) lands on the plot floor.
117
+ */
79
118
  valueToY(t) {
80
- const i = this.max - this.min;
81
- return i === 0 ? this.height / 2 : (1 - (t - this.min) / i) * this.height;
119
+ const i = this.hi - this.lo;
120
+ if (i === 0) return this.height / 2;
121
+ const s = this.transform;
122
+ return s === null ? (1 - (t - this.lo) / i) * this.height : this.isPlottable(t) ? (1 - (s.forward(t) - this.lo) / i) * this.height : this.height;
82
123
  }
83
124
  /** Convert a value to a Y position in physical (bitmap) pixels, snapped to
84
125
  * the pixel grid — for axis-aligned edges that have to stay crisp. */
@@ -101,21 +142,42 @@ class T {
101
142
  valueToSnappedY(t) {
102
143
  return (this.valueToBitmapY(t) + u(this.pixelRatio)) / this.pixelRatio;
103
144
  }
104
- /** Convert a Y position in CSS pixels back to a value. */
145
+ /** Convert a Y position in CSS pixels back to a value — the exact inverse of {@link valueToY}. */
105
146
  yToValue(t) {
106
- const i = this.max - this.min;
107
- return i === 0 ? this.min : this.max - t / this.height * i;
147
+ const i = this.hi - this.lo;
148
+ if (i === 0) return this.min;
149
+ const s = this.hi - t / this.height * i;
150
+ return this.transform === null ? s : this.transform.inverse(s);
151
+ }
152
+ /** The value bars and stacks grow from: `0`, or the plot floor's value on a scale with no place for 0 (log). */
153
+ baselineValue() {
154
+ return this.isPlottable(0) ? 0 : this.min;
155
+ }
156
+ /**
157
+ * The value `t` of the way from `from` to `to` in pixels — a plain lerp on a
158
+ * linear axis, a geometric one on log. `t` past `[0, 1]` overshoots along the
159
+ * axis. An endpoint the transform can't place returns `to`, so a gap stays a gap.
160
+ */
161
+ lerpValue(t, i, s) {
162
+ const n = this.transform;
163
+ if (n === null) return t + (i - t) * s;
164
+ if (!this.isPlottable(t) || !this.isPlottable(i)) return i;
165
+ const r = n.forward(t);
166
+ return n.inverse(r + (n.forward(i) - r) * s);
108
167
  }
109
168
  /**
110
169
  * Generate evenly spaced "nice" tick values for the current range and
111
170
  * chart height. Pure read — resolution happens in `update()`.
112
171
  */
113
172
  niceTickValues() {
114
- if (this.customTickGenerator) return this.customTickGenerator({ min: this.min, max: this.max }).slice(0, l);
173
+ return this.customTickGenerator ? this.customTickGenerator({ min: this.min, max: this.max }).slice(0, a) : this.ownTicks !== null ? this.ownTicks.slice(0, a) : this.linearTickValues();
174
+ }
175
+ /** {1,2,5}×10^k ticks at the resolved interval, limited to what the transform can place. */
176
+ linearTickValues() {
115
177
  if (this.resolvedInterval == null) return [];
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 = [];
117
- for (let n = 0; n < a; n++) s.push(i + n * t);
118
- return s;
178
+ const t = this.resolvedInterval, i = Math.ceil(this.min / t) * t, s = Math.max(0, Math.min(a, Math.floor((this.max - i) / t) + 1)), n = [];
179
+ for (let r = 0; r < s; r++) n.push(i + r * t);
180
+ return this.transform === null ? n : n.filter((r) => this.isPlottable(r));
119
181
  }
120
182
  getRange() {
121
183
  return { min: this.min, max: this.max };
@@ -132,13 +194,49 @@ class T {
132
194
  */
133
195
  formatY(t) {
134
196
  if (this.customFormat) return this.customFormat(t);
197
+ const i = this.ownTicks !== null ? this.transform?.format : void 0;
198
+ if (i) return i(t);
135
199
  if (this.max - this.min >= 1e6) return o(t);
136
- const i = this.resolvedInterval, a = i != null && i > 0 ? Math.max(0, -Math.floor(Math.log10(i))) : 1;
137
- return t.toFixed(a);
200
+ const s = this.resolvedInterval, n = s != null && s > 0 ? Math.max(0, -Math.floor(Math.log10(s))) : 1;
201
+ return t.toFixed(n);
202
+ }
203
+ resolveTicks() {
204
+ this.ownTicks = null;
205
+ const t = this.transform;
206
+ if (t === null) {
207
+ this.resolveInterval((this.max - this.min) * this.minLabelSpacing / this.height);
208
+ return;
209
+ }
210
+ const i = this.hi - this.lo;
211
+ if (!(i > 0) || this.height <= 0) {
212
+ this.resolvedInterval = null;
213
+ return;
214
+ }
215
+ if (this.resolveInterval(this.transformedGapFloor(t, i)), !t.ticks) return;
216
+ const s = this.linearTickValues(), n = t.ticks({
217
+ min: this.min,
218
+ max: this.max,
219
+ height: this.height,
220
+ minSpacing: this.minLabelSpacing,
221
+ labelCount: this.labelCountHint,
222
+ linear: s
223
+ });
224
+ this.ownTicks = n === s ? null : n;
225
+ }
226
+ /**
227
+ * Smallest linear interval whose ticks stay `minLabelSpacing` apart on the
228
+ * transformed axis. A monotonic mapping squeezes its gaps hardest at one
229
+ * end — the top for log (concave), the bottom for a convex one — so
230
+ * checking both ends covers it.
231
+ */
232
+ transformedGapFloor(t, i) {
233
+ const s = this.minLabelSpacing / this.height * i, n = this.max - t.inverse(Math.max(this.lo, this.hi - s)), r = t.inverse(Math.min(this.hi, this.lo + s)) - this.min;
234
+ return Math.max(n, r);
138
235
  }
139
236
  /**
140
237
  * Resolve the interval to a {1,2,5}×10^k tick size that satisfies the pixel
141
- * floor and (optionally) targets `labelCountHint` labels.
238
+ * floor and (optionally) targets `labelCountHint` labels. `gapFloorValue` is
239
+ * the smallest interval that clears `minLabelSpacing` anywhere on the axis.
142
240
  *
143
241
  * Hysteresis: the band [0.8×, 1.25×] is anchored to **rawInterval at last
144
242
  * snap**, not to `lastInterval`. After an escalation (e.g. 100 → 200),
@@ -147,26 +245,26 @@ class T {
147
245
  * *not* de-escalate. This prevents the 2-tier flicker users see when new
148
246
  * candles nudge the Y range across a raw-interval threshold.
149
247
  */
150
- resolveInterval() {
248
+ resolveInterval(t) {
151
249
  if (this.max <= this.min || this.height <= 0) {
152
250
  this.resolvedInterval = null;
153
251
  return;
154
252
  }
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));
156
- let s = t / a;
157
- if (s < i && (s = i), this.lastInterval != null && this.lastRawInterval != null) {
158
- const r = s >= this.lastRawInterval * 0.8 && s <= this.lastRawInterval * 1.25, h = this.lastInterval >= i;
159
- if (r && h) {
253
+ const i = this.max - this.min, s = this.labelCountHint != null ? Math.max(1, this.labelCountHint) : Math.max(1, Math.floor(this.height / this.minLabelSpacing));
254
+ let n = i / s;
255
+ if (n < t && (n = t), this.lastInterval != null && this.lastRawInterval != null) {
256
+ const l = n >= this.lastRawInterval * 0.8 && n <= this.lastRawInterval * 1.25, h = this.lastInterval >= t;
257
+ if (l && h) {
160
258
  this.resolvedInterval = this.lastInterval;
161
259
  return;
162
260
  }
163
261
  }
164
- let n = c(s);
165
- if (this.labelCountHint != null && this.countTicks(n) < this.labelCountHint) {
166
- const r = v(s);
167
- r >= i && r > 0 && (n = r);
262
+ let r = c(n);
263
+ if (this.labelCountHint != null && this.countTicks(r) < this.labelCountHint) {
264
+ const l = f(n);
265
+ l >= t && l > 0 && (r = l);
168
266
  }
169
- this.resolvedInterval = n, this.lastInterval = n, this.lastRawInterval = s;
267
+ this.resolvedInterval = r, this.lastInterval = r, this.lastRawInterval = n;
170
268
  }
171
269
  countTicks(t) {
172
270
  if (!(t > 0)) return 0;
@@ -174,12 +272,13 @@ class T {
174
272
  return Math.max(0, Math.floor((this.max - i) / t) + 1);
175
273
  }
176
274
  }
177
- function x(e) {
275
+ function p(e) {
178
276
  return typeof e == "number" && Number.isFinite(e) && e >= 2 ? Math.floor(e) : null;
179
277
  }
180
- function p(e) {
278
+ function x(e) {
181
279
  return typeof e == "number" && Number.isFinite(e) && e > 0 ? e : null;
182
280
  }
183
281
  export {
184
- T as YScale
282
+ d as YScale,
283
+ g as isPlottableOn
185
284
  };