diff --git a/package-lock.json b/package-lock.json
index e21fa28..7f5703a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,7 @@
"react-hot-toast": "^2.4.1",
"react-markdown": "^9.0.1",
"react-multi-email": "^1.0.23",
- "recharts": "^2.12.7",
+ "recharts": "^2.13.0",
"remark-gfm": "^4.0.0",
"sharp": "^0.33.4",
"swr": "^2.2.5"
@@ -11483,14 +11483,14 @@
}
},
"node_modules/recharts": {
- "version": "2.12.7",
- "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz",
- "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==",
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.0.tgz",
+ "integrity": "sha512-sbfxjWQ+oLWSZEWmvbq/DFVdeRLqqA6d0CDjKx2PkxVVdoXo16jvENCE+u/x7HxOO+/fwx//nYRwb8p8X6s/lQ==",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
- "react-is": "^16.10.2",
+ "react-is": "^18.3.1",
"react-smooth": "^4.0.0",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
@@ -11520,6 +11520,11 @@
"node": ">=6"
}
},
+ "node_modules/recharts/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
diff --git a/package.json b/package.json
index d5de3c6..926b908 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"react-hot-toast": "^2.4.1",
"react-markdown": "^9.0.1",
"react-multi-email": "^1.0.23",
- "recharts": "^2.12.7",
+ "recharts": "^2.13.0",
"remark-gfm": "^4.0.0",
"sharp": "^0.33.4",
"swr": "^2.2.5"
diff --git a/src/app/dashboard/models/[modelId]/components/DatasetResults.tsx b/src/app/dashboard/models/[modelId]/components/DatasetResults.tsx
index e88a0d9..eeff44f 100644
--- a/src/app/dashboard/models/[modelId]/components/DatasetResults.tsx
+++ b/src/app/dashboard/models/[modelId]/components/DatasetResults.tsx
@@ -34,6 +34,8 @@ import { getKeyValue, useDisclosure } from '@nextui-org/react';
import DoaTableCell from '@/app/dashboard/models/[modelId]/components/DoaTableCell';
import FeatureEditModal from '@/app/dashboard/models/[modelId]/components/FeatureEditModal';
import DoaModal from '@/app/dashboard/models/[modelId]/components/DoaModal';
+import { Tab, Tabs } from '@nextui-org/tabs';
+import PBPKPlots from '@/app/dashboard/models/[modelId]/components/PBPKPlots';
const log = logger.child({ module: 'dataset' });
@@ -136,107 +138,117 @@ export default function DatasetResults({
const tableRows = resultTableData.rows;
return (
-
- {dataset?.status === 'FAILURE' && model.isAdmin && (
-
-
- }
- >
-
- legacy prediction service: {model.legacyPredictionService}
-
-
- Failure reason: {dataset?.failureReason}
-
-
-
-
- )}
-
- Result
-
-
-
- ID {datasetId}
-
- {getDatasetStatusNode(dataset)}
-
- {!isLoaded && (
-
-
-
-
-
-
- )}
- {isLoaded && dataset?.status === 'SUCCESS' && (
- <>
-
-
}
- className="my-2"
- onPress={() => downloadResultsCSV(model)}
- >
- Export CSV
-
+
+
+
+
+ {dataset?.status === 'FAILURE' && model.isAdmin && (
+
+
+ }
+ >
+
+ legacy prediction service: {model.legacyPredictionService}
+
+
+ Failure reason: {dataset?.failureReason}
+
+
+
+
+ )}
+
+
+ ID {datasetId}
+
+ {getDatasetStatusNode(dataset)}
+
+ {!isLoaded && (
+
+
+
+
+
+
+ )}
+ {isLoaded && dataset?.status === 'SUCCESS' && (
+ <>
+
+ }
+ className="my-2"
+ onPress={() => downloadResultsCSV(model)}
+ >
+ Export CSV
+
+
+
+
+ {(column) => (
+ {column.label}
+ )}
+
+
+
+ {(item) => (
+
+ {(columnKey) => {
+ if (columnKey === 'doa') {
+ return (
+
+ {
+ setSelectedRow(item);
+ onDoaModalOpen();
+ }}
+ />
+
+ );
+ }
+ return (
+
+ {getKeyValue(item, columnKey)}
+
+ );
+ }}
+
+ )}
+
+
+ >
+ )}
+
+ {selectedRow && (
+
+ )}
-
-
- {(column) => (
- {column.label}
- )}
-
-
-
- {(item) => (
-
- {(columnKey) => {
- if (columnKey === 'doa') {
- return (
-
- {
- setSelectedRow(item);
- onDoaModalOpen();
- }}
- />
-
- );
- }
- return (
- {getKeyValue(item, columnKey)}
- );
- }}
-
- )}
-
-
- >
- )}
-
- {selectedRow && (
-
- )}
+
+ {model.type === 'R_PBPK' && dataset && dataset.status === 'SUCCESS' && (
+
+
+
+ )}
+
);
}
diff --git a/src/app/dashboard/models/[modelId]/components/PBPKPlots.tsx b/src/app/dashboard/models/[modelId]/components/PBPKPlots.tsx
new file mode 100644
index 0000000..9d7c72c
--- /dev/null
+++ b/src/app/dashboard/models/[modelId]/components/PBPKPlots.tsx
@@ -0,0 +1,152 @@
+import { DatasetDto, FeatureDto, ModelDto } from '@/app/api.types';
+import React, { useMemo } from 'react';
+import {
+ CartesianGrid,
+ Legend,
+ Line,
+ LineChart,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from 'recharts';
+
+interface PBPKPlotsProps {
+ model: ModelDto;
+ dataset: DatasetDto;
+}
+
+const groupByUnits = (
+ dependentFeatures: FeatureDto[],
+): Partial
> => {
+ return Object.groupBy(
+ dependentFeatures,
+ (feature) => feature.units ?? 'No Units',
+ );
+};
+
+type PBPKPlot = {
+ unit: string;
+ plotData: PBPKPlotData[];
+};
+
+type PBPKPlotData = {
+ time: number;
+} & {
+ [key: string]: number;
+};
+
+function generatePlots(
+ groupFeaturesByUnits: Partial>,
+ dataset: DatasetDto,
+): PBPKPlot[] {
+ return Object.entries(groupFeaturesByUnits).map(([unit, features]) => {
+ const plotData = dataset.result!.map((resultRow: any) => {
+ const time = parseFloat(resultRow.time);
+ const featureValues: Record = {};
+ features!.forEach((feature: FeatureDto) => {
+ return (featureValues[feature.key] = parseFloat(
+ resultRow[feature.key],
+ ));
+ });
+
+ return {
+ time,
+ ...featureValues,
+ };
+ });
+
+ return {
+ unit,
+ plotData,
+ };
+ });
+}
+
+const lineColors = [
+ '#3498db', // Bright Blue
+ '#f1c40f', // Vibrant Yellow
+ '#e74c3c', // Deep Red
+ '#2ecc71', // Lime Green
+ '#9b59b6', // Rich Plum
+ '#1abc9c', // Seafoam Green
+ '#16a085', // Forest Green
+ '#27ae60', // Mint Green
+ '#2980b9', // Navy Blue
+ '#8e44ad', // Lavender
+ '#d35400', // Burnt Orange
+ '#c0392b', // Crimson Red
+ '#7f8c8d', // Dark Gray
+ '#2c3e50', // Charcoal Gray
+ '#95a5a6', // Light Gray
+ '#f7dc6f', // Golden Yellow
+ '#66d9ef', // Sky Blue
+ '#b2fffc', // Pale Blue
+ '#8bc34a', // Olive Green
+ '#ff99cc', // Pastel Pink
+];
+
+export default function PBPKPlots({ model, dataset }: PBPKPlotsProps) {
+ const groupFeaturesByUnits: Partial> = useMemo(
+ () =>
+ Object.groupBy(
+ model.dependentFeatures,
+ (feature) => feature.units ?? 'No Units',
+ ),
+ [model.dependentFeatures],
+ );
+
+ if (!dataset.result || !groupFeaturesByUnits) {
+ return null;
+ }
+
+ const plots: PBPKPlot[] = generatePlots(groupFeaturesByUnits, dataset);
+
+ return (
+
+ {plots.map((plot, index) => (
+ <>
+
+
+
+
+
+
+
+
+
+ {Object.entries(plot.plotData[0]).map(([key, value], index) => {
+ if (key === 'time') return null;
+ return (
+
+ );
+ })}
+
+
+
+ >
+ ))}
+
+ );
+}