ai-town-clone / components /characters /RelationshipGraph.tsx
Muthukumarank's picture
Add components/characters/RelationshipGraph.tsx
a73f07a verified
Raw
History Blame Contribute Delete
4.06 kB
/**
* AI Town — Relationship Graph Visualization
* =============================================
* Shows connections between characters with sentiment colors.
*/
import React from "react";
import { View, Text, ScrollView } from "react-native";
import { Canvas, Circle, Line, vec } from "@shopify/react-native-skia";
interface Relationship {
fromCharacterId: string;
toCharacterId: string;
sentiment: number;
trust: number;
interactionCount: number;
relationshipType: string;
}
interface Character {
_id: string;
name: string;
avatar: string;
mood: string;
}
interface RelationshipGraphProps {
characters: Character[];
relationships: Relationship[];
}
export function RelationshipGraph({ characters, relationships }: RelationshipGraphProps) {
if (characters.length === 0) {
return (
<View className="flex-1 items-center justify-center py-20">
<Text className="text-gray-400">No characters yet</Text>
</View>
);
}
// Arrange characters in a circle
const centerX = 160;
const centerY = 160;
const radius = 120;
const positions = characters.map((_, i) => {
const angle = (i / characters.length) * Math.PI * 2 - Math.PI / 2;
return {
x: centerX + Math.cos(angle) * radius,
y: centerY + Math.sin(angle) * radius,
};
});
return (
<View className="bg-gray-900 rounded-2xl p-4">
<Text className="text-white font-bold text-lg mb-3">🕸️ Relationship Web</Text>
<Canvas style={{ width: 320, height: 320 }}>
{/* Draw relationship edges */}
{relationships.map((rel, i) => {
const fromIdx = characters.findIndex((c) => c._id === rel.fromCharacterId);
const toIdx = characters.findIndex((c) => c._id === rel.toCharacterId);
if (fromIdx === -1 || toIdx === -1) return null;
const color = rel.sentiment > 0.5 ? "#22c55e" : rel.sentiment > 0 ? "#60a5fa" : rel.sentiment > -0.3 ? "#f59e0b" : "#ef4444";
const opacity = Math.min(1, Math.abs(rel.sentiment) + 0.3);
return (
<Line
key={`edge-${i}`}
p1={vec(positions[fromIdx].x, positions[fromIdx].y)}
p2={vec(positions[toIdx].x, positions[toIdx].y)}
color={color}
strokeWidth={Math.max(1, rel.interactionCount * 0.5)}
opacity={opacity}
/>
);
})}
{/* Draw character nodes */}
{positions.map((pos, i) => (
<Circle key={`node-${i}`} cx={pos.x} cy={pos.y} r={20} color="#374151" />
))}
{positions.map((pos, i) => (
<Circle key={`inner-${i}`} cx={pos.x} cy={pos.y} r={16} color="#1f2937" />
))}
</Canvas>
{/* Character labels */}
<View className="flex-row flex-wrap gap-2 mt-3 justify-center">
{characters.map((char, i) => (
<View key={char._id} className="flex-row items-center gap-1 bg-gray-800 px-2 py-1 rounded-full">
<Text className="text-sm">{char.avatar}</Text>
<Text className="text-gray-300 text-xs">{char.name}</Text>
</View>
))}
</View>
{/* Legend */}
<View className="flex-row justify-center gap-4 mt-3">
<View className="flex-row items-center gap-1">
<View className="w-3 h-3 rounded-full bg-green-500" />
<Text className="text-gray-400 text-xs">Close</Text>
</View>
<View className="flex-row items-center gap-1">
<View className="w-3 h-3 rounded-full bg-blue-500" />
<Text className="text-gray-400 text-xs">Friendly</Text>
</View>
<View className="flex-row items-center gap-1">
<View className="w-3 h-3 rounded-full bg-amber-500" />
<Text className="text-gray-400 text-xs">Neutral</Text>
</View>
<View className="flex-row items-center gap-1">
<View className="w-3 h-3 rounded-full bg-red-500" />
<Text className="text-gray-400 text-xs">Tense</Text>
</View>
</View>
</View>
);
}