D3 二维图表的绘制系列(十三)多符号散点图

上一篇: 基础散点图 https://blog.csdn.net/zjw_python/article/details/98483109

下一篇: 气泡图 https://blog.csdn.net/zjw_python/article/details/98485368

代码结构和初始化画布的Chart对象介绍,请先看 https://blog.csdn.net/zjw_python/article/details/98182540

本图完整的源码地址: https://github.com/zjw666/D3_demo/tree/master/src/scatterChart/multiSymbolChart

1 图表效果

在这里插入图片描述

2 数据

x,y,x1,y1,x2,y2,x3,y3
24,12,95,45,35,75,32,85
39,20,74,62,82,24,34,46
78,15,14,64,3,75,64,19
15,80,32,67,45,62,84,39
36,70,10,68,36,10,38,49
7,11,67,2,67,14,34,20
36,13,31,47,95,9,74,73
34,89,34,75,5,98,3,2
9,95,74,69,31,47,32,20
72,35,26,65,16,36,99,65
46,78,85,42,32,14,43,12
73,95,8,6,81,62,10,95

3 关键代码

导入数据

d3.csv('./data.csv', function(d){
    return {
        x: +d.x,
        y: +d.y,
        x1: +d.x1,
        y1: +d.y1,
        x2: +d.x2,
        y2: +d.y2,
        x3: +d.x3,
        y3: +d.y3,
    };
}).then(function(data){
......

样式参数配置

const config = {
        margins: {top: 80, left: 80, bottom: 50, right: 80},
        textColor: 'black',
        gridColor: 'gray',
        ShowGridX: [10, 20, 30, 40, 50, 60, 70 ,80, 90, 100],
        ShowGridY: [10, 20, 30, 40, 50, 60, 70 ,80, 90, 100],
        title: '多符号散点图',
        pointSize: 100,
        hoverColor: 'white',
        animateDuration: 1000
    }

尺度转换,这里要注意,X和Y轴的最大值的计算

/* ----------------------------尺度转换------------------------  */
    chart.scaleX = d3.scaleLinear()
                    .domain([0, Math.ceil(d3.max(data, (d) => getMaxNum(d,'x'))/10)*10])
                    .range([0, chart.getBodyWidth()]);
    
    chart.scaleY = d3.scaleLinear()
                    .domain([0, Math.ceil(d3.max(data, (d) => getMaxNum(d,'y'))/10)*10])
                    .range([chart.getBodyHeight(), 0]);
    
    //获取数据行最大x值或y值
    function getMaxNum(d, type){
        const nums = [];
        Object.keys(d).forEach((key) => {
            if (key.indexOf(type) > -1) nums.push(d[key]);
        })
        return d3.max(nums);
    }

渲染数据点,为方便数据绑定,我们先转换了一下数据的结构,渲染步骤跟基础散点图类似,区别在于我们使用path元素,通过d3.symbol()指定不同的数据点形状

/* ----------------------------渲染数据点------------------------  */
    chart.renderPoints = function(){
        /*
            改变数据结构,方便渲染
            [
                [[x,y],[x,y],...],
                [[x1,y1],[x1,y1],...],
                [[x2,y2],[x2,y2],...],
                [[x3,y3],[x3,y3],...],
            ]
        */
        const tempData = data.map((d) => {
            const items = [];
            d3.permute(d, Object.keys(d)).forEach((item,i,array) => {
                if (i % 2 === 0){
                    items.push([array[i],array[i+1],i/2]);
                }
            });
            return items;
        });

        const multiData = d3.zip.apply(this,tempData);

        let groups = chart.body().selectAll('.g')
                        .data(multiData);

        let points = groups.enter()
                              .append('g')
                           .merge(groups)
                              .attr('class', (d,i) => 'g points-' + i)
                              .attr('fill', (d,i) => chart._colors(i))
                              .selectAll('.point')
                              .data((d) => d);
            
            groups.exit()
                    .remove();

            points.enter()
                    .append('path')
                    .classed('point', true)
                  .merge(points)
                    .attr('transform', (d) => 'translate(' + chart.scaleX(d[0]) + ',' + chart.scaleY(d[1]) + ')')
                    .attr('d', d3.symbol().type(function (d) {
                        return d3.symbols[d[2]];
                    }).size(1))
                    .transition().duration(config.animateDuration)
                    .attr('d', d3.symbol().type(function (d) {
                        return d3.symbols[d[2]];
                    }).size(config.pointSize));
                    
            
            points.exit()
                    .remove();
            
    }

接着就是渲染坐标轴、文本标签和网格线等

/* ----------------------------渲染坐标轴------------------------  */
    chart.renderX = function(){
        chart.svg().insert('g','.body')
                .attr('transform', 'translate(' + chart.bodyX() + ',' + (chart.bodyY() + chart.getBodyHeight()) + ')')
                .attr('class', 'xAxis')
                .call(d3.axisBottom(chart.scaleX));
    }

    chart.renderY = function(){
        chart.svg().insert('g','.body')
                .attr('transform', 'translate(' + chart.bodyX() + ',' + chart.bodyY() + ')')
                .attr('class', 'yAxis')
                .call(d3.axisLeft(chart.scaleY));
    }

    chart.renderAxis = function(){
        chart.renderX();
        chart.renderY();
    }

    /* ----------------------------渲染文本标签------------------------  */
    chart.renderText = function(){
        d3.select('.xAxis').append('text')
                            .attr('class', 'axisText')
                            .attr('x', chart.getBodyWidth())
                            .attr('y', 0)
                            .attr('fill', config.textColor)
                            .attr('dy', 30)
                            .text('X');

        d3.select('.yAxis').append('text')
                            .attr('class', 'axisText')
                            .attr('x', 0)
                            .attr('y', 0)
                            .attr('fill', config.textColor)
                            .attr('dx', '-30')
                            .attr('dy', '10')
                            .text('Y');
    }

    /* ----------------------------渲染网格线------------------------  */
    chart.renderGrid = function(){
        d3.selectAll('.yAxis .tick')
            .each(function(d, i){
                if (config.ShowGridY.indexOf(d) > -1){
                    d3.select(this).append('line')
                        .attr('class','grid')
                        .attr('stroke', config.gridColor)
                        .attr('x1', 0)
                        .attr('y1', 0)
                        .attr('x2', chart.getBodyWidth())
                        .attr('y2', 0);
                }
            });

        d3.selectAll('.xAxis .tick')
            .each(function(d, i){
                if (config.ShowGridX.indexOf(d) > -1){
                    d3.select(this).append('line')
                        .attr('class','grid')
                        .attr('stroke', config.gridColor)
                        .attr('x1', 0)
                        .attr('y1', 0)
                        .attr('x2', 0)
                        .attr('y2', -chart.getBodyHeight());
                }
            });
    }

添加鼠标交互事件,与基础散点图相似

/* ----------------------------绑定鼠标交互事件------------------------  */
    chart.addMouseOn = function(){
        //防抖函数
        function debounce(fn, time){
            let timeId = null;
            return function(){
                const context = this;
                const event = d3.event;
                timeId && clearTimeout(timeId)
                timeId = setTimeout(function(){
                    d3.event = event;
                    fn.apply(context, arguments);
                }, time);
            }
        }

        d3.selectAll('.point')
            .on('mouseover', function(d){
                const e = d3.event;
                const position = d3.mouse(chart.svg().node());
                e.target.style.cursor = 'hand'

                d3.select(e.target)
                    .attr('fill', config.hoverColor);
                
                chart.svg()
                    .append('text')
                    .classed('tip', true)
                    .attr('x', position[0]+5)
                    .attr('y', position[1])
                    .attr('fill', config.textColor)
                    .text('x: ' + d[0] + ', y: ' + d[1]);
            })
            .on('mouseleave', function(d){
                const e = d3.event;
                
                d3.select(e.target)
                    .attr('fill', chart._colors(d[2]));
                    
                d3.select('.tip').remove();
            })
            .on('mousemove', debounce(function(){
                    const position = d3.mouse(chart.svg().node());
                    d3.select('.tip')
                    .attr('x', position[0]+5)
                    .attr('y', position[1]-5);
                }, 6)
            );
    }

大功告成!!!

发布了250 篇原创文章 · 获赞 88 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/zjw_python/article/details/98483989