D3.jsV5入门教程
最近一直在开发3D可视化组件,偶尔也开发2D的组件。但是2D我也是用3D的方式开发的,感觉有点浪费。所以打算入手一下D3,毕竟D3还是挺有市场的灵活性非常大,很适合二次开发。
-
d3官网:https://d3js.org/
-
d3案例:https://github.com/d3/d3/wiki/Gallery
-
d3教程:https://github.com/d3/d3/wiki/Tutorials
-
d3 API:https://github.com/d3/d3/blob/master/API.md
-
mbostock案例地址:https://bl.ocks.org/mbostock
-
d3的中文网址:https://d3js.org.cn/
-
observable D3的案例:https://observablehq.com/@d3?tab=collections
-
JQuery之家:SVG系列教程:http://www.htmleaf.com/ziliaoku/qianduanjiaocheng/201507082192.html
-
SVG精髓》 阅读笔记 :https://github.com/xswei/SVG_Essentials
-
SVG 教程 : https://www.nhooo.com/svg/svg-tutorial.html
-
MDN :https://developer.mozilla.org/zh-CN/docs/Web/SVG
-
svg animate :https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/animate
-
SVG SMIL animation动画详解:https://www.zhangxinxu.com/wordpress/2014/08/so-powerful-svg-smil-animation/
-
SVG颜色渐变动画效果 :http://www.htmleaf.com/ziliaoku/qianduanjiaocheng/201504141680.html
提示:D3的V3-V4-V5版本之间API变化很大,所以自己学习过程要查询最新的API文档,不要盲目复制别人的代码,切记,切记~
重要提示:D3的V3-V4-V5版本之间API变化很大,所以自己学习过程要查询最新的API文档,不要盲目复制别人的代码,切记,切记~
1 安装D3
-
通过npm的方式安装:
npm install d3 --save
,我现在的版本是5.9.2
然后引入d3import * as d3 from "d3";
-
直接引入
<script src="https://d3js.org/d3.v5.min.js"></script>
2 D3数据驱动的文档
d3的主页有个简短的文档,很有必要认真了解一下。
2.1简介
- D3允许您将任意数据绑定到文档对象模型(DOM),然后将数据驱动的转换应用于文档。例如,您可以使用D3从数组中生成HTML表。或者,使用相同的数据创建具有平滑过渡和交互的交互式SVG条形图。
D3不是一个单一的框架,旨在提供所有可能的功能。相反,D3解决了问题的关键:基于数据有效地处理文档。这避免了专有的表示,并提供了非凡的灵活性,暴露了HTML,SVG和CSS等Web标准的全部功能。D3的开销极小,支持大型数据集和交互动画的动态行为。D3的功能风格允许通过各种官方和社区开发的模块重用代码。
2.2选择器
2.3 动态属性
3 制作一个柱状图
从制作一个简单的柱状图开始来理解d3的运行方式。
3.1 绘制柱状图和值
- 定义两个参数,作为svg的宽和高
- 定义一个svg,通过select选择器选择body对象,然后添加一个svg子对象并设置宽高以及背景颜色
- 定义一个数组作为数据集
- 定义四个参数作为边距
- 定义两个参数作为柱状图的宽度和整体间隔(宽度加间隙)
- 在svg对象中通过select选择器选择rect,然后绑定数据,通过enter方式后,在添加rect。绘制矩形需要几个默认参数,x,y,width,height,这和canvas类似。这一步难点是数据的绑定。匿名函数中的d是数据集中的数据,i是索引号。这样就绘制了数据集中8个数据,数据的值就是矩形图的高度(暂时没有做映射处理)。
- 绘制文字,这个和上面的原理类似,就是注意一下定位问题就ok。
在这个最简单的柱状图中需要重点理解的地方有两个:
- 选择器,这是D3的基础之一,和css选择器类似。
- 数据绑定。这也是D3的核心之一。需要慢慢体会
const width = 400, height = 400;
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', width)
.style('background-color','lightblue')
var array = [100, 200, 300, 150, 160, 130,80,160]
var padding = { top: 20, right: 20, bottom: 30, left: 40 }
var rectStep = 40;
var rectWidth = 30
svg.selectAll('rect')
.data(array)
.enter()
.append('rect')
.attr('fill', 'steelblue')
.attr('x', function (d, i) {
return padding.left + i * rectStep;
})
.attr('y', function (d) {
return height - padding.bottom - d;
})
.attr('width', rectWidth)
.attr('height', function (d) {
return d
})
svg.selectAll('text')
.data(array)
.enter()
.append('text')
.attr('fill', "white")
.attr('font-size', '14px')
.attr('text-anchor', 'middle')
.attr('x', function (d, i) {
return padding.left + i * rectStep
})
.attr('y', function (d) {
return height - padding.bottom - d;
})
.attr('dx', rectWidth / 2)
.attr('dy', '1em')
.text(function (d) {
return d
})
3.2 绘制坐标轴
- d3.scaleLinear 创建定量线性比例尺,指定定义域domain和值域range。就是个线性函数。
- d3.axisBottom - 创建一个底部轴生成器,作为X轴,
- 创建一个组把x轴添加进去
Y轴创建的方式类似不在复述,这样一个最简单的坐标轴就绘制出来了。数据集是个一位数据,所以这个Y轴是有意义的,X轴就没有什么指定意义了。
var xlinear = d3.scaleLinear()
.domain([0, 10])
.range([0, 320])
var Xaxis = d3.axisBottom()
.scale(xlinear)
var g = svg.append('g')
.attr('transform',`translate(${padding.left},${height- padding.bottom})`)
Xaxis(g)
var ylinear = d3.scaleLinear()
.domain([d3.max(array), 0])
.range([0, d3.max(array)])
var gy = svg.append('g')
.attr('transform',`translate(${padding.left},${height- padding.bottom-d3.max(array)})`)
var yAxis = d3.axisLeft(ylinear);
yAxis(gy)
3.3 更新数据
更新一组数据,如右边的图所示。因为值没有做映射处理所以Y轴发生了变化。图形边的不合理了。
简单粗暴的方式就是在更新数据前过remove方法删除整个svg。然后绘制一个新的。这种方式操作简单可行性高啊。
var svg = d3.select('svg');
svg.remove();
第二种方式就是改变指定的矩形的高度。同时也要考虑Y轴的变化,以及数据数量的变化。多了添加柱状图,少了删除柱状图,数据太多了就要考虑改变宽度来摆放更多的数据。总之要做到灵活摆放,合理显示。
进入-更新-退出模式enter-update-exit
var data = [100, 159, 125, 135, 189, 145, 111, 136, 158, 20, 40, 198]
var colorScale = d3.scaleLinear()
.domain([0, 200])
.range(['#666666', '#4682b4'])
function render() {
var bars = d3.select('body').selectAll('div.h-bar')
.data(data)
bars.enter()
.append('div')
.attr('class', 'h-bar')
.merge(bars)
.style('width', function (d) {
return d + 'px';
})
.style('background-color', (d) => {
return colorScale(d)
})
.text(function (d) {
return d
})
bars.exit().remove()
}
render()
setInterval(() => {
data.shift()
data.push(Math.round(Math.random() * 180 + 20))
render()
}, 1500);
// css样式
.h-bar {
min-height: 15px;
min-width: 10px;
background-color: steelblue;
margin-bottom: 2px;
font-size: 11px;
color: #f0f8ff;
text-align: right;
padding-right: 2px;
}
关于Update、Enter、Exit可以看下这篇教程:https://wiki.jikexueyuan.com/project/d3wiki/enterexit.html讲解的很到位。
由于d3的链式操纵,我们可以把三个过程写在一起。
看下面代码,以一个矩形集为案例
- 先创建一个rect选择器
- 接入新的数据集
- 更新数据,就是Update操作,修改已有rect的属性,如果为空,则不会执行任何操作。
- enter部分,就是添加新的矩形,
- Exit部分,就是数据集中的数据比原来的rect数量还少,就删除多余的rect。
比如之前有3个rect,现在的数据集有6个。那么就会先更新3个,接着创建3个,然后直接退出。
比如之前有6个rect,现在的数据集有3个。那么就会先更新3个,接着不创建rect,最后删除原来多余的3个rect。
特么今天发现连在一起写有问题还是分开干吧。估计链式的问题。