当前位置: 首页 > Web前端 > HTML5

使用D3.js实现动态条形图(Barchartrace)

时间:2023-04-05 19:58:58 HTML5

公司项目需要动态条形图,即条形图竞赛。想在网上找个demo,发现没有合适的,就自己写了一个。完整代码可以在我的GitHub上查看。动态条形图非常适合比较多个数据集的趋势。该演示基于d3的v6版本。由于它只使用了一些最基本的API,所以它应该可以在以前的版本中运行。本文仅提供一个实现思路,并没有详细解释api和“进入、更新、退出”模式,所以在阅读之前对它们有一个基本的了解。首先,您需要找到合适的数据源。这里我刚从网上找了一个。初始化变量constwidth=1200,height=600,margin={top:20,bottom:0,left:50,right:80};constchartWidth=width-(margin.left+margin.right),chartHeight=height-(margin.top+margin.bottom);constdata=[];constcount=10;constduration=500;constbarPadding=20;constbarHeight=(chartHeight-(barPadding*count))/count;constgetDate=()=>dataOri[0][dateIndex];让dateIndex=1;让date=getDate();让dataSlice=[];让图表=null,scale=null,axis=null,svg=null,dateTitle=null;首先设置高度、宽度、边距和图表大小。data存储格式化后的数据。由于图表不可能显示数据源中的所有行,所以这里只取前10行。每10秒切换一列。列间距为20。每个条形的宽度是通过从图表高度中减去条形间距除以条形数再除以条形数获得的。定义一个函数获取当前列表头,这里是日期,赋值给date。定义dataSlice存储当前日期下的所有数据。最后定义chart存放图表实例,scale存放刻度,axis存放坐标轴,svg存放画布,dateTitle存放当前列表头。constcreateSvg=()=>svg=d3.select('#chart').append('svg').attr('width',width).attr('高度',高度);创建一个svg来设置宽度和高度,并附加到预先编写的容器中。格式化数据函数randomRgbColor(){constr=Math.floor(Math.random()*256);constg=Math.floor(Math.random()*256);constb=Math.floor(Math.random()*256);return`rgb(${r},${g},${b})`;}首先声明一个函数来创建随机颜色,用于给条形图着色。我们的数据源是这种形式,需要简单格式化成为一个itemconstformatData=()=>{dataOri[0].forEach((date,index)=>{if(index>0){dataOri.forEach((row,rowIndex)=>{if(rowIndex>0){data.push({name:row[0],value:Number(row[index]),lastValue:index>1?Number(row[index-1]):0,date:date,color:randomRgbColor()});}});}});}两层循环,第一层循环列,第二层循环行,保存输入行标题、数据和上一列的数据。如果不是,则写入0、列标题和随机颜色来为条形图着色。至于lastValue的使用,后面会详细说明。格式化数据如图:constsliceData=()=>dataSlice=data.filter(d=>d.date===date).sort((a,b)=>b.value-a.value).slice(0,计数);筛选出当天的数据,倒序取前10。创建坐标轴constcreateScale=()=>scale=d3.scaleLinear().domain([0,d3.max(dataSlice,d=>d.value)]).range([0,chartWidth]);首先把刻度创建好,定义范围从0到当天的最大值,取值范围从0到图表的宽度。对d3.jsAPI不熟悉的同学可以去官网辅导或者自行百度。常用的基本就那么几个。坐标轴的最终效果如下图所示:需要简单配置坐标轴constrenderAxis=()=>{createScale();axis=d3.axisTop().scale(scale).ticks(5).tickPadding(10).tickSize(0);svg.append('g').classed('axis',true).style('transform',`translate3d(${margin.left}px,${margin.top}px,0)`).call(axis);}调用之前定义的scale函数创建一个scale,然后设置顶轴,ticks设置5个scale(这个方法比较有意思,虽然设置了5,但不一定是5,可能大于5或者小于5),tickPadding设置刻度和值的距离,tickSize设置刻度线的长度,这里不显示。设置完成后,追加到图表上,水平移动让位给margin的位置。创建一条参考线这条垂直线就是参考线,它从坐标轴刻度延伸出来,贯穿整个图表。constrenderAxisLine=()=>{d3.selectAll('g.axisg.tick').select('line.grid-line').remove();d3.selectAll('g.axisg.tick').append('line').classed('grid-line',true).attr('stroke','black').attr('x1',0).attr('y1',0).attr('x2',0).attr('y2',chartHeight);}由于参考线随着数据的变化而不断变化,所以这个函数会被反复调用,所以需要在开始Wire时清除上一组数据的引用。然后用刻度线在坐标轴的每个位置附加一条线。x1和y1是直线相对于父元素的左端点,x2和y2是右端点。由于需要贯穿整个图表,所以右端点的y坐标设置为chartHeight。在列标题图表的右下按钮创建日期,即列标题constrenderDateTitle=()=>{dateTitle=svg.append('text').classed('date-title',true).text(日期).attr('x',chartWidth-margin.top).attr('y',chartHeight-margin.left).attr('fill','rgb(128,128,128)').attr('font-size',40).attr('text-anchor','end')}移动到右下角,设置颜色。这里重点介绍text-anchor,它主要作为svg中标签的一个属性来设置文本的对齐方式,设置为end表示文本字符串结束,即当前文本的初始位置。创建图表主体constcreateChart=()=>{chart=svg.append('g').classed('chart',true).style('transform',`translate3d(${margin.left}px,${margin.top}px,0)`);}创建一个容器来存储多个条并将它们移动到中心。constrenderChart=()=>{//进入、更新、退出模式}这个函数存放了“进入、更新、退出”模式的代码。由于这种模式可以扩展成一篇文章来单独讲解,所以不太了解的同学还是建议先自己了解一下。constbars=chart.selectAll('g.bar').data(dataSlice,(d)=>d.name);让barsEnter;barsEnter=bars.enter().append('g').classed('bar',true).style('transform',(d,i)=>`translate3d(0,${calTranslateY(i)}px,0)`);dateIndex>1&&barsEnter.transition().duration(this.duration).style('transform',(d,i)=>`translate3d(0,${calTranslateY(i,'end')}px,0)`);barsEnter.append('rect').style('width',d=>scale(d.value)).style('height',barHeight+'px').style('fill',d=>d.color);barsEnter.append('text').classed('label',true).text(d=>d.name).attr('x','-5').attr('y',barPadding).attr('font-size',14).style('text-anchor','end');barsEnter.append('text').classed('value',true).text(d=>d.value).attr('x',d=>scale(d.value)+10).attr('y',barPadding);给dataSlice绑定图形,barsEnter表示给data绑定图形,设置它的宽高和颜色,bar形状左边的y轴,这里是对应国家的名字,右边是数值标签。这里使用了一个效用函数:constcalTranslateY=(i,end)=>{if(dateIndex===1||end){return(barHeight+barPadding)*i+(barPadding/2);}else{return(barHeight+barPadding)*(count+1);}}当数据为第一列或传到end时,bar的纵轴位置在排序位置,否则放在图表外,等待进入。bars.transition().duration(duration).ease(d3.easeLinear).style('transform',function(d,i){return'translate3d(0,'+calTranslateY(i,'end')+'px,0)';}).select('rect').style('width',function(d){returnscale(d.value)+'px';});bars.select('text.value').transition().duration(duration).ease(d3.easeLinear).attr('x',function(d){returnscale(d.value)+10;}).tween('text',function(d){consttextDom=this;consti=d3.interpolateRound(d.lastValue,d.value);return(t)=>textDom.textContent=i(t);});更新模式,第一个方法chain的目的是对条形进行排序,并根据值设置宽度。第一个方法链是设置右边标注的value,自定义一个valuetransition,让value的增加不那么生硬。这里使用的是一开始格式化数据时设置的lastValue。bars.exit().transition().duration(duration).ease(d3.easeLinear).style('transform',function(d,i){return'translate3d(0,'+calTranslateY(i)+'px,0)';}).style('width',function(d){returnscale(d.value)+'px';}).remove();退出模式,退出后将横条移到屏幕外并删除。调用方法constinit=()=>{createSvg();//创建一个svgformatData();//格式化数据sliceData();//截取当天的数据renderAxis();//渲染轴renderAxisLine();//渲染指标线renderDateTitle();//渲染日期createChart();//创建图表renderChart();//渲染图表createTicker();//创建定时器}init();方法,最后一个createTicker方法没有声明functioncreateTicker(){constticker=d3.interval(()=>{if(dateIndex{createScale();axis.scale().domain([0,d3.max(dataSlice,d=>d.value)]);svg.select('g.axis')。transition().duration(duration).ease(d3.easeLinear).call(axis);d3.selectAll('g.axisg.ticktext').attr('font-size',14);}这个方法用于在数据变化时更新坐标轴。总结动态条形图的所有功能都开发好了,打开页面就可以看到动画效果。完整的代码可以从我的GitHub下载。其实这张图也算是d3的入门级效果了。掌握了“进入、更新、退出”的模式和转场后,就可以进行开发了。本文提供的思路并不是图表实现的最优方案。如果有更好的实现方法,欢迎留言讨论。