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.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;
|
|
@@ -1712,6 +1968,7 @@ var PAUSE_PROGRESS_SPEED = 0.12;
|
|
|
1712
1968
|
var PAUSE_CATCHUP_SPEED = 0.08;
|
|
1713
1969
|
var PAUSE_CATCHUP_SPEED_FAST = 0.22;
|
|
1714
1970
|
var LOADING_ALPHA_SPEED = 0.14;
|
|
1971
|
+
var SERIES_TOGGLE_SPEED = 0.1;
|
|
1715
1972
|
var CANDLE_LERP_SPEED = 0.25;
|
|
1716
1973
|
var CANDLE_WIDTH_TRANS_MS = 300;
|
|
1717
1974
|
var LINE_MORPH_MS = 500;
|
|
@@ -2035,6 +2292,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2035
2292
|
const configRef = useRef(config);
|
|
2036
2293
|
configRef.current = config;
|
|
2037
2294
|
const displayValueRef = useRef(config.value);
|
|
2295
|
+
const displayValuesRef = useRef(/* @__PURE__ */ new Map());
|
|
2296
|
+
const seriesAlphaRef = useRef(/* @__PURE__ */ new Map());
|
|
2038
2297
|
const displayMinRef = useRef(0);
|
|
2039
2298
|
const displayMaxRef = useRef(0);
|
|
2040
2299
|
const targetMinRef = useRef(0);
|
|
@@ -2067,12 +2326,15 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2067
2326
|
const hoverXRef = useRef(null);
|
|
2068
2327
|
const scrubAmountRef = useRef(0);
|
|
2069
2328
|
const lastHoverRef = useRef(null);
|
|
2329
|
+
const lastHoverEntriesRef = useRef([]);
|
|
2070
2330
|
const chartRevealRef = useRef(0);
|
|
2071
2331
|
const pauseProgressRef = useRef(0);
|
|
2072
2332
|
const timeDebtRef = useRef(0);
|
|
2073
2333
|
const lastDataRef = useRef([]);
|
|
2334
|
+
const lastMultiSeriesRef = useRef([]);
|
|
2074
2335
|
const frozenNowRef = useRef(0);
|
|
2075
2336
|
const pausedDataRef = useRef(null);
|
|
2337
|
+
const pausedMultiDataRef = useRef(null);
|
|
2076
2338
|
const loadingAlphaRef = useRef(config.loading ? 1 : 0);
|
|
2077
2339
|
const displayCandleRef = useRef(null);
|
|
2078
2340
|
const liveBirthAlphaRef = useRef(1);
|
|
@@ -2252,6 +2514,17 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2252
2514
|
pausedLineDataRef.current = null;
|
|
2253
2515
|
pausedLineValueRef.current = null;
|
|
2254
2516
|
}
|
|
2517
|
+
} else if (cfg.isMultiSeries && cfg.multiSeries) {
|
|
2518
|
+
if (cfg.paused && pausedMultiDataRef.current === null) {
|
|
2519
|
+
const snap = /* @__PURE__ */ new Map();
|
|
2520
|
+
for (const s of cfg.multiSeries) {
|
|
2521
|
+
if (s.data.length >= 2) snap.set(s.id, { data: s.data.slice(), value: s.value });
|
|
2522
|
+
}
|
|
2523
|
+
if (snap.size > 0) pausedMultiDataRef.current = snap;
|
|
2524
|
+
}
|
|
2525
|
+
if (!cfg.paused) {
|
|
2526
|
+
pausedMultiDataRef.current = null;
|
|
2527
|
+
}
|
|
2255
2528
|
} else {
|
|
2256
2529
|
if (cfg.paused && pausedDataRef.current === null && cfg.data.length >= 2) {
|
|
2257
2530
|
pausedDataRef.current = cfg.data.slice();
|
|
@@ -2262,7 +2535,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2262
2535
|
}
|
|
2263
2536
|
const points = isCandle ? [] : pausedDataRef.current ?? cfg.data;
|
|
2264
2537
|
const effectiveCandles = isCandle ? pausedCandlesRef.current ?? (cfg.candles ?? []) : [];
|
|
2265
|
-
const
|
|
2538
|
+
const hasMultiData = cfg.isMultiSeries && cfg.multiSeries ? cfg.multiSeries.some((s) => s.data.length >= 2) : false;
|
|
2539
|
+
const hasData = isCandle ? effectiveCandles.length >= 2 : hasMultiData || points.length >= 2;
|
|
2266
2540
|
const pad = cfg.padding;
|
|
2267
2541
|
const chartH = h - pad.top - pad.bottom;
|
|
2268
2542
|
const pauseTarget = cfg.paused ? 1 : 0;
|
|
@@ -2298,11 +2572,23 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2298
2572
|
rangeInitedRef.current = false;
|
|
2299
2573
|
}
|
|
2300
2574
|
let useStash;
|
|
2575
|
+
let useMultiStash = false;
|
|
2301
2576
|
if (isCandle) {
|
|
2302
2577
|
useStash = !hasData && chartReveal > 5e-3 && lastCandlesRef.current.length > 0;
|
|
2303
2578
|
} else {
|
|
2304
|
-
|
|
2305
|
-
if (
|
|
2579
|
+
useMultiStash = !hasData && chartReveal > 5e-3 && lastMultiSeriesRef.current.length > 0;
|
|
2580
|
+
if (hasMultiData && cfg.multiSeries) {
|
|
2581
|
+
lastMultiSeriesRef.current = cfg.multiSeries.map((s) => ({
|
|
2582
|
+
id: s.id,
|
|
2583
|
+
data: s.data.slice(),
|
|
2584
|
+
value: s.value,
|
|
2585
|
+
palette: s.palette,
|
|
2586
|
+
label: s.label
|
|
2587
|
+
}));
|
|
2588
|
+
}
|
|
2589
|
+
if (hasData && !cfg.isMultiSeries) lastMultiSeriesRef.current = [];
|
|
2590
|
+
useStash = !useMultiStash && !hasData && chartReveal > 5e-3 && lastDataRef.current.length >= 2;
|
|
2591
|
+
if (hasData && !cfg.isMultiSeries) lastDataRef.current = points;
|
|
2306
2592
|
}
|
|
2307
2593
|
if (isCandle) {
|
|
2308
2594
|
const lmt = lineModeTransRef.current;
|
|
@@ -2324,8 +2610,8 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2324
2610
|
lineModeProgRef.current = lmt.to;
|
|
2325
2611
|
}
|
|
2326
2612
|
}
|
|
2327
|
-
if (!hasData && !useStash) {
|
|
2328
|
-
const loadingColor = isCandle ? cfg.palette.gridLabel : void 0;
|
|
2613
|
+
if (!hasData && !useStash && !useMultiStash) {
|
|
2614
|
+
const loadingColor = isCandle || cfg.isMultiSeries || lastMultiSeriesRef.current.length > 0 ? cfg.palette.gridLabel : void 0;
|
|
2329
2615
|
if (loadingAlpha > 0.01) {
|
|
2330
2616
|
drawLoading(ctx, w, h, pad, cfg.palette, now_ms, loadingAlpha, loadingColor);
|
|
2331
2617
|
}
|
|
@@ -2748,6 +3034,250 @@ function useLivelineEngine(canvasRef, containerRef, config) {
|
|
|
2748
3034
|
badgeRef.current.container.style.display = "none";
|
|
2749
3035
|
}
|
|
2750
3036
|
}
|
|
3037
|
+
} else if (cfg.isMultiSeries && cfg.multiSeries && cfg.multiSeries.length > 0 || useMultiStash) {
|
|
3038
|
+
const effectiveMultiSeries = useMultiStash ? lastMultiSeriesRef.current : cfg.multiSeries;
|
|
3039
|
+
let labelReserve = 0;
|
|
3040
|
+
if (effectiveMultiSeries.some((s) => s.label)) {
|
|
3041
|
+
ctx.font = '600 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif';
|
|
3042
|
+
let maxLabelW = 0;
|
|
3043
|
+
for (const s of effectiveMultiSeries) {
|
|
3044
|
+
if (s.label) {
|
|
3045
|
+
const lw = ctx.measureText(s.label).width;
|
|
3046
|
+
if (lw > maxLabelW) maxLabelW = lw;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
labelReserve = Math.max(0, maxLabelW - 2) * chartReveal;
|
|
3050
|
+
}
|
|
3051
|
+
const chartW = w - pad.left - pad.right - labelReserve;
|
|
3052
|
+
const buffer = WINDOW_BUFFER;
|
|
3053
|
+
if (!useMultiStash) {
|
|
3054
|
+
const currentIds = new Set(effectiveMultiSeries.map((s) => s.id));
|
|
3055
|
+
for (const key of displayValuesRef.current.keys()) {
|
|
3056
|
+
if (!currentIds.has(key)) displayValuesRef.current.delete(key);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
const firstSeries = effectiveMultiSeries[0];
|
|
3060
|
+
const transition = windowTransitionRef.current;
|
|
3061
|
+
if (hasData) frozenNowRef.current = Date.now() / 1e3 - timeDebtRef.current;
|
|
3062
|
+
const now = useMultiStash ? frozenNowRef.current : Date.now() / 1e3 - timeDebtRef.current;
|
|
3063
|
+
const smoothValues = /* @__PURE__ */ new Map();
|
|
3064
|
+
for (const s of effectiveMultiSeries) {
|
|
3065
|
+
let dv = displayValuesRef.current.get(s.id);
|
|
3066
|
+
if (dv === void 0) dv = s.value;
|
|
3067
|
+
if (!useMultiStash) {
|
|
3068
|
+
const adaptiveSpeed2 = computeAdaptiveSpeed(
|
|
3069
|
+
s.value,
|
|
3070
|
+
dv,
|
|
3071
|
+
displayMinRef.current,
|
|
3072
|
+
displayMaxRef.current,
|
|
3073
|
+
cfg.lerpSpeed,
|
|
3074
|
+
noMotion
|
|
3075
|
+
);
|
|
3076
|
+
dv = lerp(dv, s.value, adaptiveSpeed2, pausedDt);
|
|
3077
|
+
const prevRange = displayMaxRef.current - displayMinRef.current || 1;
|
|
3078
|
+
if (Math.abs(dv - s.value) < prevRange * VALUE_SNAP_THRESHOLD) dv = s.value;
|
|
3079
|
+
displayValuesRef.current.set(s.id, dv);
|
|
3080
|
+
}
|
|
3081
|
+
smoothValues.set(s.id, dv);
|
|
3082
|
+
}
|
|
3083
|
+
const hiddenIds = cfg.hiddenSeriesIds;
|
|
3084
|
+
const seriesAlphas = seriesAlphaRef.current;
|
|
3085
|
+
for (const s of effectiveMultiSeries) {
|
|
3086
|
+
let alpha = seriesAlphas.get(s.id) ?? 1;
|
|
3087
|
+
const target = hiddenIds?.has(s.id) ? 0 : 1;
|
|
3088
|
+
alpha = noMotion ? target : lerp(alpha, target, SERIES_TOGGLE_SPEED, pausedDt);
|
|
3089
|
+
if (alpha < 0.01) alpha = 0;
|
|
3090
|
+
if (alpha > 0.99) alpha = 1;
|
|
3091
|
+
seriesAlphas.set(s.id, alpha);
|
|
3092
|
+
}
|
|
3093
|
+
const firstData = pausedMultiDataRef.current?.get(firstSeries.id)?.data ?? firstSeries.data;
|
|
3094
|
+
const windowResult = updateWindowTransition(
|
|
3095
|
+
cfg,
|
|
3096
|
+
transition,
|
|
3097
|
+
displayWindowRef.current,
|
|
3098
|
+
displayMinRef.current,
|
|
3099
|
+
displayMaxRef.current,
|
|
3100
|
+
noMotion,
|
|
3101
|
+
now_ms,
|
|
3102
|
+
now,
|
|
3103
|
+
firstData,
|
|
3104
|
+
smoothValues.get(firstSeries.id) ?? firstSeries.value,
|
|
3105
|
+
buffer
|
|
3106
|
+
);
|
|
3107
|
+
if (transition.startMs > 0 && effectiveMultiSeries.length > 1) {
|
|
3108
|
+
const targetRightEdge = now + cfg.windowSecs * buffer;
|
|
3109
|
+
const targetLeftEdge = targetRightEdge - cfg.windowSecs;
|
|
3110
|
+
let unionMin = Infinity;
|
|
3111
|
+
let unionMax = -Infinity;
|
|
3112
|
+
for (const s of effectiveMultiSeries) {
|
|
3113
|
+
const sData = pausedMultiDataRef.current?.get(s.id)?.data ?? s.data;
|
|
3114
|
+
const sv = smoothValues.get(s.id) ?? s.value;
|
|
3115
|
+
const targetVisible = [];
|
|
3116
|
+
for (const p of sData) {
|
|
3117
|
+
if (p.time >= targetLeftEdge - 2 && p.time <= targetRightEdge) targetVisible.push(p);
|
|
3118
|
+
}
|
|
3119
|
+
if (targetVisible.length > 0) {
|
|
3120
|
+
const range = computeRange(targetVisible, sv, cfg.referenceLine?.value, cfg.exaggerate);
|
|
3121
|
+
if (range.min < unionMin) unionMin = range.min;
|
|
3122
|
+
if (range.max > unionMax) unionMax = range.max;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
if (isFinite(unionMin) && isFinite(unionMax)) {
|
|
3126
|
+
transition.rangeToMin = unionMin;
|
|
3127
|
+
transition.rangeToMax = unionMax;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
displayWindowRef.current = windowResult.windowSecs;
|
|
3131
|
+
const windowSecs = windowResult.windowSecs;
|
|
3132
|
+
const windowTransProgress = windowResult.windowTransProgress;
|
|
3133
|
+
const isWindowTransitioning = transition.startMs > 0;
|
|
3134
|
+
const rightEdge = now + windowSecs * buffer;
|
|
3135
|
+
const leftEdge = rightEdge - windowSecs;
|
|
3136
|
+
const filterRight = rightEdge - (rightEdge - now) * pauseProgress;
|
|
3137
|
+
const seriesEntries = [];
|
|
3138
|
+
let globalMin = Infinity;
|
|
3139
|
+
let globalMax = -Infinity;
|
|
3140
|
+
for (const s of effectiveMultiSeries) {
|
|
3141
|
+
const snap = pausedMultiDataRef.current?.get(s.id);
|
|
3142
|
+
const seriesData = snap?.data ?? s.data;
|
|
3143
|
+
const visible = [];
|
|
3144
|
+
for (const p of seriesData) {
|
|
3145
|
+
if (p.time >= leftEdge - 2 && p.time <= filterRight) visible.push(p);
|
|
3146
|
+
}
|
|
3147
|
+
const sv = smoothValues.get(s.id) ?? s.value;
|
|
3148
|
+
const alpha = seriesAlphas.get(s.id) ?? 1;
|
|
3149
|
+
if (visible.length >= 2) {
|
|
3150
|
+
if (alpha > 0.01) {
|
|
3151
|
+
const range = computeRange(visible, sv, cfg.referenceLine?.value, cfg.exaggerate);
|
|
3152
|
+
if (range.min < globalMin) globalMin = range.min;
|
|
3153
|
+
if (range.max > globalMax) globalMax = range.max;
|
|
3154
|
+
}
|
|
3155
|
+
seriesEntries.push({ visible, smoothValue: sv, palette: s.palette, label: s.label, alpha });
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
if (seriesEntries.length === 0) {
|
|
3159
|
+
if (loadingAlpha > 0.01) {
|
|
3160
|
+
drawLoading(ctx, w, h, pad, cfg.palette, now_ms, loadingAlpha, cfg.palette.gridLabel);
|
|
3161
|
+
}
|
|
3162
|
+
if (1 - loadingAlpha > 0.01) {
|
|
3163
|
+
drawEmpty(ctx, w, h, pad, cfg.palette, 1 - loadingAlpha, now_ms, false, cfg.emptyText);
|
|
3164
|
+
}
|
|
3165
|
+
ctx.save();
|
|
3166
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
3167
|
+
const fadeGrad = ctx.createLinearGradient(pad.left, 0, pad.left + FADE_EDGE_WIDTH, 0);
|
|
3168
|
+
fadeGrad.addColorStop(0, "rgba(0, 0, 0, 1)");
|
|
3169
|
+
fadeGrad.addColorStop(1, "rgba(0, 0, 0, 0)");
|
|
3170
|
+
ctx.fillStyle = fadeGrad;
|
|
3171
|
+
ctx.fillRect(0, 0, pad.left + FADE_EDGE_WIDTH, h);
|
|
3172
|
+
ctx.restore();
|
|
3173
|
+
if (badgeRef.current) badgeRef.current.container.style.display = "none";
|
|
3174
|
+
rafRef.current = requestAnimationFrame(draw);
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
const computedRange = { min: isFinite(globalMin) ? globalMin : 0, max: isFinite(globalMax) ? globalMax : 1 };
|
|
3178
|
+
const adaptiveSpeed = cfg.lerpSpeed + ADAPTIVE_SPEED_BOOST * 0.5;
|
|
3179
|
+
const rangeResult = updateRange(
|
|
3180
|
+
computedRange,
|
|
3181
|
+
rangeInitedRef.current,
|
|
3182
|
+
targetMinRef.current,
|
|
3183
|
+
targetMaxRef.current,
|
|
3184
|
+
displayMinRef.current,
|
|
3185
|
+
displayMaxRef.current,
|
|
3186
|
+
isWindowTransitioning,
|
|
3187
|
+
windowTransProgress,
|
|
3188
|
+
transition,
|
|
3189
|
+
adaptiveSpeed,
|
|
3190
|
+
chartH,
|
|
3191
|
+
pausedDt
|
|
3192
|
+
);
|
|
3193
|
+
rangeInitedRef.current = rangeResult.rangeInited;
|
|
3194
|
+
targetMinRef.current = rangeResult.targetMin;
|
|
3195
|
+
targetMaxRef.current = rangeResult.targetMax;
|
|
3196
|
+
displayMinRef.current = rangeResult.displayMin;
|
|
3197
|
+
displayMaxRef.current = rangeResult.displayMax;
|
|
3198
|
+
const { minVal, maxVal, valRange } = rangeResult;
|
|
3199
|
+
const layout = {
|
|
3200
|
+
w,
|
|
3201
|
+
h,
|
|
3202
|
+
pad,
|
|
3203
|
+
chartW,
|
|
3204
|
+
chartH,
|
|
3205
|
+
leftEdge,
|
|
3206
|
+
rightEdge,
|
|
3207
|
+
minVal,
|
|
3208
|
+
maxVal,
|
|
3209
|
+
valRange,
|
|
3210
|
+
toX: (t) => pad.left + (t - leftEdge) / (rightEdge - leftEdge) * chartW,
|
|
3211
|
+
toY: (v) => pad.top + (1 - (v - minVal) / valRange) * chartH
|
|
3212
|
+
};
|
|
3213
|
+
const hoverPx = hoverXRef.current;
|
|
3214
|
+
let drawHoverX = null;
|
|
3215
|
+
let drawHoverTime = null;
|
|
3216
|
+
let isActiveHover = false;
|
|
3217
|
+
let hoverEntries = [];
|
|
3218
|
+
if (hoverPx !== null && hoverPx >= pad.left && hoverPx <= w - pad.right) {
|
|
3219
|
+
const maxHoverX = layout.toX(now);
|
|
3220
|
+
const clampedX = Math.min(hoverPx, maxHoverX);
|
|
3221
|
+
const t = leftEdge + (clampedX - pad.left) / chartW * (rightEdge - leftEdge);
|
|
3222
|
+
drawHoverX = clampedX;
|
|
3223
|
+
drawHoverTime = t;
|
|
3224
|
+
isActiveHover = true;
|
|
3225
|
+
for (const entry of seriesEntries) {
|
|
3226
|
+
if ((entry.alpha ?? 1) < 0.5) continue;
|
|
3227
|
+
const v = interpolateAtTime(entry.visible, t);
|
|
3228
|
+
if (v !== null) {
|
|
3229
|
+
hoverEntries.push({ color: entry.palette.line, label: entry.label ?? "", value: v });
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
lastHoverRef.current = { x: clampedX, value: hoverEntries[0]?.value ?? 0, time: t };
|
|
3233
|
+
lastHoverEntriesRef.current = hoverEntries;
|
|
3234
|
+
cfg.onHover?.({ time: t, value: hoverEntries[0]?.value ?? 0, x: clampedX, y: layout.toY(hoverEntries[0]?.value ?? 0) });
|
|
3235
|
+
}
|
|
3236
|
+
const scrubTarget = isActiveHover ? 1 : 0;
|
|
3237
|
+
if (noMotion) {
|
|
3238
|
+
scrubAmountRef.current = scrubTarget;
|
|
3239
|
+
} else {
|
|
3240
|
+
scrubAmountRef.current += (scrubTarget - scrubAmountRef.current) * SCRUB_LERP_SPEED;
|
|
3241
|
+
if (scrubAmountRef.current < 0.01) scrubAmountRef.current = 0;
|
|
3242
|
+
if (scrubAmountRef.current > 0.99) scrubAmountRef.current = 1;
|
|
3243
|
+
}
|
|
3244
|
+
if (!isActiveHover && scrubAmountRef.current > 0 && lastHoverRef.current) {
|
|
3245
|
+
drawHoverX = lastHoverRef.current.x;
|
|
3246
|
+
drawHoverTime = lastHoverRef.current.time;
|
|
3247
|
+
hoverEntries = lastHoverEntriesRef.current;
|
|
3248
|
+
}
|
|
3249
|
+
drawMultiFrame(ctx, layout, {
|
|
3250
|
+
series: seriesEntries,
|
|
3251
|
+
now,
|
|
3252
|
+
showGrid: cfg.showGrid,
|
|
3253
|
+
showPulse: cfg.showPulse,
|
|
3254
|
+
referenceLine: cfg.referenceLine,
|
|
3255
|
+
hoverX: drawHoverX,
|
|
3256
|
+
hoverTime: drawHoverTime,
|
|
3257
|
+
hoverEntries,
|
|
3258
|
+
scrubAmount: scrubAmountRef.current,
|
|
3259
|
+
windowSecs,
|
|
3260
|
+
formatValue: cfg.formatValue,
|
|
3261
|
+
formatTime: cfg.formatTime,
|
|
3262
|
+
gridState: gridStateRef.current,
|
|
3263
|
+
timeAxisState: timeAxisStateRef.current,
|
|
3264
|
+
dt,
|
|
3265
|
+
targetWindowSecs: cfg.windowSecs,
|
|
3266
|
+
tooltipY: cfg.tooltipY,
|
|
3267
|
+
tooltipOutline: cfg.tooltipOutline,
|
|
3268
|
+
chartReveal,
|
|
3269
|
+
pauseProgress,
|
|
3270
|
+
now_ms,
|
|
3271
|
+
primaryPalette: cfg.palette
|
|
3272
|
+
});
|
|
3273
|
+
const bgAlpha = 1 - chartReveal;
|
|
3274
|
+
if (bgAlpha > 0.01 && revealTarget === 0 && !cfg.loading) {
|
|
3275
|
+
const bgEmptyAlpha = (1 - loadingAlpha) * bgAlpha;
|
|
3276
|
+
if (bgEmptyAlpha > 0.01) {
|
|
3277
|
+
drawEmpty(ctx, w, h, pad, cfg.palette, bgEmptyAlpha, now_ms, true, cfg.emptyText);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
if (badgeRef.current) badgeRef.current.container.style.display = "none";
|
|
2751
3281
|
} else {
|
|
2752
3282
|
const effectivePoints = useStash ? lastDataRef.current : points;
|
|
2753
3283
|
const adaptiveSpeed = computeAdaptiveSpeed(
|
|
@@ -2957,6 +3487,7 @@ var defaultFormatTime = (t) => {
|
|
|
2957
3487
|
function Liveline({
|
|
2958
3488
|
data,
|
|
2959
3489
|
value,
|
|
3490
|
+
series: seriesProp,
|
|
2960
3491
|
theme = "dark",
|
|
2961
3492
|
color = "#3b82f6",
|
|
2962
3493
|
window: windowSecs = 30,
|
|
@@ -2996,6 +3527,8 @@ function Liveline({
|
|
|
2996
3527
|
lineData,
|
|
2997
3528
|
lineValue,
|
|
2998
3529
|
onModeChange,
|
|
3530
|
+
onSeriesToggle,
|
|
3531
|
+
seriesToggleCompact = false,
|
|
2999
3532
|
className,
|
|
3000
3533
|
style
|
|
3001
3534
|
}) {
|
|
@@ -3008,8 +3541,27 @@ function Liveline({
|
|
|
3008
3541
|
const modeBarRef = useRef2(null);
|
|
3009
3542
|
const modeBtnRefs = useRef2(/* @__PURE__ */ new Map());
|
|
3010
3543
|
const [modeIndicatorStyle, setModeIndicatorStyle] = useState(null);
|
|
3544
|
+
const [hiddenSeries, setHiddenSeries] = useState(/* @__PURE__ */ new Set());
|
|
3545
|
+
const lastSeriesPropRef = useRef2(seriesProp);
|
|
3546
|
+
if (seriesProp && seriesProp.length > 0) lastSeriesPropRef.current = seriesProp;
|
|
3011
3547
|
const palette = useMemo(() => resolveTheme(color, theme), [color, theme]);
|
|
3012
3548
|
const isDark = theme === "dark";
|
|
3549
|
+
const isMultiSeries = seriesProp != null && seriesProp.length > 0;
|
|
3550
|
+
const showSeriesToggle = (lastSeriesPropRef.current?.length ?? 0) > 1;
|
|
3551
|
+
const seriesPalettes = useMemo(() => {
|
|
3552
|
+
if (!seriesProp || seriesProp.length === 0) return null;
|
|
3553
|
+
return resolveSeriesPalettes(seriesProp, theme);
|
|
3554
|
+
}, [seriesProp, theme]);
|
|
3555
|
+
const multiSeries = useMemo(() => {
|
|
3556
|
+
if (!seriesProp || !seriesPalettes) return void 0;
|
|
3557
|
+
return seriesProp.map((s, i) => ({
|
|
3558
|
+
id: s.id,
|
|
3559
|
+
data: s.data,
|
|
3560
|
+
value: s.value,
|
|
3561
|
+
palette: seriesPalettes.get(s.id) ?? resolveTheme(s.color || SERIES_COLORS[i % SERIES_COLORS.length], theme),
|
|
3562
|
+
label: s.label
|
|
3563
|
+
}));
|
|
3564
|
+
}, [seriesProp, seriesPalettes, theme]);
|
|
3013
3565
|
const showMomentum = momentum !== false;
|
|
3014
3566
|
const momentumOverride = typeof momentum === "string" ? momentum : void 0;
|
|
3015
3567
|
const pad = {
|
|
@@ -3051,6 +3603,22 @@ function Liveline({
|
|
|
3051
3603
|
});
|
|
3052
3604
|
}
|
|
3053
3605
|
}, [activeMode, onModeChange]);
|
|
3606
|
+
const handleSeriesToggle = useCallback2((id) => {
|
|
3607
|
+
setHiddenSeries((prev) => {
|
|
3608
|
+
const next = new Set(prev);
|
|
3609
|
+
if (next.has(id)) {
|
|
3610
|
+
next.delete(id);
|
|
3611
|
+
onSeriesToggle?.(id, true);
|
|
3612
|
+
} else {
|
|
3613
|
+
const totalSeries = seriesProp?.length ?? 0;
|
|
3614
|
+
const visibleCount = totalSeries - next.size;
|
|
3615
|
+
if (visibleCount <= 1) return prev;
|
|
3616
|
+
next.add(id);
|
|
3617
|
+
onSeriesToggle?.(id, false);
|
|
3618
|
+
}
|
|
3619
|
+
return next;
|
|
3620
|
+
});
|
|
3621
|
+
}, [seriesProp?.length, onSeriesToggle]);
|
|
3054
3622
|
const ws = windowStyle ?? "default";
|
|
3055
3623
|
useLivelineEngine(canvasRef, containerRef, {
|
|
3056
3624
|
data,
|
|
@@ -3059,10 +3627,10 @@ function Liveline({
|
|
|
3059
3627
|
windowSecs: effectiveWindowSecs,
|
|
3060
3628
|
lerpSpeed,
|
|
3061
3629
|
showGrid: grid,
|
|
3062
|
-
showBadge: badge,
|
|
3063
|
-
showMomentum,
|
|
3630
|
+
showBadge: isMultiSeries ? false : badge,
|
|
3631
|
+
showMomentum: isMultiSeries ? false : showMomentum,
|
|
3064
3632
|
momentumOverride,
|
|
3065
|
-
showFill: fill,
|
|
3633
|
+
showFill: isMultiSeries ? false : fill,
|
|
3066
3634
|
referenceLine,
|
|
3067
3635
|
formatValue,
|
|
3068
3636
|
formatTime,
|
|
@@ -3071,7 +3639,7 @@ function Liveline({
|
|
|
3071
3639
|
showPulse: pulse,
|
|
3072
3640
|
scrub,
|
|
3073
3641
|
exaggerate,
|
|
3074
|
-
degenOptions,
|
|
3642
|
+
degenOptions: isMultiSeries ? void 0 : degenOptions,
|
|
3075
3643
|
badgeTail,
|
|
3076
3644
|
badgeVariant,
|
|
3077
3645
|
tooltipY,
|
|
@@ -3088,7 +3656,10 @@ function Liveline({
|
|
|
3088
3656
|
liveCandle,
|
|
3089
3657
|
lineMode,
|
|
3090
3658
|
lineData,
|
|
3091
|
-
lineValue
|
|
3659
|
+
lineValue,
|
|
3660
|
+
multiSeries,
|
|
3661
|
+
isMultiSeries,
|
|
3662
|
+
hiddenSeriesIds: hiddenSeries
|
|
3092
3663
|
});
|
|
3093
3664
|
const cursorStyle = scrub ? cursor : "default";
|
|
3094
3665
|
const activeColor = isDark ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.55)";
|
|
@@ -3112,7 +3683,7 @@ function Liveline({
|
|
|
3112
3683
|
}
|
|
3113
3684
|
}
|
|
3114
3685
|
),
|
|
3115
|
-
(windows && windows.length > 0 || onModeChange) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6, marginLeft: pad.left }, children: [
|
|
3686
|
+
(windows && windows.length > 0 || onModeChange || showSeriesToggle) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6, marginLeft: pad.left }, children: [
|
|
3116
3687
|
windows && windows.length > 0 && /* @__PURE__ */ jsxs(
|
|
3117
3688
|
"div",
|
|
3118
3689
|
{
|
|
@@ -3180,20 +3751,20 @@ function Liveline({
|
|
|
3180
3751
|
style: {
|
|
3181
3752
|
position: "relative",
|
|
3182
3753
|
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
|
|
3754
|
+
gap: ws === "text" ? 4 : 2,
|
|
3755
|
+
background: ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
|
|
3756
|
+
borderRadius: ws === "rounded" ? 999 : 6,
|
|
3757
|
+
padding: ws === "text" ? 0 : ws === "rounded" ? 3 : 2
|
|
3187
3758
|
},
|
|
3188
3759
|
children: [
|
|
3189
|
-
modeIndicatorStyle && /* @__PURE__ */ jsx("div", { style: {
|
|
3760
|
+
ws !== "text" && modeIndicatorStyle && /* @__PURE__ */ jsx("div", { style: {
|
|
3190
3761
|
position: "absolute",
|
|
3191
|
-
top: 2,
|
|
3762
|
+
top: ws === "rounded" ? 3 : 2,
|
|
3192
3763
|
left: modeIndicatorStyle.left,
|
|
3193
3764
|
width: modeIndicatorStyle.width,
|
|
3194
|
-
height: "calc(100% - 4px)",
|
|
3765
|
+
height: ws === "rounded" ? "calc(100% - 6px)" : "calc(100% - 4px)",
|
|
3195
3766
|
background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)",
|
|
3196
|
-
borderRadius: 4,
|
|
3767
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3197
3768
|
transition: "left 0.25s cubic-bezier(0.4, 0, 0.2, 1), width 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
3198
3769
|
pointerEvents: "none"
|
|
3199
3770
|
} }),
|
|
@@ -3209,7 +3780,7 @@ function Liveline({
|
|
|
3209
3780
|
position: "relative",
|
|
3210
3781
|
zIndex: 1,
|
|
3211
3782
|
padding: "5px 7px",
|
|
3212
|
-
borderRadius: 4,
|
|
3783
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3213
3784
|
border: "none",
|
|
3214
3785
|
cursor: "pointer",
|
|
3215
3786
|
background: "transparent",
|
|
@@ -3240,7 +3811,7 @@ function Liveline({
|
|
|
3240
3811
|
position: "relative",
|
|
3241
3812
|
zIndex: 1,
|
|
3242
3813
|
padding: "5px 7px",
|
|
3243
|
-
borderRadius: 4,
|
|
3814
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3244
3815
|
border: "none",
|
|
3245
3816
|
cursor: "pointer",
|
|
3246
3817
|
background: "transparent",
|
|
@@ -3297,7 +3868,58 @@ function Liveline({
|
|
|
3297
3868
|
)
|
|
3298
3869
|
]
|
|
3299
3870
|
}
|
|
3300
|
-
)
|
|
3871
|
+
),
|
|
3872
|
+
showSeriesToggle && /* @__PURE__ */ jsx("div", { style: {
|
|
3873
|
+
display: "inline-flex",
|
|
3874
|
+
gap: ws === "text" ? 4 : 2,
|
|
3875
|
+
background: ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)",
|
|
3876
|
+
borderRadius: ws === "rounded" ? 999 : 6,
|
|
3877
|
+
padding: ws === "text" ? 0 : ws === "rounded" ? 3 : 2,
|
|
3878
|
+
opacity: isMultiSeries ? 1 : 0,
|
|
3879
|
+
transition: "opacity 0.4s",
|
|
3880
|
+
pointerEvents: isMultiSeries ? "auto" : "none"
|
|
3881
|
+
}, children: (lastSeriesPropRef.current ?? []).map((s, si) => {
|
|
3882
|
+
const isHidden = hiddenSeries.has(s.id);
|
|
3883
|
+
const seriesColor = s.color || SERIES_COLORS[si % SERIES_COLORS.length];
|
|
3884
|
+
return /* @__PURE__ */ jsxs(
|
|
3885
|
+
"button",
|
|
3886
|
+
{
|
|
3887
|
+
onClick: () => handleSeriesToggle(s.id),
|
|
3888
|
+
style: {
|
|
3889
|
+
position: "relative",
|
|
3890
|
+
zIndex: 1,
|
|
3891
|
+
fontSize: 11,
|
|
3892
|
+
padding: seriesToggleCompact ? ws === "text" ? "2px 4px" : "5px 7px" : ws === "text" ? "2px 6px" : "3px 8px",
|
|
3893
|
+
borderRadius: ws === "rounded" ? 999 : 4,
|
|
3894
|
+
border: "none",
|
|
3895
|
+
cursor: "pointer",
|
|
3896
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
3897
|
+
fontWeight: 500,
|
|
3898
|
+
background: isHidden ? "transparent" : ws === "text" ? "transparent" : isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.035)",
|
|
3899
|
+
color: isHidden ? inactiveColor : activeColor,
|
|
3900
|
+
opacity: isHidden ? 0.4 : 1,
|
|
3901
|
+
transition: "opacity 0.2s, background 0.15s, color 0.2s",
|
|
3902
|
+
lineHeight: "16px",
|
|
3903
|
+
display: "flex",
|
|
3904
|
+
alignItems: "center",
|
|
3905
|
+
gap: seriesToggleCompact ? 0 : 4
|
|
3906
|
+
},
|
|
3907
|
+
children: [
|
|
3908
|
+
/* @__PURE__ */ jsx("span", { style: {
|
|
3909
|
+
width: seriesToggleCompact ? 8 : 6,
|
|
3910
|
+
height: seriesToggleCompact ? 8 : 6,
|
|
3911
|
+
borderRadius: "50%",
|
|
3912
|
+
background: seriesColor,
|
|
3913
|
+
flexShrink: 0,
|
|
3914
|
+
opacity: isHidden ? 0.4 : 1,
|
|
3915
|
+
transition: "opacity 0.2s"
|
|
3916
|
+
} }),
|
|
3917
|
+
!seriesToggleCompact && (s.label ?? s.id)
|
|
3918
|
+
]
|
|
3919
|
+
},
|
|
3920
|
+
s.id
|
|
3921
|
+
);
|
|
3922
|
+
}) })
|
|
3301
3923
|
] }),
|
|
3302
3924
|
/* @__PURE__ */ jsx(
|
|
3303
3925
|
"div",
|