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:
Investor Flow Build
2026-06-30 09:28:11 -04:00
parent cc1213fb64
commit a09eba4d42
+45 -11
View File
@@ -8,6 +8,7 @@ import {
BarChart, BarChart,
CartesianGrid, CartesianGrid,
Cell, Cell,
Defs,
Legend, Legend,
Line, Line,
LineChart, LineChart,
@@ -157,14 +158,40 @@ export function ChartLabPanel() {
const upColor = DAY_COLORS.up; const upColor = DAY_COLORS.up;
const downColor = DAY_COLORS.down; const downColor = DAY_COLORS.down;
// Build a custom bar fill that respects up/down. // Custom shape for OHLC candlestick rendering: wick (high→low line) + body (open↔close rectangle).
const CandleBar = (props: any) => { const CandleShape = (props: any) => {
const { x, y, width, height, payload } = props; 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 ( return (
<Bar dataKey="range" {...props}> <g>
<Cell fill={fill} opacity={0.35} /> {/* Wick: vertical line from high to low */}
</Bar> <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> <p className="text-xs text-[#5a5b6a] mb-2">{UI_STRINGS.priceChartCaption}</p>
<div className="h-72"> <div className="h-72">
<ResponsiveContainer width="100%" height="100%"> <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" /> <CartesianGrid strokeDasharray="3 3" stroke="#2a2b3a" />
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "#5a5b6a" }} interval="preserveStartEnd" /> <XAxis dataKey="date" tick={{ fontSize: 10, fill: "#5a5b6a" }} interval="preserveStartEnd" />
<YAxis domain={["auto", "auto"]} tick={{ fontSize: 10, fill: "#5a5b6a" }} /> <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10, fill: "#5a5b6a" }} />
<Tooltip <Tooltip
labelFormatter={(label) => `Date: ${label}`} labelFormatter={(label) => `Date: ${label}`}
formatter={(value: any, name: string) => { 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")) { if (name.startsWith("ema")) {
const period = name.replace("ema", ""); const period = name.replace("ema", "");
return [`$${value?.toFixed(2)}`, `${UI_STRINGS[`ema${period}Label` as keyof typeof UI_STRINGS] ?? `EMA ${period}`}`]; 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}`}`; return `${UI_STRINGS[`ema${period}Label` as keyof typeof UI_STRINGS] ?? `EMA ${period}`}`;
}} }}
/> />
<Bar
dataKey="c"
shape={<CandleShape />}
name="candle"
opacity={0.85}
/>
<Line <Line
type="monotone" type="monotone"
dataKey="c" dataKey="c"
@@ -208,6 +241,7 @@ export function ChartLabPanel() {
strokeWidth={1.5} strokeWidth={1.5}
dot={false} dot={false}
name="close" name="close"
strokeOpacity={0.6}
/> />
{Object.entries(EMA_COLORS).map(([period, color]) => ( {Object.entries(EMA_COLORS).map(([period, color]) => (
<Line <Line
@@ -220,7 +254,7 @@ export function ChartLabPanel() {
name={`ema${period}`} name={`ema${period}`}
/> />
))} ))}
</LineChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
<figcaption className="text-[11px] text-[#5a5b6a] mt-1"> <figcaption className="text-[11px] text-[#5a5b6a] mt-1">
@@ -280,7 +314,7 @@ export function ChartLabPanel() {
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
<figcaption className="text-[11px] text-[#5a5b6a] mt-1"> <figcaption className="text-[11px] text-[#5a5b6a] mt-1">
{UI_STRINGS.rsiTooltip} {UI_STRINGS.rsiCaption}
</figcaption> </figcaption>
</figure> </figure>
</div> </div>
@@ -294,7 +328,7 @@ export function ChartLabPanel() {
<BarChart data={chartData}> <BarChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#2a2b3a" /> <CartesianGrid strokeDasharray="3 3" stroke="#2a2b3a" />
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "#5a5b6a" }} interval="preserveStartEnd" hide /> <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 <Tooltip
formatter={(value: number) => { formatter={(value: number) => {
const label = value > 1 ? "above typical" : value < 1 ? "below typical" : "at typical"; const label = value > 1 ? "above typical" : value < 1 ? "below typical" : "at typical";