喜迎
春节

可视化D3(一)


D3是一个JavaScript函数库,用于创建动态、交互式数据可视化,支持各种图表、地图和复杂的视觉表现,广泛应用于展示。

1 D3概述

  • D3版本下载:https://github.com/d3/d3/releaseshttps://d3js.org
  • GitHub下载的ZIP包只是未进行编译的D3源代码文件,不能直接使用。
  • 选择元素(选择集)、绑定数据是D3最基础的内容,通常需要一起使用。
    • 选择元素:d3.select()(选所有指定元素的第一个)、d3.selectAll()(选指定的全部元素)。
    • 绑定数据:datum()(绑定数据到选择集)、data()(绑定数组到选择集,各项值对应元素绑定)。
<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>D3概述</title>

    <!-- 使用官网链接的引入,需要梯子 -->
    <!-- <script src='http://d3js.org/d3.v3.min.js' charset='utf-8'> -->
    <!-- </script> -->

    <!-- 使用CDN节点的链接引入 -->
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
</head>

<body style='text-align: center'><br />
    <p></p>
    <p></p>
    <p></p><br />

    <h3></h3>
    <h3></h3>
    <h3></h3>

    <script>
        // datum()
        var str = 'datum()';

        // 选择元素
        var body = d3.select('body');
        var p = body.selectAll('p');
        
        // 绑定数据
        p.datum(str);
        p.text(function (x, y) {
            return '第 ' + y + ' 个元素绑定的数据是 ' + x;
        });

        // data()
        var dataset = ['耐心', '坚持', '勇敢'];
        var body = d3.select('body');
        var h = body.selectAll('h3');
        h.data(dataset)
            .text(function (x, y) {
                return '我要' + x + '!';
            });
    </script>
</body>

</html>

1-1 选择元素

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>选择元素</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
</head>

<body style='text-align: center'><br />
    <p id='p1' class='c1'>富强</p>
    <p id='p2' class='c2'>民主</p>
    <p id='p3' class='c3'>文明</p>
    <p id='p4' class='c4'>和谐</p>
    <p id='p5' class='c5'>自由</p>
    <p id='p6' class='c6'>平等</p>
    <p id='p7' class='c6'>公正</p>
    <p id='p8' class='c6'>法治</p>

    <script>
        // 选择所有元素
        d3.select('body')
            .selectAll('p')
            .style('color', 'blue');

        // 选择第一个元素
        d3.select('body')
            .select('p')
            .style('color', 'red');

        // 选择第二个元素
        d3.select('#p2')
            .style('color', 'green');

        // 选择后三个元素
        d3.selectAll('.c6')
            .style('color', 'orange');
    </script>
</body>

</html>

1-2 插入元素

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>插入元素</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
</head>

<body style='text-align: center'><br />
    <p id='p1' class='c1'>富强</p>
    <p id='p2' class='c2'>民主</p>
    <p id='p3' class='c3'>文明</p>
    <p id='p4' class='c4'>和谐</p>
    <p id='p5' class='c5'>自由</p>
    <p id='p6' class='c6'>平等</p>
    <p id='p7' class='c6'>公正</p>
    <p id='p8' class='c6'>法治</p>

    <script>
        // insert():在选择集前面插入元素
        d3.select('body')
            .insert('p', '#p1')
            .text('社会主义核心价值观');

        // append():在选择集末尾插入元素
        d3.select('body')
            .append('p')
            .text('爱国');

        d3.select('body')
            .append('p')
            .text('敬业');

        d3.select('body')
            .append('p')
            .text('诚信');

        d3.select('body')
            .append('p')
            .text('友善');
    </script>
</body>

</html>

1-3 删除元素

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>删除元素</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
</head>

<body style='text-align: center'><br />
    <p id='p1' class='c1'>富强</p>
    <p id='p2' class='c2'>民主</p>
    <p id='p3' class='c3'>文明</p>
    <p id='p4' class='c4'>和谐</p>
    <p id='p5' class='c5'>自由</p>
    <p id='p6' class='c6'>平等</p>
    <p id='p7' class='c6'>公正</p>
    <p id='p8' class='c6'>法治</p>

    <script>
        // remove():删除一个元素
        d3.select('#p8')
            .remove();
    </script>
</body>

</html>

2 矩形图

  • HTML5提供了SVG和Canvas两种强有力的画布。
  • SVG:D3提供了众多的SVG图形生成器,都只支持SVG,建议使用该画布。
    • SVG绘制的是矢量图,将图像进行放大不会失真,可为每个元素添加JavaScript事件处理器。
    • 每个图形均视为对象,更改对象属性,图形也将改变,x轴正方向水平向右,y轴则垂直向下。
  • Canvas
    • 一旦图形被绘制完成,就不会继续得到浏览器的关注。
    • 如果位置发生了变化,那么整个场景也需要重新绘制。

2-1 横向矩形

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>横向矩形</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <script>
        // 数据:矩形的宽度
        var dataset = [280, 210, 170, 130, 90, 160, 110];

        // 添加画布:画布的宽度
        var width = 300;

        // 添加画布:画布的高度
        var height = 300;
        
        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', width)
            // 设定高度
            .attr('height', height);

        // 每个矩形所占的像素高度(包括空白)
        var rectHeight = 25;

        // 绘制横向矩形
        svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr('x', 20)
            .attr('y', function (d, i) {
                return i * rectHeight;
            })
            .attr('width', function (d) {
                return d;
            })
            .attr('height', rectHeight - 2)
            .attr('fill', 'steelblue');
    </script>
