/* global React */
// trajectory.jsx — three variants: spark, strip, ledger
// Exposes window.Trajectory, window.SparkTrj, window.StripTrj, window.LedgerTrj
const { useState } = React;
function dirOf(trj) {
if (!trj || trj.length < 2) return "mixed";
const first = trj[0], last = trj[trj.length - 1];
if (last === 0 && first > 0) return "down";
if (first === 0 && last > 0) return "up";
if (last > first) return "up";
if (last < first) return "down";
return "mixed";
}
function deltaLabel(trj, kind) {
if (!trj) return "—";
const first = trj[0], last = trj[trj.length - 1];
if (kind === "dodged") return "DODGED";
if (last === 0 && first > 0) return "FADED";
if (first === 0 && last > 0) return "EMERGED";
if (last > first) return `+${last - first}`;
if (last < first) return `−${first - last}`;
return "FLAT";
}
// Variant A — sparkline + delta chip (inline density)
function SparkTrj({ trj, kind = "emerged", quarters }) {
if (!trj) {
return (
— · — · — · —
DODGED
);
}
const dir = kind === "dodged" ? "dodged"
: kind === "disappeared" ? "down"
: kind === "emerged" ? "up"
: dirOf(trj);
return (
{trj.map((n, i) => (
{n}
{i < trj.length - 1 && ›}
))}
{deltaLabel(trj, kind)}
);
}
// Variant B — quarter strip with hover quotes (for drill-down)
function StripTrj({ trj, kind = "emerged", quarters, peakIndex, onCellClick }) {
if (!trj) return null;
const max = Math.max(...trj, 1);
const peakIdx = peakIndex != null ? peakIndex : trj.indexOf(Math.max(...trj));
const [hoverIdx, setHoverIdx] = useState(null);
return (
Mentions per quarter
{trj.map((n, i) => (
setHoverIdx(i)}
onMouseLeave={() => setHoverIdx(null)}
onClick={() => onCellClick && onCellClick(i)}
>
{quarters[i]}
{n}
))}
);
}
// Variant C — stacked-bar mention ledger (where the topic lives)
// segments: [opening, qa, financial] per quarter
function LedgerTrj({ trj, kind = "emerged", quarters, segments }) {
if (!trj) return null;
const max = Math.max(...trj, 1);
// Synthetic split if segments missing: weighted to opening for emerged, qa for disappeared
const splits = segments || trj.map(n => {
if (n === 0) return [0, 0, 0];
if (kind === "emerged") return [Math.round(n*0.6), Math.round(n*0.3), n - Math.round(n*0.6) - Math.round(n*0.3)];
if (kind === "disappeared") return [Math.round(n*0.25), Math.round(n*0.6), n - Math.round(n*0.25) - Math.round(n*0.6)];
return [Math.round(n*0.4), Math.round(n*0.4), n - 2*Math.round(n*0.4)];
});
return (
{trj.map((n, i) => {
const s = splits[i] || [0,0,0];
const totalH = (n / max) * 80;
return (
{n === 0 ? null : [0,1,2].map(si => (
))}
{quarters[i]}
{n}
);
})}
);
}
Object.assign(window, { SparkTrj, StripTrj, LedgerTrj });