fix-chartlab-findings (ornith-35): real OHLC candlesticks (CandleShape), remove dead CandleBar, rsiCaption figcaption, relvol domain
Re-review by qwopus35b pending. SPA lint 2/2 green.
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
Defs,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
@@ -157,14 +158,40 @@ export function ChartLabPanel() {
|
||||
const upColor = DAY_COLORS.up;
|
||||
const downColor = DAY_COLORS.down;
|
||||
|
||||
// Build a custom bar fill that respects up/down.
|
||||
const CandleBar = (props: any) => {
|
||||
// Custom shape for OHLC candlestick rendering: wick (high→low line) + body (open↔close rectangle).
|
||||
const CandleShape = (props: any) => {
|
||||
const { x, y, width, height, payload } = props;
|
||||
const fill = payload.c >= payload.o ? upColor : downColor;
|
||||
if (!payload) return null;
|
||||
const isUp = payload.c >= payload.o;
|
||||
const fill = isUp ? upColor : downColor;
|
||||
const bodyTop = Math.min(payload.o, payload.c);
|
||||
const bodyBottom = Math.max(payload.o, payload.c);
|
||||
const bodyHeight = Math.max(bodyBottom - bodyTop, 1); // minimum 1px body
|
||||
const wickTop = payload.h;
|
||||
const wickBottom = payload.l;
|
||||
|
||||
return (
|
||||
<Bar dataKey="range" {...props}>
|
||||
<Cell fill={fill} opacity={0.35} />
|
||||
</Bar>
|
||||
<g>
|
||||
{/* Wick: vertical line from high to low */}
|
||||
<line
|
||||
x1={x + width / 2}
|
||||
y1={wickTop}
|
||||
x2={x + width / 2}
|
||||
y2={wickBottom}
|
||||
stroke={fill}
|
||||
strokeWidth={1}
|
||||
/>
|
||||
{/* Body: rectangle from open to close */}
|
||||
<rect
|
||||
x={x + 1}
|
||||
y={bodyTop}
|
||||
width={Math.max(width - 2, 1)}
|
||||
height={bodyHeight}
|
||||
fill={isUp ? fill : fill}
|
||||
stroke={fill}
|
||||
strokeWidth={0.5}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -178,14 +205,14 @@ export function ChartLabPanel() {
|
||||
<p className="text-xs text-[#5a5b6a] mb-2">{UI_STRINGS.priceChartCaption}</p>
|
||||
<div className="h-72">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={chartData} margin={{ top: 8, right: 8, bottom: 8, left: 8 }}>
|
||||
<BarChart data={chartData} margin={{ top: 8, right: 8, bottom: 8, left: 8 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#2a2b3a" />
|
||||
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "#5a5b6a" }} interval="preserveStartEnd" />
|
||||
<YAxis domain={["auto", "auto"]} tick={{ fontSize: 10, fill: "#5a5b6a" }} />
|
||||
<Tooltip
|
||||
labelFormatter={(label) => `Date: ${label}`}
|
||||
formatter={(value: any, name: string) => {
|
||||
if (name === "close") return [`$${value?.toFixed(2)}`, UI_STRINGS.priceLabel];
|
||||
if (name === "candle") return [`$${value?.toFixed(2)}`, UI_STRINGS.priceLabel];
|
||||
if (name.startsWith("ema")) {
|
||||
const period = name.replace("ema", "");
|
||||
return [`$${value?.toFixed(2)}`, `${UI_STRINGS[`ema${period}Label` as keyof typeof UI_STRINGS] ?? `EMA ${period}`}`];
|
||||
@@ -201,6 +228,12 @@ export function ChartLabPanel() {
|
||||
return `${UI_STRINGS[`ema${period}Label` as keyof typeof UI_STRINGS] ?? `EMA ${period}`}`;
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="c"
|
||||
shape={<CandleShape />}
|
||||
name="candle"
|
||||
opacity={0.85}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="c"
|
||||
@@ -208,6 +241,7 @@ export function ChartLabPanel() {
|
||||
strokeWidth={1.5}
|
||||
dot={false}
|
||||
name="close"
|
||||
strokeOpacity={0.6}
|
||||
/>
|
||||
{Object.entries(EMA_COLORS).map(([period, color]) => (
|
||||
<Line
|
||||
@@ -220,7 +254,7 @@ export function ChartLabPanel() {
|
||||
name={`ema${period}`}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<figcaption className="text-[11px] text-[#5a5b6a] mt-1">
|
||||
@@ -280,7 +314,7 @@ export function ChartLabPanel() {
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<figcaption className="text-[11px] text-[#5a5b6a] mt-1">
|
||||
{UI_STRINGS.rsiTooltip}
|
||||
{UI_STRINGS.rsiCaption}
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
@@ -294,7 +328,7 @@ export function ChartLabPanel() {
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#2a2b3a" />
|
||||
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "#5a5b6a" }} interval="preserveStartEnd" hide />
|
||||
<YAxis tick={{ fontSize: 10, fill: "#5a5b6a" }} />
|
||||
<YAxis domain={[0, "dataMax"]} tick={{ fontSize: 10, fill: "#5a5b6a" }} />
|
||||
<Tooltip
|
||||
formatter={(value: number) => {
|
||||
const label = value > 1 ? "above typical" : value < 1 ? "below typical" : "at typical";
|
||||
|
||||
Reference in New Issue
Block a user