// File: insights-charts.tsx
// Path: apps/web/components/insights-charts.tsx
// Project: AI Risk Index — airiskindex.io
// Author: Simon-Pierre Boucher
// Contact: contact@spboucher.ai
// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.
//
// Description: Server-rendered SVG charts for the insights page: scatter (substitution × augmentation), wage-decile columns.
export interface ScatterPoint {
x: number; // substitution 0–100
y: number; // augmentation 0–100
size?: number | null; // employment
label: string;
}
/**
* Single-series scatter on two 0–100 axes with quadrant annotations.
* One hue (identity is not encoded), dot area ~ employment, native
* tooltips, hairline gridlines, text in ink tokens.
*/
export function ScatterPlot({ points }: { points: ScatterPoint[] }): JSX.Element {
const W = 640;
const H = 440;
const m = { t: 20, r: 18, b: 46, l: 46 };
const sx = (v: number): number => m.l + (v / 100) * (W - m.l - m.r);
const sy = (v: number): number => H - m.b - (v / 100) * (H - m.t - m.b);
const maxSize = Math.max(1, ...points.map((p) => p.size ?? 0));
const radius = (size?: number | null): number => 2.2 + 7 * Math.sqrt((size ?? 0) / maxSize);
return (
);
}
export interface DecileColumn {
label: string; // wage range
value: number; // employment-weighted mean substitution
}
/** Column chart: one value per wage decile, value labels on caps. */
export function DecileColumns({ columns }: { columns: DecileColumn[] }): JSX.Element {
const max = Math.max(1, ...columns.map((c) => c.value));
return (
{columns.map((column) => (
{column.value.toFixed(0)}
))}
{columns.map((column) => (
{column.label}
))}
);
}