79434b206e4a03bb5004969d7e38c46a.png

前言

D3近年来一直是JavaScript最重要的数据可视化库之一,在创建者Mike Bostock的维护下,前景依然无量,至少现在没有能打的:

  • D3与众多其他库的区别在于无限定制的能力(直接操作SVG)。
  • 它的底层API提供对原生SVG元素的直接控制,但它也带来了高学习曲线的成本。
  • 我们将把D3和Vue结合在一起 - 使用Vue的动态数据绑定,清晰的语法和模块化结构,可以充分发挥D3的最佳性能。 根据广泛定义,D3可拆分为以下几种分库:
0e8938f9df3e879f2466efecd9448f25.png
  1. 绝大部分的D3课程或书籍,都会着重讲解在其DOM操作功能上,但这明显与近几年来的web框架理念相违背。
  2. 用于数据可视化的D3,其核心在于使用绘图指令装饰数据,从源数据创建新的可绘制数据,生成SVG路径以及从数据和方法在DOM中创建数据可视化元素(如轴)的功能。
  3. 有许多用于管理DOM的工具,所有这些工具都可以在D3中集成数据可视化功能。这也是D3能与Vue无缝结合的原因之一。

于此,我们不需要从D3 DOM操作功能开始学起,直接通过实例来入门D3。

1. D3.js 渐进入门

以下实例的模版均为以下形式:

            Learn D3.js

First heading

复制代码

1. 选择和操作

c77c8ba67ae0bf2f816b8606a56f0e6c.png

你需要学习的第一件事是如何使用D3.js选择和操作DOM元素。该库在操作DOM方面实际上非常强大,因此理论上可以将其用作jQuery的替代品。以下代码请逐行添加运行。

// index.jsd3.select();d3.selectAll();d3.select('h1').style('color', 'red').attr('class', 'heading').text('Updated h1 tag');d3.select('body').append('p').text('First Paragraph');d3.select('body').append('p').text('Second Paragraph');d3.select('body').append('p').text('Third Paragraph');d3.selectAll('p').style('')复制代码

2.数据加载和绑定

c23884a30821960a51234efa515bf51d.png

当你要创建可视化时,了解如何加载数据以及将其绑定到DOM非常重要。所以在这个实例中,你将学到这两点。

let dataset = [1, 2, 3, 4, 5];d3.select('body')    .selectAll('p')    .data(dataset)    .enter()    .append('p') // appends paragraph for each data element    .text('D3 is awesome!!');    //.text(function(d) { return d; });复制代码

3.创建一个简单的柱状图

99ea843391ba2dd487c0ca5a04875aae.png

首先需要添加一个svg标签

Bar Chart using D3.js

复制代码

然后在index.js中添加(已添加关键注释):

// 数据集let dataset = [80, 100, 56, 120, 180, 30, 40, 120, 160];// 定义svg图形宽高,以及柱状图间距let svgWidth = 500, svgHeight = 300, barPadding = 5;// 通过图形计算每个柱状宽度let barWidth = (svgWidth / dataset.length);// 绘制图形let svg = d3.select('svg')    .attr("width", svgWidth)    .attr("height", svgHeight);// rect,长方形// 文档:http://www.w3school.com.cn/svg/svg_rect.asplet barChart = svg.selectAll("rect")    .data(dataset) //绑定数组    .enter() // 指定选择集的enter部分    .append("rect") // 添加足够数量的矩形    .attr("y", d => svgHeight - d ) // d为数据集每一项的值, 取y坐标    .attr("height", d => d) // 设定高度    .attr("width", barWidth - barPadding) // 设定宽度    .attr("transform", (d, i) =>  {        let translate = [barWidth * i, 0];         return "translate("+ translate +")";    }); // 实际是计算每一项值的x坐标复制代码

4. 在图形上方显示数值

bba0ff4c9a86004e2474e5bad5c5505a.png

这时就需要在上述代码中创建svg的 text文本

