feat: K线成交量独立pane、浅色调、移除KDJ

This commit is contained in:
Sakurasan
2026-08-28 15:37:17 +08:00
parent 813e5c345a
commit c4ed3346ab
2 changed files with 148 additions and 88 deletions
+16 -37
View File
@@ -1,7 +1,7 @@
/**
* K 线图组件(基于 TradingView Lightweight Charts v5)
* 主图:蜡烛/折线 + MA(5/10/20/30);
* 副图:成交量(pane1)、MACD(pane2)、RSI(pane3)、KDJ(pane4),可开关。
* 副图:成交量(pane1)、MACD(pane2)、RSI(pane3),可开关。
*/
import { useEffect, useRef } from "react";
import {
@@ -18,7 +18,7 @@ import {
type ISeriesApi as ISeriesAny,
type Time,
} from "lightweight-charts";
import { sma, macd as calcMacd, rsi as calcRsi, kdj as calcKdj, type KBar } from "@/lib/indicators";
import { sma, macd as calcMacd, rsi as calcRsi, type KBar } from "@/lib/indicators";
export interface KLineItem {
time: string; // ISO 日期 yyyy-mm-dd(lightweight-charts 必需)
@@ -34,7 +34,6 @@ export interface IndicatorToggles {
ma: boolean;
macd: boolean;
rsi: boolean;
kdj: boolean;
}
interface Props {
@@ -47,6 +46,9 @@ interface Props {
// lightweight-charts 无法解析 CSS 变量或 oklch() 颜色,直接用具体 hex 色。
const UP = "#ef4444";
const DOWN = "#22c55e";
// 成交量柱:浅色调,避免与 K 线柱体视觉争夺
const VOL_UP = "#fca5a5";
const VOL_DOWN = "#86efac";
const PRIMARY = "#ef4444";
const TEXT = "#71717a";
const GRID = "#e4e4e7";
@@ -58,9 +60,6 @@ const MA_PERIODS = [5, 10, 20];
const MACD_DIF = "#3b82f6";
const MACD_DEA = "#f59e0b";
const RSI_COLOR = "#a855f7";
const KDJ_K = "#3b82f6";
const KDJ_D = "#f59e0b";
const KDJ_J = "#a855f7";
export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
const containerRef = useRef<HTMLDivElement>(null);
@@ -71,10 +70,9 @@ export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
const maSeriesRef = useRef<ISeriesAny<"Line">[]>([]);
const macdSeriesRef = useRef<ISeriesAny<"Line" | "Histogram">[]>([]);
const rsiSeriesRef = useRef<ISeriesAny<"Line">[]>([]);
const kdjSeriesRef = useRef<ISeriesAny<"Line">[]>([]);
// 容器高度随指标 pane 数量增长,避免主图被压缩
const extraPanes = [indicators.macd, indicators.rsi, indicators.kdj].filter(Boolean).length;
const extraPanes = [indicators.macd, indicators.rsi].filter(Boolean).length;
// 固定图表高度:主图+成交量 300,每个指标副图 +95
const chartHeight = 300 + extraPanes * 95;
@@ -101,11 +99,15 @@ export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
});
chartRef.current = chart;
const vol = chart.addSeries(HistogramSeries, {
priceFormat: { type: "volume" },
priceScaleId: "vol",
});
vol.priceScale().applyOptions({ scaleMargins: { top: 0.8, bottom: 0 } });
// 成交量放到独立 pane1,避免与主图K线重叠
const vol = chart.addSeries(
HistogramSeries,
{
priceFormat: { type: "volume" },
priceScaleId: "vol",
},
1,
);
volSeriesRef.current = vol;
return () => {
@@ -116,7 +118,6 @@ export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
maSeriesRef.current = [];
macdSeriesRef.current = [];
rsiSeriesRef.current = [];
kdjSeriesRef.current = [];
};
}, []);
@@ -222,25 +223,6 @@ export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
);
rsiSeriesRef.current.push(s);
}
// --- KDJ(pane4)---
removeGroup(kdjSeriesRef.current, chart);
if (indicators.kdj && bars.length > 0) {
const { k, d, j } = calcKdj(bars);
const mk = (color: string) =>
chart.addSeries(
LineSeries,
{ color, lineWidth: 1, priceLineVisible: false, lastValueVisible: false },
4,
);
const kS = mk(KDJ_K);
const dS = mk(KDJ_D);
const jS = mk(KDJ_J);
kS.setData(bars.map((_, i) => ({ time: bars[i].time as Time, value: k[i] })).filter((x) => x.value != null) as { time: Time; value: number }[]);
dS.setData(bars.map((_, i) => ({ time: bars[i].time as Time, value: d[i] })).filter((x) => x.value != null) as { time: Time; value: number }[]);
jS.setData(bars.map((_, i) => ({ time: bars[i].time as Time, value: j[i] })).filter((x) => x.value != null) as { time: Time; value: number }[]);
kdjSeriesRef.current.push(kS, dS, jS);
}
}
// 模式切换:重建价格 series + 指标
@@ -309,7 +291,7 @@ export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
data.map((d) => ({
time: d.time as Time,
value: d.volume,
color: d.close >= d.open ? UP : DOWN,
color: d.close >= d.open ? VOL_UP : VOL_DOWN,
})),
);
@@ -340,9 +322,6 @@ export function KLineChart({ data, mode, hasAddedDate, indicators }: Props) {
legend.push({ color: MACD_DIF, label: "DIF" }, { color: MACD_DEA, label: "DEA" });
}
if (indicators.rsi) legend.push({ color: RSI_COLOR, label: "RSI14" });
if (indicators.kdj) {
legend.push({ color: KDJ_K, label: "K" }, { color: KDJ_D, label: "D" }, { color: KDJ_J, label: "J" });
}
return (
<div className="w-full">