Team Ai
Apppublic

IDKHowToCodeFr/tinyml-backend

sourceHugging Faceupdated 2d agoView on Hugging Face
1likes
ShapChart.tsx102 linesDownload Raw Back to components
1"use client";2 3import { motion } from "framer-motion";4import { useEffect, useState } from "react";5import { Loader2 } from "lucide-react";6 7export default function ShapChart({ patientData }: { patientData: any }) {8  const [shapData, setShapData] = useState<{feature_names: string[], shap_values: number[]} | null>(null);9  const [loading, setLoading] = useState(false);10 11  useEffect(() => {12    if (!patientData) return;13    14    const fetchExplain = async () => {15      setLoading(true);16      try {17        const res = await fetch((process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000") + "/explain", {18          method: "POST",19          headers: { "Content-Type": "application/json" },20          body: JSON.stringify(patientData)21        });22        const data = await res.json();23        if (data.shap_values) {24          setShapData(data);25        }26      } catch (err) {27        console.error("SHAP fetch error", err);28      } finally {29        setLoading(false);30      }31    };32    33    const timeout = setTimeout(fetchExplain, 2500);34    return () => clearTimeout(timeout);35  }, [patientData]);36 37  if (!shapData) {38    return (39      <div className="h-full w-full flex flex-col items-center justify-center gap-3">40        {loading ? (41          <>42            <Loader2 className="animate-spin text-ink" size={20} />43            <span className="text-slate text-[12px] font-bold uppercase tracking-[0.1em]">COMPUTING_SHAP_VECTORS...</span>44          </>45        ) : (46          <span className="text-slate text-[12px] font-bold uppercase tracking-[0.1em]">AWAITING_TELEMETRY...</span>47        )}48      </div>49    );50  }51 52  // Sort by absolute magnitude of SHAP value53  const items = shapData.feature_names.map((name, i) => ({54    name: name.toUpperCase(),55    value: shapData.shap_values[i],56    abs: Math.abs(shapData.shap_values[i])57  })).sort((a, b) => b.abs - a.abs); // show ALL features58 59  const maxAbs = Math.max(...items.map(i => i.abs), 0.1);60 61  return (62    <div className="flex flex-col gap-4 w-full h-full justify-center">63      {items.map((item, idx) => {64        const isPositive = item.value > 0;65        const widthPercent = (item.abs / maxAbs) * 50;66        67        return (68          <div key={item.name} className="flex flex-col gap-2">69            <div className="flex justify-between text-[11px] font-bold tracking-[0.1em]">70              <span className="text-ink">{item.name}</span>71              <span className={isPositive ? "text-hazard" : "text-ink"}>72                {item.value > 0 ? "+" : ""}{item.value.toFixed(3)}73              </span>74            </div>75            76            {/* Brutalist Chart Bar */}77            <div className="relative h-4 w-full bg-white border-2 border-ink flex items-center">78              {/* Midline (zero axis) */}79              <div className="absolute left-1/2 top-[-2px] bottom-[-2px] w-0.5 bg-ink z-10" />80              81              {/* Animated Value Bar */}82              <motion.div 83                initial={{ width: 0, opacity: 0 }}84                animate={{ 85                  width: `${widthPercent}%`,86                  opacity: 1,87                }}88                transition={{ type: "spring", stiffness: 60, damping: 15 }}89                className={`absolute h-full ${isPositive ? 'bg-hazard border-x-2 border-hazard' : 'bg-ink border-x-2 border-ink'}`}90                style={{91                  left: isPositive ? '50%' : 'auto',92                  right: !isPositive ? '50%' : 'auto'93                }}94              />95            </div>96          </div>97        );98      })}99    </div>100  );101}102