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,
|
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";
|
||||||
|
|||||||
Reference in New Issue
Block a user