← back to Bertha
kalshi-dash/src/components/MiniCandlestick.jsx
118 lines
/**
* MiniCandlestick — tiny SVG candlestick chart
*
* Props:
* data — array of { o, h, l, c } (open, high, low, close)
* width — SVG width in px (default 80)
* height — SVG height in px (default 32)
* bullColor — color for bullish candles (c > o)
* bearColor — color for bearish candles (c <= o)
*
* Usage:
* <MiniCandlestick data={[{o:45,h:52,l:42,c:50}, ...]} />
* <MiniCandlestick data={genCandles(seed, 9)} width={80} height={32} />
*/
/**
* Generate fake OHLC data that trends in a given direction.
* @param {number} seed — deterministic seed
* @param {number} count — number of candles
* @param {'up'|'down'|'flat'} trend
*/
export function genCandles(seed, count = 9, trend = 'flat') {
let s = seed;
const rng = () => {
s = (s * 1664525 + 1013904223) & 0xffffffff;
return (Math.abs(s) % 1000) / 1000; // 0..1
};
const trendDelta = trend === 'up' ? 0.8 : trend === 'down' ? -0.8 : 0;
let price = 45 + rng() * 10;
const candles = [];
for (let i = 0; i < count; i++) {
const move = (rng() - 0.45 + trendDelta * 0.1) * 6;
const open = price;
const close = Math.max(5, Math.min(95, price + move));
const highExtra = rng() * 3;
const lowExtra = rng() * 3;
const high = Math.max(open, close) + highExtra;
const low = Math.min(open, close) - lowExtra;
candles.push({ o: open, h: high, l: low, c: close });
price = close;
}
return candles;
}
export default function MiniCandlestick({
data,
width = 80,
height = 32,
bullColor = 'var(--green)',
bearColor = 'var(--red)',
}) {
if (!data || data.length === 0) return null;
const allLows = data.map(d => d.l);
const allHighs = data.map(d => d.h);
const minVal = Math.min(...allLows);
const maxVal = Math.max(...allHighs);
const range = maxVal - minVal || 1;
const pad = 2;
const drawH = height - pad * 2;
const drawW = width - pad * 2;
// Normalize a value to SVG y coordinate (inverted — higher = lower y)
const yOf = (v) => pad + drawH - ((v - minVal) / range) * drawH;
const n = data.length;
const slotW = drawW / n;
const bodyW = Math.max(2, slotW * 0.55);
const wickW = 1;
return (
<svg
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
style={{ display: 'block', flexShrink: 0 }}
aria-hidden="true"
>
{data.map((candle, i) => {
const isBull = candle.c >= candle.o;
const color = isBull ? bullColor : bearColor;
const cx = pad + i * slotW + slotW / 2;
const bodyTop = yOf(Math.max(candle.o, candle.c));
const bodyBot = yOf(Math.min(candle.o, candle.c));
const bodyHt = Math.max(1, bodyBot - bodyTop);
return (
<g key={i}>
{/* Wick */}
<line
x1={cx}
y1={yOf(candle.h)}
x2={cx}
y2={yOf(candle.l)}
stroke={color}
strokeWidth={wickW}
strokeOpacity={0.7}
/>
{/* Body */}
<rect
x={cx - bodyW / 2}
y={bodyTop}
width={bodyW}
height={bodyHt}
fill={color}
fillOpacity={isBull ? 0.85 : 0.75}
rx={0.5}
/>
</g>
);
})}
</svg>
);
}