</body>

</html>

2-2 纵向矩形

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>纵向矩形</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <script>
        // 数据:矩形的宽度
        var dataset = [280, 210, 170, 130, 90, 160, 110];

        // 添加画布:画布的宽度
        var width = 300;

        // 添加画布:画布的高度
        var height = 300;

        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', width)
            // 设定高度
            .attr('height', height);

        // 每个矩形所占的像素高度(包括空白)
        var rectHeight = 25;

        // 绘制纵向矩形
        svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr('y', function (d, i) {
                return height - d;
            })
            .attr('x', function (d, i) {
                return i * rectHeight;
            })
            .attr('height', function (d) {
                return d;
            })
            .attr('width', rectHeight - 2)
            .attr('fill', 'steelblue');
    </script>
</body>

</html>

2-3 常用比例尺

  • 比例尺就是一个数据映射函数,例如线性比例尺可实现类似y = ax + b的变换。
  • D3共有三类比例尺:数量型、序数型(序数、颜色比例尺)、时间型(时间比例尺)。
  • 数量型:线性、恒等、乘方、对数、量化比例尺、分位数比例尺、临界值比例尺。
  • D3中的比例尺,有定义域和值域,分别被称为domain和range。
  • 常用的比例尺
    • 线性比例尺:能将一个连续的区间,映射到另一区间。
    • 序数比例尺:定义域和值域不一定连续,属于离散值。

(1) 线性比例尺

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>线性比例尺</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
</head>

<body><br />
    <script>
        var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
        var min = d3.min(dataset);
        var max = d3.max(dataset);

        // 将dataset中最小的值映射成0,最大的值映射成300
        var linear = d3.scaleLinear()
            .domain([min, max])
            .range([0, 300]);

        console.log(linear(0.9));
        console.log(linear(2.3));
        console.log(linear(3.3));
    </script>
</body>

</html>

(2) 序数比例尺

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>序数比例尺</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
</head>

<body><br />
    <script>
        var index = [0, 1, 2, 3, 4, 5, 6];
        var color = [
            'red', 'orange', 'yellow',
            'green', 'cyan', 'blue', 'purple'
        ];

        // 0对应颜色red,1对应orange,以此类推
        var ordinal = d3.scaleOrdinal()
            .domain(index)
            .range(color);

        console.log(ordinal(0));
        console.log(ordinal(2));
        console.log(ordinal(6));
    </script>
</body>

</html>

2-4 添加比例尺

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>添加比例尺</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <script>
        var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
        var min = d3.min(dataset);
        var max = d3.max(dataset);

        // 将dataset中最小的值设为0
        var linear = d3.scaleLinear()
            .domain([0, max])
            // 最大的值映射成300
            .range([0, 300]);

        // 添加画布:画布的宽度
        var width = 300;

        // 添加画布:画布的高度
        var height = 300;
        
        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', width)
            // 设定高度
            .attr('height', height);

        // 每个矩形所占的像素高度(包括空白)
        var rectHeight = 25;

        // 绘制横向矩形
        svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr('x', 20)
            .attr('y', function (d, i) {
                return i * rectHeight;
            })
            .attr('width', function (d) {
                // 使用比例尺
                return linear(d);
            })
            .attr('height', rectHeight - 2)
            .attr('fill', 'steelblue');
    </script>
</body>

</html>

2-5 定义坐标轴

  • 要生成坐标轴,则需要用到比例尺,二者经常一起使用。
<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>定义坐标轴</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }

        .axis path,
        .axis line {
            fill: none;
            stroke: black;
            shape-rendering: crispEdges;
        }

        .axis text {
            font-family: sans-serif;
            font-size: 11px;
        }
    </style>
</head>

<body><br />
    <script>
        // 数据
        var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];

        // 定义比例尺
        var linear = d3.scaleLinear()
            .domain([0, d3.max(dataset)])
            .range([0, 300]);

        // 定义坐标轴
        var axis = d3.axisBottom(linear)
            // 指定刻度数量
            .ticks(7);

        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', 300)
            // 设定高度
            .attr('height', 300);

        // 每个矩形所占的像素高度(包括空白)
        var rectHeight = 25;

        // 绘制横向矩形
        svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr('x', 20)
            .attr('y', function (d, i) {
                return i * rectHeight;
            })
            .attr('width', function (d) {
                // 使用比例尺
                return linear(d);
            })
            .attr('height', rectHeight - 2)
            .attr('fill', 'steelblue');

        // 添加坐标轴
        svg.append('g')
            .call(axis)
            // 默认的样式不美观,在head中增加style样式
            .attr('transform', 'translate(20,130)')
            .call(axis);;
    </script>
</body>

</html>

