Files
shensheshou/components/tag-management/tag-relationship-graph.tsx
v0 2408d50cb0 refactor: overhaul UI for streamlined user experience
Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-07-18 13:47:12 +00:00

150 lines
4.0 KiB
TypeScript

"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<SVGSVGElement>(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<SVGCircleElement, Node>().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 <svg ref={svgRef} width="100%" height="100%" />
}