/* ============================================================================
StonksScout — CHART PRIMITIVES (hand-rolled SVG, no chart deps)
============================================================================ */
const { useId } = React;
/* ---- Conviction bar (signature 0–100 readout) ----------------------------- */
function ConvictionBar({ value, color, height=10, showScale=false }){
const pct = Math.max(0, Math.min(100, value));
return (
{showScale && (
050100
)}
);
}
/* ---- Big conviction gauge (detail view) — 270° arc ------------------------ */
function ConvictionGauge({ value, color, size=190, label }){
const pct = Math.max(0, Math.min(100, value));
const r = size/2 - 16, cx = size/2, cy = size/2;
const start = 135, sweep = 270;
const polar = (deg) => {
const a = (deg) * Math.PI/180;
return [cx + r*Math.cos(a), cy + r*Math.sin(a)];
};
const arc = (fromPct, toPct) => {
const a0 = start + (sweep*fromPct/100), a1 = start + (sweep*toPct/100);
const [x0,y0] = polar(a0), [x1,y1] = polar(a1);
const large = (a1-a0) > 180 ? 1 : 0;
return `M ${x0} ${y0} A ${r} ${r} 0 ${large} 1 ${x1} ${y1}`;
};
return (
);
}
/* ---- Radar / spider chart (8 factors) ------------------------------------- */
function RadarChart({ factors, color, size=300 }){
const keys = Object.keys(factors);
const n = keys.length, cx=size/2, cy=size/2, R=size/2-46;
const pt = (i, rad) => {
const a = (-90 + i*360/n) * Math.PI/180;
return [cx + rad*Math.cos(a), cy + rad*Math.sin(a)];
};
const rings = [0.25,0.5,0.75,1];
const poly = (vals) => vals.map((v,i)=>pt(i, R*v/100).join(",")).join(" ");
const gridPoly = (f) => keys.map((_,i)=>pt(i, R*f).join(",")).join(" ");
return (
);
}
/* ---- Labeled factor bars (compact, used in cards) ------------------------- */
function FactorBars({ factors, color, compact=false }){
const keys = Object.keys(factors);
return (
{keys.map(k=>(
{(FACTOR_LABELS[k]||k).replace(" ","\u00A0")}
{factors[k]}
))}
);
}
/* ---- Sparkline (price history) -------------------------------------------- */
function Sparkline({ data, color, width=120, height=34, strokeWidth=1.6, fill=true }){
const gid = useId().replace(/:/g,"");
if(!data || data.length<2) return ;
const min = Math.min(...data), max = Math.max(...data), span = (max-min)||1;
const x = (i) => (i/(data.length-1))*(width-2)+1;
const y = (v) => height-2 - ((v-min)/span)*(height-4);
const pts = data.map((v,i)=>[x(i),y(v)]);
const line = pts.map((p,i)=>(i?"L":"M")+p[0].toFixed(1)+" "+p[1].toFixed(1)).join(" ");
const area = line + ` L ${width-1} ${height} L 1 ${height} Z`;
return (
);
}
/* ---- Trend chip (tiny conviction trend with delta) ------------------------ */
function TrendSpark({ data, width=58, height=20 }){
const delta = data[data.length-1]-data[0];
const c = delta>=0 ? "#17a86a" : "#ee3f5f";
return (
{delta>=0?"+":""}{delta}
);
}
/* ---- Price chart with entry / stop / target lines (detail view) ----------- */
function PriceChart({ data, frame, color, width=560, height=220 }){
const gid = useId().replace(/:/g,"");
const padL=8, padR=64, padT=14, padB=18;
const levels = [];
if(frame){
if(frame.stop!=null) levels.push({v:frame.stop, c:"#ee3f5f", t:"STOP"});
(frame.targets||[]).forEach((t,i)=>levels.push({v:t, c:"#17a86a", t:"T"+(i+1)}));
}
const allVals = [...data, ...levels.map(l=>l.v)];
const min = Math.min(...allVals)*0.985, max = Math.max(...allVals)*1.015, span=(max-min)||1;
const x = (i)=> padL + (i/(data.length-1))*(width-padL-padR);
const y = (v)=> padT + (1-(v-min)/span)*(height-padT-padB);
const pts = data.map((v,i)=>[x(i),y(v)]);
const line = pts.map((p,i)=>(i?"L":"M")+p[0].toFixed(1)+" "+p[1].toFixed(1)).join(" ");
const area = line + ` L ${x(data.length-1)} ${height-padB} L ${padL} ${height-padB} Z`;
return (
);
}
/* ---- Analyst rating split (stacked bar) ----------------------------------- */
function AnalystSplit({ a }){
const segs = [
{ k:"Strong Buy", v:a.strong_buy, c:"#15bd80" },
{ k:"Buy", v:a.buy, c:"#17a86a" },
{ k:"Hold", v:a.hold, c:"#b9870c" },
{ k:"Sell", v:a.sell, c:"#ee3f5f" },
];
const total = segs.reduce((s,x)=>s+x.v,0)||1;
return (
{segs.map(s=> s.v>0 && (
))}
{segs.map(s=>(
{s.k}
{s.v}
))}
);
}
Object.assign(window, { ConvictionBar, ConvictionGauge, RadarChart, FactorBars, Sparkline, TrendSpark, PriceChart, AnalystSplit });