上一篇: 热力地图
下一篇: 可缩放的矩形树图
代码结构和初始化画布的Chart对象介绍,请先看 这里
本图完整的源码地址: 这里
1 图表效果
2 数据
这里用的是和普通封闭图一样的数据
{
"name": "grandfather",
"children": [
{
"name": "father",
"children": [
{
"name": "son",
"children": [
{"name": "grandson1", "house": 2},
{"name": "grandson2", "house": 3},
{"name": "grandson3", "house": 4}
]
}
]
},
{
"name": "mother1",
"children": [
{
"name": "daughter1",
"children": [
{"name": "granddaughter1", "house": 4},
{"name": "granddaughter2", "house": 2}
]
},
{
"name": "daughter2",
"children": [
{"name": "granddaughter3", "house": 4}
]
}
]
},
{
"name": "mother2",
"children": [
{
"name": "son1",
"children": [
{"name": "grandson4", "house": 6},
{"name": "granddaughter4", "house": 1}
]
},
{
"name": "son2",
"children": [
{"name": "granddaughter5", "house": 2},
{"name": "grandson5", "house": 3},
{"name": "granddaughter5", "house": 2}
]
}
]
}
]
}
3 关键代码
导入数据
d3.json('./data.json').then(function(data){
....
}
数据转换,运用d3.pack
添加布局信息,与普通封闭图一致。设置变量focusCircle
和 prevView
,其中前者为当前呈现的节点,后者为上一次放缩的节点,这里保留放缩前后节点的数据的意义在于使用interpolateZoom
插值数据用于生成过渡动画效果。
/* ----------------------------数据转换------------------------ */
const root = d3.hierarchy(data)
.sum((d) => d.house)
.sort((a,b) => a.value - b.value);
const pack = d3.pack()
.size([chart.getBodyWidth(), chart.getBodyHeight()])
pack(root);
let focusCircle = root;
let prevView;
渲染圆圈节点,与普通封闭图相同
/* ----------------------------渲染圆圈------------------------ */
let groups;
chart.renderCircle = function(){
groups = chart.body().selectAll('.g')
.data(root.descendants().slice(1));
groups.enter()
.append('g')
.attr('class', (d, i) => 'g g-' + i)
.attr('transform', () => 'translate(' + chart.getBodyWidth()/2 + ',' + chart.getBodyHeight()/2 + ')')
.append('circle')
.attr('class', 'circle')
.merge(groups.selectAll('.circle'))
.attr('fill', (d) => chart._colors(d.depth % 10));
groups.exit()
.selectAll('.circle')
.transition().duration(config.animateDuration)
.attr('r', 0)
.remove();
}
绑定鼠标交互事件,这里点击圆圈节点,使整个节点充满当前视图,点击svg
元素,则返回根节点
d3.selectAll('.g circle')
.on('mouseover', function(){
const e = d3.event;
e.target.style.cursor = 'hand'
d3.select(e.target)
.attr('stroke', config.hoverColor);
})
.on('mouseleave', function(d){
const e = d3.event;
d3.select(e.target)
.attr('stroke', null);
})
.on('click', function(d){
if (focusCircle !== d){
zoom(d);
d3.event.stopPropagation();
}
});
chart.svg()
.style("cursor", "poniter")
.on("click", () => zoom(root));
放缩的关键代码所在,记录当前点击的节点,对缩放进行插值,生成过渡动画。这里的k
是比例系数,用于确定放缩的倍数,使整个节点恰好充满视图
function zoom(d){
focusCircle = d;
chart.svg()
.transition()
.duration(config.animateDuration)
.tween("zoom", d => {
const i = d3.interpolateZoom(prevView, [focusCircle.x, focusCircle.y, focusCircle.r * 2]);
return t => zoomTo(i(t));
});
}
function zoomTo(v){
const k = chart.getBodyHeight() / v[2];
prevView = v;
d3.selectAll("circle")
.attr("transform", (d) => {
return `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`;
})
.attr("r", (d) => d.r * k);
d3.selectAll(".text")
.attr("transform", (d) => {
return `translate(${(d.x - v[0]) * k + chart.getBodyWidth()/2},${(d.y - v[1]) * k + chart.getBodyHeight()/2}) scale(${k})`;
})
}