2-6 完整矩形图

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>完整矩形图</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <script>
        // 添加SVG画布,设置画布大小
        width = 300;
        height = 300;

        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', width)
            // 设定高度
            .attr('height', height);

        // 画布周边的空白
        var padding = {
            left: 30,
            right: 30,
            top: 30,
            bottom: 30
        };

        // 定义一个数组
        var dataset = [10, 30, 50, 70, 33, 24, 17, 9];

        // 定义x轴比例尺
        var xScale = d3.scaleBand()
            .domain(d3.range(dataset.length))
            .range([0, width - padding.left - padding.right]);

        // 定义y轴比例尺
        var yScale = d3.scaleLinear()
            .domain([0, d3.max(dataset)])
            .range([height - padding.top - padding.bottom, 0]);

        // 定义x轴
        var xAxis = d3.axisBottom(xScale);

        // 定义y轴
        var yAxis = d3.axisLeft(yScale);

        // 矩形之间的空白
        var rectPadding = 5;

        // 添加矩形元素
        var rects = svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            )
            .attr('x', function (d, i) {
                return xScale(i) + rectPadding / 2;
            })
            .attr('y', function (d) {
                return yScale(d);
            })
            .attr('width', xScale.bandwidth() - rectPadding)
            .attr('height', function (d) {
                return height - padding.top - padding.bottom - yScale(d);
            })
            .attr('fill', 'steelblue');

        // 添加文字
        var texts = svg.selectAll('text')
            .data(dataset)
            .enter()
            .append('text')
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            )
            .attr('x', function (d, i) {
                return xScale(i) + (xScale.bandwidth() - rectPadding) / 2;
            })
            .attr('y', function (d) {
                return yScale(d) - 5;
            })
            .attr('dx', '0.1em')
            .attr('dy', '1.5em')
            .text(function (d) {
                return d;
            })
            .style('fill', '#FFF')
            .style('text-anchor', 'middle');

        // 添加x轴
        svg.append('g')
            .call(xAxis)
            .attr(
                'transform',
                'translate(' + padding.left + ','
                + (height - padding.bottom) + ')'
            );

        // 添加y轴
        svg.append('g')
            .call(yAxis)
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            );
    </script>
</body>

</html>

3 动态效果

  • D3提供了4种方法用于实现图形的过渡,即动态效果,代表从状态A变成了状态B。
  • transition():启动过渡效果,前后是图形变化前后的状态(形状、位置、颜色等)。
  • duration():指定过渡的持续时间,单位为毫秒。
  • ease():指定过渡的方式,常用方式有以下几种。
    • 线性变化(d3.easeLinear)、弹性变化(d3.easeElastic)。
    • 弹跳变化(d3.easeBounce)、圆形变化(d3.easeCircle)。
  • delay():指定延迟时间,表示一定时间后才开始转变,可对整体或个别指定延迟。

3-1 简单动态效果

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>简单动态效果</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg width='410' height='410'>
        <circle id='c1'></circle>
        <circle id='c2'></circle>
        <circle id='c3'></circle>
    </svg>

    <script>
        // 获取SVG画布
        var svg = d3.select('svg');

        // 第一个圆移动x坐标
        var circle1 = svg.select('#c1')
            .attr('cx', 100)
            .attr('cy', 100)
            .attr('r', 20)
            .style('fill', 'green');

        // 在1秒(1000毫秒)内将圆心坐标由100变为300
        circle1.transition()
            .duration(1000)
            .attr('cx', 300);

        // 第二个圆移动x坐标,并改变颜色
        var circle2 = svg.select('#c2')
            .attr('cx', 100)
            .attr('cy', 200)
            .attr('r', 20)
            .style('fill', 'green');

        // 在1.5秒(1500毫秒)内将圆心坐标由100变为300
        circle2.transition()
            .duration(1500)
            .attr('cx', 300)
            // 将颜色从绿色变为橘色
            .style('fill', 'orange');

        // 第三个圆移动x坐标
        var circle3 = svg.select('#c3')
            .attr('cx', 100)
            .attr('cy', 300)
            // 改变颜色和半径
            .attr('r', 20)
            .style('fill', 'green');
            
        // 在2秒(2000毫秒)内将圆心坐标由100变为300
        circle3.transition()
            // 并将颜色从绿色变为橘色
            .duration(2000)
            .ease(d3.easeBounce)
            .attr('cx', 300)
            .style('fill', 'orange')
            // 半径从20变成35
            .attr('r', 35);
    </script>
</body>

</html>

3-2 矩形动态效果

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>矩形动态效果</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <script>
        // 添加SVG画布,设置画布大小
        width = 300;
        height = 300;

        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', width)
            // 设定高度
            .attr('height', height);

        // 画布周边的空白
        var padding = {
            left: 30,
            right: 30,
            top: 30,
            bottom: 30
        };

        // 定义一个数组
        var dataset = [10, 30, 50, 70, 33, 24, 17, 9];

        // 定义x轴比例尺
        var xScale = d3.scaleBand()
            .domain(d3.range(dataset.length))
            .range([0, width - padding.left - padding.right]);

        // 定义y轴比例尺
        var yScale = d3.scaleLinear()
            .domain([0, d3.max(dataset)])
            .range([height - padding.top - padding.bottom, 0]);

        // 定义x轴
        var xAxis = d3.axisBottom(xScale);

        // 定义y轴
        var yAxis = d3.axisLeft(yScale);

        // 矩形之间的空白
        var rectPadding = 5;

        // 添加矩形元素
        var rects = svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            )
            .attr('x', function (d, i) {
                return xScale(i) + rectPadding / 2;
            })
            .attr('y', function (d) {
                return yScale(d);
            })
            .attr('width', xScale.bandwidth() - rectPadding)
            .attr('height', function (d) {
                return height - padding.top - padding.bottom - yScale(d);
            })
            .attr('fill', 'steelblue');

        // 添加文字
        var texts = svg.selectAll('text')
            .data(dataset)
            .enter()
            .append('text')
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            )
            .attr('x', function (d, i) {
                return xScale(i) + (xScale.bandwidth() - rectPadding) / 2;
            })
            // y轴文字稍作修改
            .attr('y', function (d) {
                var min = yScale.domain()[0];
                return yScale(min);
            })
            .transition()
            .delay(function (d, i) {
                return i * 200;
            })
            .duration(2000)
            .ease(d3.easeBounce)
            .attr('y', function (d) {
                return yScale(d) - 5;
            })
            .attr('dx', '0.1em')
            .attr('dy', '1.5em')
            .text(function (d) {
                return d;
            })
            .style('fill', '#FFF')
            .style('text-anchor', 'middle');

        // 添加x轴
        svg.append('g')
            .call(xAxis)
            .attr(
                'transform',
                'translate(' + padding.left + ','
                + (height - padding.bottom) + ')'
            );

        // 添加y轴
        svg.append('g')
            .call(yAxis)
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            );
    </script>