let text = svg.selectAll("text")    .data(dataset)    .enter()    .append("text")    .text(d => d)    .attr("y", (d, i) => svgHeight - d - 2)    .attr("x", (d, i) =>  barWidth * i)    .attr("fill", "#A64C38");复制代码

过程比较简单,就是返回文本,计算x/y坐标,并填充颜色。

5. scales: 比例尺函数

D3中有个重要的概念就是比例尺。比例尺就是把一组输入域映射到输出域的函数。映射就是两个数据集之间元素相互对应的关系。比如输入是1,输出是100,输入是5,输出是10000,那么这其中的映射关系就是你所定义的比例尺。

D3中有各种比例尺函数,有连续性的,有非连续性的,在本例子中,你将学到d3.scaleLinear() ,线性比例尺

5.1 d3.scaleLinear(),线性比例尺

使用d3.scaleLinear()创造一个线性比例尺,其中:

  • domain()是输入域
  • range()是输出域
  • 相当于将domain中的数据集映射到range的数据集中。
let scale = d3.scaleLinear().domain([1,5]).range([0,100])复制代码

映射关系:

945e213c287b996c502889f9ba34e79d.png

值得注意的是,上述代码只是定义了一个映射规则,映射的输入值并不局限于domain()中的输入域。

scale(1) // 输出:0scale(4) // 输出:75scale(5) // 输出:100scale(-1) // 输出:-50scale(10) // 输出:225复制代码

于是我们来改造3~4的例子:

let dataset = [1,2,3,4,5];let svgWidth = 500, svgHeight = 300, barPadding = 5;let barWidth = (svgWidth / dataset.length);let svg = d3.select('svg')    .attr("width", svgWidth)    .attr("height", svgHeight);    let yScale = d3.scaleLinear()    .domain([0, d3.max(dataset)])    .range([0, svgHeight]);        let barChart = svg.selectAll("rect")    .data(dataset)    .enter()    .append("rect")    .attr("y", d => svgHeight - yScale(d))    .attr("height", d => yScale(d))    .attr("width", barWidth - barPadding)    .attr("transform", (d, i) => {        let translate = [barWidth * i, 0];         return "translate("+ translate +")";    });复制代码

然后就会得到以下图形:

b0346819fd0e4beecf9b488e900db623.png

6. Axes:轴

0104531aeecb3468018c8f0f9a1c8bdd.png

轴是任何图表的组成部分,本例子中将会用到上面讲到的比例尺函数。

let data= [80, 100, 56, 120, 180, 30, 40, 120, 160];let svgWidth = 500, svgHeight = 300;let svg = d3.select('svg')    .attr("width", svgWidth)    .attr("height", svgHeight);// 首先是拿最大值构建x轴坐标let xScale = d3.scaleLinear()    .domain([0, d3.max(data)])    .range([0, svgWidth]);         // 接下来是反转值,用作y轴坐标。let yScale = d3.scaleLinear()    .domain([0, d3.max(data)])    .range([svgHeight, 0]);// 横轴的API使用let x_axis = d3.axisBottom()    .scale(xScale);    // 纵轴的API使用let y_axis = d3.axisLeft()    .scale(yScale);    // 在svg中提供了如g元素这样的将多个元素组织在一起的元素。// 由g元素编组在一起的可以设置相同的颜色,可以进行坐标变换等,类似于Vue中的 svg.append("g")    .attr("transform", "translate(50, 10)")    .call(y_axis);         let xAxisTranslate = svgHeight - 20;         svg.append("g")    .attr("transform", "translate(50, " + xAxisTranslate  +")")    .call(x_axis);复制代码

7. 创建简易的SVG元素

9e00a97c9ecf1833e2bedf4d5c57244a.png

在这里面,你会创建,和元素

