import { useState, useCallback, useEffect } from 'react' import Tree from 'react-d3-tree' import { ZoomIn, ZoomOut, Maximize2 } from 'lucide-react' import './PedigreeTree.css' const PedigreeTree = ({ dogId, pedigreeData, coi }) => { const [translate, setTranslate] = useState({ x: 0, y: 0 }) const [zoom, setZoom] = useState(0.8) const [dimensions, setDimensions] = useState({ width: 0, height: 0 }) useEffect(() => { const updateDimensions = () => { const container = document.getElementById('tree-container') if (container) { setDimensions({ width: container.offsetWidth, height: container.offsetHeight }) setTranslate({ x: container.offsetWidth / 4, y: container.offsetHeight / 2 }) } } updateDimensions() window.addEventListener('resize', updateDimensions) return () => window.removeEventListener('resize', updateDimensions) }, []) const handleZoomIn = () => setZoom(z => Math.min(z + 0.2, 2)) const handleZoomOut = () => setZoom(z => Math.max(z - 0.2, 0.2)) const handleReset = () => { setZoom(0.8) setTranslate({ x: dimensions.width / 4, y: dimensions.height / 2 }) } const renderCustomNode = ({ nodeDatum }) => { const isRoot = nodeDatum.attributes?.isRoot const isMale = nodeDatum.attributes?.sex === 'male' const hasId = !!nodeDatum.attributes?.id const breed = nodeDatum.attributes?.breed // Colour palette aligned to app theme const maleColor = '#3b82f6' const femaleColor = '#ec4899' const rootGold = '#c2862a' // --primary const rootAccent = '#9b3a10' // --accent const nodeColor = isRoot ? rootGold : (isMale ? maleColor : femaleColor) const glowColor = isRoot ? 'rgba(194,134,42,0.35)' : (isMale ? 'rgba(59,130,246,0.3)' : 'rgba(236,72,153,0.3)') const ringColor = isRoot ? rootAccent : nodeColor const r = isRoot ? 46 : 38 return ( {/* Glow halo */} {/* Outer ring */} {/* Main node */} { if (hasId) window.location.href = `/dogs/${nodeDatum.attributes.id}` }} /> {/* SVG gradient definition for root node */} {isRoot && ( )} {/* Gender / crown icon */} {isRoot ? '👑' : (isMale ? '♂' : '♀')} {/* Name label */} {nodeDatum.name} {/* Breed label (subtle) */} {breed && ( {breed} )} {/* Registration number */} {nodeDatum.attributes?.registration && ( {nodeDatum.attributes.registration} )} {/* Birth year */} {nodeDatum.attributes?.birth_year && ( ({nodeDatum.attributes.birth_year}) )} ) } return (
{/* Controls */}
{coi !== null && coi !== undefined && (
COI 0.10 ? 'high' : coi > 0.05 ? 'medium' : 'low'}`}> {(coi * 100).toFixed(2)}%
)}
{/* Legend */}
Sire
Dam
Subject
{/* Zoom indicator */}
{Math.round(zoom * 100)}%
{/* Tree canvas */}
{pedigreeData && dimensions.width > 0 && ( { setZoom(z) setTranslate(t) }} orientation="horizontal" pathFunc="step" separation={{ siblings: 1.8, nonSiblings: 2.4 }} nodeSize={{ x: 280, y: 200 }} renderCustomNodeElement={renderCustomNode} enableLegacyTransitions transitionDuration={300} /> )}
) } export default PedigreeTree