</body>

</html>

3-3 数量关系处理

  • update()、enter()、exit()是D3处理选择集和数据之间数量关系不确定的重要函数。
  • update()
    • 绑定数据数量 = 对应元素,实际并不存在这样的一个函数。
    • 但是对应的元素正好满足时,直接操作即可,后面跟style等。
  • enter()
    • 绑定数据数量 > 对应元素,即:对应的元素数量不足。
    • 需添加元素,使之与绑定数据的数量相等,常跟append。
  • exit()
    • 绑定数据数量 < 对应元素,即:对应的元素数量过多。
    • 需删除元素,使之与绑定数据的数量相等,常跟remove。

3-4 图表交互操作

  • 交互操作,指在图形元素上设置一个或多个监听器,当事件发生时,做出相应的反应。
  • 用户用于交互的工具:鼠标、键盘、触屏。
    • 鼠标事件:click、dblclick、mouseover、mouseout、mousemove、mousedown、mouseup。
    • 键盘事件:keydown(按任意键)、keypress(按字符键)、keyup(释放键,不区分字母大小写)。
    • 触屏事件:touchstart(触摸点放在屏上)、touchmove(触摸点移动)、touchend(触摸点移开)。
<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>图表交互操作</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <script>
        // 添加SVG画布,设置画布大小
        width = 300;
        height = 300;

        // 选择文档中的body元素
        var svg = d3.select('body')
            // 添加一个svg元素
            .append('svg')
            // 设定宽度
            .attr('width', width)
            // 设定高度
            .attr('height', height);

        // 画布周边的空白
        var padding = {
            left: 30,
            right: 30,
            top: 30,
            bottom: 30
        };

        // 定义一个数组
        var dataset = [10, 30, 50, 70, 33, 24, 17, 9];

        // 定义x轴比例尺
        var xScale = d3.scaleBand()
            .domain(d3.range(dataset.length))
            .range([0, width - padding.left - padding.right]);

        // 定义y轴比例尺
        var yScale = d3.scaleLinear()
            .domain([0, d3.max(dataset)])
            .range([height - padding.top - padding.bottom, 0]);

        // 定义x轴
        var xAxis = d3.axisBottom(xScale);

        // 定义y轴
        var yAxis = d3.axisLeft(yScale);

        // 矩形之间的空白
        var rectPadding = 5;

        // 添加矩形元素
        var rects = svg.selectAll('rect')
            .data(dataset)
            .enter()
            .append('rect')
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            )
            .attr('x', function (d, i) {
                return xScale(i) + rectPadding / 2;
            })
            .attr('y', function (d) {
                return yScale(d);
            })
            .attr('width', xScale.bandwidth() - rectPadding)
            .attr('height', function (d) {
                return height - padding.top - padding.bottom - yScale(d);
            })
            .attr('fill', 'steelblue')
            // 鼠标移至矩形条,更改颜色
            .on('mouseover', function (d, i) {
                d3.select(this)
                    .attr('fill', 'purple');
            })
            .on('mouseout', function (d, i) {
                d3.select(this)
                    .transition()
                    .duration(500)
                    .attr('fill', 'steelblue');
            });

        // 添加文字
        var texts = svg.selectAll('text')
            .data(dataset)
            .enter()
            .append('text')
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            )
            .attr('x', function (d, i) {
                return xScale(i) + (xScale.bandwidth() - rectPadding) / 2;
            })
            .attr('y', function (d) {
                return yScale(d) - 5;
            })
            .attr('dx', '0.1em')
            .attr('dy', '1.5em')
            .text(function (d) {
                return d;
            })
            .style('fill', '#FFF')
            .style('text-anchor', 'middle');

        // 添加x轴
        svg.append('g')
            .call(xAxis)
            .attr(
                'transform',
                'translate(' + padding.left + ','
                + (height - padding.bottom) + ')'
            );

        // 添加y轴
        svg.append('g')
            .call(yAxis)
            .attr(
                'transform',
                'translate(' + padding.left + ',' + padding.top + ')'
            );
    </script>
</body>

</html>

4 布局详解

  • Layout,将不适合用于绘图的数据转换成了适合用于绘图的数据(数据转换)。
  • D3常用的布局


布局详解

