liveline 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/Liveline.tsx
2
- import { useRef as useRef2, useState, useLayoutEffect, useMemo } from "react";
2
+ import { useRef as useRef2, useState, useLayoutEffect, useMemo, useCallback as useCallback2 } from "react";
3
3
 
4
4
  // src/theme.ts
5
5
  function parseColorRgb(color) {
@@ -61,6 +61,33 @@ function resolveTheme(color, mode) {
61
61
  badgeFont: '500 11px "SF Mono", Menlo, monospace'
62
62
  };
63
63
  }
64
+ var SERIES_COLORS = [
65
+ "#3b82f6",
66
+ // blue
67
+ "#ef4444",
68
+ // red
69
+ "#22c55e",
70
+ // green
71
+ "#f59e0b",
72
+ // amber
73
+ "#8b5cf6",
74
+ // violet
75
+ "#ec4899",
76
+ // pink
77
+ "#06b6d4",
78
+ // cyan
79
+ "#f97316"
80
+ // orange
81
+ ];
82
+ function resolveSeriesPalettes(series, mode) {
83
+ const map = /* @__PURE__ */ new Map();
84
+ for (let i = 0; i < series.length; i++) {
85
+ const s = series[i];
86
+ const color = s.color || SERIES_COLORS[i % SERIES_COLORS.length];
87
+ map.set(s.id, resolveTheme(color, mode));
88
+ }
89
+ return map;
90
+ }
64
91
 
65
92
  // src/useLivelineEngine.ts
66
93
  import { useRef, useEffect, useCallback } from "react";
@@ -486,6 +513,33 @@ function drawDot(ctx, x, y, palette, pulse = true, scrubAmount = 0, now_ms = per
486
513
  }
487
514
  ctx.fill();
488
515
  }
