D3 入门笔记

1.D3是什么

D3 的全称是(Data-Driven Documents),顾名思义是一个被数据驱动的文档。听名字有点抽象,说简单一点,其实就是一个基于 web 标准的 JavaScript 的函数库,使用它主要是用来做数据可视化的。
主要借助SVG, Canvas 以及 HTML 将你的数据生动的展现出来。
  D3 可以将数据绑定到 DOM 上,然后根据数据来计算对应 DOM 的属性值。我觉得,其设计思路就是把数据映射到视觉元素(形状、长度、方向、角度、颜色、面积、文本等),并通过视觉元素的组合进行数据可视化。下面的实例使用SVG来展示。
 
2.D3安装
引入D3 最简单的方法,通过 html 里的script标签引入外部d3脚本文件。可以使用外部链接,也可以把d3.js下载到本地再引入。
  • 使用外部链接
<script src="https://d3js.org/d3.v5.min.js" />
  •  本地引入
<script src="./d3.v5.min.js" /> //src属性里写d3.js本地路径
  •  npm、yarnan安装依赖包

  npm i d3 / yarn add d3

  import * as d3 from "d3";

 

3.D3对象、选择集(d3-selection)、操作

  • 引入d3.js后,就可以获得要使用的d3对象了。每次打点调用 d3 提供的函数方法,都会返回一个 d3 对象,这样可以继续调用 d3 的其他函数方法。

  创建画布,绘制图形(Draw canvas)

 1 // Create chart dimensions
 2 // 定义svg图形宽高,以及柱状图间距
 3 const svgWidth = 500, svgHeight = 300, barPadding = 14;
 4 const margin = {
 5       top: 20,
 6       right: 40,
 7       bottom: 20,
 8       left: 40
 9 }
10 
11 // Draw canvas
12 // 绘制图形
13 const svg = d3.select('.d3-demo')
14     .attr('width', svgWidth)
15     .attr('height', svgHeight);
  • D3 选择元素提供了两个函数:select 和 selectAll

  select 选中符合条件的第一个元素。

  selectAll 选择所有与指定的 selector 匹配的元素。

  详情:https://www.d3js.org.cn/document/d3-selection/#installing

  • 选择元素后可以操作元素,操作元素主要分为增加 ( append )、插入 ( insert )、删除元素 ( remove ) 和设置获取元素属性 ( attr, style )
1 console.log(d3.select('.d3-demo3'));
2 d3.select('.d3-demo3')
3     .style('background-color', '#c3272b')
4     .append('text')
5     .text('D3-demo-1')
6     .style('color', 'red')
7     .attr('x', 20)
8     .attr('y', 20)
9                

运行结果:

 

4. 数据绑定

   数据绑定是将数据绑定到 DOM 元素上,D3 中数据绑定提供了 datum() 和data()两个函数,一般都使用data()函数。

1 d3.select("body")
2     .selectAll("p")
3     .data([4, 8, 15, 16, 23, 42])
4     .enter()
5     .append("p")
6     .text(function(d) { return "I’m number " + d + "!"; });

  运行结果:

  

  enter和exit操作 

  数据绑定的时候可能出现 DOM 元素与数据元素个数不匹配的问题,那么 enter 和 exit 就是用来处理这个问题的。enter 操作用来添加新的 DOM 元素,exit 操作用来移除多余的 DOM 元素。

  如果数据元素多于 DOM 个数时用 enter,如果数据元素少于 DOM元素,则用 exit

  在数据绑定时候存在三种情形:

  • 数据元素个数多于 DOM 元素个数
  • 数据元素与 DOM 元素个数一样
  • 数据元素个数少于 DOM 元素个数

  情形 1:数据元素个数多于 DOM 元素个数 

  如以下例子,如果文档中p标签的个数少于数组个数(6个),则使用 enter 和 append 操作来补齐 DOM 元素:

d3.select("body")
    .selectAll("p")
    .data([4, 8, 15, 16, 23, 42])
    .enter().append("p")
    .text(function(d) { return "I’m number " + d + "!"; });

  情形 2:数据元素与 DOM 元素个数一样

如果这种情况再使用 data 来绑定数据,相当于是更新了每个 DOM 元素所对应的数据,此时不需要加入新节点也不需要删除多余的节点:

//update
let p = d3.select("body")
    .selectAll("p")
    .data([4, 8, 15, 16, 23, 42])
    .text(function(d) { return d; });

  情形 3:数据元素个数少于 DOM 元素个数

  假设p元素的个数多于 6 个,数据元素个数为 6,则:

let p = d3.select("body")
    .selectAll("p")
    .data([4, 8, 15, 16, 23, 42])
    .text(function(d) { return d; });

p.exit().remove()  //移除多余的元素

  但是在实际应用中,不可能先去统计一下元素个数,因此这三种情形可以一起使用:(在画图表时一般都是直接使用append添加元素,因为画布是从空白开始添加元素的)

// Update 情形2
let p = d3.select("body")
    .selectAll("p")
    .data([4, 8, 15, 16, 23, 42])
    .text(function(d) { return d; });      

// Enter 情形1
p.enter().append("p")
    .text(function(d) { return d; });

// Exit 情形3
p.exit().remove();

   

5. 过渡(d3-transition

  D3 支持动画效果,这种动画效果可以通过对样式属性的过渡实现。

  比如对元素的背景颜色进行过渡:

d3.select("body").transition()
    .style("background-color", "black");

  详情:https://www.d3js.org.cn/document/d3-transition/#installing

     https://observablehq.com/@d3/learn-d3-animation?collection=@d3/learn-d3

 

6. 比例尺(d3-scale)

比例尺是 D3 重要概念。

对于可视化来说,比例尺是一个很方便的工具:将抽象的维度数据映射为可视化表示。比例尺这个概念跟地图比例尺的概念是一样的,要一个画布上绘制一幅世界地图,需要按一定的比例将整个世界投射到画布上。

虽然经常使用位置编码定量数据,比如将测量单位米使用像素映射。但是比例尺可以对任何视觉编码进行映射,比如颜色,描边的宽度或者符号的大小。比例尺也可以用来对任意类型的数据进行映射,比如分类数据或离散的数据。

三个要素:定义域、值域、对应法则。

其中 scaleLinear() 对应法则,domain() 是定义域,range() 是值域。

以线性比例尺为例:

let x = d3.scaleLinear()
    .domain([10, 130])
    .range([0, 960]);

x(20); // 80
x(50); // 320

  以线性比例尺为例(数值映射为颜色):

let color = d3.scaleLinear()
    .domain([10, 100])
    .range(["brown", "steelblue"]);

color(20); // "#9a3439"
color(50); // "#7b5167"

  详情:https://www.d3js.org.cn/document/d3-scale/#installing

 

7. 坐标轴(d3-axis)

坐标轴组件可以将 scales (opens new window)显示为人类友好的刻度标尺参考,减轻了在可视化中的视觉任务。

例:

// Create chart dimensions
// 定义svg图形宽高,以及柱状图间距
const svgWidth = 500, svgHeight = 300, barPadding = 14;
const margin = {
    top: 20,
    right: 40,
    bottom: 20,
    left: 40
}

// Draw canvas
// 绘制图形
const svg = d3.select('.d3-demo')
    .attr('width', svgWidth)
    .attr('height', svgHeight);

// Create scales
// 首先是拿到最大值构建x轴坐标
let xScale = d3.scaleLinear()
    .domain([0, d3.max(dataX)])
    .range([margin.left, svgWidth - margin.right]);
// 接下来是反转值,用作y轴坐标
let yScale = d3.scaleLinear()
    .domain([0, d3.max(dataY) + 20])
    .range([svgHeight - margin.bottom, margin.top])
            
// 横轴的API使用
let x_axis = d3.axisBottom()
    .scale(xScale)
            
// 纵轴的API使用
let y_axis = d3.axisLeft()
    .scale(yScale);

svg.append("g")
    .attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
    .call(x_axis);

svg.append("g")
    .attr("transform", `translate(${margin.left}, 0)`)
    .call(y_axis);
View Code

运行结果:

 

8. 基本图表

  数据来源:使d3-fetch模块中的d3.json()读取json文件的数据

{
    "code": 200,
    "message": "操作成功",
    "data": [
        {
            "name": 0,
            "value": 20
        },
        {
            "name": 1,
            "value": 40
        },
        {
            "name": 2,
            "value": 100
        },
        {
            "name": 3,
            "value": 56
        },
        {
            "name": 4,
            "value": 120
        },
        {
            "name": 5,
            "value": 180
        },
        {
            "name": 6,
            "value": 30
        },
        {
            "name": 7,
            "value": 40
        },
        {
            "name": 8,
            "value": 40
        }
    ]
}
View Code
  • 折线图
<template>
    <div id="d3Demo">
        <svg class="d3-demo"></svg>
    </div>
</template>
    
<script lang="ts">
import * as d3 from "d3";
import { defineComponent, reactive, ref, toRefs, h, watch, nextTick } from "vue";

export default defineComponent({
    name: "D3Line",
    setup(props, context) {
        let dataList = [];
        let dataX = [];
        let dataY = [];
        // 获取数据
        async function getData() {
            const xyData = await d3.json('/dataFiles/demo1.json');
            let { data: datas } = xyData;
            dataList = datas;
            datas.forEach(element => {
                dataX.push(element.name);
                dataY.push(element.value);
            });
        }

        // D3相关函数
        const d3Function = async () =>{
            // Access data
            await getData();

            // Create chart dimensions
            // 定义svg图形宽高,以及柱状图间距
            const svgWidth = 500, svgHeight = 300, barPadding = 14;
            const margin = {
                top: 20,
                right: 40,
                bottom: 20,
                left: 40
            }

            // Draw canvas
            // 绘制图形
            const svg = d3.select('.d3-demo')
                .attr('width', svgWidth)
                .attr('height', svgHeight);

            // Create scales
            // 首先是拿到最大值构建x轴坐标
            let xScale = d3.scaleLinear()
                .domain([0, d3.max(dataX)])
                .range([margin.left, svgWidth - margin.right]);
            // 接下来是反转值,用作y轴坐标
            let yScale = d3.scaleLinear()
                .domain([0, d3.max(dataY) + 20])
                .range([svgHeight - margin.bottom, margin.top])
            
            // 横轴的API使用
            let x_axis = d3.axisBottom()
                .scale(xScale)
            
            // 纵轴的API使用
            let y_axis = d3.axisLeft()
                .scale(yScale);

            // Draw data
            const t = (path) => path.transition()
                .duration(1000)
                .ease(d3.easeLinear)
                .attrTween('stroke-dasharray', function() {
                    const length = this.getTotalLength();
                    return d3.interpolate(`0, ${length}`, `${length}, ${length}`);
                })

            let line = d3.line()
                    .x(function(d) {
                        return xScale(d.name);
                    })
                    .y(function(d) {
                        return yScale(d.value);
                    })
                    .curve(d3.curveCatmullRom.alpha(0.5));

            // Draw peripherals
            // 在svg中提供了如g元素这样的将多个元素组织在一起的元素
            // 由g元素编组在一起的可以设置相同的颜色,可以进行坐标变化等,类似于Vue中的<template>

            svg.append("path")
                // .attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
                .attr('d', line(dataList))
                .attr('class', 'line-path')
                .attr('fill', 'none')
                .attr('stroke-dasharray', '0,1')
                .attr('stroke', '#c3272b')
                .attr('stroke-width', 2)
                .call(t)

            svg.append("g")
                .attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
                .call(x_axis);

            svg.append("g")
                .attr("transform", `translate(${margin.left}, 0)`)
                .call(y_axis);
            
        }

        nextTick(() => {
            d3Function();
        })
    },
});
</script>
    
<style scoped lang="scss">
#d3Demo {
    width: 100%;
    height: 100%;
    color: $test-color;
    display: flex;
    align-items: center;
    background-size: 100% 100%;
    justify-content: center;
}
</style>
View Code

  运行结果:

  

  • 柱状图