5 饼状图形

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>饼状图形</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg></svg>

    <script>
        // 画布
        var width = 500;
        var height = 500;
        var radius = Math.min(width, height) / 2;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .append('g')
            .attr(
                'transform', 
                'translate(' + width / 2 + ',' + height / 2 + ')'
            );

        // 数据
        var dataset = [30, 10, 43, 55, 13];

        // 定义一个布局
        var pie = d3.pie();

        // 数据转换
        var piedata = pie(dataset);

        // color颜色比例尺,能根据传入的索引号获取相应的颜色值
        var color = d3.scaleOrdinal(d3.schemeCategory10);

        // 绘制图形,这里绘制的是饼图,需要使用弧生成器,设置外半径和内半径
        var outerRadius = 100;

        // 内半径,为0则中间没有空白
        var innerRadius = 70;

        // 弧生成器,设置外半径和内半径
        var arc = d3.arc()
            .innerRadius(innerRadius)
            .outerRadius(outerRadius);

        // 在SVG中添加图形元素
        var arcs = svg.selectAll('g')
            // 添加足够数量的分组元素(5个)
            .data(piedata)
            .enter()
            .append('g')
            // 每个分组用于存放一段弧的相关元素
            .attr('class', 'arc');
            
        // 对每个g元素添加path
        arcs.append('path')
            .attr('fill', function (d, i) {
                return color(i);
            })
            .attr('d', function (d) {
                // 调用弧生成器,得到路径值,参数d是被绑定的数据
                return arc(d);
            });

        // 在每个弧线中心添加文本
        arcs.append('text')
            .attr('transform', function (d) {
                // arc.centroid(d)能算出弧线的中心
                return 'translate(' + arc.centroid(d) + ')';
            })
            .attr('text-anchor', 'middle')
            // text()返回的是d.data,被绑定的数据是对象
            // 包含d.startAngle、d.endAngle、d.data等
            .text(function (d) {
                return d.data;
            });
    </script>
</body>

</html>

6 力导向图

  • 力导向图,英文叫Force-Directed Graph,是绘图的一种算法。
  • 在二维或三维空间里配置节点,节点之间用线连接,称为连线。
  • 节点之间的各个连线长度几乎都相等,并且会尽可能地不相交。
  • 节点和连线都被施加了力的作用,而力则根据节点和连线的相对位置计算。
  • nodes(节点)和edges(边)都是必备属性,节点和边数组都可以有额外属性。
  • nodes数据是一个表示节点的对象数组,而edges是一个表示边的对象数组。
  • 其中,source和target子属性也是必备的,其值代表了nodes数组中的索引。
<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>力导向图</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg></svg>

    <script>
        // 画布
        var width = 800;
        height = 600;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .append('g')
            .attr(
                'transform',
                'translate(' + width / 4 + ',' + height / 4 + ')'
            );

        // color颜色比例尺,能根据传入的索引号获取相应的颜色值
        var color = d3.scaleOrdinal(d3.schemeCategory10);

        // 初始数据
        var nodes = [
            { name: '福建' }, { name: '广东' },
            { name: '福州' }, { name: '莆田' },
            { name: '广州' }, { name: '茂名' }, { name: '深圳' }
        ];
        var edges = [
            { source: 0, target: 1 }, { source: 0, target: 2 },
            { source: 0, target: 3 }, { source: 1, target: 4 },
            { source: 1, target: 5 }, { source: 1, target: 6 }
        ];

        // 定义一个力导向图的布局,连接力,排斥力,中心力
        var simulation = d3.forceSimulation(nodes)
            .force('link', d3.forceLink().links(edges).distance(150))
            .force('charge', d3.forceManyBody().strength(-500))
            .force('center', d3.forceCenter(width / 5, height / 5));

        // 绘制连线
        var svg_edges = svg.selectAll('line')
            .data(edges)
            .enter()
            .append('line')
            .style('stroke', '#ccc')
            .style('stroke-width', 1);

        // 添加节点 
        var svg_nodes = svg.selectAll('circle')
            .data(nodes)
            .enter()
            .append('circle')
            .attr('r', 15)
            .style('fill', function (d, i) {
                return color(i);
            })
            // 确保使用新的拖动API
            .call(
                d3.drag()
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
            );

        // 添加文字
        var svg_texts = svg.selectAll('text')
            .data(nodes)
            .enter()
            .append('text')
            .style('fill', 'black')
            .attr('dx', 20)
            .attr('dy', 8)
            .text(function (d) {
                return d.name;
            });

        // 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
        simulation.on('tick', function () {
            // 每个tick更新节点和连线的位置
            svg_edges.attr('x1', function (d) { return d.source.x; })
                .attr('y1', function (d) { return d.source.y; })
                .attr('x2', function (d) { return d.target.x; })
                .attr('y2', function (d) { return d.target.y; });

            svg_nodes.attr('cx', function (d) { return d.x; })
                .attr('cy', function (d) { return d.y; });

            svg_texts.attr('x', function (d) { return d.x; })
                .attr('y', function (d) { return d.y; });
        });

        // 拖动的处理函数
        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }
        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }
        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }
    </script>
</body>

</html>