let svgWidth = 600, svgHeight = 500;let svg = d3.select("svg")    .attr("width", svgWidth)    .attr("height", svgHeight)    .attr("class", "svg-container")    let line = svg.append("line")    .attr("x1", 100)    .attr("x2", 500)    .attr("y1", 50)    .attr("y2", 50)    .attr("stroke", "red");    let rect = svg.append("rect")    .attr("x", 100)    .attr("y", 100)    .attr("width", 200)    .attr("height", 100)    .attr("fill", "#9B95FF");    let circle = svg.append("circle")    .attr("cx", 200)    .attr("cy", 300)    .attr("r", 80)    .attr("fill", "#7CE8D5");复制代码

8. 创建饼图

5371cce26e0f81b5ad1da78ceaeacf59.png
let data = [    {"platform": "Android", "percentage": 40.11},     {"platform": "Windows", "percentage": 36.69},    {"platform": "iOS", "percentage": 13.06}];let svgWidth = 500, svgHeight = 300, radius =  Math.min(svgWidth, svgHeight) / 2;let svg = d3.select('svg')    .attr("width", svgWidth)    .attr("height", svgHeight);//Create group element to hold pie chart    let g = svg.append("g")    .attr("transform", "translate(" + radius + "," + radius + ")") ;// d3.scaleOrdinal() 序数比例尺// schemeCategory10, 颜色比例尺// D3提供了一些颜色比例尺,10就是10种颜色,20就是20种:let color = d3.scaleOrdinal(d3.schemeCategory10);let pie = d3.pie().value(d => d.percentage);let path = d3.arc()    .outerRadius(radius)    .innerRadius(0); let arc = g.selectAll("arc")    .data(pie(data))    .enter()    .append("g");arc.append("path")    .attr("d", path)    .attr("fill", d => color(d.data.percentage));        let label = d3.arc()    .outerRadius(radius)    .innerRadius(0);            arc.append("text")    .attr("transform",  d => `translate(${label.centroid(d)})`)    .attr("text-anchor", "middle")    .text(d => `${d.data.platform}:${d.data.percentage}%`);复制代码

9. 创建折线图

76e89fd4049f64b0bdbcb475dbb4e5b1.png

最后,你将学习如何创建折线图以显示近四个月的比特币价格。要获取数据,你将使用外部API。这个项目还将你在整个课程中学到的很多概念结合在一起,所以这是一个很好的可视化课程结束。

// 外部API,注意日期记得补零const api = 'https://api.coindesk.com/v1/bpi/historical/close.json?start=2019-03-31&end=2019-07-01';/** * dom内容加载完毕时,从API中加载数据 */document.addEventListener("DOMContentLoaded", function(event) {fetch(api)    .then(response => response.json())    .then(data => {        let parsedData = parseData(data);        drawChart(parsedData);    })    .catch(err =>  console.log(err))});/** * 将数据解析为键值对 */parseData = data =>{    let arr = [];    for (let i in data.bpi) {        arr.push({            date: new Date(i), //date            value: +data.bpi[i] //convert string to number        });    }    return arr;}/** * 创建图表 */drawChart  = data => {let svgWidth = 600, svgHeight = 400;let margin = { top: 20, right: 20, bottom: 30, left: 50 };let width = svgWidth - margin.left - margin.right;let height = svgHeight - margin.top - margin.bottom;let svg = d3.select('svg')    .attr("width", svgWidth)    .attr("height", svgHeight);    let g = svg.append("g")    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");let x = d3.scaleTime()    .rangeRound([0, width]);let y = d3.scaleLinear()    .rangeRound([height, 0]);let line = d3.line()    .x(d=> x(d.date))    .y(d=> y(d.value))    x.domain(d3.extent(data, function(d) { return d.date }));    y.domain(d3.extent(data, function(d) { return d.value }));g.append("g")    .attr("transform", "translate(0," + height + ")")    .call(d3.axisBottom(x))    .select(".domain")    .remove();g.append("g")    .call(d3.axisLeft(y))    .append("text")    .attr("fill", "#000")    .attr("transform", "rotate(-90)")    .attr("y", 6)    .attr("dy", "0.71em")    .attr("text-anchor", "end")    .text("Price ($)");g.append("path")    .datum(data)    .attr("fill", "none")    .attr("stroke", "steelblue")    .attr("stroke-linejoin", "round")    .attr("stroke-linecap", "round")    .attr("stroke-width", 1.5)    .attr("d", line);}复制代码

