制作折线图分成4步走:
1.首先划定制图区域和范围——画一个边框
2.数据处理得到绘制折线图需要的数据——绘制折线需要一个数组整体
3.画出x,y轴和折线——y轴绘制需要注意
4.补充标签,调节位置
【1】第一步:框架绘制(规定范围)
<body>
<div style="height:700px ;
width: 1000px;
border: 2px solid grey;
margin-top: 20px;
margin-left: 200px;
position: relative;
float: left;
overflow: auto;">
<svg id="line" style="height:700px ;
width: 1000px;
position: relative;
float: left;">
</svg>
</div>
</body>
补充关于position的知识:
文档流:即元素在html中按照从左到右、从上到下的正常排序顺序
其中块级元素从上到下一行行排,内联元素按照从左到右行内排序
区分position中绝对定位和相对定位:
relative:
1.相对于文档流正常位置偏移,宽高不变化,撑大容器。
2.设置以后仍然处于文档流且不影响其他元素布局,其他元素相对于设置了relative的元素的正常位置布局
Absolute:
1.设置后其宽度若没有定义,会按照里面元素内容确定其宽度
2.其定位根据父元素定位,<div> <div></div> </div>最外面的就是父元素
如果父元素没有或者没有设置绝对定位或者相对定位的话,那么元素相对于html元素定位
如果父元素设置了绝对定位或者相对定位,那么元素根据最近的父元素定位
3. absolute脱离文档流,原来的位置是空的,下面的元素会来占据位置。
Tip:没有position,float设置top、left、bottom、right值都没有效
更详细的示例推荐:CSS position 相对定位和绝对定位 | 菜鸟教程 (runoob.com) }
盒子模型相关知识补充:
Tip:与position一起出现的问题:
可能容易混淆的关于x,y属性概念
以y属性为例,大多数时候,它体现了引用元素的矩形区域的左上角的y轴坐标
采用模块化编程:利用const line={myline:function(){ }}与main.js调用line.myline()
导入数据:利用d3.json导入数据或者d3.csv
插入g标签——g标签用于分组问题,专门用于svg标签下,没有x,y属性,移动使用transform,也无width、height、fill属性
【2】第二步:数据处理
绘制折线图需要的数据(联系下文将提到的path()):
数组——内部元素是字典或者数组或者别的对象等(如图所示)
data=[{'time': ,'number': },{'time': ,'number': }]
data=[[2,5],[6,10],[6,8]]
补充js中字典的知识
字典存储形式:dic={key1 :value1,key2:value2,…}
字典中的键是值在字典中的索引
//输出最初的字典元素
for(let k in dic){
Console.log(“key:”+key+”,value”+dic[key])};
}
//字典元素按照key排序
Var res=object.keys(dic).sort()-一个一个符号比较,非正统大小比较
//添加字典元素
dataset['set']=dic[i]
Object.keys()会返回一个由一个给定对象的自身可枚举属性组成的数组
建立字典数组,获取可处理数据:
dataset=[ ]
Dataset.push({‘time’:k,’number’:dic[k]})
另外补充下关于数据需要去重的方法,如何处理:
此处可以通过引入对象来处理,因为对象内部元素不可重复
Let data={} For(let i=0;i<d.length;i++){ Data[d[i][‘first name’]]=0 } Let x_names=object.keys(data)
【3】第三步:绘制x,y轴与折线
比例尺利用:
如果是线性关系,使用scaleLinear;如果是非连续关系,使用scaleBand,domain为x轴上显示的数值等,range使用连续域,Padding设置关于间隔,padding(1)是点,值必须在【0,1】范围内
建立坐标轴:使用AxisBottom( )或AxisLeft( )等,其中参数是配置的比例尺,
const x_scale=d3.scaleBand().domain(x).range([0,800]).padding(1)
//此处x是一个数组,x=['Jan-21',……]
const y_scale=d3.scaleLinear().domain([8000,24000]).range([500,0])
//此处需要特别关注y轴比例尺range的设置,因此处y轴为朝下,而非数学中朝上,所以如果range为([0,500]),旋转之后会导致从上到下,为由小到大,所以需要改为range([500,0])
const xAxis=d3.axisBottom(x_scale)
const yAxis=d3.axisLeft(y_scale)
g1.append('g')
.call(xAxis)
.attr('transform','translate(' + 50 + ',' + 600 + ')')
.attr('fill','black')
g1.append('g')
.call(yAxis)
.attr('transform','translate(' + 50 + ',' + 100 + ')')
//关注此处的y坐标,因为本身range=>500,所以仅用向下100即可
.attr('fill','black')
利用line和path进行折线的绘制:
line()用于制定数据引用和线段基本信息
Const line=d3.line().x(d=>xscale(d.time)+50).y(d=>yscale(d.number))
path用于绘制折线
g1.append('path')
.datum(avg)
.attr('d',line)
.attr('fill','none')
//这里需要注意其fill一定为none,不然会导致三角面片显示出来
.attr('stroke','blue')
我当时学习的时候对datum()这个函数有疑惑,因为这个函数绑定的为一个数值;而data()绑定的是一个数组,为什么不用data()?我请教了一下师兄,得到了理解的答案,有同样疑惑的同学可看下面解惑。
data()用于将数组中每个元素绑定到对应的物体上,从而生成对应数据和数量的物体,这些“一个个”物体之间是彼此分离的,
datum()用于将一个数据绑定到一个元素上,而此处我们画的折线是一条折线,虽然上面有很多个点,但是它是一个整体元素,而不是“一个个”元素。
因此我们使用datum(),将该数据绑定到元素上面之后,这个数组内部包含了一系列的点(x,y)来确定一整条线的位置,从而得到一条线
如果使用data(),应该得到许多条线,而且该参数应当是一个二维数组
【4】第四步:补充标注标签,调节位置
我画的这张图上面有两条线段,我使用颜色对它们区分,因此需要标注两条直线区分,这里再介绍另外一种画线的方法,也是最原始的一种画法
g1.append('line')
.attr('x1',50+500)
.attr('y1',80)
.attr('x2',50+510)
.attr('y2',80)
.attr('stroke','blue')
.attr('fill','none')
添加文本标签:
g1.append('text').attr('x',50+515).attr('y',80).text('每月平均用户数量')
g1.append('text').attr('x',50+515).attr('y',95).text('每月峰值用户数量')
附上我绘制的折线图及代码
const line={
myLine:function(){
d3.csv("../data/AllSteamData.csv").then((value)=>{
//console.log(value)——一般先确认调试一下
let g1=d3.select('#line').append('g').attr('transform','translate(' + 20 + ',' + 20 + ')')
//利用字典中按照key值自动排序的性质,处理得到x轴时间,排序顺序为按21年一月份到近30天
let x_times={}
for(let i=0;i<10;i++){
x_times[13-i]=value[i]['Month']
}
let x=[]
for(var k in x_times){
x.push(x_times[k])
}
//处理得到avg和peak字典数组,用于折线图两条折线绘制
let avg=[]
let peak=[]
for(let i=0;i<10;i++){
avg.push({'time':x[i],'number':value[9-i]['Avg. Players']})
peak.push({'time':x[i],'number':value[9-i]['Peak Players']})
}
//console.log(avg)
//console.log(peak)
//console.log(x)
//处理完成数据需要调试验证一下有无错误
//绘制比例尺和坐标轴
const x_scale=d3.scaleBand().domain(x).range([0,800]).padding(1)
const y_scale=d3.scaleLinear().domain([8000,24000]).range([500,0])
const xAxis=d3.axisBottom(x_scale)
const yAxis=d3.axisLeft(y_scale)
g1.append('g').call(xAxis).attr('transform','translate(' + 50 + ',' + 600 + ')').attr('fill','black')
g1.append('g').call(yAxis).attr('transform','translate(' + 50 + ',' + 100 + ')').attr('fill','black')
//绘制折线,制定折线关系函数
const line=d3.line().x((d)=>x_scale(d.time)+50).y((d)=>y_scale(d.number)+100)
//这里需要注意一下y轴的坐标,因为range对应([500,0])
//所以该点对应的图像的y轴增加仅需要和y轴坐标轴的增加一致即可
g1.append('path')
.datum(avg)
.attr('d',line)
.attr('fill','none')
//这里需要注意其fill一定为none,不然会导致三角面片显示出来
.attr('stroke','blue')
g1.append('path')
.datum(peak)
.attr('d',line)
.attr('fill','none')
.attr('stroke','green')
//绘制标签
g1.append('text').attr('x',250+50).attr('y',10).text('2021年Counter-Strike游戏用户数量变化图')
g1.append('text').attr('x',40).attr('y',80).text('数量')
g1.append('text').attr('x',50+850).attr('y',600).text('时间')
g1.append('line').attr('x1',50+500).attr('y1',80).attr('x2',50+510).attr('y2',80).attr('stroke','blue').attr('fill','none')
g1.append('line').attr('x1',50+500).attr('y1',95).attr('x2',50+510).attr('y2',95).attr('stroke','green').attr('fill','none')
g1.append('text').attr('x',50+515).attr('y',80).text('每月平均用户数量')
g1.append('text').attr('x',50+515).attr('y',95).text('每月峰值用户数量')
})
}
}