前言
咱们这期就绘制一个折线图,那么咱们分析下绘制一个折线图需要什么?
- 坐标轴(一个竖着和一个横着的)
- 来点网格线美观一下
- 折线图少不了曲线平滑的
- 如果再来个绘制连线的轨迹就更好了
好上面就是咱们要一步一步把折线图绘制出来的步骤啦!!!!
先来看看效果呈现吧:
开始
开始咱们上面列出来的任务
第一步(绘制坐标轴)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const height = 500, width = 500, margin = 25;
//定义咱们的svg画布空间容器
let svg = d3.select('body')
.append('svg')
.attr('width',width)
.attr('height',height)
//绘制一个横着的坐标轴
function drawXAxis() {
//创建线性比例尺,使用坐标轴必备
const xScale = d3.scaleLinear().domain([0,10]).range([0, width - margin * 2]);
//创建底部的x的坐标轴
const xAxis = d3.axisBottom(xScale);
//使坐标轴插入svg中
svg.append('g').attr('class','x-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ height - margin })`
}).call(xAxis);
}
//绘制一个竖着的坐标轴
function drawYAxis() {
//创建线性比例尺,使用坐标轴必备
const yScale = d3.scaleLinear().domain([10, 0]).range([0, width - margin * 2]);
//创建底部的x的坐标轴
const yAxis = d3.axisLeft(yScale);
//使坐标轴插入svg中
svg.append('g').attr('class','y-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ margin })`
}).call(yAxis);
}
drawXAxis();
drawYAxis();
</script>
复制代码
效果呈现:
代码分析;其实就是俩个坐标轴,然后给平移到交错的位置也就是咱们的x轴,y轴。是不是模型出来了!!!! 咱们接着来
第二步(绘制网格线)
上一步dom呈现图:
大家线观察下咱们的坐标轴的dom结构,都是一个g容器包裹然后里面就是咱们坐标轴的小线和刻度尺的文字。 那咱们是不是把连线也放里面,就不用关心translate的问题了
代码部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const height = 500, width = 500, margin = 25;
//定义咱们的svg画布空间容器
let svg = d3.select('body')
.append('svg')
.attr('width',width)
.attr('height',height)
//绘制一个横着的坐标轴
function drawXAxis() {
//创建线性比例尺,使用坐标轴必备
const xScale = d3.scaleLinear().domain([0,10]).range([0, width - margin * 2]);
//创建底部的x的坐标轴
const xAxis = d3.axisBottom(xScale);
//使坐标轴插入svg中
svg.append('g').attr('class','x-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ height - margin })`
}).call(xAxis);
}
//绘制一个竖着的坐标轴
function drawYAxis() {
//创建线性比例尺,使用坐标轴必备
const yScale = d3.scaleLinear().domain([10,0]).range([0, width - margin * 2]);
//创建底部的x的坐标轴
const yAxis = d3.axisLeft(yScale);
//使坐标轴插入svg中
svg.append('g').attr('class','y-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ margin })`
}).call(yAxis);
}
function drawGrid() {
//绘制y轴的线
d3.selectAll('.y-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
//大家不必疑惑这个height - margin * 2 他其实就是咱们的长度啊
.attr('x2',(height - margin * 2))
.attr('y2',0)
.attr('stroke','#e4e4e4')
//绘制x轴的线
d3.selectAll('.x-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
.attr('x2',0)
.attr('y2',(- height + margin * 2))
.attr('stroke','#e4e4e4')
}
(async function draw() {
await drawXAxis();
await drawYAxis();
await drawGrid();
})();
</script>
复制代码
效果呈现:
总结:两条直线绘制成网格, 代码改动部分新增drawGrid
方法
第三步(绘制path线)
绘制折线图的的连线,其实就是一天path路径,有人该疑问这个path算位置是不是好麻烦的啊?放心d3js有相关api(
d3.line()
),帮你把一组[{x: 1, y: 1}, { x: 2, y:2 }]
给你转化成这个样的path路径M1,1 L2,2
代码绘制:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const height = 500, width = 500, margin = 25;
//定义咱们的svg画布空间容器
let svg = d3.select('body')
.append('svg')
.attr('width',width)
.attr('height',height);
//创建线性比例尺,使用坐标轴必备
const yScale = d3.scaleLinear().domain([10, 0]).range([0, width - margin * 2]);
const xScale = d3.scaleLinear().domain([0,10]).range([0, width - margin * 2]);
//绘制一个横着的坐标轴
function drawXAxis() {
//创建底部的x的坐标轴
const xAxis = d3.axisBottom(xScale);
//使坐标轴插入svg中
svg.append('g').attr('class','x-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ height - margin })`
}).call(xAxis);
}
//绘制一个竖着的坐标轴
function drawYAxis() {
//创建底部的x的坐标轴
const yAxis = d3.axisLeft(yScale);
//使坐标轴插入svg中
svg.append('g').attr('class','y-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ margin })`
}).call(yAxis);
}
function drawGrid() {
//绘制y轴的线
d3.selectAll('.y-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
//大家不必疑惑这个height - margin * 2 他其实就是咱们的长度啊
.attr('x2',(height - margin * 2))
.attr('y2',0)
.attr('stroke','#e4e4e4')
//绘制x轴的线
d3.selectAll('.x-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
.attr('x2',0)
.attr('y2',(- height + margin * 2))
.attr('stroke','#e4e4e4')
}
//数据定义, 两条线
const data = [
[
{x:0,y:6},
{x:1,y:5},
{x:2,y:3},
{x:3,y:5},
{x:4,y:5},
{x:6,y:4},
{x:7,y:3},
{x:8,y:3},
{x:9,y:2},
{x:10,y:10},
],
d3.range(10).map(function(i){
return {x:i,y:Math.min(i)}
})
]
function drawLine() {
//d3.line是把数组的坐标生成一个path路径
let line = d3.line()
.x(function(d){
//这个d就是咱们的data[0] 遍历的数据了 return也就是坐标 相当于帮咱们生成了一个 M0,0 L 1,2.....这个样
return xScale(d.x)
})
.y(function(d){
return yScale(d.y)
})
//添加path
svg.selectAll('path.path')
.data(data)
.enter()
.append('path')
.attr('class','path')
.attr('d',function(d){
return line(d)
})
.attr('stroke', '#2e6be6')
.attr('fill', 'none')
.attr('transform',`translate(${margin}, ${margin})`)
}
(async function draw() {
await drawXAxis();
await drawYAxis();
await drawGrid();
await drawLine();
})();
</script>
复制代码
效果呈现:
总结:利用
d3.line()
帮咱们生成path的路径,新增函数drawLine()
是不是感觉少了点什么?对给人感觉节点不太明显,如果要是曲线平滑的话就更好了?
折现图呈现效果优化篇
节点不太明显? 曲线平滑? 分析:
节点不明显?咱们对比chart图表发现他们在每个点位置有个圆
曲线平滑? 咱们d3js
中有对应的api绘制曲线(.curve(d3.curveCardinal)
)
代码呈现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const height = 500, width = 500, margin = 25;
//定义咱们的svg画布空间容器
let svg = d3.select('body')
.append('svg')
.attr('width',width)
.attr('height',height);
//创建线性比例尺,使用坐标轴必备
const yScale = d3.scaleLinear().domain([10, 0]).range([0, width - margin * 2]);
const xScale = d3.scaleLinear().domain([0,10]).range([0, width - margin * 2]);
//绘制一个横着的坐标轴
function drawXAxis() {
//创建底部的x的坐标轴
const xAxis = d3.axisBottom(xScale);
//使坐标轴插入svg中
svg.append('g').attr('class','x-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ height - margin })`
}).call(xAxis);
}
//绘制一个竖着的坐标轴
function drawYAxis() {
//创建底部的x的坐标轴
const yAxis = d3.axisLeft(yScale);
//使坐标轴插入svg中
svg.append('g').attr('class','y-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ margin })`
}).call(yAxis);
}
function drawGrid() {
//绘制y轴的线
d3.selectAll('.y-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
//大家不必疑惑这个height - margin * 2 他其实就是咱们的长度啊
.attr('x2',(height - margin * 2))
.attr('y2',0)
.attr('stroke','#e4e4e4')
//绘制x轴的线
d3.selectAll('.x-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
.attr('x2',0)
.attr('y2',(- height + margin * 2))
.attr('stroke','#e4e4e4')
}
//数据定义, 两条线
const data = [
[
{x:0,y:6},
{x:1,y:5},
{x:2,y:3},
{x:3,y:5},
{x:4,y:5},
{x:6,y:4},
{x:7,y:3},
{x:8,y:3},
{x:9,y:2},
{x:10,y:10},
],
d3.range(10).map(function(i){
return {x:i,y:Math.min(i)}
})
]
function drawLine() {
//d3.line是把数组的坐标生成一个path路径
let line = d3.line()
.x(function(d){
//这个d就是咱们的data[0] 遍历的数据了 return也就是坐标 相当于帮咱们生成了一个 M0,0 L 1,2.....这个样
return xScale(d.x)
})
.y(function(d){
return yScale(d.y)
})
.curve(d3.curveCardinal) //曲线效果
svg.selectAll('path.path')
.data(data)
.enter()
.append('path')
.attr('class','path')
.attr('d',function(d){
return line(d)
})
.attr('stroke', '#2e6be6')
.attr('fill', 'none')
.attr('transform',`translate(${margin}, ${margin})`)
}
function drawCircle() {
data.forEach(item => {
svg.append('g')
.selectAll('.circle')
.data(item)
.attr('class','circle')
.enter()
.append('circle')
.attr('cx',function(d){return xScale(d.x)})
.attr('cy', function(d){return yScale(d.y)})
.attr('r',4)
.attr('transform', `translate(${margin}, ${margin})`)
.attr('fill','#fff')
.attr('stroke','rgba(56, 8, 228, .5)')
});
}
(async function draw() {
await drawXAxis();
await drawYAxis();
await drawGrid();
await drawLine();
await drawCircle();
})();
</script>
复制代码
效果呈现(是不是好看多了):
总结: 新增drawCircle()
绘制圆点, 使用d3.line().curve()绘制曲线
第四步(让连线动起来)
怎么让连线动画动起来呢?
连线path动画的话使用svg的
stroke-dashoffset
和stroke-dasharray
线上的圆点的话就每个点delay
分批指定就好了
stroke-dasharray
: 用于绘制虚线
stroke-dasharray
:虚线的偏移量
那么问题来了?用虚线能绘制实线吗?当然可以
就是利用这个虚线偏移做的动画。
在《张鑫旭》大佬的博客里面发现了一段通俗易懂的解释:
用中文解释就是,一根火腿肠12厘米,要在上面画虚线,虚线间隔有15厘米,如果没有dashoffset,则火腿肠前面15厘米会被辣椒酱覆盖!实际上只有12厘米,因此,我们看到的是整个火腿肠都有辣椒酱。现在,dashoffset也是15厘米,也就是虚线要往后偏移15厘米,结果,辣椒酱要抹在火腿肠之外,也就是火腿肠上什么辣椒酱也没有。如果换成上面的直线SVG,也就是直线看不见了。我们把dashoffset值逐渐变小,则会发现,火腿肠上的辣椒酱一点一点出现了,好像辣椒酱从火腿肠根部涂抹上去一样。
呈现效果:
代码实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
const height = 500, width = 500, margin = 25;
//定义咱们的svg画布空间容器
let svg = d3.select('body')
.append('svg')
.attr('width',width)
.attr('height',height);
//创建线性比例尺,使用坐标轴必备
const yScale = d3.scaleLinear().domain([10, 0]).range([0, width - margin * 2]);
const xScale = d3.scaleLinear().domain([0,10]).range([0, width - margin * 2]);
//绘制一个横着的坐标轴
function drawXAxis() {
//创建底部的x的坐标轴
const xAxis = d3.axisBottom(xScale);
//使坐标轴插入svg中
svg.append('g').attr('class','x-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ height - margin })`
}).call(xAxis);
}
//绘制一个竖着的坐标轴
function drawYAxis() {
//创建底部的x的坐标轴
const yAxis = d3.axisLeft(yScale);
//使坐标轴插入svg中
svg.append('g').attr('class','y-axis').attr('transform',function(){
//让平移到底部x对的位置,咱们还要绘制y轴呢
return `translate(${margin}, ${ margin })`
}).call(yAxis);
}
function drawGrid() {
//绘制y轴的线
d3.selectAll('.y-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
//大家不必疑惑这个height - margin * 2 他其实就是咱们的长度啊
.attr('x2',(height - margin * 2))
.attr('y2',0)
.attr('stroke','#e4e4e4')
//绘制x轴的线
d3.selectAll('.x-axis .tick')
.append('line')
.attr('x1',0)
.attr('y1',0)
.attr('x2',0)
.attr('y2',(- height + margin * 2))
.attr('stroke','#e4e4e4')
}
//数据定义, 两条线
const data = [
[
{x:0,y:6},
{x:1,y:5},
{x:2,y:3},
{x:3,y:5},
{x:4,y:5},
{x:6,y:4},
{x:7,y:3},
{x:8,y:3},
{x:9,y:2},
{x:10,y:10},
],
d3.range(10).map(function(i){
return {x:i,y:Math.min(i)}
})
]
function drawLine() {
//d3.line是把数组的坐标生成一个path路径
let line = d3.line()
.x(function(d){
//这个d就是咱们的data[0] 遍历的数据了 return也就是坐标 相当于帮咱们生成了一个 M0,0 L 1,2.....这个样
return xScale(d.x)
})
.y(function(d){
return yScale(d.y)
})
.curve(d3.curveCardinal) //曲线效果
svg.selectAll('path.path')
.data(data)
.enter()
.append('path')
.attr('class','path')
.attr('d',function(d){
return line(d)
})
.attr('stroke', '#2e6be6')
.attr('fill', 'none')
.attr('transform',`translate(${margin}, ${margin})`)
}
function drawCircle() {
data.forEach(item => {
svg.append('g')
.selectAll('.circle')
.data(item)
.enter()
.append('circle')
.attr('class','circle')
.attr('cx',function(d){return xScale(d.x)})
.attr('cy', function(d){return yScale(d.y)})
.attr('r',4)
.attr('transform', `translate(${margin}, ${margin})`)
.attr('fill','#fff')
.attr('stroke','rgba(56, 8, 228, .5)')
.style('stroke-width',0);
});
}
function drawAnimations() {
//连线动画
svg.selectAll('path.path')
.attr('stroke', '#2e6be6')
.attr('transform','translate(25,25)')
.style('stroke-dasharray',function(){
return d3.select(this).node().getTotalLength()
})
.style('stroke-dashoffset',function(){
return d3.select(this).node().getTotalLength()
})
.transition()
.duration(2000)
.delay(200)
.ease(d3.easeLinear)
.style('stroke-dashoffset',0);
//圆点
svg.selectAll('.circle')
.style('stroke-width',0)
.transition()
.duration(1000)
.delay(function(d,i){
return i * 100
})
.ease(d3.easeLinear)
.style('stroke-width',1)
}
(async function draw() {
await drawXAxis();
await drawYAxis();
await drawGrid();
await drawLine();
await drawCircle();
await drawAnimations();
})();
</script>
复制代码
效果图:
总结
基础折线图是不是已经画出来了?嘿嘿当然还有好多功能没有实现呢tooltip
、legend
..... 等我在完善完善,搞定了发出来。
结束语
- 大家好 我是三原,多谢您的观看,我会更加努力(๑•̀ㅂ•́)و✧多多总结。
- 每个方法都是敲出来验证过通过的,有需要可以放心复制。
- 如果您给帮点个赞👍就更好了 谢谢您~~~~~
- 期待您的关注