wu981526092's picture
🚀 Deploy AgentGraph: Complete agent monitoring and knowledge graph system
c2ea5ed
import React, { useMemo } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { AlertTriangle, BarChart3 } from "lucide-react";
import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";
interface RiskTypeStats {
[riskType: string]: {
count: number;
percentage: number;
affectedEntities: Set<string>;
avgSeverity: number;
};
}
interface RiskDistributionPieChartProps {
riskTypeStats: RiskTypeStats;
totalFailures: number;
onRiskTypeClick: (riskType: string, stats: any) => void;
}
interface PieChartData {
name: string;
value: number;
percentage: number;
color: string;
severity: number;
affectedEntities: number;
}
export function RiskDistributionPieChart({
riskTypeStats,
totalFailures,
onRiskTypeClick,
}: RiskDistributionPieChartProps) {
const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null);
// Add rotation animation styles
React.useEffect(() => {
const style = document.createElement("style");
style.textContent = `
.rotating-chart {
animation: rotate 20s linear infinite;
transform-origin: center;
}
.rotating-chart:hover {
animation-play-state: paused;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
// Transform data for pie chart
const chartData = useMemo<PieChartData[]>(() => {
const colors = [
"#ef4444", // red-500 - High severity
"#f97316", // orange-500 - Medium-high severity
"#eab308", // yellow-500 - Medium severity
"#3b82f6", // blue-500 - Medium-low severity
"#8b5cf6", // purple-500 - Low severity
"#10b981", // emerald-500 - Very low severity
];
// Guard against undefined or empty riskTypeStats
if (!riskTypeStats || typeof riskTypeStats !== "object") {
return [];
}
return Object.entries(riskTypeStats)
.filter(
([riskType, stats]) => riskType && stats && typeof stats === "object"
)
.map(([riskType, stats], index) => ({
name: riskType.replace(/_/g, " "),
value: stats.count || 0,
percentage: stats.percentage || 0,
color: colors[index % colors.length] || "#6b7280",
severity: stats.avgSeverity || 0,
affectedEntities: stats.affectedEntities?.size || 0,
}));
}, [riskTypeStats]);
// Handle pie chart segment click
const handleSegmentClick = (data: PieChartData) => {
// Guard against undefined data
if (!data || !data.name) {
return;
}
const originalRiskType = Object.keys(riskTypeStats).find(
(key) => key.replace(/_/g, " ") === data.name
);
if (originalRiskType) {
onRiskTypeClick(originalRiskType, riskTypeStats[originalRiskType]);
}
};
if (totalFailures === 0) {
return (
<Card className="glass-card h-full flex flex-col">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5" />
Risk Distribution
</CardTitle>
</CardHeader>
<CardContent className="flex-1 flex items-center justify-center">
<div className="text-center py-8 text-muted-foreground">
<BarChart3 className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No risk data available</p>
<p className="text-sm">
Upload traces with failures to see distribution
</p>
</div>
</CardContent>
</Card>
);
}
return (
<Card className="glass-card shadow-sm h-full flex flex-col">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-lg font-semibold">
Risk Distribution
</CardTitle>
</div>
</div>
<p className="text-sm text-muted-foreground mt-2">
Click segments for detailed analysis
</p>
</CardHeader>
<CardContent className="flex-1">
{/* Pie Chart with legend */}
<div className="space-y-4">
{/* Rotating Pie Chart */}
<div id="risk-distribution-chart" className="h-80 w-full relative">
{/* Glass overlay effect */}
<div className="absolute inset-0 bg-gradient-to-br from-white/5 via-transparent to-white/5 rounded-lg pointer-events-none" />
<ResponsiveContainer width="100%" height="100%">
<PieChart className="rotating-chart">
<Pie
data={chartData}
cx="50%"
cy="50%"
innerRadius={70}
outerRadius={120}
paddingAngle={2}
dataKey="value"
cursor="pointer"
onClick={handleSegmentClick}
>
{chartData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.color}
stroke={
hoveredIndex === index
? "rgba(255,255,255,0.4)"
: "rgba(255,255,255,0.1)"
}
strokeWidth={hoveredIndex === index ? 2 : 1}
className="transition-all duration-200"
style={{
filter:
hoveredIndex === index
? "brightness(1.1) drop-shadow(0 0 8px rgba(255,255,255,0.3))"
: "none",
}}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
/>
))}
</Pie>
</PieChart>
</ResponsiveContainer>
</div>
{/* Legend Table */}
<div className="grid grid-cols-1 gap-2">
{chartData.map((entry, index) => (
<div
key={index}
className={`flex items-center justify-between p-3 rounded-lg border transition-all cursor-pointer ${
hoveredIndex === index
? "bg-gradient-to-r from-white/20 to-white/10 border-white/30 shadow-lg scale-[1.02]"
: "bg-gradient-to-r from-white/10 to-transparent border-white/10 hover:bg-white/5"
}`}
onClick={() => handleSegmentClick(entry)}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
>
<div className="flex items-center gap-3">
<div
className="w-4 h-4 rounded-full border border-white/20"
style={{ backgroundColor: entry.color }}
/>
<span className="text-sm font-medium text-foreground">
{entry.name}
</span>
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span className="font-medium">{entry.value} failures</span>
<span className="text-xs bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded">
{entry.percentage}%
</span>
</div>
</div>
))}
</div>
</div>
</CardContent>
</Card>
);
}