-
目录
D3绘图笔记
-
-
安装与引用
-
-
npm install d3 --save-dev
import * as d3 from 'd3';
-
-
-
选择器
-
-
d3.select() 可直接接元素名,也可以接属性与类
-
-
-
添加svg标签
-
-
this.d3 = d3.select('.myd3')
let svg = this.d3.append("svg") // 添加svg并设置好高宽
.attr("width", 600)
.attr("height", 600)
注意:没有这个svg标签后续的操作都无法显示。g标签 rect标签都需要在svg标签里才有效。
-
-
-
添加绘图位置
-
-
let g = svg.append("g").attr("transform", "translate(" + 30 + "," +30 + ")") // 绘图区域
位置如下:上30与左30的位置。注意:这里的左边位置是以竖线为参考而不是坐标值。
-
比例尺
-
定义比例尺
-
let scaleX = d3.scaleBand()
.domain(d3.range(0,5))
.rangeRound([0,500]);
scaleBand为序数比例尺,也就是可以自己设置内容
而domain则表示精度,d3.range其实就是[0,1,2,3,4,5]
所以这块也可以手动添加数组进去[‘a’,’b’,’c’,’d’]
rangeRound则是比例尺显示的实际范围,单位为像素。也就是500像素的宽度用来均匀分布比例尺
-
-
-
插入比例尺
-
-
let axisX = d3.axisBottom(scaleX);
g.append('g').attr("transform", "translate(0, " + 300 + ")").call(axisX)
axisBottom 定义比例尺方向,插入比例尺的位置为坐标下方
最后再插入到绘图标签中。位置也需要用translate重新调整。
连续比例尺
定义比例尺
let scaleY = d3.scaleLinear()
.domain([0, d3.max(this.data)])
.range([100, 0]);
let axisY = d3.axisLeft(scaleY);
g.append('g').attr("transform", "translate(0,0)").call(axisY);
d3.max则是从数组中找到数值最大的数。
连续比例尺 会根据范围比如0与 100 而自动设置尺度
而这边的range第一个参数为100则表示从100像素位置往0像素位置绘制比例尺。如下图
注意:如果设置为0到100 则坐标会从0 20 60这样由上往下递增。
只有将这两边的值对应起来才刚好绘制出正常的效果来
自定义要显示的刻度以及刻度标签
有些只是展示其中部分刻度的,如0,0.3,0.5,1
可以设置如下参数.tickValues([0,0.3,0.5,1])
此时显示的刻度,如果想换成文字呢?比如0.5的时候显示 中年人,0的时候显示少儿,1的时候显示百岁老人。
通过如下代码
.tickFormat(i => {
let str = '少儿'
switch(i){
case 0:
str = '少儿'
break;
case 0.3:
str = '儿童'
break;
case 0.5:
str = '中年'
break;
}
return str
}) // 通过i一个个展示文本
调整刻度高度与标签位置
如这种特殊需求如何实现呢?
首先是刻度,刻度绘制结束自带的path路径会将 横线左右两边都带上竖线,所以我们得先去掉竖线,也就是设置竖线的高度为0即可。
.tickSizeInner(0) // 去掉自带刻度内高度
.tickSizeOuter(0) // 去掉自带刻度外高度
如下图
最后移动标签的位置与刻度的位置即可
let _g = axisG.append("g").call(_axisX)
_g.attr("transform", "translate(" + 0 + "," + position.top + ")"); // 移动刻度位置
_g.selectAll(".tick line") // 移动最左边刻度位置
.attr("y2", item => {
// console.log(item)
if (item === 0) {
return 6
} else {
return -6
}
})
_g.selectAll(".tick text") // 移动最左边刻度对应的文字
.attr("y", item => {
// console.log(item)
if (item === 0) {
return 16
} else {
return -10
}
})
}
比例尺位置竖向排列
在之前的方案是先进行旋转,旋转之后在获取比例尺中的g标签的高度,然后再进行一次重新覆盖旋转再将要移动的高度加上去,这里就需要绘制两次。这个方案不太行,以下方案可以解决坐标旋转的问题代码如下:
_axisG.selectAll("text").attr('text-anchor', 'end').attr('y', 6).attr('transform', (d, index) => {
if (isXRoate) {
return 'translate(-10,' + 5 + ') rotate(-90)'
} else {
return 'translate(0,0)'
}
})
以上代码只需两步即可搞定,首先是将文字让其末尾对齐,也就是text-anchor:end
然后再进行旋转即可。 这里的-10是旋转之后与坐标的距离存在的偏差值,比如你当前的字体是14px,可能要往左移动7px才能达到居中,这里的5则为与上边的高度距离。最终结果如下:
比例尺均匀分布ticks
代码如下:
let axisX = d3
.axisBottom(scaleX) // 创建比例尺
.ticks(4)
这里的ticks是将连续比例尺均匀分布多少分,比如传递4则表示分布为四分。
如下图
注意:这个函数是在创建比例尺的时候调用的也不是定义比例尺时调用。(不是在scalex里调用的)
注意:这个函数也只对连续比例尺才有效,其他的无效
-
-
绘制矩形图
-
源码
-
-
g.selectAll("rect")
.data([{
name:'a',
value:100
},{
name:'b',
value:20
},{
name:'c',
value:50
},{
name:'d',
value:80
},{
name:'e',
value:200
}])
.enter()
.append("rect")
.attr("x", function(d) { return scaleX(d.name); })
.attr("y", function(d) { return scaleY(Math.max(0, d.value)); })
.attr("width", scaleX.bandwidth()*0.8)
.attr("height", function(d) { return Math.abs(scaleY(d.value) - scaleY(0)); })
.attr("fill", function(d) { return d.value < 0 ? "#f44336" : "#4caf50"; });
g.selecltAll这里也要注意矩形图绘制的地方在哪里,这里是选择的是g元素。
这个selectAl(“rect”)相当于创建了一个正方形
data 这表示里面筛入的数据,数组类型格式都不重要,重要的是数据先塞进去,后续再处理。
enter 将数据挂载进去,也就是绑定进去。
append(‘rect’) 将正方形塞进去,也就是渲染dom节点。
attr("x", function(d) { return scaleX(d.name); }) 塞进去之后在调整x轴的位置,这里第二个参数则可以接函数来动态设置值,而scaleX(d.name)函数则表示从上面的x轴中找到该名称对应的位置。比如d.name为a 则会寻找上面的 d3.scaleBand() .domain([‘a’,’b’,’c’]) 这块a对应的位置。
.attr("y", function(d) { return scaleY(Math.max(0, d.value)); })同理y轴位置也是如此。
.attr("width", scaleX.bandwidth()*0.8) 矩形的宽度
.attr("height", function(d) { return Math.abs(scaleY(d.value) - scaleY(0)); }) 矩形的高度,这边的属性其实都支持动态函数变化。
.attr("fill", function(d) { return d.value < 0 ? "#f44336" : "#4caf50"; }); 则为填充色
绘制线条
x轴网格线
代码如下:
let lineXdata = d3.range(-3, 4, 1) // x轴参考线
g.selectAll('xLine')
.data(lineXdata)
.enter()
.append('line')
.attr('x1', (d) => {
return scaleX(d)
})
.attr('y1', scaleY(maxY))
.attr('x2', (d) => {
return scaleX(d)
})
.attr('y2', scaleY(minY))
.attr('stroke', '#CCC')
.attr('stroke-width', '1')
加上此属性可改为虚线绘制
.attr("stroke-dasharray", "5,5");
以上代码则是绘制x轴网格线
绘制椭圆
代码如下:
var ellipseGenerator = d3.line() // 创建一个椭圆生成器函数
.x(function (d) { return scaleX(d[0]); })
.y(function (d) { return scaleY(d[1]); })
.curve(d3.curveLinearClosed);
var ellipsePath = g.append("path")
.attr("d", ellipseGenerator(data.circle[key]))
.attr("fill", function (d) {
var color = d3.color(otherConfig[key].color);
return `rgba(${color.r}, ${color.g}, ${color.b}, 0.5)`;
})
.style("stroke", function (d) {
return otherConfig[key].color;
})
.style("stroke-opacity", 0.7);
先定义一个椭圆函数,然后筛入数据将数据用椭圆函数处理, 最后用path将处理后端数据连接起来。这里的data.circle[key]的数据结果为[[1,1],[2,2],[3,1]...]二位坐标数组
fill则为椭圆的填充色
stroke则为边框线的颜色
绘制圆
cellg.append("circle")
.attr("transform", (d) => {
return `translate(${-circleSize/2},${-circleSize/2})`; // x轴根据次数来移动过,y轴根据坐标来移动
})
.attr("cx", function (d) {
return circleSize;
})
.attr("cy", function (d) {
return circleSize - circleSize / 2;
})
.attr("r", function (d) {
return circleSize;
})
.attr("fill", function (d) {
return colors[d.groupName].color
});
绘制方块
cellg.append("rect") // 添加一个白色背景
.attr("width", width)
.attr("height", height)
.attr("fill", "#fff")
.attr("x", 0)
.attr("y", 0);
d3内置函数
获取最大值与最小值
let arr = [0,2,54,13,66]
d3.max(arr) // 66
d3.min(arr) // 0
创建范围值
d3.range
三个参数,第一个是起始位置,第二个是结束位置,第三个是步数代码如下
d3.range(5) // [0,1,2,3,4]
d3.range(5,10) // [5, 6, 7, 8, 9]
d3.range(5,10,2) // [5, 7, 9]
数据循环each函数
在以上示例中我们可以看到几乎所有的属性都支持设置函数定义
append(‘rect’).attr("x", function(d)
而如果我想实现不同的数据类型,绘制不同的形状,或者添加不同的线条等,如何实现呢?
这里我们就可以用到each函数了。代码如下:
const pointG = g
.selectAll(".point")
.data(data.point)
.enter()
.append("g")
.attr("transform", (d) => {
return `translate(${scaleX(d.x)}, ${scaleY(d.y)})`;
});
pointG.each(function(d) {
let shape;
if (d.type === "rect") {
shape = "rect";
d3.select(this).append(shape)
.attr("width", 20)
.attr("height", 20)
.style("fill", "steelblue");
} else if (d.type === "circle") {
shape = "circle";
d3.select(this).append(shape)
.attr("r", 10)
.style("fill", "steelblue");
} else if (d.type === "triangle") {
shape = "polygon";
const points = "0,-10 10,10 -10,10"; // 定义三角形的顶点坐标
d3.select(this).append(shape)
.attr("points", points)
.style("fill", "steelblue");
}
});
也就是数据塞进去之后,我们可以通过each函数来循环数据,
而不是自己手动将数据进行过滤,再判断形状,再重新筛入过滤之后的数据来重新绘制了。
颜色类型转换函数d3.color转rgb格式
如下图