canvas绘制树形结构可视图形的实现
程序员文章站
2022-05-06 21:12:22
这篇文章主要介绍了canvas绘制树形结构可视图形的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 20-04-03...
如下图,最近项目中需要这么个树形结构可视化数据图形,找了好多可视化插件,没有找到可用的,所以就自己画了一个,代码如下。
- 树形分支是后端接口返回数据渲染,可展示多条;
- 代码可拓展,可封装;
- 点击节点可查看备注;
<canvas id="canvas" width="750" height="800"></canvas>
const canvas_options={ canvaswidth: 750, canvasheight: 800, chartzone: [70,70,750,570], //坐标绘制区域 yaxislabel: ['0%','10%','20%','30%','40%','50%','60%','70%','80%','90%','100%'],//y轴坐标text yaxislabelwidth: 70,//y轴最大宽度 yaxislabelmax: 100,//y轴最大值 middleline: 410, //中间线 pillarwidth: 10,//柱子宽度 distancebetween: 50,//柱状图绘制区域距离两边间隙 pillar: [120,70,700,750],//柱状图绘制区域 maintrunkheight: 90,//底部开始主干高度 dialogwidth: 300,//弹窗宽度 dialoglineheight: 30,//弹窗高度 dialogdrawlinemax: 4, } const nodeclick = []; var choosenode = null; const datalist={ showdatainfo: { city:[ { name: '项目1', status: 1, //状态:0已完成 1进行中 node: [ { value: 10, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, { value: 20, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, ] }, { name: '项目2', status: 0, //状态:0已完成 1进行中 node: [ { value: 10, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, { value: 50, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, { value: 100, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, ] }, { name: '项目3', status: 1, //状态:0已完成 1进行中 node: [ { value: 20, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, { value: 30, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, { value: 40, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, ] }, { name: '项目4', status: 1, //状态:0已完成 1进行中 node: [ { value: 20, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, { value: 30, date: '2020-03-12 15:50:02', content: '用于组织信息和操作,通常也作为详细信息的入口。' }, ] }, ] } } const canvas = document.getelementbyid("canvas"); const ctx = canvas.getcontext('2d'); ctx.save(); drawylabel(canvas_options,ctx); //绘制y轴坐标 drawstartbutton(ctx,canvas_options); drawdata(ctx,datalist.showdatainfo,canvas_options); canvas.addeventlistener("click",event=>{ //清除之前的弹窗 if(choosenode!=null){ ctx.clearrect(0, 0, canvas.width, canvas.height); ctx.save(); drawylabel(canvas_options,ctx); //绘制y轴坐标 drawstartbutton(ctx,canvas_options); drawdata(ctx,datalist.showdatainfo,canvas_options); choosenode = null } //判断点击节点 let rect = canvas.getboundingclientrect(); let zoom = rect.width/canvas_options.canvaswidth; let x = (event.clientx/zoom - rect.left/zoom).tofixed(2); let y = (event.clienty/zoom - rect.top/zoom).tofixed(2); for(var t=0;t<nodeclick.length;t++){ ctx.beginpath(); ctx.arc(nodeclick[t].x,nodeclick[t].y,15,0,math.pi*2,true); if(ctx.ispointinpath(x, y)){ textprewrap(ctx,`备注描述:${nodeclick[t].date}`,nodeclick[t].x+20,nodeclick[t].y+20,canvas_options.dialogwidth-40); ctx.restore(); choosenode=t break; }else{ choosenode=null } } }); //content:需要绘制的文本内容; drawx:绘制文本的x坐标; drawy:绘制文本的y坐标; //linemaxwidth:每行文本的最大宽度 function textprewrap(ctx,content,drawx, drawy, linemaxwidth){ var drawtxt=''; //当前绘制的内容 var drawline = 1;//第几行开始绘制 var drawindex=0;//当前绘制内容的索引 //判断内容是够可以一行绘制完毕 if(ctx.measuretext(content).width<=linemaxwidth){ drawdialog(ctx,canvas_options.dialogwidth,canvas_options.dialoglineheight,drawx,drawy); ctx.filltext(content.substring(drawindex,i),drawx.drawy); }else{ for(var i=0;i<content.length;i++){ drawtxt += content[i]; if(ctx.measuretext(drawtxt).width>=linemaxwidth){ drawdialog(ctx,canvas_options.dialogwidth,canvas_options.dialoglineheight,drawx,drawy); ctx.filltext(content.substring(drawindex,i+1),drawx,drawy); drawindex = i+1; drawline+=1; //drawy+=lineheight; drawtxt=''; }else{ //内容绘制完毕,但是剩下的内容宽度不到linemaxwidth if(i===content.length-1){ drawdialog(ctx,canvas_options.dialogwidth,canvas_options.dialoglineheight,drawx,drawy); ctx.filltext(content.substring(drawindex,i+1),drawx,drawy) } } } } } function drawdialog(ctx,width,height,x,y){ ctx.beginpath(); ctx.fillstyle="rgba(0,0,0,0.8)"; ctx.fillrect(x,y,width,height); ctx.font="22px ''"; ctx.fillstyle="#fff"; ctx.textalign = 'left'; ctx.textbaseline="top"; } //绘制y轴坐标 function drawylabel(options,ctx){ let labels = options.yaxislabel; let ylength = (options.chartzone[3]-options.chartzone[1])*0.98; let gap = ylength/(labels.length-1); labels.foreach((item,index)=>{ //绘制圆角背景 //this.radiusbutton(ctx,0,options.chartzone[3]-index*gap-13,50,24,8,"#313947"); //绘制坐标文字 ctx.beginpath(); ctx.fillstyle="#878787"; ctx.font="18px ''"; ctx.textalign="center"; ctx.filltext(item,25,options.chartzone[3]-index*gap+5); //绘制辅助线 ctx.beginpath(); ctx.strokestyle="#eaeaea"; ctx.strokewidth=2; ctx.moveto(options.chartzone[0],options.chartzone[3]-index*gap); ctx.lineto(options.chartzone[2],options.chartzone[3]-index*gap); ctx.stroke(); }) } //绘制开始按钮 function drawstartbutton(ctx,options){ //绘制按钮图形 this.radiusbutton(ctx,options.middleline-(160/2),options.canvasheight-50,160,50,8,'#f4c63d'); ctx.fillstyle="#fff"; ctx.font="24px ''"; ctx.textalign="center"; ctx.filltext('开始',options.middleline,options.canvasheight-15); //绘制状态 ctx.beginpath(); ctx.fillstyle="#333"; ctx.font="24px ''"; ctx.textalign = "left"; ctx.filltext("已完成",0,options.canvasheight-100); ctx.filltext("进行中",0,options.canvasheight-50); //绘制红色按钮 ctx.beginpath(); ctx.fillstyle="#d35453"; ctx.arc(options.chartzone[0]+30,options.canvasheight-100-7,8,0,2 * math.pi,true); ctx.fill(); ctx.beginpath(); ctx.strokestyle="#d35453"; ctx.arc(options.chartzone[0]+30,options.canvasheight-100-7,14,0,2 * math.pi,true); ctx.stroke(); //绘制蓝色按钮 ctx.beginpath(); ctx.fillstyle="#24b99a"; ctx.arc(options.chartzone[0]+30,options.canvasheight-50-8,8,0,2 * math.pi,true); ctx.fill(); ctx.beginpath(); ctx.strokestyle="#24b99a"; ctx.arc(options.chartzone[0]+30,options.canvasheight-50-8,14,0,2 * math.pi,true); ctx.stroke(); } //封装绘制圆角矩形函数 function radiusbutton(ctx,x,y,width,height,radius,color_back){ ctx.beginpath(); ctx.fillstyle= color_back ctx.moveto(x,y+radius); ctx.lineto(x,y+height-radius); ctx.quadraticcurveto(x,y+height,x+radius,y+height); ctx.lineto(x+width-radius,y+height); ctx.quadraticcurveto(x+width,y+height,x+width,y+height-radius); ctx.lineto(x+width,y+radius); ctx.quadraticcurveto(x+width,y,x+width-radius,y); ctx.lineto(x+radius,y); ctx.quadraticcurveto(x,y,x,y+radius); ctx.fill() } //绘制数据 function drawdata(ctx,data,options){ //const paths=[]; let number = data.city.length; //绘制矩形 data.city.foreach((item,index)=>{ let indexval = number==1?1:index; let numberval = number==1?2:number-1 let x0 = options.chartzone[0]+options.distancebetween+(options.chartzone[2]-options.chartzone[0]-options.distancebetween*2)/numberval*indexval; let value = item.node[item.node.length-1].value; let height = (value/options.yaxislabelmax*(options.chartzone[3]-options.chartzone[0])*0.98).tofixed(2); let y0=options.chartzone[3] - height; //柱状图底部 ctx.beginpath(); ctx.fillstyle= '#eee'; ctx.fillrect(x0-5,80,options.pillarwidth,options.chartzone[3]-80); //贝塞尔曲线 ctx.beginpath(); ctx.strokestyle = item.status==0?"#d35453":'#24b99a'; ctx.linewidth=options.pillarwidth; ctx.moveto(options.middleline,options.pillar[3]); //贝塞尔曲线起始点 ctx.lineto(options.middleline,options.canvasheight-50-options.maintrunkheight); //贝塞尔曲线中间竖线 ctx.quadraticcurveto(x0,options.canvasheight-50-options.maintrunkheight,x0,options.chartzone[3]); //绘制柱状图进度 ctx.lineto(x0,y0); ctx.stroke(); //绘制文字 ctx.font="28px ''"; ctx.textalign='center'; ctx.fillstyle="#333"; ctx.filltext(item.name,x0,options.chartzone[1]-20); //绘制节点 item.node.foreach((node_item,node_index)=>{ let y1= options.chartzone[3] - (node_item.value/options.yaxislabelmax*(options.chartzone[3]-options.chartzone[0])*0.98).tofixed(2); ctx.beginpath(); ctx.arc(x0,y1,15,0,math.pi*2,true); ctx.fillstyle="rgba(108,212,148,1)"; ctx.fill(); ctx.beginpath(); ctx.arc(x0,y1,9,0,math.pi*2,true); ctx.fillstyle="rgba(255,255,255,0.8)"; ctx.fill(); const pointinfo={ x:x0, y:y1, date: node_item.data, content: node_item.content, value: node_item.value }; nodeclick.push(pointinfo); }) }) }
到此这篇关于canvas绘制树形结构可视图形的实现的文章就介绍到这了,更多相关canvas树形结构内容请搜索以前的文章或继续浏览下面的相关文章,希望大家以后多多支持!