D3的安装
npm安装:npm install d3
yarn安装:yarn add d3
简单了解相关SVG知识
什么是SVG
SVG是一种XML应用,用来表示矢量图形。所有的图形有关信息被存储为纯文本,具有XML的开放性、可移植性和可交互性。
坐标定位
以页面的左上角为(0,0)坐标点,坐标以像素为单位,x轴正方向是向右,y轴正方向是向下。
矩形的表示以及特性
rect元素,使用x,y,width,height表示一个矩形。
fill属性——填充矩形的颜色,默认为黑色
<svg>
<rect x=0 y=0 width=50 height=200 fill="red"></rect>
</svg>
效果展示
线段的表示以及特性
line元素,使用x1,y1,x2,y2属性指定线段的起止点坐标。默认为0 stroke属性——笔画颜色 stroke-width属性——笔画的宽度
<svg>
<line y2=100 stroke="red"></line>
</svg>
效果展示
简单了解相关D3知识
选择集
- d3.select(selector)
选中符合条件的第一个元素,选择条件为 selector 字符串。如果没有元素被选中则返回空选择集。 - d3.selectAll(selector)
选择所有与指定的 selector 匹配的元素。被选中的元素顺序会按其在文档中的顺序排列(从上到下)。如果没有元素被选中,或者 selector 为 null 或 undefined 则返回空选择集。
d3.selectAll("p") //选中所有p元素
d3.select("p") //选中第一个p元素
d3.select("#div").selectAll("p") //选中第一个id为div下的所有p元素
d3.select("#div").select("p") //选中第一个id为div下的第一个p元素
样式、属性和文本的添加
D3里面是支持脸上调用的,和JQuery一样
//给所有的的p标签添加一个active类名,添加文本,改变字体颜色
d3.selectAll("p")
.attr("class", "active")
.text("我是p标签")
.style("color", "green")
数据绑定
- data()
绑定一个数组到选择集上,也就是一一对应的关系
//绑定数据之后,文本,属性,样式就可以通过函数的方式使用数组的值
<body>
<p>dog</p>
<p>cat</p>
<p>pig</p>
<script>
var dataset = ["deer","bitch","tigger"];
var p = d3.selectAll("p")
.data(dataset)
.text((d,i) => {//i是数组的下标,d是数组的值
return "第"+i+"个动物是"+d;
});
</script>
</body>
运行结果:
第0个动物是deer
第1个动物是bitch
第2个动物是tigger
敏锐的你学或许已经发现问题
enter、exit和update的理解
在使用data时,数组和选择集是一一对应的,那么问题来了,数据个数和选择集不匹配怎么办呢?
情形一:数组[1, 2, 3, 4, 5]绑定到三个p标签上。
不难想象,此时数组的最后两个元素没有可以与之绑定的元素,此时D3会建立两个空的元素与数组最后的两个数据相对于,那么这部分就称为Enter。
情形一:数组[1, 2]绑定到三个p标签上。
显而易见,最后一个p标签没有可以绑定的数据,此时没有数据绑定的部分就称为Exit
情形三:数组[1, 2, 3]绑定到三个p标签上。
此时数组和选择集是一一对应的,元素与数据对应的部分就称为Update
元素的添加
- append()在选择集尾部插入元素
//在body元素内部的最后位置添加一个新的p标签
<body>
<p>dog</p>
<p>cat</p>
<p>pig</p>
<script>
var dataset = ["deer","bitch","tigger","duck"];
var p = d3.select("body")
.append("p")
.text("another animal")
.style("color","red");
</script>
</body>
运行结果:
比例尺
比例尺在D3.js中是一个很重要的东西,我们可以这样理解d3.js中的比例尺——一种映射关系,从domain映射到range域。
- scaleLinear 线性比例尺
使用d3.scaleLinear()创造一个线性比例尺,而domain()是输入域,range()是输出域,相当于将domain中的数据集映射到range的数据集中。
var scaleLinear = d3.scaleLinear().domain([0,10]).range([0,300]);
console.log(scaleLinear(0))//0
console.log(scaleLinear(5))//150
console.log(scaleLinear(10))//300
- scaleBand 序数比例尺
d3.scaleBand()并不是一个连续性的比例尺,domain()中使用一个数组,不过range()需要是一个连续域。
//domain数组里的元素可以是字符串等
let scale = d3.scaleBand().domain([1,2,3,4]).range([0,100])
console.log(scale(1))//0
console.log(scale(2))//25
//当输入不是domain()中的数据集时返回undefined
console.log(scale(5))//undefined
了解一些基础之后,那就开始我们的画图之旅吧
如何绘制
准备工作
- CSS样式
<style lang="scss" scoped>
.toolTip {
position: absolute;
height: 56px;
width: 110px;
border: 0.5px solid #cccccc;
border-radius: 5px;
padding-left: 10px;
display: none;
font-size: 14px;
flex-flow: column;
justify-content: space-around;
background: #fff;
.icon {
display: inline-block;
height: 8px;
width: 8px;
border-radius: 50%;
margin-right: 5px;
}
}
</style>
- HTML结构
<template>
<div>
<h3>国外疫情TOP10——百度</h3>
<!-- tooltip -->
<div class="toolTip">
<div class="country">美国</div>
<div class="incre">
<span class="icon"></span>新增:<span class="amount">18951</span>
</div>
</div>
<svg width="1000" height="600"></svg>
</div>
</template>
矩形的绘制
- 定义一些常量来记录一些状态,方便修改。
- 由amount的大小通过线性比例尺来决定矩形的高度。
- 调整整个图形的位置
<script>
import * as d3 from "d3";
export default {
data() {
return {
epidemicData: [
{ name: "美国", amount: 20685 },
{ name: "俄罗斯", amount: 10899 },
{ name: "巴西", amount: 8446 },
{ name: "印度", amount: 3475 },
{ name: "英国", amount: 3403 },
{ name: "秘鲁", amount: 3237 },
{ name: "墨西哥", amount: 1997 },
{ name: "土耳其", amount: 1704 },
{ name: "巴基斯坦", amount: 1662 },
{ name: "智利", amount: 1658 }
]
};
},
mounted() {
const rectWidth = 20; //定义矩形的宽度
const yLength = 420; //柱状图y轴的长度
//定义矩形的高度
const rectHeight = d3
.scaleLinear()
.domain([0, this.maxHeight() + 5000])
.range([0, yLength]);
const interval = 20; //定义矩形间的间隔
//柱状图x轴的长度
const xLength = (rectWidth + interval) * this.epidemicData.length;
const baseLine = 450; //定义一个基线的位置,调整柱状图的位置
let svg = d3.select("svg");
//g——分组,可以简单的将他视为一个容器的作用
let g = svg.append("g");
let rect = g
.attr("transform", "translate(" + 100 + ", " + 0 + ")")
.selectAll("rect")
.data(this.epidemicData)
.enter()
.append("rect")
.attr("x", (d, i) => i * (rectWidth + interval))
.attr("y", d => baseLine - rectHeight(d.amount))
.attr("width", rectWidth)
.attr("height", d => rectHeight(d.amount))
.attr("fill", d => {
return this.retColor(d.amount);
});
},
methods: {
//返回最大值
maxHeight() {
return this.epidemicData.reduce((total, curVal) => {
return total > curVal.amount ? total : curVal.amount;
}, 0);
},
//根据数值范围返回颜色字符串
retColor(amount) {
if (amount < 6000) {
return "#f6b46c";
} else if (amount < 12000) {
return "#ea774d";
} else {
return "#d92121";
}
}
}
};
</script>
效果展示:
还是好看吧添加X轴
- 先生成普通的x轴
- 隐藏刻度
- 调整刻度文本的样式
- 调整x轴的位置
mounted(){
//画出x轴
const xTick = this.epidemicData.length; //x轴的刻度数目
//为坐标轴定义一个序数比例尺
let xScale = d3
.scaleBand()
.domain(this.address())
.range([0, xLength]);
//定义一个朝下的坐标轴
let xAxis = d3
.axisBottom(xScale)
.ticks(xTick)
.tickPadding(30); //设置刻度和刻度文本之间的间距
let gx = g
.append("g")
.call(xAxis)
.attr("transform", "translate(" + -45 + ", " + baseLine + ")");
//更改x轴的文本的样式
gx.selectAll("text")
.style("color", "#bebebe")
.attr("transform", "rotate(" + -45 + ")");
//将x轴的刻度隐藏起来
gx.selectAll("line").attr("stroke", "");
gx.selectAll("path").attr("stroke", "");
},
methods:{
//将epidemicData中的国家单独抽成一个数组
address() {
return this.epidemicData.reduce((total, curVal) => {
total.push(curVal.name);
return total;
}, []);
}
}
添加y轴
- 先生成普通的y轴
- 调节刻度
- 调整y轴的位置
mounted(){
const yTick = 6; //y轴刻度数目
//画出y轴
let gy = g.append("g");
let yScale = d3
.scaleLinear()
.domain([0, this.maxHeight() + 5000])
.range([yLength, 0]);
let yAxis = d3.axisLeft(yScale).ticks(yTick);
gy.call(yAxis).attr(
"transform",
"translate(" + -10 + ", " + (baseLine - yLength) + ")"
);
//更改y轴的刻度的样式
gy.selectAll("line")
.attr("stroke", "#CCC")
.attr("x2", xLength)
.attr("stroke-width", 0.5);
gy.selectAll("path").attr("stroke", "");
gy.selectAll("text").style("color", "#bebebe");
}
此时你画出的图形,y轴刻度应该遮挡住了我们的柱状图,因为d3没有向z-index这种直接调节层级的方法, 所以我们只能将y轴分组的添加提到最前面去。即将let gy = g.append("g");提到前面
添加了x轴和y轴以后的样子
添加动画
- 添加鼠标移动到柱状图上颜色改变,鼠标移除颜色还原的动画
- 添加tooltip动画
- 节流
mounted(){
const _this = this; //将Vue中的this用_this保存下来
rect
.on("mousemove", _this.throttle(_this.moveEvent, 200))
.on("mouseover", function() {
d3.select(this)
.transition()
.duration(0)
.attr("fill", "#f9d774");
})
.on("mouseout", function(d) {
d3.select(".toolTip").style("display", "none");
d3.select(this)
.transition()
.attr("fill", _this.retColor(d.amount));
});
},
methods:{
//节流
throttle(fn, duration) {
let lastTime = 0;
return function(params) {
let now = new Date().getTime();
if (now - lastTime > duration) {
fn(params);
lastTime = new Date().getTime();
}
};
},
//在矩形上移动产生的变化
moveEvent(d) {
let mouse = d3.event;
let yPosition = mouse.pageY + 20;
let xPosition = mouse.pageX + 20;
let toolTip = d3
.select(".toolTip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.style("display", "flex");
d3.select(".country").text(d.name);
d3.select(".amount").text(d.amount);
d3.select(".icon").style("background", "red");
}
}
最后
若有不足之处还请指教,如有疑惑可留下评论一起探讨。
码字不易,喜欢的伙伴点个赞支持一下啦❤