咱们这期就绘制一个折线图,那么咱们分析下绘制一个折线图需要什么?
好上面就是咱们要一步一步把折线图绘制出来的步骤啦!!!!
先来看看效果呈现吧:
开始咱们上面列出来的任务
<!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算位置是不是好麻烦的啊?放心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
..... 等我在完善完善,搞定了发出来。
阅读量:578
点赞量:0
收藏量:0