D3 二维图表的绘制系列(三十二)可缩放的封闭图

上一篇: 热力地图

下一篇: 可缩放的矩形树图

代码结构和初始化画布的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添加布局信息,与普通封闭图一致。设置变量focusCircleprevView,其中前者为当前呈现的节点,后者为上一次放缩的节点,这里保留放缩前后节点的数据的意义在于使用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})`;
                })
        }

大功告成!!!


如果觉得这篇文章帮助了您,请打赏一个小红包鼓励作者继续创作哦!!!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值