"use client";
import type { ADMETSwissADME } from "@/lib/api";
import {
ResponsiveContainer,
RadarChart,
PolarGrid,
PolarAngleAxis,
Radar,
ScatterChart,
XAxis,
YAxis,
Scatter,
CartesianGrid,
Customized,
BarChart,
Bar,
ReferenceLine,
LabelList,
} from "recharts";
const CYAN = "#4ADE80";
const GREEN = "#34D399";
const AMBER = "#FBBF24";
const ORANGE = "#FB923C";
const RED = "#F87171";
const SLATE = "#848CA4";
const INK = "#DDE0EE";
function Section({ title, children, className = "" }: { title: string; children: React.ReactNode; className?: string }) {
return (
{title}
{children}
);
}
function Row({ label, value, unit, sub, color }: { label: string; value: React.ReactNode; unit?: string; sub?: string; color?: string }) {
return (
{label}
{value}
{unit &&
{unit}}
{sub &&
{sub}
}
);
}
function Pill({ pass }: { pass: boolean }) {
return pass
? Pass
: Fail;
}
function FilterCard({ name, data }: { name: string; data: { pass: boolean; violations: string[]; violation_count: number } }) {
return (
{data.violation_count} violation{data.violation_count === 1 ? "" : "s"}
{data.violations.length > 0 && (
{data.violations.map((v, i) => - {v}
)}
)}
);
}
/* ------------------------------------------------------------------ */
/* Bioavailability Radar (SwissADME-style, 6 axes) */
/* ------------------------------------------------------------------ */
function BioavailabilityRadar({ radar }: { radar: ADMETSwissADME["bioavailability_radar"] }) {
const data = radar.axes.map((a) => {
const max = a.max || 1;
const min = a.min;
const value = Math.max(0, Math.min(1, a.value / max));
const zoneStart = Math.max(0, Math.min(1, min / max));
const zoneEnd = Math.max(0, Math.min(1, max / max));
return { axis: a.axis, label: a.label, value, zoneStart, zoneEnd, raw: a.value, min, max, note: a.note };
});
return (
Optimal zone (green band), molecule (green line).
{data.map((d) => {
const ok = d.raw >= d.min && d.raw <= d.max;
return (
{d.axis}
{d.label}
{d.raw}
{ok ? "OK" : "out"}
);
})}
{radar.all_optimal ? "All six axes within optimal ranges." : "Some axes fall outside the optimal (pink) zone."}
);
}
/* ------------------------------------------------------------------ */
/* BOILED-Egg (ScatterChart + ellipse polygons) */
/* ------------------------------------------------------------------ */
function BoiledEgg({ egg }: { egg: ADMETSwissADME["pharmacokinetics"]["boiled_egg"] }) {
const point = [{ tpsa: egg.tpsa, wlogp: egg.wlogp, region: egg.region }];
const pointColor = egg.region === "yolk" ? AMBER : egg.region === "white" ? CYAN : RED;
const Polygons = (props: any) => {
const xScale = props?.xAxisMap?.["0"]?.scale;
const yScale = props?.yAxisMap?.["0"]?.scale;
if (!xScale || !yScale) return null;
const toPoints = (arr: [number, number][]) => arr.map(([x, y]) => `${xScale(x)},${yScale(y)}`).join(" ");
return (
);
};
const CompoundDot = (props: any) => {
const { cx, cy, payload } = props;
return (
compound
);
};
return (
GI absorption — High
BBB permeant — Yes
This molecule
Region: {egg.region} (TPSA {egg.tpsa}, WLOGP {egg.wlogp})
);
}
/* ------------------------------------------------------------------ */
/* Range bars (recharts) — ESOL / WLOGP / Log Kp on shared 0-100% axis */
/* ------------------------------------------------------------------ */
type RangeItem = { key: string; value: number; display: string; note: string; color: string; tick: string };
function RangeBars({ items }: { items: RangeItem[] }) {
const data = items.map((it) => ({ key: it.key, value: it.value }));
return (
{items.map((it) => (
`${Math.round(v * 100)}%`} fill={SLATE} fontSize={10} />
))}
);
}
function norm(v: number, min: number, max: number) {
return Math.max(0, Math.min(1, (v - min) / (max - min)));
}
/* ------------------------------------------------------------------ */
/* Main panel */
/* ------------------------------------------------------------------ */
export default function SwissADMEView({ data }: { data: ADMETSwissADME }) {
const phys = data.physicochemical;
const lipo = data.lipophilicity;
const sol = data.water_solubility;
const pk = data.pharmacokinetics;
const dl = data.drug_likeness;
const mc = data.medicinal_chemistry;
const rangeItems: RangeItem[] = [
{ key: "WLOGP", value: norm(lipo.wlogp, -2, 6), display: `${lipo.wlogp}`, note: "domain -2..6", color: CYAN, tick: `${lipo.wlogp}` },
{ key: "ESOL log S", value: norm(sol.esol_log_s, -6, 0), display: `${sol.esol_log_s}`, note: "domain -6..0", color: GREEN, tick: `${sol.esol_log_s}` },
{ key: "Log Kp (skin, cm/s)", value: norm(pk.log_kp_skin, -8, -1), display: `${pk.log_kp_skin}`, note: "domain -8..-1", color: ORANGE, tick: `${pk.log_kp_skin}` },
];
return (
SwissADME-parity panel. Descriptors computed locally with RDKit (WLOGP = Wildman-Crippen). XLOGP3, MLOGP, SILICOS-IT
and iLOGP are proprietary models and are marked unavailable; consensus Log P reflects WLOGP only. BOILED-Egg and ESOL
use the published boundaries/models (Daina & Zoete 2016; Delaney 2004).
{rangeItems.map((it) => (
{it.key}
{it.tick}
{it.note}
))}
Bioavailability Score
{dl.bioavailability_score}
Martin 2005
{mc.pains_alerts.alert_count} alert(s)
{mc.pains_alerts.alerts.length > 0 && (
{mc.pains_alerts.alerts.map((a, i) => - {a}
)}
)}
{mc.brenk_alerts.alert_count} alert(s)
{mc.brenk_alerts.alerts.length > 0 && (
{mc.brenk_alerts.alerts.map((a, i) => - {a}
)}
)}
SA score (1 = easy, 10 = hard), RDKit sascorer.
Reference: Daina A., Michielin O., Zoete V. Sci Rep 7, 42717 (2017) — SwissADME.
);
}