以上原实例均来自:Learn D3 for free。

源码地址:https://scrimba.com/g/gd3js

scrimba是一个非常神奇的网站。它是使用交互式编码截屏工具构建的。

b3d536cd01055101f16bd26e292da281.gif

所有的操作都是:

暂停截屏视频 → 编辑代码 → 运行它! → 查看更改

非常值得安利一波。接下来进入第二部分:Vue中使用D3.js的正确姿势

2. Vue中使用D3.js的正确姿势

我们将使用D3和Vue构建一个基本的柱状图组件。网上有一堆例子,但我们将专注于写Vue,而不是滥用D3。

1. 安装依赖

首先,我们需要为项目安装依赖项。我们可以简单地安装和使用D3整库:

npm i d3复制代码

但我在前面讲到,实际上D3是几个分库的集合,考虑到项目的优化,我们只安装所需的模块。

17138270df8d5c266b422736112bceb7.png

使用Vue Cli 初始化项目即可。

2. 创建柱状图

b7aae90b33653ba7ad49176d2075d953.png

3. 柱状图模块导入

b7fa8806b60991867812274277c24fd9.png

4. 创建svg元素

3783b72df71d31f761fa2626e57025bf.png

因Vue数据响应的特性,我们不需要用到D3操作DOM的那套链式创建。

5. 数据与窗口大小响应

74924bb8445ef126a59a0780c8ae0a16.png

在mounted钩子中,我们将为窗口调整大小事件添加一个监听器,它将触发绘制动画,并将大小设置为新窗口的比例。我们不会立即渲染,而是等待300毫秒,以确保完全调整窗口大小。

以下是完整的BarChart.vue,请配合注释食用:

{{ title }}

复制代码

我们将从父组件App.vue获取数据:

复制代码

这时候yarn run serve后将会看到:

3f49520b0e6924e5b91e4db3ba425c6f.gif

好像还缺点显示数值,考虑到该图高度是根据比例尺生成,我们调整下y坐标:

yScale() {  return scaleLinear()    .rangeRound([this.svgHeight, 0])    .domain([this.dataMin > 0 ? 0 : this.dataMin + 2, this.dataMax + 2]);},复制代码

在AnimateLoad()末尾添加:

selectAll("text")  .data(this.data)  .enter()复制代码

最后在元素中添加:

{{ item[xKey]}} {{ item[yKey]}}复制代码
e3d9bf0bca9109f6fd18f1afc1bd360b.png

3. 参考文章

1fec6365f574b88dfed90c692854a1c0.gif
  • The Hitchhiker’s Guide to d3.js
  • D3 is not a Data Visualization Library
  • D3中常用的比例尺
  • D3 vs G2 vs Echarts
  • Dynamic Data Visualizations With Vue.js and D3

4. 总结

该库几乎凭 Mike Bostock 一人之力完成,且在学术界、专业团队中享有极大声誉。

b0e3c74a60b440fa2bf0b51a5e2104e2.png
  • D3更接近底层,与 g2、echarts 不同,d3 能直接操作 svg,所以拥有极大的自由度,几乎可以实现任何 2d 的设计需求。
  • 正如其名 Data Driven Documents,其本质是将数据与 DOM 绑定,并将数据映射至 DOM 属性上。
  • D3 长于可视化,而不止于可视化,还提供了数据处理、数据分析、DOM 操作等诸多功能。
  • 如果有想深耕数据可视化方面的前端,D3不得不学。
b7b645673b6131e39663d025c314b447.png

掌握 D3 后,限制作品水平的只会是想象力而不再是技术。

源码地址:https://github.com/roger-hiro/d3-bar-chart-vuejs

❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
  2. 关注公众号「前端劝退师」,不定期分享原创知识。
  3. 原地址:https://juejin.im/post/5d1e074af265da1bca51f8ec
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