DisplayContent.jsx
1 import React, { useEffect, useRef } from 'react'; 2 import * as d3 from 'd3'; 3 4 const DisplayContent = ({ data, onCircleClick }) => { 5 const svgRef = useRef(null); 6 7 useEffect(() => { 8 if (!data || !svgRef.current) return; 9 10 const width = 928; 11 const height = width; 12 13 const color = d3.scaleLinear() 14 .domain([0, 5]) 15 .range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"]) 16 .interpolate(d3.interpolateHcl); 17 18 const pack = data => d3.pack() 19 .size([width, height]) 20 .padding(3) 21 (d3.hierarchy(data) 22 .sum(d => d.value) 23 .sort((a, b) => b.value - a.value)); 24 25 const root = pack(data); 26 27 const svg = d3.select(svgRef.current) 28 .attr("viewBox", `-${width / 2} -${height / 2} ${width} ${height}`) 29 .attr("width", width) 30 .attr("height", height) 31 .attr("style", `max-width: 100%; height: auto; display: block; margin: 0 -14px; background: ${color(0)}; cursor: pointer;`); 32 33 svg.selectAll("*").remove(); // Clear previous content 34 35 const node = svg.append("g") 36 .selectAll("circle") 37 .data(root.descendants().slice(1)) 38 .join("circle") 39 .attr("fill", d => d.children ? color(d.depth) : "white") 40 .attr("pointer-events", d => !d.children ? "none" : null) 41 .on("mouseover", function() { d3.select(this).attr("stroke", "#000"); }) 42 .on("mouseout", function() { d3.select(this).attr("stroke", null); }) 43 .on("click", (event, d) => { 44 if (focus !== d) { 45 zoom(event, d); 46 event.stopPropagation(); 47 } 48 if (!d.children) { 49 onCircleClick(d.data.name); 50 } 51 }); 52 53 const label = svg.append("g") 54 .style("font", "10px sans-serif") 55 .attr("pointer-events", "none") 56 .attr("text-anchor", "middle") 57 .selectAll("text") 58 .data(root.descendants()) 59 .join("text") 60 .style("fill-opacity", d => d.parent === root ? 1 : 0) 61 .style("display", d => d.parent === root ? "inline" : "none") 62 .text(d => d.data.name); 63 64 svg.on("click", (event) => zoom(event, root)); 65 let focus = root; 66 let view; 67 zoomTo([focus.x, focus.y, focus.r * 2]); 68 69 function zoomTo(v) { 70 const k = width / v[2]; 71 72 view = v; 73 74 label.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`); 75 node.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`); 76 node.attr("r", d => d.r * k); 77 } 78 79 function zoom(event, d) { 80 const focus0 = focus; 81 82 focus = d; 83 84 const transition = svg.transition() 85 .duration(event.altKey ? 7500 : 750) 86 .tween("zoom", d => { 87 const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]); 88 return t => zoomTo(i(t)); 89 }); 90 91 label 92 .filter(function(d) { return d.parent === focus || this.style.display === "inline"; }) 93 .transition(transition) 94 .style("fill-opacity", d => d.parent === focus ? 1 : 0) 95 .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; }) 96 .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; }); 97 } 98 }, [data, onCircleClick]); 99 100 return <svg ref={svgRef}></svg>; 101 }; 102 103 export default DisplayContent;