"use client";
import {
CartesianGrid,
Legend,
ResponsiveContainer,
Scatter,
ScatterChart,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { AlertPill } from "./shared/alert-pill";
import { ChartLegend } from "./shared/chart-legend";
import { getSeverityColorByRiskScore } from "./shared/utils";
import type { ScatterDataPoint } from "./types";
interface ScatterPlotProps {
data: ScatterDataPoint[];
xLabel?: string;
yLabel?: string;
height?: number;
onSelectPoint?: (point: ScatterDataPoint | null) => void;
selectedPoint?: ScatterDataPoint | null;
}
const PROVIDER_COLORS = {
AWS: "var(--color-bg-data-aws)",
Azure: "var(--color-bg-data-azure)",
Google: "var(--color-bg-data-gcp)",
Default: "var(--color-text-neutral-tertiary)",
};
interface ScatterTooltipProps {
active?: boolean;
payload?: Array<{ payload: ScatterDataPoint }>;
}
const CustomTooltip = ({ active, payload }: ScatterTooltipProps) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
const severityColor = getSeverityColorByRiskScore(data.x);
return (
{data.name}
{data.x} Risk Score
);
}
return null;
};
interface ScatterDotProps {
cx: number;
cy: number;
payload: ScatterDataPoint;
selectedPoint?: ScatterDataPoint | null;
onSelectPoint?: (point: ScatterDataPoint) => void;
}
const CustomScatterDot = ({
cx,
cy,
payload,
selectedPoint,
onSelectPoint,
}: ScatterDotProps) => {
const isSelected = selectedPoint?.name === payload.name;
const size = isSelected ? 18 : 8;
const fill = isSelected
? "#86DA26"
: PROVIDER_COLORS[payload.provider as keyof typeof PROVIDER_COLORS] ||
"var(--color-text-neutral-tertiary)";
return (
onSelectPoint?.(payload)}
/>
);
};
interface LegendPayloadItem {
value: string;
color: string;
}
interface LegendProps {
payload?: LegendPayloadItem[];
}
const CustomLegend = ({ payload }: LegendProps) => {
const items = (payload || []).map((entry) => ({
label: entry.value,
color: entry.color,
}));
return ;
};
export function ScatterPlot({
data,
xLabel = "Risk Score",
yLabel = "Failed Findings",
height = 400,
onSelectPoint,
selectedPoint,
}: ScatterPlotProps) {
const handlePointClick = (point: ScatterDataPoint) => {
if (onSelectPoint) {
if (selectedPoint?.name === point.name) {
onSelectPoint(null);
} else {
onSelectPoint(point);
}
}
};
const dataByProvider = data.reduce(
(acc, point) => {
const provider = point.provider;
if (!acc[provider]) {
acc[provider] = [];
}
acc[provider].push(point);
return acc;
},
{} as Record,
);
return (
} />
} />
{Object.entries(dataByProvider).map(([provider, points]) => (
{
const dotProps = props as ScatterDotProps;
return (
);
}}
/>
))}
);
}