6-1 分子式

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>分子式</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg></svg>

    <script>
        // 画布
        var width = 800;
        height = 600;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .append('g')
            .attr(
                'transform',
                'translate(' + width / 4 + ',' + height / 3 + ')'
            );

        // color颜色比例尺,能根据传入的索引号获取相应的颜色值
        var color = d3.scaleOrdinal(d3.schemeCategory10);

        // 数据
        var graph = {
            'nodes': [
                { 'atom': '福建', 'size': 30 },
                { 'atom': '福州', 'size': 10 },
                { 'atom': '莆田', 'size': 10 },
                { 'atom': '广东', 'size': 30 },
                { 'atom': '广州', 'size': 10 },
                { 'atom': '茂名', 'size': 10 },
                { 'atom': '深圳', 'size': 10 }
            ],
            'links': [
                { 'source': 0, 'target': 0, 'bond': 1 },
                { 'source': 0, 'target': 1, 'bond': 1 },
                { 'source': 0, 'target': 2, 'bond': 1 },
                { 'source': 0, 'target': 3, 'bond': 1 },
                { 'source': 3, 'target': 3, 'bond': 1 },
                { 'source': 3, 'target': 4, 'bond': 1 },
                { 'source': 3, 'target': 5, 'bond': 1 },
                { 'source': 3, 'target': 6, 'bond': 1 }
            ]
        }

        // 定义一个力导向图的布局,连接力,排斥力
        var simulation = d3.forceSimulation(graph.nodes)
            .force('charge', d3.forceManyBody().strength(-100))
            .force('link', d3.forceLink(graph.links).distance(function (d) {
                // 根据大小调整距离
                return 20 + 2 * (d.source.size + d.target.size);
            }));

        // 定义一个半径比例尺
        var radius = d3.scaleSqrt()
            .range([0, 10])
            .domain([1, 20]);

        // 绘制连线
        var svg_edges = svg.selectAll('line')
            .data(graph.links)
            .enter()
            .append('line')
            .style('stroke', '#ccc')
            .style('stroke-width', 1);

        svg_edges.filter(function (d) { return d.bond > 1; })
            .append('line')
            .attr('class', 'separator');

        // 添加节点 
        var svg_nodes = svg.selectAll('circle')
            .data(graph.nodes)
            .enter()
            .append('circle')
            .attr('r', function (d) { return radius(d.size); })
            .style('fill', function (d, i) {
                return color(i);
            })
            // 确保使用新的拖动API
            .call(
                d3.drag()
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
            );

        // 添加文字
        var svg_texts = svg.selectAll('text')
            .data(graph.nodes)
            .enter()
            .append('text')
            .style('fill', 'black')
            .attr('dx', 20)
            .attr('dy', 8)
            .text(function (d) {
                return d.atom;
            });

        // 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
        simulation.on('tick', function () {
            // 每个tick更新节点和连线的位置
            svg_edges.attr('x1', function (d) { return d.source.x; })
                .attr('y1', function (d) { return d.source.y; })
                .attr('x2', function (d) { return d.target.x; })
                .attr('y2', function (d) { return d.target.y; });

            svg_nodes.attr('cx', function (d) { return d.x; })
                .attr('cy', function (d) { return d.y; });

            svg_texts.attr('x', function (d) { return d.x; })
                .attr('y', function (d) { return d.y; });
        });

        // 拖动的处理函数
        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }
        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }
        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }
    </script>
</body>

</html>

6-2 带文字

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>带文字</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg></svg>

    <script>
        // 画布
        var width = 800;
        height = 600;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .append('g')
            .attr(
                'transform',
                'translate(' + width / 4 + ',' + height / 3 + ')'
            );

        // color颜色比例尺,能根据传入的索引号获取相应的颜色值
        var color = d3.scaleOrdinal(d3.schemeCategory10);

        // 初始数据,链接数组
        var links = [
            { source: '福建', target: '福建', type: 'node', weight: 1 },
            { source: '福建', target: '福州', type: 'node', weight: 1 },
            { source: '福建', target: '莆田', type: 'node', weight: 1 },
            { source: '广东', target: '福建', type: 'node', weight: 1 },
            { source: '广东', target: '广东', type: 'node', weight: 1 },
            { source: '广东', target: '广州', type: 'node', weight: 1 },
            { source: '广东', target: '茂名', type: 'node', weight: 1 },
            { source: '广东', target: '深圳', type: 'node', weight: 1 }
        ];

        // 初始化一个空的节点对象映射
        var nodes = {};

        // 从链接中分离出不同的节点
        links.forEach(function (link) {
            link.source = nodes[link.source]
                || (nodes[link.source] = { name: link.source });
            link.target = nodes[link.target]
                || (nodes[link.target] = { name: link.target });
        });

        // 定义一个力导向图的布局,连接力,排斥力,中心力
        var simulation = d3.forceSimulation(Object.values(nodes))
            .force('link', d3.forceLink().links(links).distance(150))
            .force('charge', d3.forceManyBody().strength(-500))
            .force('center', d3.forceCenter(width / 5, height / 5));

        // 绘制连线
        var svg_edges = svg.selectAll('line')
            .data(links)
            .enter()
            .append('line')
            .style('stroke', '#ccc')
            .style('stroke-width', 1);

        // 添加节点 
        var svg_nodes = svg.selectAll('circle')
            .data(Object.values(nodes))
            .enter()
            .append('circle')
            .attr('r', 15)
            .style('fill', function (d, i) {
                return color(i);
            })
            // 确保使用新的拖动API
            .call(
                d3.drag()
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
            );

        // 添加文字
        var svg_texts = svg.selectAll('text')
            .data(Object.values(nodes))
            .enter()
            .append('text')
            .style('fill', 'black')
            .attr('dx', 20)
            .attr('dy', 8)
            .text(function (d) {
                return d.name;
            });

        // 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
        simulation.on('tick', function () {
            // 每个tick更新节点和连线的位置
            svg_edges.attr('x1', function (d) { return d.source.x; })
                .attr('y1', function (d) { return d.source.y; })
                .attr('x2', function (d) { return d.target.x; })
                .attr('y2', function (d) { return d.target.y; });

            svg_nodes.attr('cx', function (d) { return d.x; })
                .attr('cy', function (d) { return d.y; });

            svg_texts.attr('x', function (d) { return d.x; })
                .attr('y', function (d) { return d.y; });
        });

        // 拖动的处理函数
        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }
        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }
        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }
    </script>
