如何调整D3可缩放旭日图中最内层圆的尺寸?
这是个很实用的需求——要让旭日图的中心区域始终保持固定大小来展示悬停信息,确实得调整默认的D3分区布局逻辑。我来分享个可行的实现思路和代码示例:
核心思路
默认的d3.partition()布局会按比例分配每个层级的半径,缩放时整个视图会按比例缩小/放大。我们要做的是:
- 强制当前层级的中心节点(也就是当前根节点)始终使用固定半径
- 基于这个固定半径,重新计算外层节点的尺寸,保持层级间的相对比例
- 独立维护一个中心信息元素,不受缩放逻辑影响
具体实现步骤
- 定义固定参数:先设定好中心圆的固定半径,以及旭日图的最大外层半径
- 重写布局计算:在每次更新布局(比如缩放层级)后,覆盖中心节点的半径值,并调整外层节点的尺寸范围
- 绑定交互事件:给节点添加悬停事件,将信息渲染到中心固定位置
- 平滑过渡:确保缩放时外层节点的动画过渡自然,中心圆保持稳定
代码示例
// 基础配置 const width = 800; const height = 800; const fixedInnerRadius = 60; // 你想要的中心固定尺寸 const outerRadius = Math.min(width, height) / 2; // 创建SVG容器 const svg = d3.select('body') .append('svg') .attr('width', width) .attr('height', height) .append('g') .attr('transform', `translate(${width/2}, ${height/2})`); // 初始化中心信息文本(始终固定在中心) const centerInfo = svg.append('text') .attr('text-anchor', 'middle') .attr('dominant-baseline', 'middle') .style('font-size', '16px') .style('fill', '#333') .text('Hover over a node'); // 加载数据(这里用示例数据,替换成你的实际数据) const data = { name: 'Root', children: [ { name: 'Category 1', children: [{ name: 'Item 1a' }, { name: 'Item 1b' }] }, { name: 'Category 2', children: [{ name: 'Item 2a' }, { name: 'Item 2b' }, { name: 'Item 2c' }] } ] }; // 转换数据为分层结构 const root = d3.hierarchy(data) .sum(d => 1); // 按节点数量分配尺寸,可替换成你的数值字段 // 定义分区布局和弧生成器 const partition = d3.partition() .size([2 * Math.PI, outerRadius]); const arc = d3.arc() .startAngle(d => d.x0) .endAngle(d => d.x1) .innerRadius(d => d.r0) .outerRadius(d => d.r1); // 更新函数:处理布局调整和渲染 function update(currentRoot) { // 先执行默认分区计算 partition(currentRoot); // 强制中心节点(当前根)使用固定半径,调整外层节点尺寸 currentRoot.each(d => { if (d.depth === 0) { // 当前根节点(中心)固定尺寸 d.r0 = 0; d.r1 = fixedInnerRadius; } else { // 外层节点基于中心固定半径重新计算范围 const parentOuter = d.parent.r1; // 保留原布局的比例关系 const sizeRatio = (d.r1 - d.r0) / outerRadius; d.r0 = parentOuter; d.r1 = parentOuter + (outerRadius - fixedInnerRadius) * sizeRatio; } }); // 绑定数据并更新路径 const paths = svg.selectAll('path') .data(currentRoot.descendants().filter(d => d.depth > 0)); // 过滤中心节点,单独渲染背景圆 // 新增节点 paths.enter() .append('path') .attr('fill', (d, i) => d3.schemeCategory10[i % 10]) .attr('d', arc) .on('mouseover', (e, d) => { // 悬停时更新中心信息 centerInfo.text(`Name: ${d.data.name}\nDepth: ${d.depth}`); }) .on('mouseout', () => { centerInfo.text('Hover over a node'); }) .on('click', (e, d) => { // 点击节点缩放层级 update(d); }) .merge(paths) .transition() .duration(750) .attr('d', arc); // 移除多余节点 paths.exit().remove(); // 渲染中心固定背景圆(可选,用来突出中心区域) svg.selectAll('.center-circle') .data([currentRoot]) .enter() .append('circle') .attr('class', 'center-circle') .merge(svg.select('.center-circle')) .attr('r', fixedInnerRadius) .attr('fill', '#fff') .attr('stroke', '#ccc'); } // 初始渲染 update(root);
额外提示
- 如果你的数据是按数值权重分配尺寸,只需要把
d3.hierarchy(data).sum(d => 1)里的1替换成对应的数值字段(比如d.value)即可 - 中心信息可以扩展成更复杂的DOM元素(比如分组包含图标、多行文本),只要保持它的位置固定在SVG中心
- 可以调整过渡时间(
duration(750))来匹配你的交互体验需求
内容的提问来源于stack exchange,提问作者g_raham
相关产品推荐
相关产品推荐