<template>
    <div id="d3Demo">
        <svg class="d3-demo"></svg>
    </div>
</template>
    
<script lang="ts">
import * as d3 from "d3";
import { defineComponent, reactive, ref, toRefs, h, watch, nextTick } from "vue";

export default defineComponent({
    name: "D3Line",
    setup(props, context) {
        let dataList = [];
        let dataX = [];
        let dataY = [];
        // 获取数据
        async function getData() {
            const xyData = await d3.json('/dataFiles/demo1.json');
            let { data: datas } = xyData;
            dataList = datas;
            datas.forEach(element => {
                dataX.push(element.name);
                dataY.push(element.value);
            });
        }

        // D3相关函数
        const d3Function = async () =>{
            // Access data
            await getData();

            // Create chart dimensions
            // 定义svg图形宽高,以及柱状图间距
            const svgWidth = 500, svgHeight = 300, barPadding = 7;
            const durationTime = 1000; // 动画时长
            const margin = {
                top: 20,
                right: 40,
                bottom: 20,
                left: 40
            }

            // 通过图形计算每个柱状宽度
            let barWidth = (svgWidth - margin.left - margin.right) / dataX.length;

            // Draw canvas
            // 绘制图形
            const svg = d3.select('.d3-demo')
                .attr('width', svgWidth)
                .attr('height', svgHeight);

            // Create scales
            // 首先是拿到最大值构建x轴坐标
            let xScale = d3.scaleBand()
                .domain((dataX))
                .range([margin.left, svgWidth - margin.right])
            // 接下来是反转值,用作y轴坐标
            let yScale = d3.scaleLinear()
                .domain([0, d3.max(dataY) + 20])
                .range([svgHeight - margin.bottom, margin.top])

            // y轴值得缩放
            let yDataScale = d3.scaleLinear()
                .domain([0, d3.max(dataY) + 20])
                .range([0, svgHeight - margin.top - margin.bottom])
            
            // 横轴的API使用
            let x_axis = d3.axisBottom()
                .scale(xScale)
            
            // 纵轴的API使用
            let y_axis = d3.axisLeft()
                .scale(yScale);

            // Draw data
            const t = (val) => val.transition()
                .duration(durationTime)
                .ease(d3.easeLinear)
                .attrTween('height', function(d) {
                    return d3.interpolate('0', yDataScale(d));
                })

            // 
            const t1 = (val) => val.transition()
                .duration(durationTime)
                .ease(d3.easeLinear)
                .attrTween('transform', function(d, i) {
                    return d3.interpolate(`translate(${barWidth * i + margin.left + barPadding}, ${(svgHeight - margin.bottom)})`, `translate(${barWidth * i + margin.left + barPadding}, ${(svgHeight - margin.bottom) - yDataScale(d)})`);
                })
            
            // 处理文字动画
            const t2 = (val) => val.transition()
                .duration(durationTime)
                // .delay(1000)
                .ease(d3.easeLinear)
                .textTween((d) => {
                    return d3.interpolateRound(0, d);
                })

            // Draw peripherals
            // 在svg中提供了如g元素这样的将多个元素组织在一起的元素
            // 由g元素编组在一起的可以设置相同的颜色,可以进行坐标变化等,类似于Vue中的<template>
            
            let bar = svg.selectAll(".bar")
            .data(dataY)
            // .enter()
            // .append("g")
            .join("g")
            .attr("class", "bar")
            .call(t1)

            bar.append('rect') // 添加足够数量的矩形
                .attr('class', 'rect')
                .attr("width", barWidth - barPadding * 2) // 设定宽度
                .attr("height", d => yDataScale(d)) // 设定高度
                .style("fill", '#c3272b')
                .each(function() {
                    d3.select(this)
                        .call(t);
                })

            bar.append('text')
                .attr("transform", (d, i) =>  {
                    let translate = [barWidth / 2 - barPadding, -2]; 
                    return `translate(${translate})`;
                })
                .attr("text-anchor", "middle")
                .style("fill", '#000000')
                .each(function() {
                    d3.select(this)
                        .call(t2);
                })


            svg.append("g")
                .attr("transform", `translate(${0}, ${svgHeight - margin.bottom})`)
                .call(x_axis);

            svg.append("g")
                .attr("transform", `translate(${margin.left}, 0)`)
                .call(y_axis);
            
        }

        nextTick(() => {
            d3Function();
        })
    },
});
</script>
    
<style scoped lang="scss">
#d3Demo {
    width: 100%;
    height: 100%;
    color: $test-color;
    display: flex;
    align-items: center;
    background-size: 100% 100%;
    justify-content: center;
}
</style>
View Code

  运行结果:

  

 

 9. 小结

这篇文章只是介绍了d3的基本操作,官网有很多的知识点需要自行查看。当你看完这篇入门笔记后,能对d3有一个简单的认识,加上对SVG基本图形的了解可以创造出一些基础的图表,加油,加油!!!

  最后给一个d3画图表的基本步骤,按这个步骤可以指导你画图思路。上面的实例就是按这个步骤实现的,可以作为参考。

Demo项目开源地址:https://gitee.com/userzp/vue3-d3.git

posted @ 2022-04-07 15:36  zigood  阅读(568)  评论(0编辑  收藏  举报