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,
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";