516
+ function drawMultiDot(ctx, x, y, color, pulse = true, now_ms = performance.now(), radius = 3) {
517
+ const baseAlpha = ctx.globalAlpha;
518
+ if (pulse) {
519
+ const t = now_ms % PULSE_INTERVAL / PULSE_DURATION;
520
+ if (t < 1) {
521
+ const ringRadius = 9 + t * 10;
522
+ const pulseAlpha = 0.3 * (1 - t);
523
+ ctx.beginPath();
524
+ ctx.arc(x, y, ringRadius, 0, Math.PI * 2);
525
+ ctx.strokeStyle = color;
526
+ ctx.lineWidth = 1.5;
527
+ ctx.globalAlpha = baseAlpha * pulseAlpha;
528
+ ctx.stroke();
529
+ }
530
+ }
531
+ ctx.globalAlpha = baseAlpha;
532
+ ctx.beginPath();
533
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
534
+ ctx.fillStyle = color;
535
+ ctx.fill();
536
+ }
537
+ function drawSimpleDot(ctx, x, y, color, radius = 3) {
538
+ ctx.beginPath();
539
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
540
+ ctx.fillStyle = color;
541
+ ctx.fill();
542
+ }
489
543
  function drawArrows(ctx, x, y, momentum, palette, arrows, dt, now_ms = performance.now()) {
490
544
  const baseAlpha = ctx.globalAlpha;
491
545
  const upTarget = momentum === "up" ? 1 : 0;
@@ -584,6 +638,90 @@ function drawCrosshair(ctx, layout, palette, hoverX, hoverValue, hoverTime, form
584
638
  ctx.fillText(separator + timeText, tx + valueW, ty);
585
639
  ctx.restore();
586
640
  }
641
+ function drawMultiCrosshair(ctx, layout, palette, hoverX, hoverTime, entries, formatValue, formatTime, scrubOpacity, tooltipY, tooltipOutline, liveDotX) {
642
+ if (scrubOpacity < 0.01 || entries.length === 0) return;
643
+ const { h, pad, toY } = layout;
644
+ ctx.save();
645
+ ctx.globalAlpha = scrubOpacity * 0.5;
646
+ ctx.strokeStyle = palette.crosshairLine;
647
+ ctx.lineWidth = 1;
648
+ ctx.beginPath();
649
+ ctx.moveTo(hoverX, pad.top);
650
+ ctx.lineTo(hoverX, h - pad.bottom);
651
+ ctx.stroke();
652
+ ctx.restore();
653
+ const dotRadius = 4 * Math.min(scrubOpacity * 3, 1);
654
+ if (dotRadius > 0.5) {
655
+ ctx.globalAlpha = 1;
656
+ for (const entry of entries) {
657
+ const y = toY(entry.value);
658
+ ctx.beginPath();
659
+ ctx.arc(hoverX, y, dotRadius, 0, Math.PI * 2);
660
+ ctx.fillStyle = entry.color;
661
+ ctx.fill();
662
+ }
663
+ }
664
+ if (scrubOpacity < 0.1 || layout.w < 300) return;
665
+ ctx.save();
666
+ ctx.globalAlpha = scrubOpacity;
667
+ ctx.font = '400 13px "SF Mono", Menlo, monospace';
668
+ ctx.textAlign = "left";
669
+ const timeText = formatTime(hoverTime);
670
+ const sep = " \xB7 ";
671
+ const dotInline = " ";
672
+ const segments = [
673
+ { text: timeText, color: palette.gridLabel }
674
+ ];
675
+ for (const e of entries) {
676
+ segments.push({ text: sep, color: palette.gridLabel });
677
+ segments.push({ text: dotInline, color: e.color, isDot: true });
678
+ const label = e.label ? `${e.label} ` : "";
679
+ if (label) segments.push({ text: label, color: palette.gridLabel });
680
+ segments.push({ text: formatValue(e.value), color: palette.tooltipText });
681
+ }
682
+ let totalW = 0;
683
+ const segWidths = [];
684
+ for (const seg of segments) {
685
+ const w = seg.isDot ? 12 : ctx.measureText(seg.text).width;
686
+ segWidths.push(w);
687
+ totalW += w;
688
+ }
689
+ let tx = hoverX - totalW / 2;
690
+ const minX = pad.left + 4;
691
+ const dotRightEdge = liveDotX != null ? liveDotX + 7 : layout.w - pad.right;
692
+ const maxX = dotRightEdge - totalW;
693
+ if (tx < minX) tx = minX;
694
+ if (tx > maxX) tx = maxX;
695
+ const ty = pad.top + (tooltipY ?? 14) + 10;
696
+ if (tooltipOutline !== false) {
697
+ ctx.strokeStyle = palette.tooltipBg;
698
+ ctx.lineWidth = 3;
699
+ ctx.lineJoin = "round";
700
+ let ox2 = tx;
701
+ for (let i = 0; i < segments.length; i++) {
702
+ const seg = segments[i];
703
+ if (!seg.isDot) {
704
+ ctx.strokeText(seg.text, ox2, ty);
705
+ }
706
+ ox2 += segWidths[i];
707
+ }
708
+ }
709
+ let ox = tx;
710
+ for (let i = 0; i < segments.length; i++) {
711
+ const seg = segments[i];
712
+ if (seg.isDot) {
713
+ ctx.beginPath();
714
+ ctx.arc(ox + 4, ty - 4, 3.5, 0, Math.PI * 2);
715
+ ctx.fillStyle = seg.color;
716
+ ctx.fill();
717
+ } else {
718
+ ctx.fillStyle = seg.color;
719
+ ctx.fillText(seg.text, ox, ty);
720
+ }
721
+ ox += segWidths[i];
722
+ }
723
+ ctx.restore();
724
+ }
587
725
 
588
726
  // src/draw/referenceLine.ts
589
727
  function drawReferenceLine(ctx, layout, palette, ref) {
@@ -1430,6 +1568,124 @@ function drawFrame(ctx, layout, palette, opts) {
1430
1568
  ctx.restore();
1431
1569
  }
1432
1570
  }
1571
+ function drawMultiFrame(ctx, layout, opts) {
1572
+ const palette = opts.primaryPalette;
1573
+ const reveal = opts.chartReveal;
1574
+ const revealRamp = (start, end) => {
1575
+ const t = Math.max(0, Math.min(1, (reveal - start) / (end - start)));
1576
+ return t * t * (3 - 2 * t);
1577
+ };
1578
+ if (opts.referenceLine && reveal > 0.01) {
1579
+ ctx.save();
1580
+ if (reveal < 1) ctx.globalAlpha = reveal;
1581
+ drawReferenceLine(ctx, layout, palette, opts.referenceLine);
1582
+ ctx.restore();
1583
+ }
1584
+ if (opts.showGrid) {
1585
+ const gridAlpha = reveal < 1 ? revealRamp(0.15, 0.7) : 1;
1586
+ if (gridAlpha > 0.01) {
1587
+ ctx.save();
1588
+ if (gridAlpha < 1) ctx.globalAlpha = gridAlpha;
1589
+ drawGrid(ctx, layout, palette, opts.formatValue, opts.gridState, opts.dt);
1590
+ ctx.restore();
1591
+ }
1592
+ }
1593
+ const scrubX = opts.scrubAmount > 0.05 ? opts.hoverX : null;
1594
+ const allPts = [];
1595
+ for (let si = 0; si < opts.series.length; si++) {
1596
+ const s = opts.series[si];
1597
+ const seriesAlpha = s.alpha ?? 1;
1598
+ const secondaryFade = si > 0 && reveal < 1 ? Math.min(1, reveal * 2) : 1;
1599
+ const combinedAlpha = secondaryFade * seriesAlpha;
1600
+ if (combinedAlpha < 0.01) continue;
1601
+ ctx.save();
1602
+ if (combinedAlpha < 1) ctx.globalAlpha = combinedAlpha;
1603
+ const pts = drawLine(
1604
+ ctx,
1605
+ layout,
1606
+ s.palette,
1607
+ s.visible,
1608
+ s.smoothValue,
1609
+ opts.now,
1610
+ false,
1611
+ // no fill
1612
+ scrubX,
1613
+ opts.scrubAmount,
1614
+ reveal,
1615
+ opts.now_ms
1616
+ );
1617
+ ctx.restore();
1618
+ if (pts && pts.length > 0) {
1619
+ allPts.push({ pts, palette: s.palette, label: s.label, alpha: seriesAlpha });
1620
+ }
1621
+ }
1622
+ {
1623
+ const timeAlpha = reveal < 1 ? revealRamp(0.15, 0.7) : 1;
1624
+ if (timeAlpha > 0.01) {
1625
+ ctx.save();
1626
+ if (timeAlpha < 1) ctx.globalAlpha = timeAlpha;
1627
+ drawTimeAxis(ctx, layout, palette, opts.windowSecs, opts.targetWindowSecs, opts.formatTime, opts.timeAxisState, opts.dt);
1628
+ ctx.restore();
1629
+ }
1630
+ }
1631
+ if (reveal > 0.3 && allPts.length > 0) {
1632
+ const dotAlpha = (reveal - 0.3) / 0.7;
1633
+ const showPulse = opts.showPulse && reveal > 0.6 && opts.pauseProgress < 0.5;
1634
+ for (const entry of allPts) {
1635
+ if (entry.alpha < 0.01) continue;
1636
+ const lastPt = entry.pts[entry.pts.length - 1];
1637
+ ctx.save();
1638
+ ctx.globalAlpha = dotAlpha * entry.alpha;
1639
+ if (showPulse && entry.alpha > 0.5) {
1640
+ drawMultiDot(ctx, lastPt[0], lastPt[1], entry.palette.line, true, opts.now_ms, 3);
1641
+ } else {
1642
+ drawSimpleDot(ctx, lastPt[0], lastPt[1], entry.palette.line, 3);
1643
+ }
1644
+ if (entry.label) {
1645
+ ctx.font = '600 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif';
1646
+ ctx.textAlign = "left";
1647
+ ctx.fillStyle = entry.palette.line;
1648
+ ctx.fillText(entry.label, lastPt[0] + 6, lastPt[1] + 3.5);
1649
+ }
1650
+ ctx.restore();
1651
+ }
1652
+ }
1653
+ ctx.save();
1654
+ ctx.globalCompositeOperation = "destination-out";
1655
+ const fadeGrad = ctx.createLinearGradient(layout.pad.left, 0, layout.pad.left + FADE_EDGE_WIDTH, 0);
1656
+ fadeGrad.addColorStop(0, "rgba(0, 0, 0, 1)");
1657
+ fadeGrad.addColorStop(1, "rgba(0, 0, 0, 0)");
1658
+ ctx.fillStyle = fadeGrad;
1659
+ ctx.fillRect(0, 0, layout.pad.left + FADE_EDGE_WIDTH, layout.h);
1660
+ ctx.restore();
1661
+ if (opts.hoverX !== null && opts.hoverTime !== null && opts.hoverEntries.length > 0 && allPts.length > 0 && opts.scrubAmount > 0.01) {
1662
+ let maxLiveDotX = 0;
1663
+ for (const entry of allPts) {
1664
+ if (entry.alpha < 0.01) continue;
1665
+ const lastX = entry.pts[entry.pts.length - 1][0];
1666
+ if (lastX > maxLiveDotX) maxLiveDotX = lastX;
1667
+ }
1668
+ const distToLive = maxLiveDotX - opts.hoverX;
1669
+ const fadeStart = Math.min(80, layout.chartW * 0.3);
1670
+ const scrubOpacity = distToLive < CROSSHAIR_FADE_MIN_PX ? 0 : distToLive >= fadeStart ? opts.scrubAmount : (distToLive - CROSSHAIR_FADE_MIN_PX) / (fadeStart - CROSSHAIR_FADE_MIN_PX) * opts.scrubAmount;
1671
+ if (scrubOpacity > 0.01) {
1672
+ drawMultiCrosshair(
1673
+ ctx,
1674
+ layout,
1675
+ palette,
1676
+ opts.hoverX,
1677
+ opts.hoverTime,
1678
+ opts.hoverEntries,
1679
+ opts.formatValue,
1680
+ opts.formatTime,
1681
+ scrubOpacity,
1682
+ opts.tooltipY,
1683
+ opts.tooltipOutline,
1684
+ maxLiveDotX
1685
+ );
1686
+ }
1687
+ }
1688
+ }
1433
1689
  function drawCandleFrame(ctx, layout, palette, opts) {
1434
1690
  const { w, h, pad, chartW, chartH } = layout;
1435
1691
  const reveal = opts.chartReveal;
@@ -1702,6 +1958,7 @@ var BADGE_Y_LERP_TRANSITIONING = 0.5;
1702
1958
  var MOMENTUM_COLOR_LERP = 0.12;
1703
1959
  var WINDOW_TRANSITION_MS = 750;
1704
1960
  var WINDOW_BUFFER = 0.05;
1961
+ var WINDOW_BUFFER_NO_BADGE = 0.015;
1705
1962
  var VALUE_SNAP_THRESHOLD = 1e-3;
1706
1963
  var ADAPTIVE_SPEED_BOOST = 0.2;
1707
1964
  var MOMENTUM_GREEN = [34, 197, 94];
@@ -1712,6 +1969,7 @@ var PAUSE_PROGRESS_SPEED = 0.12;
1712
1969
  var PAUSE_CATCHUP_SPEED = 0.08;
1713
1970
  var PAUSE_CATCHUP_SPEED_FAST = 0.22;
1714
1971
  var LOADING_ALPHA_SPEED = 0.14;
1972
+ var SERIES_TOGGLE_SPEED = 0.1;
1715
1973
  var CANDLE_LERP_SPEED = 0.25;
1716
1974
  var CANDLE_WIDTH_TRANS_MS = 300;
1717
1975
  var LINE_MORPH_MS = 500;
@@ -1722,7 +1980,7 @@ var LINE_ADAPTIVE_BOOST = 0.2;
1722
1980
  var LINE_SNAP_THRESHOLD = 1e-3;
1723
1981
  var RANGE_LERP_SPEED = 0.15;
1724
1982
  var RANGE_ADAPTIVE_BOOST = 0.2;
1725
- var CANDLE_BUFFER = 0.05;
1983
+ var CANDLE_BUFFER_NO_BADGE = 0.015;
1726
1984
  function computeAdaptiveSpeed(value, displayValue, displayMin, displayMax, lerpSpeed, noMotion) {
1727
1985
  const valGap = Math.abs(value - displayValue);
1728
1986
  const prevRange = displayMax - displayMin || 1;
@@ -2035,6 +2293,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2035
2293
  const configRef = useRef(config);
2036
2294
  configRef.current = config;
2037
2295
  const displayValueRef = useRef(config.value);
2296
+ const displayValuesRef = useRef(/* @__PURE__ */ new Map());
2297
+ const seriesAlphaRef = useRef(/* @__PURE__ */ new Map());
2038
2298
  const displayMinRef = useRef(0);
2039
2299
  const displayMaxRef = useRef(0);
2040
2300
  const targetMinRef = useRef(0);
@@ -2067,12 +2327,15 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2067
2327
  const hoverXRef = useRef(null);
2068
2328
  const scrubAmountRef = useRef(0);
2069
2329
  const lastHoverRef = useRef(null);
2330
+ const lastHoverEntriesRef = useRef([]);
2070
2331
  const chartRevealRef = useRef(0);
2071
2332
  const pauseProgressRef = useRef(0);
2072
2333
  const timeDebtRef = useRef(0);
2073
2334
  const lastDataRef = useRef([]);
2335
+ const lastMultiSeriesRef = useRef([]);
2074
2336
  const frozenNowRef = useRef(0);
2075
2337
  const pausedDataRef = useRef(null);
2338
+ const pausedMultiDataRef = useRef(null);
2076
2339
  const loadingAlphaRef = useRef(config.loading ? 1 : 0);
2077
2340
  const displayCandleRef = useRef(null);
2078
2341
  const liveBirthAlphaRef = useRef(1);
@@ -2252,6 +2515,17 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2252
2515
  pausedLineDataRef.current = null;
2253
2516
  pausedLineValueRef.current = null;
2254
2517
  }
2518
+ } else if (cfg.isMultiSeries && cfg.multiSeries) {
2519
+ if (cfg.paused && pausedMultiDataRef.current === null) {
2520
+ const snap = /* @__PURE__ */ new Map();
2521
+ for (const s of cfg.multiSeries) {
2522
+ if (s.data.length >= 2) snap.set(s.id, { data: s.data.slice(), value: s.value });
2523
+ }
2524
+ if (snap.size > 0) pausedMultiDataRef.current = snap;
2525
+ }
2526
+ if (!cfg.paused) {
2527
+ pausedMultiDataRef.current = null;
2528
+ }
2255
2529
  } else {
2256
2530
  if (cfg.paused && pausedDataRef.current === null && cfg.data.length >= 2) {
2257
2531
  pausedDataRef.current = cfg.data.slice();
@@ -2262,7 +2536,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2262
2536
  }
2263
2537
  const points = isCandle ? [] : pausedDataRef.current ?? cfg.data;
2264
2538
  const effectiveCandles = isCandle ? pausedCandlesRef.current ?? (cfg.candles ?? []) : [];
2265
- const hasData = isCandle ? effectiveCandles.length >= 2 : points.length >= 2;
2539
+ const hasMultiData = cfg.isMultiSeries && cfg.multiSeries ? cfg.multiSeries.some((s) => s.data.length >= 2) : false;
2540
+ const hasData = isCandle ? effectiveCandles.length >= 2 : hasMultiData || points.length >= 2;
2266
2541
  const pad = cfg.padding;
2267
2542
  const chartH = h - pad.top - pad.bottom;
2268
2543
  const pauseTarget = cfg.paused ? 1 : 0;
@@ -2298,11 +2573,23 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2298
2573
  rangeInitedRef.current = false;
2299
2574
  }
2300
2575
  let useStash;
2576
+ let useMultiStash = false;
2301
2577
  if (isCandle) {
2302
2578
  useStash = !hasData && chartReveal > 5e-3 && lastCandlesRef.current.length > 0;
2303
2579
  } else {
2304
- useStash = !hasData && chartReveal > 5e-3 && lastDataRef.current.length >= 2;
2305
- if (hasData) lastDataRef.current = points;
2580
+ useMultiStash = !hasData && chartReveal > 5e-3 && lastMultiSeriesRef.current.length > 0;
2581
+ if (hasMultiData && cfg.multiSeries) {
2582
+ lastMultiSeriesRef.current = cfg.multiSeries.map((s) => ({
2583
+ id: s.id,
2584
+ data: s.data.slice(),
2585
+ value: s.value,
2586
+ palette: s.palette,
2587
+ label: s.label
2588
+ }));
2589
+ }
2590
+ if (hasData && !cfg.isMultiSeries) lastMultiSeriesRef.current = [];
2591
+ useStash = !useMultiStash && !hasData && chartReveal > 5e-3 && lastDataRef.current.length >= 2;
2592
+ if (hasData && !cfg.isMultiSeries) lastDataRef.current = points;
2306
2593
  }
2307
2594
  if (isCandle) {
2308
2595
  const lmt = lineModeTransRef.current;
@@ -2324,8 +2611,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2324
2611
  lineModeProgRef.current = lmt.to;
2325
2612
  }
2326
2613
  }