</body>

</html>

6-3 弧箭头*

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>弧箭头</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg></svg>

    <script>
        // 画布
        var width = 800;
        height = 600;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .append('g')
            .attr(
                'transform',
                'translate(' + width / 4 + ',' + height / 3 + ')'
            );

        // color颜色比例尺,能根据传入的索引号获取相应的颜色值
        var color = d3.scaleOrdinal(d3.schemeCategory10);

        // 初始数据,链接数组
        var links = [
            { source: '福建', target: '福建', type: 'node', weight: 1 },
            { source: '福建', target: '福州', type: 'node', weight: 1 },
            { source: '福建', target: '莆田', type: 'node', weight: 1 },
            { source: '广东', target: '福建', type: 'node', weight: 1 },
            { source: '广东', target: '广东', type: 'node', weight: 1 },
            { source: '广东', target: '广州', type: 'node', weight: 1 },
            { source: '广东', target: '茂名', type: 'node', weight: 1 },
            { source: '广东', target: '深圳', type: 'node', weight: 1 }
        ];
        
        // 初始化一个空的节点对象映射
        var nodes = {};

        // 从链接中分离出不同的节点
        links.forEach(function (link) {
            link.source = nodes[link.source]
                || (nodes[link.source] = { name: link.source });
            link.target = nodes[link.target]
                || (nodes[link.target] = { name: link.target });
        });

        // 定义一个力导向图的布局,连接力,排斥力,中心力
        var simulation = d3.forceSimulation(Object.values(nodes))
            .force('link', d3.forceLink().links(links).distance(150))
            .force('charge', d3.forceManyBody().strength(-500))
            .force('center', d3.forceCenter(width / 5, height / 5));

        // 创建箭头
        svg.append('svg:defs')
            .selectAll('marker')
            .data(['suit'])
            .enter()
            .append('svg:marker')
            // 确保ID与引用时使用的字符串匹配
            .attr('id', 'suit')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 15)
            .attr('refY', -1.5)
            .attr('markerWidth', 6)
            .attr('markerHeight', 6)
            .attr('orient', 'auto')
            .append('svg:path')
            // M0,-5表示路径的起点,坐标(0, -5)
            // L10,0表示从上一步的起点绘制一条直线到坐标(10, 0)的点
            // L0,5表示从上一步的点绘制一条直线到坐标(0, 5)的点
            .attr('d', 'M0,-5L10,0L0,5');

        // 绘制连线
        var svg_edges = svg.selectAll('.link')
            .data(links)
            .enter()
            .append('svg:path')
            .attr('class', function (d) {
                return 'link ' + d.type;
            })
            // 使用默认的箭头标记
            .attr('marker-end', function (d) {
                return 'url(#' + d.type + ')';
            })
            // 调整线宽,使其更细
            .style('stroke-width', 1)
            // 添加虚线效果
            .style('stroke-dasharray', '0,2 1');

        // 添加节点 
        var svg_nodes = svg.selectAll('circle')
            .data(Object.values(nodes))
            .enter()
            .append('circle')
            .attr('r', 15)
            .style('fill', function (d, i) {
                return color(i);
            })
            // 确保使用新的拖动API
            .call(
                d3.drag()
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
            );

        // 添加文字
        var svg_texts = svg.selectAll('text')
            .data(Object.values(nodes))
            .enter()
            .append('text')
            .style('fill', 'black')
            .attr('dx', 20)
            .attr('dy', 8)
            .text(function (d) {
                return d.name;
            });

        // 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
        simulation.on('tick', function () {
            // 每个tick更新节点和连线的位置
            svg_edges.attr('d', function (d) {
                var dx = d.target.x - d.source.x,
                    dy = d.target.y - d.source.y,
                    // 计算距离
                    dr = Math.sqrt(dx * dx + dy * dy);
                // 增大系数,使弧线更弯曲
                // 系数越大好像越接近直线,非箭头连线,设置没效果???
                dr = dr * 0.5 + 100;
                // dr + ' 0 0,1 ',第一个0指弧线的大弧形或小弧形
                // 第二个0,指弧线的扫掠方向,0代表顺时针,1代表逆时针
                // 第三个1,指弧线结束于起点顺时针旋转超过180度的位置
                return 'M' + d.source.x + ',' + d.source.y + 'A' + dr + ','
                    + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y;
            });

            svg_nodes.attr('cx', function (d) { return d.x; })
                .attr('cy', function (d) { return d.y; });

            svg_texts.attr('x', function (d) { return d.x; })
                .attr('y', function (d) { return d.y; });
        });

        // 拖动的处理函数
        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }
        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }
        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }
    </script>
</body>

</html>

6-4 带图片*

