import { useState, useEffect, useRef } from "react";
import ForceGraph2D from "react-force-graph-2d";
// import * as d3 from 'd3';
export default function CharacterGraph({ graphData }) {
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 600, height: 600 });
const [hoveredLink, setHoveredLink] = useState(null);
const fgRef = useRef();
useEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
setDimensions({
width: containerRef.current.offsetWidth,
height: Math.max(300, containerRef.current.offsetHeight),
});
}
};
updateDimensions();
window.addEventListener("resize", updateDimensions);
return () => window.removeEventListener("resize", updateDimensions);
}, []);
return (
Character Relationship Graph
{
// Draw node
ctx.beginPath();
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI);
ctx.fillStyle = "transparent";
ctx.fill();
// Always show node label
const label = node.name;
const fontSize = 20 / globalScale;
ctx.font = `${fontSize}px Arial`;
const textWidth = ctx.measureText(label).width;
const bckgDimensions = [textWidth, fontSize].map(
(n) => n + fontSize * 0.2
);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#3b82f6";
ctx.fillText(label, node.x, node.y);
node.__bckgDimensions = bckgDimensions;
}}
onLinkHover={(link) => setHoveredLink(link ? `${link.source.id}-${link.target.id}` : null)}
nodePointerAreaPaint={(node, color, ctx) => {
ctx.fillStyle = color;
const bckgDimensions = node.__bckgDimensions;
bckgDimensions &&
ctx.fillRect(
node.x - bckgDimensions[0] / 2,
node.y - bckgDimensions[1] / 2,
...bckgDimensions
);
}}
linkCanvasObject={(link, ctx, globalScale) => {
const start = link.source;
const end = link.target;
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, end.y);
ctx.strokeStyle = "#9ca3af";
ctx.lineWidth = 0.5;
ctx.stroke();
// Only draw label if link is hovered
const linkId = `${link.source.id}-${link.target.id}`;
if (hoveredLink === linkId && link.label) {
const textPos = {
x: start.x + (end.x - start.x) / 2,
y: start.y + (end.y - start.y) / 2
};
const fontSize = 3 + 1/globalScale;
ctx.font = `${fontSize}px Arial`;
const textWidth = ctx.measureText(link.label).width;
const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillRect(
textPos.x - bckgDimensions[0] / 2,
textPos.y - bckgDimensions[1] / 2,
...bckgDimensions
);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#666';
ctx.fillText(link.label, textPos.x, textPos.y);
}
}}
onNodeDragEnd={node => {
node.fx = node.x;
node.fy = node.y;
}}
linkDirectionalArrowLength={3.5}
linkDirectionalArrowRelPos={3}
linkWidth={1}
backgroundColor="#ffffff"
width={dimensions.width}
height={dimensions.height}
onEngineStop={() => fgRef.current.zoomToFit(600)}
cooldownTicks={100}
/>
);
}