"use client" import { useEffect, useRef } from "react" import * as d3 from "d3" interface Node { id: string name: string category: string value: number } interface Link { source: string target: string value: number } const mockData = { nodes: [ { id: "1", name: "高价值用户", category: "用户价值", value: 20 }, { id: "2", name: "90后", category: "人口属性", value: 15 }, { id: "3", name: "游戏爱好者", category: "兴趣爱好", value: 18 }, { id: "4", name: "流失风险高", category: "流失风险", value: 12 }, { id: "5", name: "北京地区", category: "地理位置", value: 10 }, { id: "6", name: "周末活跃", category: "活跃时间", value: 14 }, { id: "7", name: "高消费", category: "消费能力", value: 16 }, { id: "8", name: "App渠道", category: "渠道来源", value: 8 }, ], links: [ { source: "1", target: "7", value: 5 }, { source: "2", target: "3", value: 8 }, { source: "2", target: "6", value: 3 }, { source: "3", target: "7", value: 6 }, { source: "4", target: "6", value: 4 }, { source: "5", target: "8", value: 2 }, { source: "6", target: "3", value: 7 }, { source: "7", target: "8", value: 3 }, { source: "1", target: "3", value: 5 }, { source: "2", target: "5", value: 4 }, { source: "4", target: "1", value: 6 }, ], } const categoryColors = { 用户价值: "#0088FE", 人口属性: "#00C49F", 兴趣爱好: "#FFBB28", 流失风险: "#FF8042", 地理位置: "#8884d8", 活跃时间: "#82ca9d", 消费能力: "#ffc658", 渠道来源: "#8dd1e1", } export function TagRelationshipGraph() { const svgRef = useRef(null) useEffect(() => { if (!svgRef.current) return const svg = d3.select(svgRef.current) svg.selectAll("*").remove() const width = svgRef.current.clientWidth const height = svgRef.current.clientHeight // 创建力导向图 const simulation = d3 .forceSimulation(mockData.nodes as d3.SimulationNodeDatum[]) .force( "link", d3 .forceLink(mockData.links) .id((d: any) => d.id) .distance(100), ) .force("charge", d3.forceManyBody().strength(-200)) .force("center", d3.forceCenter(width / 2, height / 2)) // 绘制连线 const link = svg .append("g") .attr("stroke", "#999") .attr("stroke-opacity", 0.6) .selectAll("line") .data(mockData.links) .join("line") .attr("stroke-width", (d) => Math.sqrt(d.value)) // 绘制节点 const node = svg .append("g") .selectAll("circle") .data(mockData.nodes) .join("circle") .attr("r", (d) => d.value * 0.8) .attr("fill", (d) => categoryColors[d.category as keyof typeof categoryColors]) .call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended) as any) // 添加标签 const text = svg .append("g") .selectAll("text") .data(mockData.nodes) .join("text") .text((d) => d.name) .attr("font-size", 10) .attr("dx", 15) .attr("dy", 4) // 更新位置 simulation.on("tick", () => { link .attr("x1", (d: any) => d.source.x) .attr("y1", (d: any) => d.source.y) .attr("x2", (d: any) => d.target.x) .attr("y2", (d: any) => d.target.y) node.attr("cx", (d: any) => d.x).attr("cy", (d: any) => d.y) text.attr("x", (d: any) => d.x).attr("y", (d: any) => d.y) }) // 拖拽函数 function dragstarted(event: any, d: any) { if (!event.active) simulation.alphaTarget(0.3).restart() d.fx = d.x d.fy = d.y } function dragged(event: any, d: any) { d.fx = event.x d.fy = event.y } function dragended(event: any, d: any) { if (!event.active) simulation.alphaTarget(0) d.fx = null d.fy = null } return () => { simulation.stop() } }, []) return }