<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>带图片</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body><br />
    <svg></svg>

    <script>
        // 画布
        var width = 800;
        height = 600;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .append('g')
            .attr(
                'transform',
                'translate(' + width / 4 + ',' + height / 3 + ')'
            );

        // color颜色比例尺,能根据传入的索引号获取相应的颜色值
        var color = d3.scaleOrdinal(d3.schemeCategory10);

        // 初始数据,链接数组
        var links = [
            { source: '福建', target: '福建', type: 'node', weight: 1 },
            { source: '福建', target: '福州', type: 'node', weight: 1 },
            { source: '福建', target: '莆田', type: 'node', weight: 1 },
            { source: '广东', target: '福建', type: 'node', weight: 1 },
            { source: '广东', target: '广东', type: 'node', weight: 1 },
            { source: '广东', target: '广州', type: 'node', weight: 1 },
            { source: '广东', target: '茂名', type: 'node', weight: 1 },
            { source: '广东', target: '深圳', type: 'node', weight: 1 }
        ];

        // 初始化一个空的节点对象映射
        var nodes = {};

        // 从链接中分离出不同的节点
        links.forEach(function (link) {
            link.source = nodes[link.source]
                || (nodes[link.source] = { name: link.source });
            link.target = nodes[link.target]
                || (nodes[link.target] = { name: link.target });
        });

        // 定义一个力导向图的布局,连接力,排斥力,中心力
        var simulation = d3.forceSimulation(Object.values(nodes))
            .force('link', d3.forceLink().links(links).distance(150))
            .force('charge', d3.forceManyBody().strength(-500))
            .force('center', d3.forceCenter(width / 5, height / 5));

        // 绘制连线
        var svg_edges = svg.selectAll('line')
            .data(links)
            .enter()
            .append('line')
            .style('stroke', '#ccc')
            .style('stroke-width', 1);

        // 添加节点 
        var svg_nodes = svg.selectAll('circle')
            .data(Object.values(nodes))
            .enter()
            .append('circle')
            .attr('r', 15)
            .style('fill', function (d, i) {
                return color(i);
            })
            .call(
                d3.drag()
                    .on('start', dragstarted)
                    .on('drag', dragged)
                    .on('end', dragended)
            );

        // 添加图片
        // svg_nodes.append('image')
        //     .attr('xlink:href', './file/avater1.png')
        //     .attr('x', -8)
        //     .attr('y', -8)
        //     .attr('width', 16)
        //     .attr('height', 16);

        // 为不同节点设置不同图片,图片效果出不来???
        svg_nodes.append('image')
            .attr('xlink:href', function (d) {
                // 根据节点数据返回不同的图片URL
                return d.name === '福建' ? './file/avater2.png' :
                    d.name === '广东' ? './file/avater3.png' :
                        './file/avater1.png';
            })
            .attr('x', -8)
            .attr('y', -8)
            .attr('width', 16)
            .attr('height', 16);

        // 添加提示,这里和添加文字是否有区别???
        svg_nodes.append('title')
            .text(function (d) { return d.name; });

        // 添加文字
        var svg_texts = svg.selectAll('text')
            .data(Object.values(nodes))
            .enter()
            .append('text')
            .style('fill', 'black')
            .attr('dx', 20)
            .attr('dy', 8)
            .text(function (d) {
                return d.name;
            });

        // 力导向图不断运动,每一时刻都在发生更新,须不断更新节点和连线位置
        simulation.on('tick', function () {
            // 每个tick更新节点和连线的位置
            svg_edges.attr('x1', function (d) { return d.source.x; })
                .attr('y1', function (d) { return d.source.y; })
                .attr('x2', function (d) { return d.target.x; })
                .attr('y2', function (d) { return d.target.y; });

            svg_nodes.attr('cx', function (d) { return d.x; })
                .attr('cy', function (d) { return d.y; });

            svg_texts.attr('x', function (d) { return d.x; })
                .attr('y', function (d) { return d.y; });
        });

        // 拖动的处理函数
        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }
        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }
        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }
    </script>
</body>

</html>

7 可视化地图

  • 制作地图需要Json文件,将Json格式应用于地理上的文件,叫GeoJson文件。
<!DOCTYPE html>
<html lang='en'>

<head>
    <meta charset='UTF-8' />
    <title>可视化地图</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js'>
    </script>
    <!-- 引入TopoJSON -->
    <script src='https://cdn.bootcdn.net/ajax/libs/topojson/3.0.2/topojson.js'>
    </script>
    <style>
        svg {
            display: block;
            margin: auto;
        }

        .country {
            fill: steelblue;
            stroke: white;
            stroke-width: 0.5px;
        }

        .country:hover {
            fill: orange;
        }
    </style>
</head>

<body><br />
    <svg></svg>
    
    <script>
        // 设置SVG容器
        var width = 960;
        height = 600;
        var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height);

        // 投影设置:GeoJson中的地图数据都是三维的经纬度,而网页显示为二维
        // 此时需要设定一个投影函数,用于经纬度转换
        const projection = d3.geoMercator()
            // scale为缩放因子,数值越小缩放效果越大
            .scale(90)
            .translate([width / 2, height / 2]);

        // 地理路径生成器
        const path = d3.geoPath()
            .projection(projection);

        // 加载数据
        d3.json('https://d3js.org/world-110m.v1.json')
            .then(data => {
                // 转换TopoJSON数据
                const countries = topojson.feature(
                    data, data.objects.countries
                ).features;

                // 绘制国家
                svg.selectAll('.country')
                    .data(countries)
                    .enter()
                    .append('path')
                    .attr('class', 'country')
                    .attr('d', path)
                    .attr('fill', 'steelblue')
                    .attr('stroke', 'white')
                    .attr('stroke-width', 0.5)
                    .on('mouseover', function (event) {
                        d3.select(this)
                            .transition()
                            .duration(200)
                            .style('fill', 'orange');
                    })
                    .on('mouseout', function (event) {
                        d3.select(this)
                            .transition()
                            .duration(200)
                            .style('fill', 'steelblue');
                    });
            });
    </script>
</body>

</html>

文章作者: bsf
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 bsf !
评 论
  目录