Expanic
Update code
1eb43b0
Raw
History Blame Contribute Delete
7.59 kB
"use client"
import React, { useRef, useMemo } from "react"
import { Canvas, useFrame } from "@react-three/fiber"
import { OrbitControls, Sphere, Html, Stars, Float } from "@react-three/drei"
import * as THREE from "three"
interface PredictionOutput {
sepsis: number
respiration: number
coagulation: number
liver: number
cardiovascular: number
cns: number
renal: number
hours_beforesepsis: number
fod: number
hours_beforedeath: number
}
interface OrganProps {
position: [number, number, number]
baseColor: string
riskScore?: number // 0-4 SOFA score or 0-1 probability
label: string
size?: number
}
function getColorByRisk(baseColor: string, riskScore: number = 0, maxScore: number = 4): string {
const normalizedRisk = Math.min(riskScore / maxScore, 1)
if (normalizedRisk >= 0.75) return "#ef4444" // Critical - Red
if (normalizedRisk >= 0.5) return "#f59e0b" // Warning - Orange
if (normalizedRisk >= 0.25) return "#eab308" // Elevated - Yellow
return baseColor // Normal - Original color
}
function Organ({ position, baseColor, riskScore = 0, label, size = 0.3 }: OrganProps) {
const meshRef = useRef<THREE.Mesh>(null)
const color = getColorByRisk(baseColor, riskScore)
const isHighRisk = riskScore >= 2
useFrame((state) => {
if (meshRef.current) {
const t = state.clock.getElapsedTime()
// More intense pulse for high-risk organs
const pulseIntensity = isHighRisk ? 0.15 : 0.05
const pulseSpeed = isHighRisk ? 3 : 2
meshRef.current.scale.setScalar(1 + Math.sin(t * pulseSpeed) * pulseIntensity)
}
})
return (
<Float speed={isHighRisk ? 2 : 1} rotationIntensity={0.2} floatIntensity={0.3}>
<mesh position={position} ref={meshRef}>
<sphereGeometry args={[size, 32, 32]} />
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={isHighRisk ? 0.8 : 0.4}
opacity={0.9}
transparent
roughness={0.3}
metalness={0.2}
/>
<Html distanceFactor={10} style={{ pointerEvents: 'none' }}>
<div className={`
px-2 py-1 rounded-lg text-xs font-medium whitespace-nowrap shadow-xl border
${isHighRisk
? 'bg-red-950/90 text-red-200 border-red-500/50 animate-pulse'
: 'bg-slate-900/90 text-white border-slate-700'
}
`}>
{label}
{riskScore > 0 && (
<span className={`ml-1.5 ${isHighRisk ? 'text-red-400' : 'text-slate-400'}`}>
({riskScore.toFixed(1)})
</span>
)}
</div>
</Html>
</mesh>
</Float>
)
}
interface BodyProps {
prediction: PredictionOutput | null
}
function RotatingBody({ prediction }: BodyProps) {
const groupRef = useRef<THREE.Group>(null)
useFrame((state, delta) => {
if (groupRef.current) {
groupRef.current.rotation.y += delta * 0.15
}
})
// Calculate overall risk for body wireframe color
const overallRisk = useMemo(() => {
if (!prediction) return 0
return prediction.sepsis
}, [prediction])
const bodyColor = overallRisk >= 0.7 ? "#ef4444" : overallRisk >= 0.4 ? "#f59e0b" : "#64748b"
return (
<group ref={groupRef}>
{/* Human Body Outline - Capsule torso */}
<mesh position={[0, 0, 0]}>
<capsuleGeometry args={[0.7, 2.2, 4, 16]} />
<meshStandardMaterial
color={bodyColor}
wireframe
opacity={0.15}
transparent
emissive={bodyColor}
emissiveIntensity={overallRisk >= 0.4 ? 0.2 : 0}
/>
</mesh>
{/* Head */}
<mesh position={[0, 1.8, 0]}>
<sphereGeometry args={[0.4, 16, 16]} />
<meshStandardMaterial
color={bodyColor}
wireframe
opacity={0.1}
transparent
/>
</mesh>
{/* Organ Highlights with prediction data */}
<Organ
position={[0, 0.4, 0.45]}
baseColor="#ef4444"
riskScore={prediction?.cardiovascular || 0}
label="Heart (CVS)"
size={0.28}
/>
<Organ
position={[-0.35, -0.1, 0.35]}
baseColor="#eab308"
riskScore={prediction?.liver || 0}
label="Liver"
size={0.25}
/>
<Organ
position={[0, 0.7, 0.1]}
baseColor="#3b82f6"
riskScore={prediction?.respiration || 0}
label="Lungs"
size={0.35}
/>
<Organ
position={[0, -0.7, 0.3]}
baseColor="#a855f7"
riskScore={prediction?.renal || 0}
label="Kidneys"
size={0.22}
/>
<Organ
position={[0, 1.5, 0.1]}
baseColor="#10b981"
riskScore={prediction?.cns || 0}
label="CNS (Brain)"
size={0.3}
/>
<Organ
position={[0.35, -0.1, 0.35]}
baseColor="#f59e0b"
riskScore={prediction?.coagulation || 0}
label="Coagulation"
size={0.2}
/>
</group>
)
}
interface BodyVisualizerProps {
prediction?: PredictionOutput | null
}
export default function BodyVisualizer({ prediction = null }: BodyVisualizerProps) {
return (
<div className="w-full h-full min-h-[400px] relative">
<Canvas
camera={{ position: [0, 0, 5.5], fov: 50 }}
gl={{ antialias: true, alpha: true }}
>
{/* Lighting */}
<ambientLight intensity={0.4} />
<pointLight position={[10, 10, 10]} intensity={0.8} color="#ffffff" />
<pointLight position={[-10, -10, -10]} intensity={0.3} color="#06b6d4" />
<spotLight
position={[0, 10, 5]}
intensity={0.5}
angle={0.3}
penumbra={0.5}
color="#3b82f6"
/>
{/* Star field background */}
<Stars
radius={100}
depth={50}
count={3000}
factor={4}
saturation={0.5}
fade
speed={0.5}
/>
{/* Controls */}
<OrbitControls
enableZoom={true}
enablePan={false}
autoRotate
autoRotateSpeed={0.3}
minDistance={3}
maxDistance={8}
/>
{/* Body Visualization */}
<RotatingBody prediction={prediction} />
</Canvas>
</div>
)
}