近期在做线路图实时刷新的功能,用到的技术主要有d3、svg、websocket。整体思路就是解析线路图json,使用d3动态生成svg矢量图;当出现报警信息时websocket主动向前端推送变化数据,前端监听到数据变化后进行局部刷新及动态展示。感觉d3比较像jq,可以操作dom,但是使用上又区别于jq。svg、d3也是从头开始学的,在这里对d3进行简单总结,有问题希望给予指正。
1、元素选择
select() -- 选择指定元素 例如:标签,也可以是某个具体的id等。但是只能获取到相匹配的第一个
selectAll() -- 选中所有元素 例如:所有p标签
const pageSvg = d3.select('#svgContent') //获取#svgContent
const body = d3.select(body) // 获取dom中的body
const p = body.selectAll('p') // 获取body中所有的p标签
2、数据绑定
data() -- 将一个数组绑定到选择集上
datum() -- 将单个数据绑定到选择集上
ps:请保证dom中body下有四个标签
const dataList = [1,2,3,4]
d3.select("body").selectAll("p").data(this.dataList).text((d,i) => {
return '第' + i + '个元素是' + d
})
const str = "112"
d3.select("body").selectAll("p").datum(str).text(function(d,i){
return '第' + i + '个元素是' + d
})
3、理解update、enter、exit
简单理解为 enter() -- 不够就补 exit -- 多了就删
借用一张大神的图大家就很容易理解
update、enter: data会在依次添加到选择集中,当data数据中个数大于选择集个数时,调用enter().append('p') 会自动补齐p标签
const p = d3.select("#svgContent").selectAll('p');
const update = p.data(this.dataList);
const enter = update.enter();
update.text(function(d,i){
return i + '--------------' + d
})
const pEnter = enter.append('p'); //可以根据数量自动添加p
pEnter.text(function(d,i){
return i + '-------------' + d
})
update、exit: data会在依次添加到选择集中,当出现选择集个数大于data数据个数时,自动删除多余的dom标签,相当于remove()
const p = d3.select("#svgContent").selectAll('p');
const update = p.data(this.dataList);
const exit = update.exit();
update.text(function(d,i){
return i + '--------------' + d
})
exit.text(function(d,i){
return i + '-------------' + d
})
4、插入、删除元素
在选择集之后插入元素 append()
d3.select("#svgContent").append('div').style('width','200px').style('height','200px').style('background','red')
在选择集之前插入元素 insert()
d3.select("#svgContent").insert('p','.test').text('ceshi ')
删除选中元素 remove()
d3.select("#svgContent").select('p','.test').remove()
5、比例尺
比例尺就是把输入域映射到输出域的函数。比如将0-10映射到0-100。 其中,domain为输入域,range为输出域,即将domain中的数据映射成range中的数据。
这里主要介绍下线性比例尺scaleLinear() 和 序数比例尺scaleOrdinal()
const datasets = [1.1,0.9,3,2.4,3.1]
const svg = d3.select('#svgContent') //获取画布
const g = svg.append('g') // 定义装图形的位置,并修改其位置
.attr('transform','translate('+ this.marge.top +','+ this.marge.left +')')
const min = d3.min(datasets);
const max = d3.max(datasets);
const scaleLinear = d3.scaleLinear().domain([min,max]).range([0,10]) //定义比例尺,将最小值和最大值映射成0-10
const rectHeight = 30;
g.selectAll('rect')
.data(this.dataset)
.enter()
.append('rect')
.attr('x',20)
.attr('y',function(d,i){
return i * rectHeight
})
.attr('width',function(d){
return scaleLinear(d) // 使用比例尺
})
.attr('height',rectHeight - 5)
.attr('fill','red')
const index = [0,1,2,3,4]
const color = ["red","blue","yellow","black","green"]
const scaleOrdinal = d3.scaleOrdinal()
.domain(index)
.range(color);
scaleOrdinal(0) // red
scaleOrdinal(1) // blue
6、svg+d3绘制柱状图
实现思路:
(1) 获取svg画布,并获取宽度和高度
(2) 定义一个组,用于绘制图形,并设置组的位置。设置组用于整体的放大、缩小、平移等操作
(3) 定义x坐标轴并绘制
// 定义x坐标轴
const xScale =
d3.scaleBand()
.domain(d3.range(this.dataset.length))
.rangeRound([0,width-this.marge.left-this.marge.right]);
const xAxis = d3.axisBottom(xScale)
g.append('g')
.attr('transform','translate('+0+','+ (height- top1 - bottom1)+')')
(4) 定义y坐标轴并绘制
// 定义y坐标轴
const yScale = d3.scaleLinear()
.domain([0,d3.max(this.dataset)])
.range([height-this.marge.top-this.marge.bottom,0]);
const yAxis = d3.axisLeft(yScale)
g.append('g').attr('transform','translate(0,0)').call(yAxis)
(5) 为每个矩形和文字设置分组
(6) 设置矩形间距
(7) 绘制矩形
(8) 绘制文本
以下案例在vue中写的
// 获取svg画布
const svg = d3.select('#svgContent')
// 获取svg的宽度和高度
const width = svg.attr('width')
const height = svg.attr('height')
// 定义图形展示区域,并设置位置
const g = svg.append('g')
.attr('transform','translate('+ this.marge.top +','+ this.marge.left +')')
// 定义x坐标轴
const xScale =
d3.scaleBand()
.domain(d3.range(this.dataset.length))
.rangeRound([0,width-this.marge.left-this.marge.right]);
//d3.scaleBand().domain(d3.range(this.dataset.length)).rangeRound([0,width-this.marge.left - this.marge.right])
const xAxis = d3.axisBottom(xScale)
// 定义y坐标轴
const yScale = d3.scaleLinear()
.domain([0,d3.max(this.dataset)])
.range([height-this.marge.top-this.marge.bottom,0]);
//d3.scaleLinear().domain([0,d3.max(this.dataset)]).range([height-this.marge.top - this.marge.bottom,0])
const yAxis = d3.axisLeft(yScale)
const top1 = this.marge.top
const bottom1 = this.marge.bottom
g.append('g')
.attr('transform','translate('+0+','+ (height- top1 - bottom1)+')')
.call(xAxis)
g.append('g').attr('transform','translate(0,0)').call(yAxis)
// 为每个矩形和文字设置分组
const gs = g.selectAll('.rect').data(this.dataset).enter().append('g')
//设置矩形间距
const rectPadding = 20;
//绘制矩形
gs.append('rect')
.attr('x',function(d, i){
return xScale(i) + rectPadding/2;
})
.attr('y',function(d,i){
return yScale(d)
})
.attr('width',function(){
return xScale.step() - rectPadding
})
.attr('height',function(d,i){
return height - top1- bottom1 - yScale(d)
})
.attr('fill','blue')
//绘制文字
gs.append('text')
.attr('x',function(d,i){
return xScale(i);
})
.attr('y',function(d,i){
return yScale(d) - 28
})
.attr('dx',function(){
return (xScale.step()-rectPadding)/2;
})
.attr('dy',function(d,i){
return 20
})
.text(function(d){
return d
})