liveline 0.0.5 → 0.0.6
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.cjs +644 -22
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +645 -23
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -88,6 +88,33 @@ function resolveTheme(color, mode) {
|
|
|
88
88
|
badgeFont: '500 11px "SF Mono", Menlo, monospace'
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
var SERIES_COLORS = [
|
|
92
|
+
"#3b82f6",
|
|
93
|
+
// blue
|
|
94
|
+
"#ef4444",
|
|
95
|
+
// red
|
|
96
|
+
"#22c55e",
|
|
97
|
+
// green
|
|
98
|
+
"#f59e0b",
|
|
99
|
+
// amber
|
|
100
|
+
"#8b5cf6",
|
|
101
|
+
// violet
|
|
102
|
+
"#ec4899",
|
|
103
|
+
// pink
|
|
104
|
+
"#06b6d4",
|
|
105
|
+
// cyan
|
|
106
|
+
"#f97316"
|
|
107
|
+
// orange
|
|
108
|
+
];
|
|
109
|
+
function resolveSeriesPalettes(series, mode) {
|
|
110
|
+
const map = /* @__PURE__ */ new Map();
|
|
111
|
+
for (let i = 0; i < series.length; i++) {
|
|
112
|
+
const s = series[i];
|
|
113
|
+
const color = s.color || SERIES_COLORS[i % SERIES_COLORS.length];
|
|
114
|
+
map.set(s.id, resolveTheme(color, mode));
|
|
115
|
+
}
|
|
116
|
+
return map;
|
|
117
|
+
}
|
|
91
118
|
|
|
92
119
|
// src/useLivelineEngine.ts
|
|
93
120
|
var import_react = require("react");
|
|
@@ -513,6 +540,33 @@ function drawDot(ctx, x, y, palette, pulse = true, scrubAmount = 0, now_ms = per
|
|
|
513
540
|
}
|
|
514
541
|
ctx.fill();
|
|
515
542
|
}
|
|
543
|
+
function drawMultiDot(ctx, x, y, color, pulse = true, now_ms = performance.now(), radius = 3) {
|
|
544
|
+
const baseAlpha = ctx.globalAlpha;
|
|
545
|
+
if (pulse) {
|
|
546
|
+
const t = now_ms % PULSE_INTERVAL / PULSE_DURATION;
|
|
547
|
+
if (t < 1) {
|
|
548
|
+
const ringRadius = 9 + t * 10;
|
|
549
|
+
const pulseAlpha = 0.3 * (1 - t);
|
|
550
|
+
ctx.beginPath();
|
|
551
|
+
ctx.arc(x, y, ringRadius, 0, Math.PI * 2);
|
|
552
|
+
ctx.strokeStyle = color;
|
|
553
|
+
ctx.lineWidth = 1.5;
|
|
554
|
+
ctx.globalAlpha = baseAlpha * pulseAlpha;
|
|
555
|
+
ctx.stroke();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
ctx.globalAlpha = baseAlpha;
|
|
559
|
+
ctx.beginPath();
|
|
560
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
561
|
+
ctx.fillStyle = color;
|
|
562
|
+
ctx.fill();
|
|
563
|
+
}
|
|
564
|
+
function drawSimpleDot(ctx, x, y, color, radius = 3) {
|
|
565
|
+
ctx.beginPath();
|
|
566
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
567
|
+
ctx.fillStyle = color;
|
|
568
|
+
ctx.fill();
|
|
569
|
+
}
|
|
516
570
|
function drawArrows(ctx, x, y, momentum, palette, arrows, dt, now_ms = performance.now()) {
|
|
517
571
|
const baseAlpha = ctx.globalAlpha;
|
|
518
572
|
const upTarget = momentum === "up" ? 1 : 0;
|
|
@@ -611,6 +665,90 @@ function drawCrosshair(ctx, layout, palette, hoverX, hoverValue, hoverTime, form
|
|
|
611
665
|
ctx.fillText(separator + timeText, tx + valueW, ty);
|
|
612
666
|
ctx.restore();
|
|
613
667
|
}
|
|
668
|
+
function drawMultiCrosshair(ctx, layout, palette, hoverX, hoverTime, entries, formatValue, formatTime, scrubOpacity, tooltipY, tooltipOutline, liveDotX) {
|
|
669
|
+
if (scrubOpacity < 0.01 || entries.length === 0) return;
|
|
670
|
+
const { h, pad, toY } = layout;
|
|
671
|
+
ctx.save();
|
|
672
|
+
ctx.globalAlpha = scrubOpacity * 0.5;
|
|
673
|
+
ctx.strokeStyle = palette.crosshairLine;
|
|
674
|
+
ctx.lineWidth = 1;
|
|
675
|
+
ctx.beginPath();
|
|
676
|
+
ctx.moveTo(hoverX, pad.top);
|
|
677
|
+
ctx.lineTo(hoverX, h - pad.bottom);
|
|
678
|
+
ctx.stroke();
|
|
679
|
+
ctx.restore();
|
|
680
|
+
const dotRadius = 4 * Math.min(scrubOpacity * 3, 1);
|
|
681
|
+
if (dotRadius > 0.5) {
|
|
682
|
+
ctx.globalAlpha = 1;
|
|
683
|
+
for (const entry of entries) {
|
|
684
|
+
const y = toY(entry.value);
|
|
685
|
+
ctx.beginPath();
|
|
686
|
+
ctx.arc(hoverX, y, dotRadius, 0, Math.PI * 2);
|
|
687
|
+
ctx.fillStyle = entry.color;
|
|
688
|
+
ctx.fill();
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (scrubOpacity < 0.1 || layout.w < 300) return;
|
|
692
|
+
ctx.save();
|
|
693
|
+
ctx.globalAlpha = scrubOpacity;
|
|
694
|
+
ctx.font = '400 13px "SF Mono", Menlo, monospace';
|
|
695
|
+
ctx.textAlign = "left";
|
|
696
|
+
const timeText = formatTime(hoverTime);
|
|
697
|
+
const sep = " \xB7 ";
|
|
698
|
+
const dotInline = " ";
|
|
699
|
+
const segments = [
|
|
700
|
+
{ text: timeText, color: palette.gridLabel }
|
|
701
|
+
];
|
|
702
|
+
for (const e of entries) {
|
|
703
|
+
segments.push({ text: sep, color: palette.gridLabel });
|
|
704
|
+
segments.push({ text: dotInline, color: e.color, isDot: true });
|
|
705
|
+
const label = e.label ? `${e.label} ` : "";
|
|
706
|
+
if (label) segments.push({ text: label, color: palette.gridLabel });
|
|
707
|
+
segments.push({ text: formatValue(e.value), color: palette.tooltipText });
|
|
708
|
+
}
|
|
709
|
+
let totalW = 0;
|
|
710
|
+
const segWidths = [];
|
|
711
|
+
for (const seg of segments) {
|
|
712
|
+
const w = seg.isDot ? 12 : ctx.measureText(seg.text).width;
|
|
713
|
+
segWidths.push(w);
|
|
714
|
+
totalW += w;
|
|
715
|
+
}
|
|
716
|
+
let tx = hoverX - totalW / 2;
|
|
717
|
+
const minX = pad.left + 4;
|
|
718
|
+
const dotRightEdge = liveDotX != null ? liveDotX + 7 : layout.w - pad.right;
|
|
719
|
+
const maxX = dotRightEdge - totalW;
|
|
720
|
+
if (tx < minX) tx = minX;
|
|
721
|
+
if (tx > maxX) tx = maxX;
|
|
722
|
+
const ty = pad.top + (tooltipY ?? 14) + 10;
|
|
723
|
+
if (tooltipOutline !== false) {
|
|
724
|
+
ctx.strokeStyle = palette.tooltipBg;
|
|
725
|
+
ctx.lineWidth = 3;
|
|
726
|
+
ctx.lineJoin = "round";
|
|
727
|
+
let ox2 = tx;
|
|
728
|
+
for (let i = 0; i < segments.length; i++) {
|
|
729
|
+
const seg = segments[i];
|
|
730
|
+
if (!seg.isDot) {
|
|
731
|
+
ctx.strokeText(seg.text, ox2, ty);
|
|
732
|
+
}
|
|
733
|
+
ox2 += segWidths[i];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
let ox = tx;
|
|
737
|
+
for (let i = 0; i < segments.length; i++) {
|
|
738
|
+
const seg = segments[i];
|
|
739
|
+
if (seg.isDot) {
|
|
740
|
+
ctx.beginPath();
|
|
741
|
+
ctx.arc(ox + 4, ty - 4, 3.5, 0, Math.PI * 2);
|
|
742
|
+
ctx.fillStyle = seg.color;
|
|
743
|
+
ctx.fill();
|
|
744
|
+
} else {
|
|
745
|
+
ctx.fillStyle = seg.color;
|
|
746
|
+
ctx.fillText(seg.text, ox, ty);
|
|
747
|
+
}
|
|
748
|
+
ox += segWidths[i];
|
|
749
|
+
}
|
|
750
|
+
ctx.restore();
|
|
751
|
+
}
|
|
614
752
|
|
|
615
753
|
// src/draw/referenceLine.ts
|
|
616
754
|
function drawReferenceLine(ctx, layout, palette, ref) {
|
|
@@ -1457,6 +1595,124 @@ function drawFrame(ctx, layout, palette, opts) {
|
|
|
1457
1595
|
ctx.restore();
|
|
1458
1596
|
}
|
|
1459
1597
|
}
|
|
1598
|
+
function drawMultiFrame(ctx, layout, opts) {
|
|
1599
|
+
const palette = opts.primaryPalette;
|
|
1600
|
+
const reveal = opts.chartReveal;
|
|
1601
|
+
const revealRamp = (start, end) => {
|
|
1602
|
+
const t = Math.max(0, Math.min(1, (reveal - start) / (end - start)));
|
|
1603
|
+
return t * t * (3 - 2 * t);
|
|
1604
|
+
};
|
|
1605
|
+
if (opts.referenceLine && reveal > 0.01) {
|
|
1606
|
+
ctx.save();
|
|
1607
|
+
if (reveal < 1) ctx.globalAlpha = reveal;
|
|
1608
|
+
drawReferenceLine(ctx, layout, palette, opts.referenceLine);
|
|
1609
|
+
ctx.restore();
|
|
1610
|
+
}
|
|
1611
|
+
if (opts.showGrid) {
|
|
1612
|
+
const gridAlpha = reveal < 1 ? revealRamp(0.15, 0.7) : 1;
|
|
1613
|
+
if (gridAlpha > 0.01) {
|
|
1614
|
+
ctx.save();
|
|
1615
|
+
if (gridAlpha < 1) ctx.globalAlpha = gridAlpha;
|
|
1616
|
+
drawGrid(ctx, layout, palette, opts.formatValue, opts.gridState, opts.dt);
|
|
1617
|
+
ctx.restore();
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
const scrubX = opts.scrubAmount > 0.05 ? opts.hoverX : null;
|
|
1621
|
+
const allPts = [];
|
|
1622
|
+
for (let si = 0; si < opts.series.length; si++) {
|
|
1623
|
+
const s = opts.series[si];
|
|
1624
|
+
const seriesAlpha = s.alpha ?? 1;
|
|
1625
|
+
const secondaryFade = si > 0 && reveal < 1 ? Math.min(1, reveal * 2) : 1;
|
|
1626
|
+
const combinedAlpha = secondaryFade * seriesAlpha;
|
|
1627
|
+
if (combinedAlpha < 0.01) continue;
|
|
1628
|
+
ctx.save();
|
|
1629
|
+
if (combinedAlpha < 1) ctx.globalAlpha = combinedAlpha;
|
|
1630
|
+
const pts = drawLine(
|
|
1631
|
+
ctx,
|
|
1632
|
+
layout,
|
|
1633
|
+
s.palette,
|
|
1634
|
+
s.visible,
|
|
1635
|
+
s.smoothValue,
|
|
1636
|
+
opts.now,
|
|
1637
|
+
false,
|
|
1638
|
+
// no fill
|
|
1639
|
+
scrubX,
|
|
1640
|
+
opts.scrubAmount,
|
|
1641
|
+
reveal,
|
|
1642
|
+
opts.now_ms
|
|
1643
|
+
);
|
|
1644
|
+
ctx.restore();
|
|
1645
|
+
if (pts && pts.length > 0) {
|
|
1646
|
+
allPts.push({ pts, palette: s.palette, label: s.label, alpha: seriesAlpha });
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
{
|
|
1650
|
+
const timeAlpha = reveal < 1 ? revealRamp(0.15, 0.7) : 1;
|
|
1651
|
+
if (timeAlpha > 0.01) {
|
|
1652
|
+
ctx.save();
|
|
1653
|
+
if (timeAlpha < 1) ctx.globalAlpha = timeAlpha;
|
|
1654
|
+
drawTimeAxis(ctx, layout, palette, opts.windowSecs, opts.targetWindowSecs, opts.formatTime, opts.timeAxisState, opts.dt);
|
|
1655
|
+
ctx.restore();
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (reveal > 0.3 && allPts.length > 0) {
|
|
1659
|
+
const dotAlpha = (reveal - 0.3) / 0.7;
|
|
1660
|
+
const showPulse = opts.showPulse && reveal > 0.6 && opts.pauseProgress < 0.5;
|
|
1661
|
+
for (const entry of allPts) {
|
|
1662
|
+
if (entry.alpha < 0.01) continue;
|
|
1663
|
+
const lastPt = entry.pts[entry.pts.length - 1];
|
|
1664
|
+
ctx.save();
|
|
1665
|
+
ctx.globalAlpha = dotAlpha * entry.alpha;
|
|
1666
|
+
if (showPulse && entry.alpha > 0.5) {
|
|
1667
|
+
drawMultiDot(ctx, lastPt[0], lastPt[1], entry.palette.line, true, opts.now_ms, 3);
|
|
1668
|
+
} else {
|
|
1669
|
+
drawSimpleDot(ctx, lastPt[0], lastPt[1], entry.palette.line, 3);
|
|
1670
|
+
}
|
|
1671
|
+
if (entry.label) {
|
|
1672
|
+
ctx.font = '600 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif';
|
|
1673
|
+
ctx.textAlign = "left";
|
|
1674
|
+
ctx.fillStyle = entry.palette.line;
|
|
1675
|
+
ctx.fillText(entry.label, lastPt[0] + 6, lastPt[1] + 3.5);
|
|
1676
|
+
}
|
|
1677
|
+
ctx.restore();
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
ctx.save();
|
|
1681
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
1682
|
+
const fadeGrad = ctx.createLinearGradient(layout.pad.left, 0, layout.pad.left + FADE_EDGE_WIDTH, 0);
|
|
1683
|
+
fadeGrad.addColorStop(0, "rgba(0, 0, 0, 1)");
|
|
1684
|
+
fadeGrad.addColorStop(1, "rgba(0, 0, 0, 0)");
|
|
1685
|
+
ctx.fillStyle = fadeGrad;
|
|
1686
|
+
ctx.fillRect(0, 0, layout.pad.left + FADE_EDGE_WIDTH, layout.h);
|
|
1687
|
+
ctx.restore();
|
|
1688
|
+
if (opts.hoverX !== null && opts.hoverTime !== null && opts.hoverEntries.length > 0 && allPts.length > 0 && opts.scrubAmount > 0.01) {
|
|
1689
|
+
let maxLiveDotX = 0;
|
|
1690
|
+
for (const entry of allPts) {
|
|
1691
|
+
if (entry.alpha < 0.01) continue;
|
|
1692
|
+
const lastX = entry.pts[entry.pts.length - 1][0];
|
|
1693
|
+
if (lastX > maxLiveDotX) maxLiveDotX = lastX;
|
|
1694
|
+
}
|
|
1695
|
+
const distToLive = maxLiveDotX - opts.hoverX;
|
|
1696
|
+
const fadeStart = Math.min(80, layout.chartW * 0.3);
|
|
1697
|
+
const scrubOpacity = distToLive < CROSSHAIR_FADE_MIN_PX ? 0 : distToLive >= fadeStart ? opts.scrubAmount : (distToLive - CROSSHAIR_FADE_MIN_PX) / (fadeStart - CROSSHAIR_FADE_MIN_PX) * opts.scrubAmount;
|
|
1698
|
+
if (scrubOpacity > 0.01) {
|
|
1699
|
+
drawMultiCrosshair(
|
|
1700
|
+
ctx,
|
|
1701
|
+
layout,
|
|
1702
|
+
palette,
|
|
1703
|
+
opts.hoverX,
|
|
1704
|
+
opts.hoverTime,
|
|
1705
|
+
opts.hoverEntries,
|
|
1706
|
+
opts.formatValue,
|
|
1707
|
+
opts.formatTime,
|
|
1708
|
+
scrubOpacity,
|
|
1709
|
+
opts.tooltipY,
|
|
1710
|
+
opts.tooltipOutline,
|
|
1711
|
+
maxLiveDotX
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1460
1716
|
function drawCandleFrame(ctx, layout, palette, opts) {
|
|
1461
1717
|
const { w, h, pad, chartW, chartH } = layout;
|
|
1462
1718
|
const reveal = opts.chartReveal;
|
|
@@ -1739,6 +1995,7 @@ var PAUSE_PROGRESS_SPEED = 0.12;
|
|
|
1739
1995
|
var PAUSE_CATCHUP_SPEED = 0.08;
|
|
1740
1996
|
var PAUSE_CATCHUP_SPEED_FAST = 0.22;
|
|
1741
1997
|
var LOADING_ALPHA_SPEED = 0.14;
|
|
1998
|
+
var SERIES_TOGGLE_SPEED = 0.1;
|
|
1742
1999
|
var CANDLE_LERP_SPEED = 0.25;
|
|
1743
2000
|
var CANDLE_WIDTH_TRANS_MS = 300;
|
|
1744
2001
|
var LINE_MORPH_MS = 500;
|
|
@@ -2062,6 +2319,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2062
2319
|
const configRef = (0, import_react.useRef)(config);
|
|
2063
2320
|
configRef.current = config;
|
|
2064
2321
|
const displayValueRef = (0, import_react.useRef)(config.value);
|
|
2322
|
+
const displayValuesRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
2323
|
+
const seriesAlphaRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
2065
2324
|
const displayMinRef = (0, import_react.useRef)(0);
|
|
2066
2325
|
const displayMaxRef = (0, import_react.useRef)(0);
|
|
2067
2326
|
const targetMinRef = (0, import_react.useRef)(0);
|
|
@@ -2094,12 +2353,15 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2094
2353
|
const hoverXRef = (0, import_react.useRef)(null);
|
|
2095
2354
|
const scrubAmountRef = (0, import_react.useRef)(0);
|
|
2096
2355
|
const lastHoverRef = (0, import_react.useRef)(null);
|
|
2356
|
+
const lastHoverEntriesRef = (0, import_react.useRef)([]);
|
|
2097
2357
|
const chartRevealRef = (0, import_react.useRef)(0);
|
|
2098
2358
|
const pauseProgressRef = (0, import_react.useRef)(0);
|
|
2099
2359
|
const timeDebtRef = (0, import_react.useRef)(0);
|
|
2100
2360
|
const lastDataRef = (0, import_react.useRef)([]);
|
|
2361
|
+
const lastMultiSeriesRef = (0, import_react.useRef)([]);
|
|
2101
2362
|
const frozenNowRef = (0, import_react.useRef)(0);
|
|
2102
2363
|
const pausedDataRef = (0, import_react.useRef)(null);
|
|
2364
|
+
const pausedMultiDataRef = (0, import_react.useRef)(null);
|
|
2103
2365
|
const loadingAlphaRef = (0, import_react.useRef)(config.loading ? 1 : 0);
|
|
2104
2366
|
const displayCandleRef = (0, import_react.useRef)(null);
|
|
2105
2367
|
const liveBirthAlphaRef = (0, import_react.useRef)(1);
|
|
@@ -2279,6 +2541,17 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2279
2541
|
pausedLineDataRef.current = null;
|
|
2280
2542
|
pausedLineValueRef.current = null;
|
|
2281
2543
|
}
|
|
2544
|
+
} else if (cfg.isMultiSeries && cfg.multiSeries) {
|
|
2545
|
+
if (cfg.paused && pausedMultiDataRef.current === null) {
|
|
2546
|
+
const snap = /* @__PURE__ */ new Map();
|
|
2547
|
+
for (const s of cfg.multiSeries) {
|
|
2548
|
+
if (s.data.length >= 2) snap.set(s.id, { data: s.data.slice(), value: s.value });
|
|
2549
|
+
}
|
|
2550
|
+
if (snap.size > 0) pausedMultiDataRef.current = snap;
|
|
2551
|
+
}
|
|
2552
|
+
if (!cfg.paused) {
|
|
2553
|
+
pausedMultiDataRef.current = null;
|
|
2554
|
+
}
|
|
2282
2555
|
} else {
|
|
2283
2556
|
if (cfg.paused && pausedDataRef.current === null && cfg.data.length >= 2) {
|
|
2284
2557
|
pausedDataRef.current = cfg.data.slice();
|
|
@@ -2289,7 +2562,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2289
2562
|
}
|
|
2290
2563
|
const points = isCandle ? [] : pausedDataRef.current ?? cfg.data;
|
|
2291
2564
|
const effectiveCandles = isCandle ? pausedCandlesRef.current ?? (cfg.candles ?? []) : [];
|
|
2292
|
-
const
|
|
2565
|
+
const hasMultiData = cfg.isMultiSeries && cfg.multiSeries ? cfg.multiSeries.some((s) => s.data.length >= 2) : false;
|
|
2566
|
+
const hasData = isCandle ? effectiveCandles.length >= 2 : hasMultiData || points.length >= 2;
|
|
2293
2567
|
const pad = cfg.padding;
|
|
2294
2568
|
const chartH = h - pad.top - pad.bottom;
|
|
2295
2569
|
const pauseTarget = cfg.paused ? 1 : 0;
|
|
@@ -2325,11 +2599,23 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2325
2599
|
rangeInitedRef.current = false;
|
|
2326
2600
|
}
|
|
2327
2601
|
let useStash;
|
|
2602
|
+
let useMultiStash = false;
|
|
2328
2603
|
if (isCandle) {
|
|
2329
2604
|
useStash = !hasData && chartReveal > 5e-3 && lastCandlesRef.current.length > 0;
|
|
2330
2605
|
} else {
|
|
2331
|
-
|
|
2332
|
-
if (
|
|
2606
|
+
useMultiStash = !hasData && chartReveal > 5e-3 && lastMultiSeriesRef.current.length > 0;
|
|
2607
|
+
if (hasMultiData && cfg.multiSeries) {
|
|
2608
|
+
lastMultiSeriesRef.current = cfg.multiSeries.map((s) => ({
|
|
2609
|
+
id: s.id,
|
|
2610
|
+
data: s.data.slice(),
|
|
2611
|
+
value: s.value,
|
|
2612
|
+
palette: s.palette,
|
|
2613
|
+
label: s.label
|
|
2614
|
+
}));
|
|
2615
|
+
}
|
|
2616
|
+
if (hasData && !cfg.isMultiSeries) lastMultiSeriesRef.current = [];
|
|
2617
|
+
useStash = !useMultiStash && !hasData && chartReveal > 5e-3 && lastDataRef.current.length >= 2;
|
|
2618
|
+
if (hasData && !cfg.isMultiSeries) lastDataRef.current = points;
|
|
2333
2619
|
}
|
|
2334
2620
|
if (isCandle) {
|
|
2335
2621
|
const lmt = lineModeTransRef.current;
|
|
@@ -2351,8 +2637,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2351
2637
|
lineModeProgRef.current = lmt.to;
|
|
2352
2638
|
}
|
|
2353
2639
|
}
|
|
2354
|
-
if (!hasData && !useStash) {
|
|
2355
|
-
const loadingColor = isCandle ? cfg.palette.gridLabel : void 0;
|
|
2640
|
+
if (!hasData && !useStash && !useMultiStash) {
|
|
2641
|
+
const loadingColor = isCandle || cfg.isMultiSeries || lastMultiSeriesRef.current.length > 0 ? cfg.palette.gridLabel : void 0;
|
|
2356
2642
|
if (loadingAlpha > 0.01) {
|
|
2357
2643
|
drawLoading(ctx, w, h, pad, cfg.palette, now_ms, loadingAlpha, loadingColor);
|
|
2358
2644
|
}
|
|
@@ -2775,6 +3061,250 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2775
3061
|
badgeRef.current.container.style.display = "none";
|
|
2776
3062
|
}
|
|
2777
3063
|
}
|
|
3064
|
+
} else if (cfg.isMultiSeries && cfg.multiSeries && cfg.multiSeries.length > 0 || useMultiStash) {
|
|
3065
|
+
const effectiveMultiSeries = useMultiStash ? lastMultiSeriesRef.current : cfg.multiSeries;
|
|
3066
|
+
let labelReserve = 0;
|
|
3067
|
+
if (effectiveMultiSeries.some((s) => s.label)) {
|
|
3068
|
+
ctx.font = '600 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif';
|
|
3069
|
+
let maxLabelW = 0;
|
|
3070
|
+
for (const s of effectiveMultiSeries) {
|
|
3071
|
+
if (s.label) {
|
|
3072
|
+
const lw = ctx.measureText(s.label).width;
|
|
3073
|
+
if (lw > maxLabelW) maxLabelW = lw;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
labelReserve = Math.max(0, maxLabelW - 2) * chartReveal;
|
|
3077
|
+
}
|
|
3078
|
+
const chartW = w - pad.left - pad.right - labelReserve;
|
|
3079
|
+
const buffer = WINDOW_BUFFER;
|
|
3080
|
+
if (!useMultiStash) {
|
|
3081
|
+
const currentIds = new Set(effectiveMultiSeries.map((s) => s.id));
|
|
3082
|
+
for (const key of displayValuesRef.current.keys()) {
|
|
3083
|
+
if (!currentIds.has(key)) displayValuesRef.current.delete(key);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
const firstSeries = effectiveMultiSeries[0];
|
|
3087
|
+
const transition = windowTransitionRef.current;
|
|
3088
|
+
if (hasData) frozenNowRef.current = Date.now() / 1e3 - timeDebtRef.current;
|
|
3089
|
+
const now = useMultiStash ? frozenNowRef.current : Date.now() / 1e3 - timeDebtRef.current;
|
|
3090
|
+
const smoothValues = /* @__PURE__ */ new Map();
|
|
3091
|
+
for (const s of effectiveMultiSeries) {
|
|
3092
|
+
let dv = displayValuesRef.current.get(s.id);
|
|
3093
|
+
if (dv === void 0) dv = s.value;
|
|
3094
|
+
if (!useMultiStash) {
|
|
3095
|
+
const adaptiveSpeed2 = computeAdaptiveSpeed(
|
|
3096
|
+
s.value,
|
|
3097
|
+
dv,
|
|
3098
|
+
displayMinRef.current,
|
|
3099
|
+
displayMaxRef.current,
|
|
3100
|
+
cfg.lerpSpeed,
|
|
3101
|
+
noMotion
|
|
3102
|
+
);
|
|
3103
|
+
dv = lerp(dv, s.value, adaptiveSpeed2, pausedDt);
|
|
3104
|
+
const prevRange = displayMaxRef.current - displayMinRef.current || 1;
|
|
3105
|
+
if (Math.abs(dv - s.value) < prevRange * VALUE_SNAP_THRESHOLD) dv = s.value;
|
|
3106
|
+
displayValuesRef.current.set(s.id, dv);
|
|
3107
|
+
}
|
|
3108
|
+
smoothValues.set(s.id, dv);
|
|
3109
|
+
}
|
|
3110
|
+
const hiddenIds = cfg.hiddenSeriesIds;
|
|
3111
|
+
const seriesAlphas = seriesAlphaRef.current;
|
|
3112
|
+
for (const s of effectiveMultiSeries) {
|
|
3113
|
+
let alpha = seriesAlphas.get(s.id) ?? 1;
|
|
3114
|
+
const target = hiddenIds?.has(s.id) ? 0 : 1;
|
|
3115
|
+
alpha = noMotion ? target : lerp(alpha, target, SERIES_TOGGLE_SPEED, pausedDt);
|
|
3116
|
+
if (alpha < 0.01) alpha = 0;
|
|
3117
|
+
if (alpha > 0.99) alpha = 1;
|
|
3118
|
+
seriesAlphas.set(s.id, alpha);
|
|
3119
|
+
}
|
|
3120
|
+
const firstData = pausedMultiDataRef.current?.get(firstSeries.id)?.data ?? firstSeries.data;
|
|
3121
|
+
const windowResult = updateWindowTransition(
|
|
3122
|
+
cfg,
|
|
3123
|
+
transition,
|
|
3124
|
+
displayWindowRef.current,
|
|
3125
|
+
displayMinRef.current,
|
|
3126
|
+
displayMaxRef.current,
|
|
3127
|
+
noMotion,
|
|
3128
|
+
now_ms,
|
|
3129
|
+
now,
|
|
3130
|
+
firstData,
|
|
3131
|
+
smoothValues.get(firstSeries.id) ?? firstSeries.value,
|
|
3132
|
+
buffer
|
|
3133
|
+
);
|
|
3134
|
+
if (transition.startMs > 0 && effectiveMultiSeries.length > 1) {
|
|
3135
|
+
const targetRightEdge = now + cfg.windowSecs * buffer;
|
|
3136
|
+
const targetLeftEdge = targetRightEdge - cfg.windowSecs;
|
|
3137
|
+
let unionMin = Infinity;
|
|
3138
|
+
let unionMax = -Infinity;
|
|
3139
|
+
for (const s of effectiveMultiSeries) {
|
|
3140
|
+
const sData = pausedMultiDataRef.current?.get(s.id)?.data ?? s.data;
|
|
3141
|
+
const sv = smoothValues.get(s.id) ?? s.value;
|
|
3142
|
+
const targetVisible = [];
|
|
3143
|
+
for (const p of sData) {
|
|
3144
|
+
if (p.time >= targetLeftEdge - 2 && p.time <= targetRightEdge) targetVisible.push(p);
|
|
3145
|
+
}
|
|
3146
|
+
if (targetVisible.length > 0) {
|
|
3147
|
+
const range = computeRange(targetVisible, sv, cfg.referenceLine?.value, cfg.exaggerate);
|
|
3148
|
+
if (range.min < unionMin) unionMin = range.min;
|
|
3149
|
+
if (range.max > unionMax) unionMax = range.max;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
if (isFinite(unionMin) && isFinite(unionMax)) {
|
|
3153
|
+
transition.rangeToMin = unionMin;
|
|
3154
|
+
transition.rangeToMax = unionMax;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
displayWindowRef.current = windowResult.windowSecs;
|
|
3158
|
+
const windowSecs = windowResult.windowSecs;
|
|
3159
|
+
const windowTransProgress = windowResult.windowTransProgress;
|
|
3160
|
+
const isWindowTransitioning = transition.startMs > 0;
|
|
3161
|
+
const rightEdge = now + windowSecs * buffer;
|
|
3162
|
+
const leftEdge = rightEdge - windowSecs;
|
|
3163
|
+
const filterRight = rightEdge - (rightEdge - now) * pauseProgress;
|
|
3164
|
+
const seriesEntries = [];
|
|
3165
|
+
let globalMin = Infinity;
|
|
3166
|
+
let globalMax = -Infinity;
|
|
3167
|
+
for (const s of effectiveMultiSeries) {
|
|
3168
|
+
const snap = pausedMultiDataRef.current?.get(s.id);
|
|
3169
|
+
const seriesData = snap?.data ?? s.data;
|
|
3170
|
+
const visible = [];
|
|
3171
|
+
for (const p of seriesData) {
|
|
3172
|
+
if (p.time >= leftEdge - 2 && p.time <= filterRight) visible.push(p);
|
|
3173
|
+
}
|
|
3174
|
+
const sv = smoothValues.get(s.id) ?? s.value;
|
|
3175
|
+
const alpha = seriesAlphas.get(s.id) ?? 1;
|
|
3176
|
+
if (visible.length >= 2) {
|
|
3177
|
+
if (alpha > 0.01) {
|
|
3178
|
+
const range = computeRange(visible, sv, cfg.referenceLine?.value, cfg.exaggerate);
|
|
3179
|
+
if (range.min < globalMin) globalMin = range.min;
|
|
3180
|
+
if (range.max > globalMax) globalMax = range.max;
|
|
3181
|
+
}
|
|
3182
|
+
seriesEntries.push({ visible, smoothValue: sv, palette: s.palette, label: s.label, alpha });
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
if (seriesEntries.length === 0) {
|
|
3186
|
+
if (loadingAlpha > 0.01) {
|
|
3187
|
+
drawLoading(ctx, w, h, pad, cfg.palette, now_ms, loadingAlpha, cfg.palette.gridLabel);
|
|
3188
|
+
}
|
|
3189
|
+
if (1 - loadingAlpha > 0.01) {
|
|
3190
|
+
drawEmpty(ctx, w, h, pad, cfg.palette, 1 - loadingAlpha, now_ms, false, cfg.emptyText);
|
|
3191
|
+
}
|
|
3192
|
+
ctx.save();
|
|
3193
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
3194
|
+
const fadeGrad = ctx.createLinearGradient(pad.left, 0, pad.left + FADE_EDGE_WIDTH, 0);
|
|
3195
|
+
fadeGrad.addColorStop(0, "rgba(0, 0, 0, 1)");
|
|
3196
|
+
fadeGrad.addColorStop(1, "rgba(0, 0, 0, 0)");
|
|
3197
|
+
ctx.fillStyle = fadeGrad;
|
|
3198
|
+
ctx.fillRect(0, 0, pad.left + FADE_EDGE_WIDTH, h);
|
|
3199
|
+
ctx.restore();
|
|
3200
|
+
if (badgeRef.current) badgeRef.current.container.style.display = "none";
|
|
3201
|
+
rafRef.current = requestAnimationFrame(draw);
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
const computedRange = { min: isFinite(globalMin) ? globalMin : 0, max: isFinite(globalMax) ? globalMax : 1 };
|
|
3205
|
+
const adaptiveSpeed = cfg.lerpSpeed + ADAPTIVE_SPEED_BOOST * 0.5;
|
|
3206
|
+
const rangeResult = updateRange(
|
|
3207
|
+
computedRange,
|
|
3208
|
+
rangeInitedRef.current,
|
|
3209
|
+
targetMinRef.current,
|
|
3210
|
+
targetMaxRef.current,
|
|
3211
|
+
displayMinRef.current,
|
|
3212
|
+
displayMaxRef.current,
|
|
3213
|
+
isWindowTransitioning,
|
|
3214
|
+
windowTransProgress,
|
|
3215
|
+
transition,
|
|
3216
|
+
adaptiveSpeed,
|
|
3217
|
+
chartH,
|
|
3218
|
+
pausedDt
|
|
3219
|
+
);
|
|
3220
|
+
rangeInitedRef.current = rangeResult.rangeInited;
|
|
3221
|
+
targetMinRef.current = rangeResult.targetMin;
|
|
3222
|
+
targetMaxRef.current = rangeResult.targetMax;
|
|
3223
|
+
displayMinRef.current = rangeResult.displayMin;
|
|
3224
|
+
displayMaxRef.current = rangeResult.displayMax;
|
|
3225
|
+
const { minVal, maxVal, valRange } = rangeResult;
|
|
3226
|
+
const layout = {
|
|
3227
|
+
w,
|
|
3228
|
+
h,
|
|
3229
|
+
pad,
|
|
3230
|
+
chartW,
|
|
3231
|
+
chartH,
|
|
3232
|
+
leftEdge,
|
|
3233
|
+
rightEdge,
|
|
3234
|
+
minVal,
|
|
3235
|
+
maxVal,
|
|
3236
|
+
valRange,
|
|
3237
|
+
toX: (t) => pad.left + (t - leftEdge) / (rightEdge - leftEdge) * chartW,
|
|
3238
|
+
toY: (v) => pad.top + (1 - (v - minVal) / valRange) * chartH
|
|
3239
|
+
};
|
|
3240
|
+
const hoverPx = hoverXRef.current;
|
|
3241
|
+
let drawHoverX = null;
|
|
3242
|
+
let drawHoverTime = null;
|
|
3243
|
+
let isActiveHover = false;
|
|
3244
|
+
let hoverEntries = [];
|
|
3245
|
+
if (hoverPx !== null && hoverPx >= pad.left && hoverPx <= w - pad.right) {
|
|
3246
|
+
const maxHoverX = layout.toX(now);
|
|
3247
|
+
const clampedX = Math.min(hoverPx, maxHoverX);
|
|
3248
|
+
const t = leftEdge + (clampedX - pad.left) / chartW * (rightEdge - leftEdge);
|
|
3249
|
+
drawHoverX = clampedX;
|
|
3250
|
+
drawHoverTime = t;
|
|
3251
|
+
isActiveHover = true;
|
|
3252
|
+
for (const entry of seriesEntries) {
|
|
3253
|
+
if ((entry.alpha ?? 1) < 0.5) continue;
|
|
3254
|
+
const v = interpolateAtTime(entry.visible, t);
|
|
3255
|
+
if (v !== null) {
|
|
3256
|
+
hoverEntries.push({ color: entry.palette.line, label: entry.label ?? "", value: v });
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
lastHoverRef.current = { x: clampedX, value: hoverEntries[0]?.value ?? 0, time: t };
|
|
3260
|
+
lastHoverEntriesRef.current = hoverEntries;
|
|
3261
|
+
cfg.onHover?.({ time: t, value: hoverEntries[0]?.value ?? 0, x: clampedX, y: layout.toY(hoverEntries[0]?.value ?? 0) });
|
|
3262
|
+
}
|
|
3263
|
+
const scrubTarget = isActiveHover ? 1 : 0;
|
|
3264
|
+
if (noMotion) {
|
|
3265
|
+
scrubAmountRef.current = scrubTarget;
|
|
3266
|
+
} else {
|
|
3267
|
+
scrubAmountRef.current += (scrubTarget - scrubAmountRef.current) * SCRUB_LERP_SPEED;
|
|
3268
|
+
if (scrubAmountRef.current < 0.01) scrubAmountRef.current = 0;
|
|
3269
|
+
if (scrubAmountRef.current > 0.99) scrubAmountRef.current = 1;
|
|
3270
|
+
}
|
|
3271
|
+
if (!isActiveHover && scrubAmountRef.current > 0 && lastHoverRef.current) {
|
|
3272
|
+
drawHoverX = lastHoverRef.current.x;
|
|
3273
|
+
drawHoverTime = lastHoverRef.current.time;
|
|
3274
|
+
hoverEntries = lastHoverEntriesRef.current;
|
|
3275
|
+
}
|
|
3276
|
+
drawMultiFrame(ctx, layout, {
|
|
3277
|
+
series: seriesEntries,
|
|
3278
|
+
now,
|
|
3279
|
+
showGrid: cfg.showGrid,
|
|
3280
|
+
showPulse: cfg.showPulse,
|
|
3281
|
+
referenceLine: cfg.referenceLine,
|
|
3282
|
+
hoverX: drawHoverX,
|
|
3283
|
+
hoverTime: drawHoverTime,
|
|
3284
|
+
hoverEntries,
|
|
3285
|
+
scrubAmount: scrubAmountRef.current,
|
|
3286
|
+
windowSecs,
|
|
3287
|
+
formatValue: cfg.formatValue,
|
|
3288
|
+
formatTime: cfg.formatTime,
|
|
3289
|
+
gridState: gridStateRef.current,
|
|
3290
|
+
timeAxisState: timeAxisStateRef.current,
|
|
3291
|
+
dt,
|
|
3292
|
+
targetWindowSecs: cfg.windowSecs,
|
|
3293
|
+
tooltipY: cfg.tooltipY,
|
|
3294
|
+
tooltipOutline: cfg.tooltipOutline,
|
|
3295
|
+
chartReveal,
|
|
3296
|
+
pauseProgress,
|
|
3297
|
+
now_ms,
|
|
3298
|
+
primaryPalette: cfg.palette
|
|
3299
|
+
});
|
|
3300
|
+
const bgAlpha = 1 - chartReveal;
|
|
3301
|
+
if (bgAlpha > 0.01 && revealTarget === 0 && !cfg.loading) {
|
|
3302
|
+
const bgEmptyAlpha = (1 - loadingAlpha) * bgAlpha;
|
|
3303
|
+
if (bgEmptyAlpha > 0.01) {
|
|
3304
|
+
drawEmpty(ctx, w, h, pad, cfg.palette, bgEmptyAlpha, now_ms, true, cfg.emptyText);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (badgeRef.current) badgeRef.current.container.style.display = "none";
|
|
2778
3308
|
} else {
|
|
2779
3309
|
const effectivePoints = useStash ? lastDataRef.current : points;
|
|
2780
3310
|
const adaptiveSpeed = computeAdaptiveSpeed(
|
|
@@ -2984,6 +3514,7 @@ var defaultFormatTime = (t) => {
|
|
|
2984
3514
|
function Liveline({
|
|
2985
3515
|
data,
|
|
2986
3516
|
value,
|
|
3517
|
+
series: seriesProp,
|
|
2987
3518
|
theme = "dark",
|
|
2988
3519
|
color = "#3b82f6",
|
|
2989
3520
|
window: windowSecs = 30,
|
|
@@ -3023,6 +3554,8 @@ function Liveline({
|
|
|
3023
3554
|
lineData,
|
|
3024
3555
|
lineValue,
|
|
3025
3556
|
onModeChange,
|
|
3557
|
+
onSeriesToggle,
|
|
3558
|
+
seriesToggleCompact = false,
|
|
3026
3559
|
className,
|
|
3027
3560
|
style
|
|
3028
3561
|
}) {
|
|
@@ -3035,8 +3568,27 @@ function Liveline({
|
|
|
3035
3568
|
const modeBarRef = (0, import_react2.useRef)(null);
|
|
3036
3569
|
const modeBtnRefs = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
3037
3570
|
const [modeIndicatorStyle, setModeIndicatorStyle] = (0, import_react2.useState)(null);
|
|
3571
|
+
const [hiddenSeries, setHiddenSeries] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
|
|
3572
|
+
const lastSeriesPropRef = (0, import_react2.useRef)(seriesProp);
|
|
3573
|
+
if (seriesProp && seriesProp.length > 0) lastSeriesPropRef.current = seriesProp;
|
|
3038
3574
|
const palette = (0, import_react2.useMemo)(() => resolveTheme(color, theme), [color, theme]);
|
|
3039
3575
|
const isDark = theme === "dark";
|
|
3576
|
+
const isMultiSeries = seriesProp != null && seriesProp.length > 0;
|
|
3577
|
+
const showSeriesToggle = (lastSeriesPropRef.current?.length ?? 0) > 1;
|
|
3578
|
+
const seriesPalettes = (0, import_react2.useMemo)(() => {
|
|
3579
|
+
if (!seriesProp || seriesProp.length === 0) return null;
|
|
3580
|
+
return resolveSeriesPalettes(seriesProp, theme);
|
|
3581
|
+
}, [seriesProp, theme]);
|
|
3582
|
+
const multiSeries = (0, import_react2.useMemo)(() => {
|
|
3583
|
+
if (!seriesProp || !seriesPalettes) return void 0;
|
|
3584
|
+
return seriesProp.map((s, i) => ({
|
|
3585
|
+
id: s.id,
|
|
3586
|
+
data: s.data,
|
|
3587
|
+
value: s.value,
|
|
3588
|
+
palette: seriesPalettes.get(s.id) ?? resolveTheme(s.color || SERIES_COLORS[i % SERIES_COLORS.length], theme),
|
|
3589
|
+
label: s.label
|
|
3590
|
+
}));
|
|
3591
|
+
}, [seriesProp, seriesPalettes, theme]);
|
|
3040
3592
|
const showMomentum = momentum !== false;
|
|
3041
3593
|
const momentumOverride = typeof momentum === "string" ? momentum : void 0;
|
|
3042
3594
|
const pad = {
|
|
@@ -3078,6 +3630,22 @@ function Liveline({
|
|
|
3078
3630
|
});
|
|
3079
3631
|
}
|
|
3080
3632
|
}, [activeMode, onModeChange]);
|
|
3633
|
+
const handleSeriesToggle = (0, import_react2.useCallback)((id) => {
|
|
3634
|
+
setHiddenSeries((prev) => {
|
|
3635
|
+
const next = new Set(prev);
|
|
3636
|
+
if (next.has(id)) {
|
|
3637
|
+
next.delete(id);
|
|
3638
|
+
onSeriesToggle?.(id, true);
|
|
3639
|
+
} else {
|
|
3640
|
+
const totalSeries = seriesProp?.length ?? 0;
|
|
3641
|
+
const visibleCount = totalSeries - next.size;
|
|
3642
|
+
if (visibleCount <= 1) return prev;
|
|
3643
|
+
next.add(id);
|
|
3644
|
+
onSeriesToggle?.(id, false);
|
|
3645
|
+
}
|
|
3646
|
+
return next;
|
|
3647
|
+
});
|
|
3648
|
+
}, [seriesProp?.length, onSeriesToggle]);
|
|
3081
3649
|
const ws = windowStyle ?? "default";
|
|
3082
3650
|
useLivelineEngine(canvasRef, containerRef, {
|
|
3083
3651
|
data,
|
|
@@ -3086,10 +3654,10 @@ function Liveline({
|
|
|
3086
3654
|
windowSecs: effectiveWindowSecs,
|
|
3087
3655
|
lerpSpeed,
|
|
3088
3656
|
showGrid: grid,
|
|
3089
|
-
showBadge: badge,
|
|
3090
|
-
showMomentum,
|
|
3657
|
+
showBadge: isMultiSeries ? false : badge,
|
|
3658
|
+
showMomentum: isMultiSeries ? false : showMomentum,
|
|
3091
3659
|
momentumOverride,
|
|
3092
|
-
showFill: fill,
|
|
3660
|
+
showFill: isMultiSeries ? false : fill,
|
|
3093
3661
|
referenceLine,
|
|
3094
3662
|
formatValue,
|
|
3095
3663
|
formatTime,
|
|
@@ -3098,7 +3666,7 @@ function Liveline({
|
|
|
3098
3666
|
showPulse: pulse,
|
|
3099
3667
|
scrub,
|
|
3100
3668
|
exaggerate,
|
|
3101
|
-
degenOptions,
|
|
3669
|
+
degenOptions: isMultiSeries ? void 0 : degenOptions,
|
|
3102
3670
|
badgeTail,
|
|
3103
3671
|
badgeVariant,
|
|
3104
3672
|
tooltipY,
|
|
@@ -3115,7 +3683,10 @@ function Liveline({
|
|
|
3115
3683
|
liveCandle,
|
|
3116
3684
|
lineMode,
|
|
3117
3685
|
lineData,
|
|
3118
|
-
lineValue
|
|
3686
|
+
lineValue,
|
|
3687
|
+
multiSeries,
|
|
3688
|
+
isMultiSeries,
|
|
3689
|
+
hiddenSeriesIds: hiddenSeries
|
|
3119
3690
|
});
|
|
3120
3691
|
const cursorStyle = scrub ? cursor : "default";
|
|
3121
3692
|
const activeColor = isDark ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.55)";
|
|
@@ -3139,7 +3710,7 @@ function Liveline({
|
|
|
3139
3710
|
}
|
|
3140
3711
|
}
|
|
3141
3712
|
),
|
|
3142
|
-
(windows && windows.length > 0 || onModeChange) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6, marginLeft: pad.left }, children: [
|
|
3713
|
+
(windows && windows.length > 0 || onModeChange || showSeriesToggle) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6, marginLeft: pad.left }, children: [
|
|
3143
3714
|
windows && windows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
3144
3715
|
"div",
|
|
3145
3716
|
{
|
|
@@ -3207,20 +3778,20 @@ function Liveline({
|
|
|
3207
3778
|
style: {
|
|
3208
3779
|
position: "relative",
|
|
3209
3780
|
display: "inline-flex",
|
|
3210
|
-
gap: 2,
|
|
3211
|
-
background: isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
|
|
3212
|
-
borderRadius: 6,
|
|
3213
|
-
padding: 2
|
|
3781
|
+
gap: ws === "text" ? 4 : 2,
|
|
3782
|
+
background: ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
|
|
3783
|
+
borderRadius: ws === "rounded" ? 999 : 6,
|
|
3784
|
+
padding: ws === "text" ? 0 : ws === "rounded" ? 3 : 2
|
|
3214
3785
|
},
|
|
3215
3786
|
children: [
|
|
3216
|
-
modeIndicatorStyle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
3787
|
+
ws !== "text" && modeIndicatorStyle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
3217
3788
|
position: "absolute",
|
|
3218
|
-
top: 2,
|
|
3789
|
+
top: ws === "rounded" ? 3 : 2,
|
|
3219
3790
|
left: modeIndicatorStyle.left,
|
|
3220
3791
|
width: modeIndicatorStyle.width,
|
|
3221
|
-
height: "calc(100% - 4px)",
|
|
3792
|
+
height: ws === "rounded" ? "calc(100% - 6px)" : "calc(100% - 4px)",
|
|
3222
3793
|
background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)",
|
|
3223
|
-
borderRadius: 4,
|
|
3794
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3224
3795
|
transition: "left 0.25s cubic-bezier(0.4, 0, 0.2, 1), width 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
3225
3796
|
pointerEvents: "none"
|
|
3226
3797
|
} }),
|
|
@@ -3236,7 +3807,7 @@ function Liveline({
|
|
|
3236
3807
|
position: "relative",
|
|
3237
3808
|
zIndex: 1,
|
|
3238
3809
|
padding: "5px 7px",
|
|
3239
|
-
borderRadius: 4,
|
|
3810
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3240
3811
|
border: "none",
|
|
3241
3812
|
cursor: "pointer",
|
|
3242
3813
|
background: "transparent",
|
|
@@ -3267,7 +3838,7 @@ function Liveline({
|
|
|
3267
3838
|
position: "relative",
|
|
3268
3839
|
zIndex: 1,
|
|
3269
3840
|
padding: "5px 7px",
|
|
3270
|
-
borderRadius: 4,
|
|
3841
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3271
3842
|
border: "none",
|
|
3272
3843
|
cursor: "pointer",
|
|
3273
3844
|
background: "transparent",
|
|
@@ -3324,7 +3895,58 @@ function Liveline({
|
|
|
3324
3895
|
)
|
|
3325
3896
|
]
|
|
3326
3897
|
}
|
|
3327
|
-
)
|
|
3898
|
+
),
|
|
3899
|
+
showSeriesToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
3900
|
+
display: "inline-flex",
|
|
3901
|
+
gap: ws === "text" ? 4 : 2,
|
|
3902
|
+
background: ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
|
|
3903
|
+
borderRadius: ws === "rounded" ? 999 : 6,
|
|
3904
|
+
padding: ws === "text" ? 0 : ws === "rounded" ? 3 : 2,
|
|
3905
|
+
opacity: isMultiSeries ? 1 : 0,
|
|
3906
|
+
transition: "opacity 0.4s",
|
|
3907
|
+
pointerEvents: isMultiSeries ? "auto" : "none"
|
|
3908
|
+
}, children: (lastSeriesPropRef.current ?? []).map((s, si) => {
|
|
3909
|
+
const isHidden = hiddenSeries.has(s.id);
|
|
3910
|
+
const seriesColor = s.color || SERIES_COLORS[si % SERIES_COLORS.length];
|
|
3911
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
3912
|
+
"button",
|
|
3913
|
+
{
|
|
3914
|
+
onClick: () => handleSeriesToggle(s.id),
|
|
3915
|
+
style: {
|
|
3916
|
+
position: "relative",
|
|
3917
|
+
zIndex: 1,
|
|
3918
|
+
fontSize: 11,
|
|
3919
|
+
padding: seriesToggleCompact ? ws === "text" ? "2px 4px" : "5px 7px" : ws === "text" ? "2px 6px" : "3px 8px",
|
|
3920
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3921
|
+
border: "none",
|
|
3922
|
+
cursor: "pointer",
|
|
3923
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
3924
|
+
fontWeight: 500,
|
|
3925
|
+
background: isHidden ? "transparent" : ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)",
|
|
3926
|
+
color: isHidden ? inactiveColor : activeColor,
|
|
3927
|
+
opacity: isHidden ? 0.4 : 1,
|
|
3928
|
+
transition: "opacity 0.2s, background 0.15s, color 0.2s",
|
|
3929
|
+
lineHeight: "16px",
|
|
3930
|
+
display: "flex",
|
|
3931
|
+
alignItems: "center",
|
|
3932
|
+
gap: seriesToggleCompact ? 0 : 4
|
|
3933
|
+
},
|
|
3934
|
+
children: [
|
|
3935
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
|
|
3936
|
+
width: seriesToggleCompact ? 8 : 6,
|
|
3937
|
+
height: seriesToggleCompact ? 8 : 6,
|
|
3938
|
+
borderRadius: "50%",
|
|
3939
|
+
background: seriesColor,
|
|
3940
|
+
flexShrink: 0,
|
|
3941
|
+
opacity: isHidden ? 0.4 : 1,
|
|
3942
|
+
transition: "opacity 0.2s"
|
|
3943
|
+
} }),
|
|
3944
|
+
!seriesToggleCompact && (s.label ?? s.id)
|
|
3945
|
+
]
|
|
3946
|
+
},
|
|
3947
|
+
s.id
|
|
3948
|
+
);
|
|
3949
|
+
}) })
|
|
3328
3950
|
] }),
|
|
3329
3951
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3330
3952
|
"div",
|