2327
- if (!hasData && !useStash) {
2328
- const loadingColor = isCandle ? cfg.palette.gridLabel : void 0;
2614
+ if (!hasData && !useStash && !useMultiStash) {
2615
+ const loadingColor = isCandle || cfg.isMultiSeries || lastMultiSeriesRef.current.length > 0 ? cfg.palette.gridLabel : void 0;
2329
2616
  if (loadingAlpha > 0.01) {
2330
2617
  drawLoading(ctx, w, h, pad, cfg.palette, now_ms, loadingAlpha, loadingColor);
2331
2618
  }
@@ -2345,6 +2632,7 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2345
2632
  return;
2346
2633
  }
2347
2634
  if (isCandle) {
2635
+ const candleBuffer = CANDLE_BUFFER_NO_BADGE;
2348
2636
  if (hasData) frozenNowRef.current = Date.now() / 1e3 - timeDebtRef.current;
2349
2637
  const now = hasData || chartReveal < 5e-3 ? Date.now() / 1e3 - timeDebtRef.current : frozenNowRef.current;
2350
2638
  const rawLive = pausedCandlesRef.current ? pausedLiveRef.current ?? void 0 : cfg.liveCandle;
@@ -2387,7 +2675,7 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2387
2675
  cwt.rangeFromMin = displayMinRef.current;
2388
2676
  cwt.rangeFromMax = displayMaxRef.current;
2389
2677
  const curWindow = displayWindowRef.current;
2390
- const re = now + curWindow * CANDLE_BUFFER;
2678
+ const re = now + curWindow * candleBuffer;
2391
2679
  const le = re - curWindow;
2392
2680
  const targetVis = [];
2393
2681
  for (const c of effectiveCandles) {
@@ -2438,13 +2726,13 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2438
2726
  effectiveCandles,
2439
2727
  rawLive,
2440
2728
  candleWidthSecs,
2441
- CANDLE_BUFFER
2729
+ candleBuffer
2442
2730
  );
2443
2731
  displayWindowRef.current = windowResult.windowSecs;
2444
2732
  const windowSecs = windowResult.windowSecs;
2445
2733
  const windowTransProgress = windowResult.windowTransProgress;
2446
2734
  const isWindowTransitioning = transition.startMs > 0;
2447
- const rightEdge = now + windowSecs * CANDLE_BUFFER;
2735
+ const rightEdge = now + windowSecs * candleBuffer;
2448
2736
  const leftEdge = rightEdge - windowSecs;
2449
2737
  let smoothLive;
2450
2738
  if (rawLive) {
@@ -2748,6 +3036,250 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2748
3036
  badgeRef.current.container.style.display = "none";
2749
3037
  }
2750
3038
  }
3039
+ } else if (cfg.isMultiSeries && cfg.multiSeries && cfg.multiSeries.length > 0 || useMultiStash) {
3040
+ const effectiveMultiSeries = useMultiStash ? lastMultiSeriesRef.current : cfg.multiSeries;
3041
+ let labelReserve = 0;
3042
+ if (effectiveMultiSeries.some((s) => s.label)) {
3043
+ ctx.font = '600 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif';
3044
+ let maxLabelW = 0;
3045
+ for (const s of effectiveMultiSeries) {
3046
+ if (s.label) {
3047
+ const lw = ctx.measureText(s.label).width;
3048
+ if (lw > maxLabelW) maxLabelW = lw;
3049
+ }
3050
+ }
3051
+ labelReserve = Math.max(0, maxLabelW - 2) * chartReveal;
3052
+ }
3053
+ const chartW = w - pad.left - pad.right - labelReserve;
3054
+ const buffer = cfg.showBadge ? WINDOW_BUFFER : WINDOW_BUFFER_NO_BADGE;
3055
+ if (!useMultiStash) {
3056
+ const currentIds = new Set(effectiveMultiSeries.map((s) => s.id));
3057
+ for (const key of displayValuesRef.current.keys()) {
3058
+ if (!currentIds.has(key)) displayValuesRef.current.delete(key);
3059
+ }
3060
+ }
3061
+ const firstSeries = effectiveMultiSeries[0];
3062
+ const transition = windowTransitionRef.current;
3063
+ if (hasData) frozenNowRef.current = Date.now() / 1e3 - timeDebtRef.current;
3064
+ const now = useMultiStash ? frozenNowRef.current : Date.now() / 1e3 - timeDebtRef.current;
3065
+ const smoothValues = /* @__PURE__ */ new Map();
3066
+ for (const s of effectiveMultiSeries) {
3067
+ let dv = displayValuesRef.current.get(s.id);
3068
+ if (dv === void 0) dv = s.value;
3069
+ if (!useMultiStash) {
3070
+ const adaptiveSpeed2 = computeAdaptiveSpeed(
3071
+ s.value,
3072
+ dv,
3073
+ displayMinRef.current,
3074
+ displayMaxRef.current,
3075
+ cfg.lerpSpeed,
3076
+ noMotion
3077
+ );
3078
+ dv = lerp(dv, s.value, adaptiveSpeed2, pausedDt);
3079
+ const prevRange = displayMaxRef.current - displayMinRef.current || 1;
3080
+ if (Math.abs(dv - s.value) < prevRange * VALUE_SNAP_THRESHOLD) dv = s.value;
3081
+ displayValuesRef.current.set(s.id, dv);
3082
+ }
3083
+ smoothValues.set(s.id, dv);
3084
+ }
3085
+ const hiddenIds = cfg.hiddenSeriesIds;
3086
+ const seriesAlphas = seriesAlphaRef.current;
3087
+ for (const s of effectiveMultiSeries) {
3088
+ let alpha = seriesAlphas.get(s.id) ?? 1;
3089
+ const target = hiddenIds?.has(s.id) ? 0 : 1;
3090
+ alpha = noMotion ? target : lerp(alpha, target, SERIES_TOGGLE_SPEED, pausedDt);
3091
+ if (alpha < 0.01) alpha = 0;
3092
+ if (alpha > 0.99) alpha = 1;
3093
+ seriesAlphas.set(s.id, alpha);
3094
+ }
3095
+ const firstData = pausedMultiDataRef.current?.get(firstSeries.id)?.data ?? firstSeries.data;
3096
+ const windowResult = updateWindowTransition(
3097
+ cfg,
3098
+ transition,
3099
+ displayWindowRef.current,
3100
+ displayMinRef.current,
3101
+ displayMaxRef.current,
3102
+ noMotion,
3103
+ now_ms,
3104
+ now,
3105
+ firstData,
3106
+ smoothValues.get(firstSeries.id) ?? firstSeries.value,
3107
+ buffer
3108
+ );
3109
+ if (transition.startMs > 0 && effectiveMultiSeries.length > 1) {
3110
+ const targetRightEdge = now + cfg.windowSecs * buffer;
3111
+ const targetLeftEdge = targetRightEdge - cfg.windowSecs;
3112
+ let unionMin = Infinity;
3113
+ let unionMax = -Infinity;
3114
+ for (const s of effectiveMultiSeries) {
3115
+ const sData = pausedMultiDataRef.current?.get(s.id)?.data ?? s.data;
3116
+ const sv = smoothValues.get(s.id) ?? s.value;
3117
+ const targetVisible = [];
3118
+ for (const p of sData) {
3119
+ if (p.time >= targetLeftEdge - 2 && p.time <= targetRightEdge) targetVisible.push(p);
3120
+ }
3121
+ if (targetVisible.length > 0) {
3122
+ const range = computeRange(targetVisible, sv, cfg.referenceLine?.value, cfg.exaggerate);
3123
+ if (range.min < unionMin) unionMin = range.min;
3124
+ if (range.max > unionMax) unionMax = range.max;
3125
+ }
3126
+ }
3127
+ if (isFinite(unionMin) && isFinite(unionMax)) {
3128
+ transition.rangeToMin = unionMin;
3129
+ transition.rangeToMax = unionMax;
3130
+ }
3131
+ }
3132
+ displayWindowRef.current = windowResult.windowSecs;
3133
+ const windowSecs = windowResult.windowSecs;
3134
+ const windowTransProgress = windowResult.windowTransProgress;
3135
+ const isWindowTransitioning = transition.startMs > 0;
3136
+ const rightEdge = now + windowSecs * buffer;
3137
+ const leftEdge = rightEdge - windowSecs;
3138
+ const filterRight = rightEdge - (rightEdge - now) * pauseProgress;
3139
+ const seriesEntries = [];
3140
+ let globalMin = Infinity;
3141
+ let globalMax = -Infinity;
3142
+ for (const s of effectiveMultiSeries) {
3143
+ const snap = pausedMultiDataRef.current?.get(s.id);
3144
+ const seriesData = snap?.data ?? s.data;
3145
+ const visible = [];
3146
+ for (const p of seriesData) {
3147
+ if (p.time >= leftEdge - 2 && p.time <= filterRight) visible.push(p);
3148
+ }
3149
+ const sv = smoothValues.get(s.id) ?? s.value;
3150
+ const alpha = seriesAlphas.get(s.id) ?? 1;
3151
+ if (visible.length >= 2) {
3152
+ if (alpha > 0.01) {
3153
+ const range = computeRange(visible, sv, cfg.referenceLine?.value, cfg.exaggerate);
3154
+ if (range.min < globalMin) globalMin = range.min;
3155
+ if (range.max > globalMax) globalMax = range.max;
3156
+ }
3157
+ seriesEntries.push({ visible, smoothValue: sv, palette: s.palette, label: s.label, alpha });
3158
+ }
3159
+ }
3160
+ if (seriesEntries.length === 0) {
3161
+ if (loadingAlpha > 0.01) {
3162
+ drawLoading(ctx, w, h, pad, cfg.palette, now_ms, loadingAlpha, cfg.palette.gridLabel);
3163
+ }
3164
+ if (1 - loadingAlpha > 0.01) {
3165
+ drawEmpty(ctx, w, h, pad, cfg.palette, 1 - loadingAlpha, now_ms, false, cfg.emptyText);
3166
+ }
3167
+ ctx.save();
3168
+ ctx.globalCompositeOperation = "destination-out";
3169
+ const fadeGrad = ctx.createLinearGradient(pad.left, 0, pad.left + FADE_EDGE_WIDTH, 0);
3170
+ fadeGrad.addColorStop(0, "rgba(0, 0, 0, 1)");
3171
+ fadeGrad.addColorStop(1, "rgba(0, 0, 0, 0)");
3172
+ ctx.fillStyle = fadeGrad;
3173
+ ctx.fillRect(0, 0, pad.left + FADE_EDGE_WIDTH, h);
3174
+ ctx.restore();
3175
+ if (badgeRef.current) badgeRef.current.container.style.display = "none";
3176
+ rafRef.current = requestAnimationFrame(draw);
3177
+ return;
3178
+ }
3179
+ const computedRange = { min: isFinite(globalMin) ? globalMin : 0, max: isFinite(globalMax) ? globalMax : 1 };
3180
+ const adaptiveSpeed = cfg.lerpSpeed + ADAPTIVE_SPEED_BOOST * 0.5;
3181
+ const rangeResult = updateRange(
3182
+ computedRange,
3183
+ rangeInitedRef.current,
3184
+ targetMinRef.current,
3185
+ targetMaxRef.current,
3186
+ displayMinRef.current,
3187
+ displayMaxRef.current,
3188
+ isWindowTransitioning,
3189
+ windowTransProgress,
3190
+ transition,
3191
+ adaptiveSpeed,
3192
+ chartH,
3193
+ pausedDt
3194
+ );
3195
+ rangeInitedRef.current = rangeResult.rangeInited;
3196
+ targetMinRef.current = rangeResult.targetMin;
3197
+ targetMaxRef.current = rangeResult.targetMax;
3198
+ displayMinRef.current = rangeResult.displayMin;
3199
+ displayMaxRef.current = rangeResult.displayMax;
3200
+ const { minVal, maxVal, valRange } = rangeResult;
3201
+ const layout = {
3202
+ w,
3203
+ h,
3204
+ pad,
3205
+ chartW,
3206
+ chartH,
3207
+ leftEdge,
3208
+ rightEdge,
3209
+ minVal,
3210
+ maxVal,
3211
+ valRange,
3212
+ toX: (t) => pad.left + (t - leftEdge) / (rightEdge - leftEdge) * chartW,
3213
+ toY: (v) => pad.top + (1 - (v - minVal) / valRange) * chartH
3214
+ };
3215
+ const hoverPx = hoverXRef.current;
3216
+ let drawHoverX = null;
3217
+ let drawHoverTime = null;
3218
+ let isActiveHover = false;
3219
+ let hoverEntries = [];
3220
+ if (hoverPx !== null && hoverPx >= pad.left && hoverPx <= w - pad.right) {
3221
+ const maxHoverX = layout.toX(now);
3222
+ const clampedX = Math.min(hoverPx, maxHoverX);
3223
+ const t = leftEdge + (clampedX - pad.left) / chartW * (rightEdge - leftEdge);
3224
+ drawHoverX = clampedX;
3225
+ drawHoverTime = t;
3226
+ isActiveHover = true;
3227
+ for (const entry of seriesEntries) {
3228
+ if ((entry.alpha ?? 1) < 0.5) continue;
3229
+ const v = interpolateAtTime(entry.visible, t);
3230
+ if (v !== null) {
3231
+ hoverEntries.push({ color: entry.palette.line, label: entry.label ?? "", value: v });
3232
+ }
3233
+ }
3234
+ lastHoverRef.current = { x: clampedX, value: hoverEntries[0]?.value ?? 0, time: t };
3235
+ lastHoverEntriesRef.current = hoverEntries;
3236
+ cfg.onHover?.({ time: t, value: hoverEntries[0]?.value ?? 0, x: clampedX, y: layout.toY(hoverEntries[0]?.value ?? 0) });
3237
+ }
3238
+ const scrubTarget = isActiveHover ? 1 : 0;
3239
+ if (noMotion) {
3240
+ scrubAmountRef.current = scrubTarget;
3241
+ } else {
3242
+ scrubAmountRef.current += (scrubTarget - scrubAmountRef.current) * SCRUB_LERP_SPEED;
3243
+ if (scrubAmountRef.current < 0.01) scrubAmountRef.current = 0;
3244
+ if (scrubAmountRef.current > 0.99) scrubAmountRef.current = 1;
3245
+ }
3246
+ if (!isActiveHover && scrubAmountRef.current > 0 && lastHoverRef.current) {
3247
+ drawHoverX = lastHoverRef.current.x;
3248
+ drawHoverTime = lastHoverRef.current.time;
3249
+ hoverEntries = lastHoverEntriesRef.current;
3250
+ }
3251
+ drawMultiFrame(ctx, layout, {
3252
+ series: seriesEntries,
3253
+ now,
3254
+ showGrid: cfg.showGrid,
3255
+ showPulse: cfg.showPulse,
3256
+ referenceLine: cfg.referenceLine,
3257
+ hoverX: drawHoverX,
3258
+ hoverTime: drawHoverTime,
3259
+ hoverEntries,
3260
+ scrubAmount: scrubAmountRef.current,
3261
+ windowSecs,
3262
+ formatValue: cfg.formatValue,
3263
+ formatTime: cfg.formatTime,
3264
+ gridState: gridStateRef.current,
3265
+ timeAxisState: timeAxisStateRef.current,
3266
+ dt,
3267
+ targetWindowSecs: cfg.windowSecs,
3268
+ tooltipY: cfg.tooltipY,
3269
+ tooltipOutline: cfg.tooltipOutline,
3270
+ chartReveal,
3271
+ pauseProgress,
3272
+ now_ms,
3273
+ primaryPalette: cfg.palette
3274
+ });
3275
+ const bgAlpha = 1 - chartReveal;
3276
+ if (bgAlpha > 0.01 && revealTarget === 0 && !cfg.loading) {
3277
+ const bgEmptyAlpha = (1 - loadingAlpha) * bgAlpha;
3278
+ if (bgEmptyAlpha > 0.01) {
3279
+ drawEmpty(ctx, w, h, pad, cfg.palette, bgEmptyAlpha, now_ms, true, cfg.emptyText);
3280
+ }
3281
+ }
3282
+ if (badgeRef.current) badgeRef.current.container.style.display = "none";
2751
3283
  } else {
2752
3284
  const effectivePoints = useStash ? lastDataRef.current : points;
2753
3285
  const adaptiveSpeed = computeAdaptiveSpeed(
@@ -2769,8 +3301,9 @@ function useLivelineEngine(canvasRef, containerRef, config) {
2769
3301
  }
2770
3302
  const smoothValue = displayValueRef.current;
2771
3303
  const chartW = w - pad.left - pad.right;
2772
- const needsArrowRoom = cfg.showMomentum;
2773
- const buffer = needsArrowRoom ? Math.max(WINDOW_BUFFER, 37 / Math.max(chartW, 1)) : WINDOW_BUFFER;
3304
+ const baseBuffer = cfg.showBadge ? WINDOW_BUFFER : WINDOW_BUFFER_NO_BADGE;
3305
+ const needsArrowRoom = cfg.showMomentum && cfg.showBadge;
3306
+ const buffer = needsArrowRoom ? Math.max(baseBuffer, 37 / Math.max(chartW, 1)) : baseBuffer;
2774
3307
  const transition = windowTransitionRef.current;
2775
3308
  if (hasData) frozenNowRef.current = Date.now() / 1e3 - timeDebtRef.current;
2776
3309
  const now = useStash ? frozenNowRef.current : Date.now() / 1e3 - timeDebtRef.current;
@@ -2957,6 +3490,7 @@ var defaultFormatTime = (t) => {
2957
3490
  function Liveline({
2958
3491
  data,
2959
3492
  value,
3493
+ series: seriesProp,
2960
3494
  theme = "dark",
2961
3495
  color = "#3b82f6",
2962
3496
  window: windowSecs = 30,
@@ -2996,6 +3530,9 @@ function Liveline({
2996
3530
  lineData,
2997
3531
  lineValue,
2998
3532
  onModeChange,
3533
+ onSeriesToggle,
3534
+ seriesToggleCompact = false,
3535
+ lineWidth,
2999
3536
  className,
3000
3537
  style
3001
3538
  }) {
@@ -3008,13 +3545,37 @@ function Liveline({
3008
3545
  const modeBarRef = useRef2(null);
3009
3546
  const modeBtnRefs = useRef2(/* @__PURE__ */ new Map());
3010
3547
  const [modeIndicatorStyle, setModeIndicatorStyle] = useState(null);
3011
- const palette = useMemo(() => resolveTheme(color, theme), [color, theme]);
3548
+ const [hiddenSeries, setHiddenSeries] = useState(/* @__PURE__ */ new Set());
3549
+ const lastSeriesPropRef = useRef2(seriesProp);
3550
+ if (seriesProp && seriesProp.length > 0) lastSeriesPropRef.current = seriesProp;
3551
+ const palette = useMemo(() => {
3552
+ const p = resolveTheme(color, theme);
3553
+ if (lineWidth != null) p.lineWidth = lineWidth;
3554
+ return p;
3555
+ }, [color, theme, lineWidth]);
3012
3556
  const isDark = theme === "dark";
3557
+ const isMultiSeries = seriesProp != null && seriesProp.length > 0;
3558
+ const showSeriesToggle = (lastSeriesPropRef.current?.length ?? 0) > 1;
3559
+ const seriesPalettes = useMemo(() => {
3560
+ if (!seriesProp || seriesProp.length === 0) return null;
3561
+ return resolveSeriesPalettes(seriesProp, theme);
3562
+ }, [seriesProp, theme]);
3563
+ const multiSeries = useMemo(() => {
3564
+ if (!seriesProp || !seriesPalettes) return void 0;
3565
+ return seriesProp.map((s, i) => ({
3566
+ id: s.id,
3567
+ data: s.data,
3568
+ value: s.value,
3569
+ palette: seriesPalettes.get(s.id) ?? resolveTheme(s.color || SERIES_COLORS[i % SERIES_COLORS.length], theme),
3570
+ label: s.label
3571
+ }));
3572
+ }, [seriesProp, seriesPalettes, theme]);
3013
3573
  const showMomentum = momentum !== false;
3014
3574
  const momentumOverride = typeof momentum === "string" ? momentum : void 0;
3575
+ const defaultRight = badge ? 80 : grid ? 54 : 12;
3015
3576
  const pad = {
3016
3577
  top: paddingOverride?.top ?? 12,
3017
- right: paddingOverride?.right ?? 80,
3578
+ right: paddingOverride?.right ?? defaultRight,
3018
3579
  bottom: paddingOverride?.bottom ?? 28,
3019
3580
  left: paddingOverride?.left ?? 12
3020
3581
  };
@@ -3051,6 +3612,22 @@ function Liveline({
3051
3612
  });
3052
3613
  }
3053
3614
  }, [activeMode, onModeChange]);
3615
+ const handleSeriesToggle = useCallback2((id) => {
3616
+ setHiddenSeries((prev) => {
3617
+ const next = new Set(prev);
3618
+ if (next.has(id)) {
3619
+ next.delete(id);
3620
+ onSeriesToggle?.(id, true);
3621
+ } else {
3622
+ const totalSeries = seriesProp?.length ?? 0;
3623
+ const visibleCount = totalSeries - next.size;
3624
+ if (visibleCount <= 1) return prev;
3625
+ next.add(id);
3626
+ onSeriesToggle?.(id, false);
3627
+ }
3628
+ return next;
3629
+ });
3630
+ }, [seriesProp?.length, onSeriesToggle]);
3054
3631
  const ws = windowStyle ?? "default";
3055
3632
  useLivelineEngine(canvasRef, containerRef, {
3056
3633
  data,
@@ -3059,10 +3636,10 @@ function Liveline({
3059
3636
  windowSecs: effectiveWindowSecs,
3060
3637
  lerpSpeed,
3061
3638
  showGrid: grid,
3062
- showBadge: badge,
3063
- showMomentum,
3639
+ showBadge: isMultiSeries ? false : badge,
3640
+ showMomentum: isMultiSeries ? false : showMomentum,
3064
3641
  momentumOverride,
3065
- showFill: fill,
3642
+ showFill: isMultiSeries ? false : fill,
3066
3643
  referenceLine,
3067
3644
  formatValue,
3068
3645
  formatTime,
@@ -3071,7 +3648,7 @@ function Liveline({
3071
3648
  showPulse: pulse,
3072
3649
  scrub,
3073
3650
  exaggerate,
3074
- degenOptions,
3651
+ degenOptions: isMultiSeries ? void 0 : degenOptions,
3075
3652
  badgeTail,
3076
3653
  badgeVariant,
3077
3654
  tooltipY,
@@ -3088,7 +3665,10 @@ function Liveline({
3088
3665
  liveCandle,
3089
3666
  lineMode,
3090
3667
  lineData,
3091
- lineValue
3668
+ lineValue,
3669
+ multiSeries,
3670
+ isMultiSeries,
3671
+ hiddenSeriesIds: hiddenSeries
3092
3672
  });
3093
3673
  const cursorStyle = scrub ? cursor : "default";
3094
3674
  const activeColor = isDark ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.55)";
@@ -3112,7 +3692,7 @@ function Liveline({
3112
3692
  }
3113
3693
  }
3114
3694
  ),
3115
- (windows && windows.length > 0 || onModeChange) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6, marginLeft: pad.left }, children: [
3695
+ (windows && windows.length > 0 || onModeChange || showSeriesToggle) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6, marginLeft: pad.left }, children: [
3116
3696
  windows && windows.length > 0 && /* @__PURE__ */ jsxs(
3117
3697
  "div",
3118
3698
  {
@@ -3180,20 +3760,20 @@ function Liveline({
3180
3760
  style: {
3181
3761
  position: "relative",
3182
3762
  display: "inline-flex",
3183
- gap: 2,
3184
- background: isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
3185
- borderRadius: 6,
3186
- padding: 2
3763
+ gap: ws === "text" ? 4 : 2,
3764
+ background: ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
3765
+ borderRadius: ws === "rounded" ? 999 : 6,
3766
+ padding: ws === "text" ? 0 : ws === "rounded" ? 3 : 2
3187
3767
  },
3188
3768
  children: [
3189
- modeIndicatorStyle && /* @__PURE__ */ jsx("div", { style: {
3769
+ ws !== "text" && modeIndicatorStyle && /* @__PURE__ */ jsx("div", { style: {
3190
3770
  position: "absolute",
3191
- top: 2,
3771
+ top: ws === "rounded" ? 3 : 2,
3192
3772
  left: modeIndicatorStyle.left,
3193
3773
  width: modeIndicatorStyle.width,
3194
- height: "calc(100% - 4px)",
3774
+ height: ws === "rounded" ? "calc(100% - 6px)" : "calc(100% - 4px)",
3195
3775
  background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)",
3196
- borderRadius: 4,
3776
+ borderRadius: ws === "rounded" ? 999 : 4,
3197
3777
  transition: "left 0.25s cubic-bezier(0.4, 0, 0.2, 1), width 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
3198
3778
  pointerEvents: "none"
3199
3779
  } }),
@@ -3209,7 +3789,7 @@ function Liveline({
3209
3789
  position: "relative",
3210
3790
  zIndex: 1,
3211
3791
  padding: "5px 7px",
3212
- borderRadius: 4,
3792
+ borderRadius: ws === "rounded" ? 999 : 4,
3213
3793
  border: "none",
3214
3794
  cursor: "pointer",
3215
3795
  background: "transparent",
@@ -3240,7 +3820,7 @@ function Liveline({
3240
3820
  position: "relative",
3241
3821
  zIndex: 1,
3242
3822
  padding: "5px 7px",
3243
- borderRadius: 4,
3823
+ borderRadius: ws === "rounded" ? 999 : 4,
3244
3824
  border: "none",
3245
3825
  cursor: "pointer",
3246
3826
  background: "transparent",
@@ -3297,7 +3877,58 @@ function Liveline({
3297
3877
  )
3298
3878
  ]
3299
3879
  }
3300
- )
3880
+ ),
3881
+ showSeriesToggle && /* @__PURE__ */ jsx("div", { style: {
3882
+ display: "inline-flex",
3883
+ gap: ws === "text" ? 4 : 2,
3884
+ background: ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
3885
+ borderRadius: ws === "rounded" ? 999 : 6,
3886
+ padding: ws === "text" ? 0 : ws === "rounded" ? 3 : 2,
3887
+ opacity: isMultiSeries ? 1 : 0,
3888
+ transition: "opacity 0.4s",
3889
+ pointerEvents: isMultiSeries ? "auto" : "none"
3890
+ }, children: (lastSeriesPropRef.current ?? []).map((s, si) => {
3891
+ const isHidden = hiddenSeries.has(s.id);
3892
+ const seriesColor = s.color || SERIES_COLORS[si % SERIES_COLORS.length];
3893
+ return /* @__PURE__ */ jsxs(
3894
+ "button",
3895
+ {
3896
+ onClick: () => handleSeriesToggle(s.id),
3897
+ style: {
3898
+ position: "relative",
3899
+ zIndex: 1,
3900
+ fontSize: 11,
3901
+ padding: seriesToggleCompact ? ws === "text" ? "2px 4px" : "5px 7px" : ws === "text" ? "2px 6px" : "3px 8px",
3902
+ borderRadius: ws === "rounded" ? 999 : 4,
3903
+ border: "none",
3904
+ cursor: "pointer",
3905
+ fontFamily: "system-ui, -apple-system, sans-serif",
3906
+ fontWeight: 500,
3907
+ background: isHidden ? "transparent" : ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)",
3908
+ color: isHidden ? inactiveColor : activeColor,
3909
+ opacity: isHidden ? 0.4 : 1,
3910
+ transition: "opacity 0.2s, background 0.15s, color 0.2s",
3911
+ lineHeight: "16px",
3912
+ display: "flex",
3913
+ alignItems: "center",
3914
+ gap: seriesToggleCompact ? 0 : 4
3915
+ },
3916
+ children: [
3917
+ /* @__PURE__ */ jsx("span", { style: {
3918
+ width: seriesToggleCompact ? 8 : 6,
3919
+ height: seriesToggleCompact ? 8 : 6,
3920
+ borderRadius: "50%",
3921
+ background: seriesColor,
3922
+ flexShrink: 0,
3923
+ opacity: isHidden ? 0.4 : 1,
3924
+ transition: "opacity 0.2s"
3925
+ } }),
3926
+ !seriesToggleCompact && (s.label ?? s.id)
3927
+ ]
3928
+ },
3929
+ s.id
3930
+ );
3931
+ }) })
3301
3932
  ] }),
3302
3933
  /* @__PURE__ */ jsx(
3303
3934
  "div",