@akcelik/strct 0.28.0 → 0.29.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.
@@ -8330,6 +8330,64 @@ function smoothPath(p) {
8330
8330
  function pathFor(pts, curve) {
8331
8331
  return curve === 'linear' ? linearPath(pts) : curve === 'step' ? stepPath(pts) : smoothPath(pts);
8332
8332
  }
8333
+ /** A real value — `null` and `NaN` both mark a data gap. */
8334
+ function isVal(v) {
8335
+ return v != null && !Number.isNaN(v);
8336
+ }
8337
+ /** Split a nullable point array into contiguous non-gap segments. */
8338
+ function segs(pts) {
8339
+ const out = [];
8340
+ let cur = [];
8341
+ for (const p of pts) {
8342
+ if (p)
8343
+ cur.push(p);
8344
+ else if (cur.length) {
8345
+ out.push(cur);
8346
+ cur = [];
8347
+ }
8348
+ }
8349
+ if (cur.length)
8350
+ out.push(cur);
8351
+ return out;
8352
+ }
8353
+ /** Line path that breaks at gaps (one sub-path per segment). */
8354
+ function pathForSegs(pts, curve) {
8355
+ return segs(pts)
8356
+ .map((s) => pathFor(s, curve))
8357
+ .join('');
8358
+ }
8359
+ /** Area path per segment — the fill drops to the baseline at gap edges, never across. */
8360
+ function areaForSegs(pts, curve, base) {
8361
+ return segs(pts)
8362
+ .map((s) => `${pathFor(s, curve)}L${round$1(s[s.length - 1].x)},${round$1(base)}L${round$1(s[0].x)},${round$1(base)}Z`)
8363
+ .join('');
8364
+ }
8365
+ /** Closed band between per-point upper and lower bounds (gap-aware). */
8366
+ function bandPath(upper, lower, curve) {
8367
+ let out = '';
8368
+ let u = [];
8369
+ let l = [];
8370
+ const flush = () => {
8371
+ if (u.length > 1) {
8372
+ const back = pathFor([...l].reverse(), curve).replace(/^M/, 'L');
8373
+ out += pathFor(u, curve) + back + 'Z';
8374
+ }
8375
+ u = [];
8376
+ l = [];
8377
+ };
8378
+ for (let i = 0; i < upper.length; i++) {
8379
+ const up = upper[i];
8380
+ const lo = lower[i];
8381
+ if (up && lo) {
8382
+ u.push(up);
8383
+ l.push(lo);
8384
+ }
8385
+ else
8386
+ flush();
8387
+ }
8388
+ flush();
8389
+ return out;
8390
+ }
8333
8391
  /**
8334
8392
  * Single- or multi-series chart (line / area / bar). Dependency-free SVG,
8335
8393
  * token-coloured.
@@ -8346,7 +8404,7 @@ function pathFor(pts, curve) {
8346
8404
  * <strct-chart [series]="[{data:inArr,label:'In'},{data:outArr,label:'Out'}]" legend />
8347
8405
  */
8348
8406
  class StrctChart {
8349
- /** Single-series data (or use `series`). */
8407
+ /** Single-series data (or use `series`). `null` / `NaN` marks a gap — the line breaks. */
8350
8408
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
8351
8409
  /** Multiple series; when set it takes precedence over `data`. */
8352
8410
  series = input(null, ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
@@ -8404,6 +8462,28 @@ class StrctChart {
8404
8462
  * `[valueFormat]="v => v.toFixed(3) + ' %'"`. When null, the raw value is shown.
8405
8463
  */
8406
8464
  valueFormat = input(null, ...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
8465
+ /** Vertical event / annotation markers ("alarm raised", "deploy", …). */
8466
+ annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
8467
+ /**
8468
+ * Drive the crosshair externally (e.g. mirror a sibling chart's hover for a
8469
+ * vCenter-style synced dashboard). A local pointer wins while over this chart.
8470
+ */
8471
+ activeIndex = input(null, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
8472
+ /** Drag-select a range; the selection is emitted through `brushChange`. */
8473
+ brush = input(false, { ...(ngDevMode ? { debugName: "brush" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
8474
+ /**
8475
+ * Drag-select **zooms into** the selected range (implies `brush`).
8476
+ * Double-click, Escape or the reset chip zooms back out.
8477
+ */
8478
+ zoom = input(false, { ...(ngDevMode ? { debugName: "zoom" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
8479
+ /** Tooltip text for a gap (null) point. */
8480
+ gapText = input('no data', ...(ngDevMode ? [{ debugName: "gapText" }] : /* istanbul ignore next */ []));
8481
+ /** Accessible label of the reset-zoom chip (localizable). */
8482
+ resetLabel = input('Reset zoom', ...(ngDevMode ? [{ debugName: "resetLabel" }] : /* istanbul ignore next */ []));
8483
+ /** Emits the hovered point index (or null on leave) — wire cross-chart sync with it. */
8484
+ hoverIndex = output();
8485
+ /** Emits the brushed [startIndex, endIndex] (inclusive), or null when cleared. */
8486
+ brushChange = output();
8407
8487
  pad = PAD;
8408
8488
  color = computed(() => COLOR$1[this.status()], ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
8409
8489
  showArea = computed(() => this.area() || this.type() === 'area', ...(ngDevMode ? [{ debugName: "showArea" }] : /* istanbul ignore next */ []));
@@ -8420,8 +8500,22 @@ class StrctChart {
8420
8500
  firstData = true;
8421
8501
  /** Reactive OS motion preference (tracks live changes, not just first load). */
8422
8502
  reduceMotion = signal(typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches, ...(ngDevMode ? [{ debugName: "reduceMotion" }] : /* istanbul ignore next */ []));
8423
- // Hover state.
8503
+ // Hover state. `hoverIdx` is the local pointer/keyboard index; `dispIdx` is
8504
+ // what actually renders — local wins, else the externally driven activeIndex.
8424
8505
  hoverIdx = signal(null, ...(ngDevMode ? [{ debugName: "hoverIdx" }] : /* istanbul ignore next */ []));
8506
+ dispIdx = computed(() => {
8507
+ const local = this.hoverIdx();
8508
+ const i = local ?? this.activeIndex();
8509
+ if (i == null)
8510
+ return null;
8511
+ const [s, e] = this.domain();
8512
+ return i >= s && i <= e ? i : null;
8513
+ }, ...(ngDevMode ? [{ debugName: "dispIdx" }] : /* istanbul ignore next */ []));
8514
+ // Zoom / brush state (indices are always in the full-data domain).
8515
+ viewRange = signal(null, ...(ngDevMode ? [{ debugName: "viewRange" }] : /* istanbul ignore next */ []));
8516
+ brushDrag = signal(null, ...(ngDevMode ? [{ debugName: "brushDrag" }] : /* istanbul ignore next */ []));
8517
+ brushEnabled = computed(() => this.brush() || this.zoom(), ...(ngDevMode ? [{ debugName: "brushEnabled" }] : /* istanbul ignore next */ []));
8518
+ zoomed = computed(() => this.viewRange() !== null, ...(ngDevMode ? [{ debugName: "zoomed" }] : /* istanbul ignore next */ []));
8425
8519
  constructor() {
8426
8520
  const destroyRef = inject(DestroyRef);
8427
8521
  if (typeof matchMedia !== 'undefined') {
@@ -8481,6 +8575,16 @@ class StrctChart {
8481
8575
  isEmpty = computed(() => this.allData().every((d) => d.length === 0), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
8482
8576
  /** Number of x slots. */
8483
8577
  nx = computed(() => Math.max(1, ...this.allData().map((d) => d.length)), ...(ngDevMode ? [{ debugName: "nx" }] : /* istanbul ignore next */ []));
8578
+ /** Visible index window: the brush-zoom range, or the full data. */
8579
+ domain = computed(() => {
8580
+ const n = this.nx();
8581
+ const vr = this.viewRange();
8582
+ if (!vr)
8583
+ return [0, n - 1];
8584
+ const s = Math.max(0, Math.min(vr[0], n - 1));
8585
+ const e = Math.max(s, Math.min(vr[1], n - 1));
8586
+ return e > s ? [s, e] : [0, n - 1];
8587
+ }, ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
8484
8588
  /** Left padding: wider when the y-axis labels are shown. */
8485
8589
  pl = computed(() => (this.yAxis() ? Y_AXIS_GUTTER : PAD.l), ...(ngDevMode ? [{ debugName: "pl" }] : /* istanbul ignore next */ []));
8486
8590
  chartW() {
@@ -8489,16 +8593,46 @@ class StrctChart {
8489
8593
  chartH() {
8490
8594
  return this.height() - PAD.t - PAD.b;
8491
8595
  }
8492
- /** Horizontal gap between two data points, in px. */
8596
+ /** Horizontal gap between two data points, in px (of the visible window). */
8493
8597
  stepX() {
8494
- const n = this.nx();
8495
- return n > 1 ? this.chartW() / (n - 1) : 0;
8598
+ const [s, e] = this.domain();
8599
+ return e > s ? this.chartW() / (e - s) : 0;
8496
8600
  }
8601
+ /** Real values inside the visible window (multi-aware; bands included). */
8602
+ visibleValues = computed(() => {
8603
+ const [s, e] = this.domain();
8604
+ const out = [];
8605
+ const push = (v) => {
8606
+ if (isVal(v))
8607
+ out.push(v);
8608
+ };
8609
+ const ser = this.seriesResolved();
8610
+ if (ser) {
8611
+ const N = this.nx();
8612
+ for (const x of ser) {
8613
+ const data = x.data ?? [];
8614
+ const offset = N - data.length;
8615
+ for (let gi = Math.max(s, offset); gi <= e; gi++) {
8616
+ const li = gi - offset;
8617
+ if (li < 0 || li >= data.length)
8618
+ continue;
8619
+ push(data[li]);
8620
+ push(x.upper?.[li]);
8621
+ }
8622
+ }
8623
+ }
8624
+ else {
8625
+ const d = this.data();
8626
+ for (let i = s; i <= Math.min(e, d.length - 1); i++)
8627
+ push(d[i]);
8628
+ }
8629
+ return out;
8630
+ }, ...(ngDevMode ? [{ debugName: "visibleValues" }] : /* istanbul ignore next */ []));
8497
8631
  yMax = computed(() => {
8498
8632
  const explicit = this.max();
8499
8633
  if (explicit != null)
8500
8634
  return explicit || 1;
8501
- const m = Math.max(0, ...this.allData().flat());
8635
+ const m = Math.max(0, ...this.visibleValues());
8502
8636
  return m === 0 ? 1 : m * 1.1;
8503
8637
  }, ...(ngDevMode ? [{ debugName: "yMax" }] : /* istanbul ignore next */ []));
8504
8638
  yMin = computed(() => this.min() ?? 0, ...(ngDevMode ? [{ debugName: "yMin" }] : /* istanbul ignore next */ []));
@@ -8509,50 +8643,46 @@ class StrctChart {
8509
8643
  const c = Math.max(lo, Math.min(hi, v));
8510
8644
  return PAD.t + (1 - (c - lo) / range) * this.chartH();
8511
8645
  }
8512
- // ── Single-series geometry (unchanged output) ─────────────────
8646
+ // ── Single-series geometry (gap-aware; null keeps its x-slot) ──
8513
8647
  points = computed(() => {
8514
8648
  const d = this.data();
8515
- const step = this.stepX();
8516
- const pl = this.pl();
8517
- return d.map((v, i) => ({ x: pl + i * step, y: this.yOf(v) }));
8649
+ return d.map((v, i) => (isVal(v) ? { x: this.xOf(i), y: this.yOf(v) } : null));
8518
8650
  }, ...(ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []));
8651
+ /** Non-gap points only (dot rendering). */
8652
+ dotPts = computed(() => this.points().filter((p) => p !== null), ...(ngDevMode ? [{ debugName: "dotPts" }] : /* istanbul ignore next */ []));
8519
8653
  head = computed(() => {
8520
8654
  const p = this.points();
8521
- return p.length ? p[p.length - 1] : null;
8655
+ for (let i = p.length - 1; i >= 0; i--)
8656
+ if (p[i])
8657
+ return p[i];
8658
+ return null;
8522
8659
  }, ...(ngDevMode ? [{ debugName: "head" }] : /* istanbul ignore next */ []));
8523
8660
  plotPoints = computed(() => {
8524
8661
  const p = this.points();
8525
- if (!this.live() || p.length < 2)
8662
+ if (!this.live() || p.length < 2 || !p[0])
8526
8663
  return p;
8527
8664
  return [{ x: p[0].x - this.stepX(), y: p[0].y }, ...p];
8528
8665
  }, ...(ngDevMode ? [{ debugName: "plotPoints" }] : /* istanbul ignore next */ []));
8529
- linePath = computed(() => pathFor(this.plotPoints(), this.curve()), ...(ngDevMode ? [{ debugName: "linePath" }] : /* istanbul ignore next */ []));
8530
- areaPath = computed(() => {
8531
- const line = this.linePath();
8532
- const p = this.plotPoints();
8533
- if (!line || !p.length)
8534
- return '';
8535
- const base = this.height() - PAD.b;
8536
- return `${line}L${round$1(p[p.length - 1].x)},${round$1(base)}L${round$1(p[0].x)},${round$1(base)}Z`;
8537
- }, ...(ngDevMode ? [{ debugName: "areaPath" }] : /* istanbul ignore next */ []));
8666
+ linePath = computed(() => pathForSegs(this.plotPoints(), this.curve()), ...(ngDevMode ? [{ debugName: "linePath" }] : /* istanbul ignore next */ []));
8667
+ areaPath = computed(() => areaForSegs(this.plotPoints(), this.curve(), this.height() - PAD.b), ...(ngDevMode ? [{ debugName: "areaPath" }] : /* istanbul ignore next */ []));
8538
8668
  // ── Multi-series geometry ─────────────────────────────────────
8539
8669
  multiSeries = computed(() => {
8540
8670
  const s = this.seriesResolved();
8541
8671
  if (!s)
8542
8672
  return [];
8543
8673
  const N = this.nx();
8544
- const step = this.stepX();
8545
- const pl = this.pl();
8546
8674
  const base = this.height() - PAD.b;
8547
8675
  return s.map((x) => {
8548
8676
  const data = x.data ?? [];
8549
8677
  const offset = N - data.length; // right-align shorter series
8550
- const pts = data.map((v, i) => ({ x: pl + (offset + i) * step, y: this.yOf(v) }));
8678
+ const ptOf = (v, i) => isVal(v) ? { x: this.xOf(offset + i), y: this.yOf(v) } : null;
8679
+ const pts = data.map((v, i) => ptOf(v, i));
8551
8680
  const curve = x.curve ?? this.curve();
8552
- const path = pathFor(pts, curve);
8681
+ const path = pathForSegs(pts, curve);
8553
8682
  const area = x.area ?? false;
8554
- const areaPath = area && pts.length
8555
- ? `${path}L${round$1(pts[pts.length - 1].x)},${round$1(base)}L${round$1(pts[0].x)},${round$1(base)}Z`
8683
+ const areaPath = area ? areaForSegs(pts, curve, base) : '';
8684
+ const band = x.lower && x.upper
8685
+ ? bandPath(x.upper.map((v, i) => ptOf(v, i)), x.lower.map((v, i) => ptOf(v, i)), curve)
8556
8686
  : '';
8557
8687
  return {
8558
8688
  color: COLOR$1[x.status ?? this.status()],
@@ -8562,26 +8692,42 @@ class StrctChart {
8562
8692
  pts,
8563
8693
  path,
8564
8694
  areaPath,
8695
+ bandPath: band,
8565
8696
  offset,
8566
8697
  data,
8698
+ lower: x.lower,
8699
+ upper: x.upper,
8567
8700
  };
8568
8701
  });
8569
8702
  }, ...(ngDevMode ? [{ debugName: "multiSeries" }] : /* istanbul ignore next */ []));
8570
8703
  legendItems = computed(() => this.multiSeries()
8571
8704
  .filter((s) => s.label)
8572
8705
  .map((s) => ({ label: s.label, color: s.color, dash: s.dash })), ...(ngDevMode ? [{ debugName: "legendItems" }] : /* istanbul ignore next */ []));
8573
- // ── Bars (single-series only) ─────────────────────────────────
8706
+ // ── Bars (single-series only; gaps render no bar, keep their slot) ──
8574
8707
  bars = computed(() => {
8575
8708
  const d = this.data();
8709
+ const [s, e] = this.domain();
8576
8710
  const chartW = this.chartW();
8577
8711
  const base = this.height() - PAD.b;
8578
- const slot = d.length ? chartW / d.length : chartW;
8712
+ const last = Math.min(e, d.length - 1);
8713
+ const count = Math.max(1, last - s + 1);
8714
+ const slot = chartW / count;
8579
8715
  const w = slot * 0.6;
8580
8716
  const pl = this.pl();
8581
- return d.map((v, i) => {
8717
+ const out = [];
8718
+ for (let i = s; i <= last; i++) {
8719
+ const v = d[i];
8720
+ if (!isVal(v))
8721
+ continue;
8582
8722
  const h = ((Math.max(0, v) - this.yMin()) / (this.yMax() - this.yMin() || 1)) * this.chartH();
8583
- return { x: pl + i * slot + (slot - w) / 2, y: base - Math.max(0, h), w, h: Math.max(0, h) };
8584
- });
8723
+ out.push({
8724
+ x: pl + (i - s) * slot + (slot - w) / 2,
8725
+ y: base - Math.max(0, h),
8726
+ w,
8727
+ h: Math.max(0, h),
8728
+ });
8729
+ }
8730
+ return out;
8585
8731
  }, ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
8586
8732
  // ── Axes ───────────────────────────────────────────────────────
8587
8733
  gridY = computed(() => {
@@ -8616,34 +8762,49 @@ class StrctChart {
8616
8762
  const ls = this.labels();
8617
8763
  const fmt = this.xFormat();
8618
8764
  const xt = this.xTicks();
8619
- let items;
8620
- if (xt && xt > 1 && ls.length > xt) {
8621
- const stepI = (ls.length - 1) / (xt - 1);
8622
- const idxs = Array.from(new Set(Array.from({ length: xt }, (_, k) => Math.round(k * stepI))));
8623
- items = idxs.map((i) => ({ l: ls[i], i }));
8765
+ const [s, e] = this.domain();
8766
+ const last = Math.min(e, ls.length - 1);
8767
+ let idxs;
8768
+ if (xt && xt > 1 && last - s + 1 > xt) {
8769
+ const stepI = (last - s) / (xt - 1);
8770
+ idxs = Array.from(new Set(Array.from({ length: xt }, (_, k) => s + Math.round(k * stepI))));
8624
8771
  }
8625
8772
  else {
8626
- items = ls.map((l, i) => ({ l, i }));
8627
- }
8628
- return items.map(({ l, i }) => ({ text: fmt ? fmt(l, i) : l, i }));
8773
+ idxs = Array.from({ length: Math.max(0, last - s + 1) }, (_, k) => s + k);
8774
+ }
8775
+ return idxs
8776
+ .filter((i) => i >= 0 && i < ls.length)
8777
+ .map((i) => ({
8778
+ text: fmt ? fmt(ls[i], i) : ls[i],
8779
+ i,
8780
+ }));
8629
8781
  }, ...(ngDevMode ? [{ debugName: "displayLabels" }] : /* istanbul ignore next */ []));
8630
- // ── Hover ──────────────────────────────────────────────────────
8782
+ // ── Hover (driven by dispIdx: local pointer, else external activeIndex) ──
8631
8783
  hoverX = computed(() => {
8632
- const i = this.hoverIdx();
8784
+ const i = this.dispIdx();
8633
8785
  if (i == null)
8634
8786
  return null;
8635
- return this.pl() + i * this.stepX();
8787
+ return this.xOf(i);
8636
8788
  }, ...(ngDevMode ? [{ debugName: "hoverX" }] : /* istanbul ignore next */ []));
8637
8789
  hoverPt = computed(() => {
8638
- const i = this.hoverIdx();
8790
+ const i = this.dispIdx();
8639
8791
  const p = this.points();
8640
8792
  return i != null && i >= 0 && i < p.length ? p[i] : null;
8641
8793
  }, ...(ngDevMode ? [{ debugName: "hoverPt" }] : /* istanbul ignore next */ []));
8642
8794
  hoverValue = computed(() => {
8643
- const i = this.hoverIdx();
8795
+ const i = this.dispIdx();
8644
8796
  const d = this.data();
8645
- return i != null && i >= 0 && i < d.length ? d[i] : '';
8797
+ const v = i != null && i >= 0 && i < d.length ? d[i] : null;
8798
+ return isVal(v) ? v : '';
8646
8799
  }, ...(ngDevMode ? [{ debugName: "hoverValue" }] : /* istanbul ignore next */ []));
8800
+ /** The hovered slot exists but holds a gap (single-series). */
8801
+ hoverGap = computed(() => {
8802
+ const i = this.dispIdx();
8803
+ if (i == null || this.isMulti())
8804
+ return false;
8805
+ const d = this.data();
8806
+ return i >= 0 && i < d.length && !isVal(d[i]);
8807
+ }, ...(ngDevMode ? [{ debugName: "hoverGap" }] : /* istanbul ignore next */ []));
8647
8808
  /** hoverValue run through valueFormat (unit / precision), else the raw value. */
8648
8809
  hoverValueText = computed(() => {
8649
8810
  const v = this.hoverValue();
@@ -8658,20 +8819,24 @@ class StrctChart {
8658
8819
  return f ? f(this.absDelta()) : String(this.absDelta());
8659
8820
  }, ...(ngDevMode ? [{ debugName: "absDeltaText" }] : /* istanbul ignore next */ []));
8660
8821
  hoverLabel = computed(() => {
8661
- const i = this.hoverIdx();
8822
+ const i = this.dispIdx();
8662
8823
  const l = this.labels();
8663
8824
  return i != null && i >= 0 && i < l.length ? l[i] : '';
8664
8825
  }, ...(ngDevMode ? [{ debugName: "hoverLabel" }] : /* istanbul ignore next */ []));
8665
- /** Change from the previous point (single-series; null at the first point). */
8826
+ /** Change from the previous point (single-series; null at the first point or across a gap). */
8666
8827
  hoverDelta = computed(() => {
8667
- const i = this.hoverIdx();
8828
+ const i = this.dispIdx();
8668
8829
  const d = this.data();
8669
- return i != null && i > 0 && i < d.length ? d[i] - d[i - 1] : null;
8830
+ if (i == null || i <= 0 || i >= d.length)
8831
+ return null;
8832
+ const cur = d[i];
8833
+ const prev = d[i - 1];
8834
+ return isVal(cur) && isVal(prev) ? cur - prev : null;
8670
8835
  }, ...(ngDevMode ? [{ debugName: "hoverDelta" }] : /* istanbul ignore next */ []));
8671
8836
  absDelta = computed(() => Math.abs(this.hoverDelta() ?? 0), ...(ngDevMode ? [{ debugName: "absDelta" }] : /* istanbul ignore next */ []));
8672
8837
  /** Second tooltip line: "Xs ago" while live, else the x label. */
8673
8838
  hoverMeta = computed(() => {
8674
- const i = this.hoverIdx();
8839
+ const i = this.dispIdx();
8675
8840
  if (i == null)
8676
8841
  return '';
8677
8842
  if (this.live()) {
@@ -8684,59 +8849,102 @@ class StrctChart {
8684
8849
  const l = this.labels();
8685
8850
  return i >= 0 && i < l.length ? l[i] : '';
8686
8851
  }, ...(ngDevMode ? [{ debugName: "hoverMeta" }] : /* istanbul ignore next */ []));
8687
- /** Per-series value at the hovered index (multi-series tooltip). */
8852
+ /** Per-series value at the hovered index (multi-series tooltip; bands as `avg (min–max)`). */
8688
8853
  hoverRows = computed(() => {
8689
- const i = this.hoverIdx();
8854
+ const i = this.dispIdx();
8690
8855
  const s = this.multiSeries();
8691
8856
  if (i == null || !s.length)
8692
8857
  return [];
8693
8858
  const f = this.valueFormat();
8859
+ const fmt = (v) => (f ? f(v) : String(v));
8694
8860
  return s
8695
8861
  .map((x) => {
8696
8862
  const li = i - x.offset;
8697
- const v = li >= 0 && li < x.data.length ? x.data[li] : null;
8863
+ const raw = li >= 0 && li < x.data.length ? x.data[li] : null;
8864
+ const v = isVal(raw) ? raw : null;
8865
+ const lo = x.lower?.[li];
8866
+ const hi = x.upper?.[li];
8867
+ const band = isVal(lo) && isVal(hi) ? ` (${fmt(lo)}–${fmt(hi)})` : '';
8698
8868
  return {
8699
8869
  label: x.label,
8700
8870
  color: x.color,
8701
8871
  value: v,
8702
- text: v == null ? '' : f ? f(v) : String(v),
8872
+ text: v == null ? '' : fmt(v) + band,
8703
8873
  };
8704
8874
  })
8705
8875
  .filter((r) => r.value !== null);
8706
8876
  }, ...(ngDevMode ? [{ debugName: "hoverRows" }] : /* istanbul ignore next */ []));
8707
8877
  /** Per-series hover dots (multi-series). */
8708
8878
  hoverDots = computed(() => {
8709
- const i = this.hoverIdx();
8879
+ const i = this.dispIdx();
8710
8880
  const s = this.multiSeries();
8711
8881
  if (i == null || !s.length)
8712
8882
  return [];
8713
8883
  return s
8714
8884
  .map((x) => {
8715
8885
  const li = i - x.offset;
8716
- return li >= 0 && li < x.pts.length
8717
- ? { x: x.pts[li].x, y: x.pts[li].y, color: x.color }
8718
- : null;
8886
+ const p = li >= 0 && li < x.pts.length ? x.pts[li] : null;
8887
+ return p ? { x: p.x, y: p.y, color: x.color } : null;
8719
8888
  })
8720
8889
  .filter((d) => d !== null);
8721
8890
  }, ...(ngDevMode ? [{ debugName: "hoverDots" }] : /* istanbul ignore next */ []));
8891
+ // ── Annotations ────────────────────────────────────────────────
8892
+ annotationLines = computed(() => {
8893
+ const [s, e] = this.domain();
8894
+ return this.annotations()
8895
+ .filter((a) => a.index >= s && a.index <= e)
8896
+ .map((a) => ({
8897
+ x: this.xOf(a.index),
8898
+ color: COLOR$1[a.status ?? 'accent'],
8899
+ dashed: a.dashed ?? true,
8900
+ label: a.label ?? '',
8901
+ index: a.index,
8902
+ }));
8903
+ }, ...(ngDevMode ? [{ debugName: "annotationLines" }] : /* istanbul ignore next */ []));
8904
+ /** The annotation sitting exactly under the crosshair, if any. */
8905
+ annAt = computed(() => {
8906
+ const i = this.dispIdx();
8907
+ if (i == null)
8908
+ return null;
8909
+ return this.annotationLines().find((a) => a.index === i && a.label) ?? null;
8910
+ }, ...(ngDevMode ? [{ debugName: "annAt" }] : /* istanbul ignore next */ []));
8911
+ // ── Brush / zoom ───────────────────────────────────────────────
8912
+ brushRect = computed(() => {
8913
+ const d = this.brushDrag();
8914
+ if (!d)
8915
+ return null;
8916
+ const x1 = this.xOf(Math.min(d.a, d.b));
8917
+ const x2 = this.xOf(Math.max(d.a, d.b));
8918
+ return { x: round$1(x1), w: round$1(Math.max(1, x2 - x1)) };
8919
+ }, ...(ngDevMode ? [{ debugName: "brushRect" }] : /* istanbul ignore next */ []));
8920
+ /** Zoom back out to the full data window (also emits `brushChange` null). */
8921
+ resetZoom() {
8922
+ this.brushDrag.set(null);
8923
+ if (this.viewRange() !== null) {
8924
+ this.viewRange.set(null);
8925
+ this.brushChange.emit(null);
8926
+ }
8927
+ }
8722
8928
  /** Screen-reader summary of the whole chart (role="img" name). */
8723
8929
  chartAria = computed(() => {
8724
8930
  const f = this.valueFormat() ?? ((v) => String(Math.round(v * 100) / 100));
8725
8931
  if (this.isMulti()) {
8726
8932
  const parts = this.multiSeries().map((s) => {
8727
- const last = s.data.length ? f(s.data[s.data.length - 1]) : '';
8933
+ const reals = s.data.filter(isVal);
8934
+ const last = reals.length ? f(reals[reals.length - 1]) : '';
8728
8935
  return `${s.label || 'series'} latest ${last}`;
8729
8936
  });
8730
8937
  return `Chart, ${this.multiSeries().length} series: ${parts.join('; ')}`;
8731
8938
  }
8732
8939
  const d = this.data();
8733
- if (!d.length)
8940
+ const reals = d.filter(isVal);
8941
+ if (!reals.length)
8734
8942
  return this.emptyText();
8735
- return `Chart, ${d.length} points. Min ${f(Math.min(...d))}, max ${f(Math.max(...d))}, latest ${f(d[d.length - 1])}`;
8943
+ return `Chart, ${d.length} points. Min ${f(Math.min(...reals))}, max ${f(Math.max(...reals))}, latest ${f(reals[reals.length - 1])}`;
8736
8944
  }, ...(ngDevMode ? [{ debugName: "chartAria" }] : /* istanbul ignore next */ []));
8737
8945
  /** aria-live text announcing the hovered / keyboard-selected point. */
8738
8946
  srText = computed(() => {
8739
- const i = this.hoverIdx();
8947
+ const i = this.dispIdx();
8740
8948
  if (i == null)
8741
8949
  return '';
8742
8950
  if (this.isMulti()) {
@@ -8746,61 +8954,221 @@ class StrctChart {
8746
8954
  return `${this.hoverMeta() || 'point ' + (i + 1)}: ${rows}`;
8747
8955
  }
8748
8956
  const meta = this.hoverMeta();
8749
- return `${meta ? meta + ': ' : ''}${this.hoverValueText()}`;
8957
+ const value = this.hoverGap() ? this.gapText() : this.hoverValueText();
8958
+ return `${meta ? meta + ': ' : ''}${value}`;
8750
8959
  }, ...(ngDevMode ? [{ debugName: "srText" }] : /* istanbul ignore next */ []));
8751
- /** Pixel x of a data index (shared by the plot and the x-axis labels). */
8960
+ /** Pixel x of a data index (shared by the plot, labels and annotations). */
8752
8961
  xOf(i) {
8753
- return this.pl() + i * this.stepX();
8962
+ const [s] = this.domain();
8963
+ return this.pl() + (i - s) * this.stepX();
8964
+ }
8965
+ /** Set the local hover index, emitting `hoverIndex` on change. */
8966
+ setHover(i) {
8967
+ if (i === this.hoverIdx())
8968
+ return;
8969
+ this.hoverIdx.set(i);
8970
+ this.hoverIndex.emit(i);
8971
+ }
8972
+ onLeave() {
8973
+ this.setHover(null);
8754
8974
  }
8755
- /** Keyboard access to the crosshair: arrows walk the points. */
8975
+ /** Keyboard access: arrows walk the points; Escape unwinds brush → zoom → crosshair. */
8756
8976
  onKey(event) {
8757
- if (!this.interactive() || this.type() === 'bar')
8977
+ if (event.key === 'Escape') {
8978
+ if (this.brushDrag()) {
8979
+ this.brushDrag.set(null);
8980
+ }
8981
+ else if (this.zoomed()) {
8982
+ this.resetZoom();
8983
+ }
8984
+ else {
8985
+ this.setHover(null);
8986
+ }
8758
8987
  return;
8759
- const n = this.nx();
8760
- if (!n)
8988
+ }
8989
+ if (!this.interactive() || this.type() === 'bar')
8761
8990
  return;
8991
+ const [s, e] = this.domain();
8762
8992
  const cur = this.hoverIdx();
8763
8993
  let next = null;
8764
8994
  switch (event.key) {
8765
8995
  case 'ArrowRight':
8766
- next = cur == null ? 0 : Math.min(n - 1, cur + 1);
8996
+ next = cur == null ? s : Math.min(e, cur + 1);
8767
8997
  break;
8768
8998
  case 'ArrowLeft':
8769
- next = cur == null ? n - 1 : Math.max(0, cur - 1);
8999
+ next = cur == null ? e : Math.max(s, cur - 1);
8770
9000
  break;
8771
9001
  case 'Home':
8772
- next = 0;
9002
+ next = s;
8773
9003
  break;
8774
9004
  case 'End':
8775
- next = n - 1;
9005
+ next = e;
8776
9006
  break;
8777
- case 'Escape':
8778
- this.hoverIdx.set(null);
8779
- return;
8780
9007
  default:
8781
9008
  return;
8782
9009
  }
8783
9010
  event.preventDefault();
8784
- this.hoverIdx.set(next);
9011
+ this.setHover(next);
8785
9012
  }
8786
- onMove(event) {
8787
- if (!this.interactive() || (this.type() === 'bar' && !this.isMulti()))
8788
- return;
9013
+ /** Data index under the pointer, clamped to the visible window. */
9014
+ idxAt(event) {
8789
9015
  const el = this.svgRef()?.nativeElement;
8790
- const n = this.nx();
8791
9016
  if (!el || this.isEmpty())
8792
- return;
9017
+ return null;
8793
9018
  const rect = el.getBoundingClientRect();
8794
9019
  if (!rect.width)
8795
- return;
9020
+ return null;
8796
9021
  const plFrac = this.pl() / this.width();
8797
9022
  const rFrac = PAD.r / this.width();
8798
9023
  const fx = ((event.clientX - rect.left) / rect.width - plFrac) / (1 - plFrac - rFrac);
8799
- const idx = Math.max(0, Math.min(n - 1, Math.round(fx * (n - 1))));
8800
- this.hoverIdx.set(idx);
9024
+ const [s, e] = this.domain();
9025
+ return Math.max(s, Math.min(e, s + Math.round(fx * (e - s))));
9026
+ }
9027
+ onDown(event) {
9028
+ if (!this.brushEnabled() || this.isEmpty())
9029
+ return;
9030
+ const i = this.idxAt(event);
9031
+ if (i == null)
9032
+ return;
9033
+ event.currentTarget?.setPointerCapture?.(event.pointerId);
9034
+ this.brushDrag.set({ a: i, b: i });
9035
+ this.setHover(null);
9036
+ event.preventDefault();
9037
+ }
9038
+ onMove(event) {
9039
+ const drag = this.brushDrag();
9040
+ if (drag) {
9041
+ const i = this.idxAt(event);
9042
+ if (i != null && i !== drag.b)
9043
+ this.brushDrag.set({ a: drag.a, b: i });
9044
+ return;
9045
+ }
9046
+ if (!this.interactive() || (this.type() === 'bar' && !this.isMulti()))
9047
+ return;
9048
+ const i = this.idxAt(event);
9049
+ if (i != null)
9050
+ this.setHover(i);
9051
+ }
9052
+ onUp() {
9053
+ const d = this.brushDrag();
9054
+ if (!d)
9055
+ return;
9056
+ this.brushDrag.set(null);
9057
+ const lo = Math.min(d.a, d.b);
9058
+ const hi = Math.max(d.a, d.b);
9059
+ if (hi - lo < 1)
9060
+ return; // a click, not a selection
9061
+ if (this.zoom())
9062
+ this.viewRange.set([lo, hi]);
9063
+ this.brushChange.emit([lo, hi]);
9064
+ }
9065
+ onDblClick() {
9066
+ if (this.brushEnabled())
9067
+ this.resetZoom();
9068
+ }
9069
+ // ── Export (FR-CHART-13) ───────────────────────────────────────
9070
+ /**
9071
+ * The rendered chart as a standalone SVG string — theme colors resolved to
9072
+ * literals, background baked in, y-tick / x-label text included.
9073
+ */
9074
+ toSVG() {
9075
+ const svg = this.svgRef()?.nativeElement;
9076
+ if (!svg || typeof getComputedStyle === 'undefined')
9077
+ return '';
9078
+ const doc = svg.ownerDocument;
9079
+ const NS = 'http://www.w3.org/2000/svg';
9080
+ const clone = svg.cloneNode(true);
9081
+ clone.setAttribute('xmlns', NS);
9082
+ clone.removeAttribute('class');
9083
+ clone.removeAttribute('style');
9084
+ clone.removeAttribute('tabindex');
9085
+ const PROPS = [
9086
+ 'stroke',
9087
+ 'fill',
9088
+ 'stroke-width',
9089
+ 'stroke-dasharray',
9090
+ 'stroke-linecap',
9091
+ 'stroke-linejoin',
9092
+ 'opacity',
9093
+ 'fill-opacity',
9094
+ 'stroke-opacity',
9095
+ ];
9096
+ const src = Array.from(svg.querySelectorAll('*'));
9097
+ const dst = Array.from(clone.querySelectorAll('*'));
9098
+ src.forEach((el, i) => {
9099
+ const cs = getComputedStyle(el);
9100
+ for (const p of PROPS) {
9101
+ const v = cs.getPropertyValue(p);
9102
+ if (v)
9103
+ dst[i].setAttribute(p, v);
9104
+ }
9105
+ dst[i].removeAttribute('class');
9106
+ });
9107
+ const rootCs = getComputedStyle(svg);
9108
+ const bg = rootCs.getPropertyValue('--bg-1').trim();
9109
+ const fg = rootCs.getPropertyValue('--t3').trim() || '#888';
9110
+ const hasLabels = this.displayLabels().length > 0;
9111
+ const w = this.width();
9112
+ const h = this.height() + (hasLabels ? 18 : 0);
9113
+ clone.setAttribute('viewBox', `0 0 ${w} ${h}`);
9114
+ clone.setAttribute('width', String(w));
9115
+ clone.setAttribute('height', String(h));
9116
+ if (bg) {
9117
+ const rect = doc.createElementNS(NS, 'rect');
9118
+ rect.setAttribute('width', String(w));
9119
+ rect.setAttribute('height', String(h));
9120
+ rect.setAttribute('fill', bg);
9121
+ clone.insertBefore(rect, clone.firstChild);
9122
+ }
9123
+ const text = (x, y, content, anchor) => {
9124
+ const t = doc.createElementNS(NS, 'text');
9125
+ t.setAttribute('x', String(round$1(x)));
9126
+ t.setAttribute('y', String(round$1(y)));
9127
+ t.setAttribute('fill', fg);
9128
+ t.setAttribute('font-size', '10');
9129
+ t.setAttribute('font-family', 'monospace');
9130
+ t.setAttribute('text-anchor', anchor);
9131
+ t.textContent = content;
9132
+ clone.appendChild(t);
9133
+ };
9134
+ for (const tick of this.yAxisTicks())
9135
+ text(this.pl() - 6, tick.y + 3.5, tick.text, 'end');
9136
+ if (hasLabels) {
9137
+ for (const l of this.displayLabels())
9138
+ text(this.xOf(l.i), this.height() + 12, l.text, 'middle');
9139
+ }
9140
+ return new XMLSerializer().serializeToString(clone);
9141
+ }
9142
+ /** The chart as a PNG data URL at the given scale (default 2×). */
9143
+ toPNG(scale = 2) {
9144
+ const s = this.toSVG();
9145
+ const w = this.width();
9146
+ const h = this.height() + (this.displayLabels().length ? 18 : 0);
9147
+ return new Promise((resolve, reject) => {
9148
+ if (!s) {
9149
+ reject(new Error('chart is not rendered'));
9150
+ return;
9151
+ }
9152
+ const img = new Image();
9153
+ img.onload = () => {
9154
+ const canvas = document.createElement('canvas');
9155
+ canvas.width = Math.round(w * scale);
9156
+ canvas.height = Math.round(h * scale);
9157
+ const ctx = canvas.getContext('2d');
9158
+ if (!ctx) {
9159
+ reject(new Error('no 2d canvas context'));
9160
+ return;
9161
+ }
9162
+ ctx.scale(scale, scale);
9163
+ ctx.drawImage(img, 0, 0);
9164
+ resolve(canvas.toDataURL('image/png'));
9165
+ };
9166
+ img.onerror = () => reject(new Error('SVG rasterization failed'));
9167
+ img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(s);
9168
+ });
8801
9169
  }
8802
9170
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
8803
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctChart, isStandalone: true, selector: "strct-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, area: { classPropertyName: "area", publicName: "area", isSignal: true, isRequired: false, transformFunction: null }, glow: { classPropertyName: "glow", publicName: "glow", isSignal: true, isRequired: false, transformFunction: null }, live: { classPropertyName: "live", publicName: "live", isSignal: true, isRequired: false, transformFunction: null }, interval: { classPropertyName: "interval", publicName: "interval", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, dots: { classPropertyName: "dots", publicName: "dots", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, xTicks: { classPropertyName: "xTicks", publicName: "xTicks", isSignal: true, isRequired: false, transformFunction: null }, xFormat: { classPropertyName: "xFormat", publicName: "xFormat", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, yAxis: { classPropertyName: "yAxis", publicName: "yAxis", isSignal: true, isRequired: false, transformFunction: null }, yTicks: { classPropertyName: "yTicks", publicName: "yTicks", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, agoFormat: { classPropertyName: "agoFormat", publicName: "agoFormat", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-chart--glow": "glow()", "style.--strct-chart-c": "color()" }, classAttribute: "strct-chart" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, isSignal: true }], ngImport: i0, template: `
9171
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctChart, isStandalone: true, selector: "strct-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, area: { classPropertyName: "area", publicName: "area", isSignal: true, isRequired: false, transformFunction: null }, glow: { classPropertyName: "glow", publicName: "glow", isSignal: true, isRequired: false, transformFunction: null }, live: { classPropertyName: "live", publicName: "live", isSignal: true, isRequired: false, transformFunction: null }, interval: { classPropertyName: "interval", publicName: "interval", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, dots: { classPropertyName: "dots", publicName: "dots", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, xTicks: { classPropertyName: "xTicks", publicName: "xTicks", isSignal: true, isRequired: false, transformFunction: null }, xFormat: { classPropertyName: "xFormat", publicName: "xFormat", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, yAxis: { classPropertyName: "yAxis", publicName: "yAxis", isSignal: true, isRequired: false, transformFunction: null }, yTicks: { classPropertyName: "yTicks", publicName: "yTicks", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, agoFormat: { classPropertyName: "agoFormat", publicName: "agoFormat", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, brush: { classPropertyName: "brush", publicName: "brush", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, gapText: { classPropertyName: "gapText", publicName: "gapText", isSignal: true, isRequired: false, transformFunction: null }, resetLabel: { classPropertyName: "resetLabel", publicName: "resetLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hoverIndex: "hoverIndex", brushChange: "brushChange" }, host: { properties: { "class.strct-chart--glow": "glow()", "class.strct-chart--brush": "brush() || zoom()", "style.--strct-chart-c": "color()" }, classAttribute: "strct-chart" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, isSignal: true }], ngImport: i0, template: `
8804
9172
  @if (isEmpty()) {
8805
9173
  <div class="strct-chart__empty" [style.height.px]="height()">{{ emptyText() }}</div>
8806
9174
  } @else {
@@ -8835,10 +9203,14 @@ class StrctChart {
8835
9203
  [attr.width]="width()"
8836
9204
  [attr.height]="height()"
8837
9205
  [style.height.px]="height()"
9206
+ (pointerdown)="onDown($event)"
8838
9207
  (pointermove)="onMove($event)"
8839
- (pointerleave)="hoverIdx.set(null)"
9208
+ (pointerup)="onUp()"
9209
+ (lostpointercapture)="onUp()"
9210
+ (pointerleave)="onLeave()"
9211
+ (dblclick)="onDblClick()"
8840
9212
  (keydown)="onKey($event)"
8841
- (blur)="hoverIdx.set(null)"
9213
+ (blur)="onLeave()"
8842
9214
  >
8843
9215
  <defs>
8844
9216
  <linearGradient [attr.id]="gradId" x1="0" y1="0" x2="0" y2="1">
@@ -8862,6 +9234,19 @@ class StrctChart {
8862
9234
  }
8863
9235
  }
8864
9236
 
9237
+ <!-- Event / annotation markers: behind the data, above the grid. -->
9238
+ @for (a of annotationLines(); track $index) {
9239
+ <line
9240
+ class="strct-chart__ann"
9241
+ [class.strct-chart__ann--dashed]="a.dashed"
9242
+ [attr.x1]="a.x"
9243
+ [attr.x2]="a.x"
9244
+ [attr.y1]="pad.t"
9245
+ [attr.y2]="height() - pad.b"
9246
+ [attr.stroke]="a.color"
9247
+ />
9248
+ }
9249
+
8865
9250
  @if (type() === 'bar' && !isMulti()) {
8866
9251
  @for (b of bars(); track $index) {
8867
9252
  <rect
@@ -8882,6 +9267,9 @@ class StrctChart {
8882
9267
  >
8883
9268
  @if (isMulti()) {
8884
9269
  @for (s of multiSeries(); track $index) {
9270
+ @if (s.bandPath) {
9271
+ <path class="strct-chart__band" [attr.d]="s.bandPath" [attr.fill]="s.color" />
9272
+ }
8885
9273
  @if (s.area && s.areaPath) {
8886
9274
  <path
8887
9275
  class="strct-chart__area strct-chart__area--flat"
@@ -8916,7 +9304,7 @@ class StrctChart {
8916
9304
  [attr.stroke]="color()"
8917
9305
  />
8918
9306
  @if (dots()) {
8919
- @for (p of points(); track $index) {
9307
+ @for (p of dotPts(); track $index) {
8920
9308
  <circle
8921
9309
  class="strct-chart__dot"
8922
9310
  [attr.cx]="p.x"
@@ -8942,6 +9330,16 @@ class StrctChart {
8942
9330
  />
8943
9331
  }
8944
9332
 
9333
+ @if (brushRect(); as br) {
9334
+ <rect
9335
+ class="strct-chart__brush"
9336
+ [attr.x]="br.x"
9337
+ [attr.y]="pad.t"
9338
+ [attr.width]="br.w"
9339
+ [attr.height]="height() - pad.t - pad.b"
9340
+ />
9341
+ }
9342
+
8945
9343
  @if (interactive() && hoverX() !== null) {
8946
9344
  <line
8947
9345
  class="strct-chart__cross"
@@ -9002,12 +9400,29 @@ class StrctChart {
9002
9400
  }
9003
9401
  }
9004
9402
 
9403
+ @for (a of annotationLines(); track $index) {
9404
+ @if (a.label) {
9405
+ <div class="strct-chart__ann-label" [style.left.px]="a.x" [style.color]="a.color">
9406
+ {{ a.label }}
9407
+ </div>
9408
+ }
9409
+ }
9410
+
9411
+ @if (zoomed()) {
9412
+ <button type="button" class="strct-chart__reset" (click)="resetZoom()">
9413
+ ⟲ {{ resetLabel() }}
9414
+ </button>
9415
+ }
9416
+
9005
9417
  @if (interactive() && hoverX() !== null) {
9006
9418
  @if (isMulti()) {
9007
9419
  <div class="strct-chart__tip strct-chart__tip--multi" [style.left.px]="hoverX()">
9008
9420
  @if (hoverMeta()) {
9009
9421
  <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9010
9422
  }
9423
+ @if (annAt(); as ann) {
9424
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9425
+ }
9011
9426
  @for (r of hoverRows(); track r.label) {
9012
9427
  <span class="strct-chart__tip-row">
9013
9428
  <span class="strct-chart__tip-sw" [style.background]="r.color"></span>
@@ -9039,6 +9454,17 @@ class StrctChart {
9039
9454
  }
9040
9455
  </span>
9041
9456
  }
9457
+ @if (annAt(); as ann) {
9458
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9459
+ }
9460
+ </div>
9461
+ } @else if (hoverGap()) {
9462
+ <!-- A gap point: keep the time slot, say "no data" instead of a value. -->
9463
+ <div class="strct-chart__tip strct-chart__tip--gap" [style.left.px]="hoverX()">
9464
+ <span class="strct-chart__tip-v">{{ gapText() }}</span>
9465
+ @if (hoverMeta()) {
9466
+ <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9467
+ }
9042
9468
  </div>
9043
9469
  }
9044
9470
  }
@@ -9053,7 +9479,7 @@ class StrctChart {
9053
9479
  <div class="strct-chart__labels">
9054
9480
  @for (l of displayLabels(); track $index) {
9055
9481
  <span
9056
- [class.strct-chart__label--active]="interactive() && hoverIdx() === l.i"
9482
+ [class.strct-chart__label--active]="interactive() && dispIdx() === l.i"
9057
9483
  [style.left.px]="xOf(l.i)"
9058
9484
  >{{ l.text }}</span
9059
9485
  >
@@ -9061,7 +9487,7 @@ class StrctChart {
9061
9487
  </div>
9062
9488
  }
9063
9489
  }
9064
- `, isInline: true, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9490
+ `, isInline: true, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart__band{opacity:.13;stroke:none}.strct-chart__ann{stroke-width:1;opacity:.8;vector-effect:non-scaling-stroke}.strct-chart__ann--dashed{stroke-dasharray:4 3}.strct-chart__ann-label{position:absolute;top:0;transform:translate(-50%);pointer-events:none;font-size:12px;font-weight:600;background:var(--bg-1);padding:0 3px;border-radius:3px;white-space:nowrap}.strct-chart--brush .strct-chart__svg{cursor:crosshair}.strct-chart__brush{fill:var(--acc);opacity:.16;stroke:var(--acc50);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__reset{position:absolute;top:6px;right:8px;z-index:3;display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border:1px solid var(--b2);border-radius:99px;background:var(--bg-a);color:var(--t2);font-family:var(--font);font-size:12px;font-weight:600;cursor:pointer}.strct-chart__reset:hover{color:var(--t1);border-color:var(--acc50)}.strct-chart__reset:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-chart__tip--gap{top:6px;transform:translate(-50%)}.strct-chart__tip-ann{font-size:12px;font-weight:600}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9065
9491
  }
9066
9492
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctChart, decorators: [{
9067
9493
  type: Component,
@@ -9100,10 +9526,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9100
9526
  [attr.width]="width()"
9101
9527
  [attr.height]="height()"
9102
9528
  [style.height.px]="height()"
9529
+ (pointerdown)="onDown($event)"
9103
9530
  (pointermove)="onMove($event)"
9104
- (pointerleave)="hoverIdx.set(null)"
9531
+ (pointerup)="onUp()"
9532
+ (lostpointercapture)="onUp()"
9533
+ (pointerleave)="onLeave()"
9534
+ (dblclick)="onDblClick()"
9105
9535
  (keydown)="onKey($event)"
9106
- (blur)="hoverIdx.set(null)"
9536
+ (blur)="onLeave()"
9107
9537
  >
9108
9538
  <defs>
9109
9539
  <linearGradient [attr.id]="gradId" x1="0" y1="0" x2="0" y2="1">
@@ -9127,6 +9557,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9127
9557
  }
9128
9558
  }
9129
9559
 
9560
+ <!-- Event / annotation markers: behind the data, above the grid. -->
9561
+ @for (a of annotationLines(); track $index) {
9562
+ <line
9563
+ class="strct-chart__ann"
9564
+ [class.strct-chart__ann--dashed]="a.dashed"
9565
+ [attr.x1]="a.x"
9566
+ [attr.x2]="a.x"
9567
+ [attr.y1]="pad.t"
9568
+ [attr.y2]="height() - pad.b"
9569
+ [attr.stroke]="a.color"
9570
+ />
9571
+ }
9572
+
9130
9573
  @if (type() === 'bar' && !isMulti()) {
9131
9574
  @for (b of bars(); track $index) {
9132
9575
  <rect
@@ -9147,6 +9590,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9147
9590
  >
9148
9591
  @if (isMulti()) {
9149
9592
  @for (s of multiSeries(); track $index) {
9593
+ @if (s.bandPath) {
9594
+ <path class="strct-chart__band" [attr.d]="s.bandPath" [attr.fill]="s.color" />
9595
+ }
9150
9596
  @if (s.area && s.areaPath) {
9151
9597
  <path
9152
9598
  class="strct-chart__area strct-chart__area--flat"
@@ -9181,7 +9627,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9181
9627
  [attr.stroke]="color()"
9182
9628
  />
9183
9629
  @if (dots()) {
9184
- @for (p of points(); track $index) {
9630
+ @for (p of dotPts(); track $index) {
9185
9631
  <circle
9186
9632
  class="strct-chart__dot"
9187
9633
  [attr.cx]="p.x"
@@ -9207,6 +9653,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9207
9653
  />
9208
9654
  }
9209
9655
 
9656
+ @if (brushRect(); as br) {
9657
+ <rect
9658
+ class="strct-chart__brush"
9659
+ [attr.x]="br.x"
9660
+ [attr.y]="pad.t"
9661
+ [attr.width]="br.w"
9662
+ [attr.height]="height() - pad.t - pad.b"
9663
+ />
9664
+ }
9665
+
9210
9666
  @if (interactive() && hoverX() !== null) {
9211
9667
  <line
9212
9668
  class="strct-chart__cross"
@@ -9267,12 +9723,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9267
9723
  }
9268
9724
  }
9269
9725
 
9726
+ @for (a of annotationLines(); track $index) {
9727
+ @if (a.label) {
9728
+ <div class="strct-chart__ann-label" [style.left.px]="a.x" [style.color]="a.color">
9729
+ {{ a.label }}
9730
+ </div>
9731
+ }
9732
+ }
9733
+
9734
+ @if (zoomed()) {
9735
+ <button type="button" class="strct-chart__reset" (click)="resetZoom()">
9736
+ ⟲ {{ resetLabel() }}
9737
+ </button>
9738
+ }
9739
+
9270
9740
  @if (interactive() && hoverX() !== null) {
9271
9741
  @if (isMulti()) {
9272
9742
  <div class="strct-chart__tip strct-chart__tip--multi" [style.left.px]="hoverX()">
9273
9743
  @if (hoverMeta()) {
9274
9744
  <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9275
9745
  }
9746
+ @if (annAt(); as ann) {
9747
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9748
+ }
9276
9749
  @for (r of hoverRows(); track r.label) {
9277
9750
  <span class="strct-chart__tip-row">
9278
9751
  <span class="strct-chart__tip-sw" [style.background]="r.color"></span>
@@ -9304,6 +9777,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9304
9777
  }
9305
9778
  </span>
9306
9779
  }
9780
+ @if (annAt(); as ann) {
9781
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9782
+ }
9783
+ </div>
9784
+ } @else if (hoverGap()) {
9785
+ <!-- A gap point: keep the time slot, say "no data" instead of a value. -->
9786
+ <div class="strct-chart__tip strct-chart__tip--gap" [style.left.px]="hoverX()">
9787
+ <span class="strct-chart__tip-v">{{ gapText() }}</span>
9788
+ @if (hoverMeta()) {
9789
+ <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9790
+ }
9307
9791
  </div>
9308
9792
  }
9309
9793
  }
@@ -9318,7 +9802,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9318
9802
  <div class="strct-chart__labels">
9319
9803
  @for (l of displayLabels(); track $index) {
9320
9804
  <span
9321
- [class.strct-chart__label--active]="interactive() && hoverIdx() === l.i"
9805
+ [class.strct-chart__label--active]="interactive() && dispIdx() === l.i"
9322
9806
  [style.left.px]="xOf(l.i)"
9323
9807
  >{{ l.text }}</span
9324
9808
  >
@@ -9329,9 +9813,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9329
9813
  `, host: {
9330
9814
  class: 'strct-chart',
9331
9815
  '[class.strct-chart--glow]': 'glow()',
9816
+ '[class.strct-chart--brush]': 'brush() || zoom()',
9332
9817
  '[style.--strct-chart-c]': 'color()',
9333
- }, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"] }]
9334
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], area: [{ type: i0.Input, args: [{ isSignal: true, alias: "area", required: false }] }], glow: [{ type: i0.Input, args: [{ isSignal: true, alias: "glow", required: false }] }], live: [{ type: i0.Input, args: [{ isSignal: true, alias: "live", required: false }] }], interval: [{ type: i0.Input, args: [{ isSignal: true, alias: "interval", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], dots: [{ type: i0.Input, args: [{ isSignal: true, alias: "dots", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], xTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "xTicks", required: false }] }], xFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "xFormat", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], yAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "yAxis", required: false }] }], yTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "yTicks", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], agoFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "agoFormat", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], svgRef: [{ type: i0.ViewChild, args: ['svg', { isSignal: true }] }] } });
9818
+ }, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart__band{opacity:.13;stroke:none}.strct-chart__ann{stroke-width:1;opacity:.8;vector-effect:non-scaling-stroke}.strct-chart__ann--dashed{stroke-dasharray:4 3}.strct-chart__ann-label{position:absolute;top:0;transform:translate(-50%);pointer-events:none;font-size:12px;font-weight:600;background:var(--bg-1);padding:0 3px;border-radius:3px;white-space:nowrap}.strct-chart--brush .strct-chart__svg{cursor:crosshair}.strct-chart__brush{fill:var(--acc);opacity:.16;stroke:var(--acc50);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__reset{position:absolute;top:6px;right:8px;z-index:3;display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border:1px solid var(--b2);border-radius:99px;background:var(--bg-a);color:var(--t2);font-family:var(--font);font-size:12px;font-weight:600;cursor:pointer}.strct-chart__reset:hover{color:var(--t1);border-color:var(--acc50)}.strct-chart__reset:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-chart__tip--gap{top:6px;transform:translate(-50%)}.strct-chart__tip-ann{font-size:12px;font-weight:600}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"] }]
9819
+ }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], area: [{ type: i0.Input, args: [{ isSignal: true, alias: "area", required: false }] }], glow: [{ type: i0.Input, args: [{ isSignal: true, alias: "glow", required: false }] }], live: [{ type: i0.Input, args: [{ isSignal: true, alias: "live", required: false }] }], interval: [{ type: i0.Input, args: [{ isSignal: true, alias: "interval", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], dots: [{ type: i0.Input, args: [{ isSignal: true, alias: "dots", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], xTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "xTicks", required: false }] }], xFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "xFormat", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], yAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "yAxis", required: false }] }], yTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "yTicks", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], agoFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "agoFormat", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }], brush: [{ type: i0.Input, args: [{ isSignal: true, alias: "brush", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], gapText: [{ type: i0.Input, args: [{ isSignal: true, alias: "gapText", required: false }] }], resetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "resetLabel", required: false }] }], hoverIndex: [{ type: i0.Output, args: ["hoverIndex"] }], brushChange: [{ type: i0.Output, args: ["brushChange"] }], svgRef: [{ type: i0.ViewChild, args: ['svg', { isSignal: true }] }] } });
9335
9820
 
9336
9821
  const PALETTE = [
9337
9822
  'var(--acc)',