侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

canvas绘制树形结构可视图形的实现

技术  /  管理员 发布于 7年前   477

如下图,最近项目中需要这么个树形结构可视化数据图形,找了好多可视化插件,没有找到可用的,所以就自己画了一个,代码如下。

  • 树形分支是后端接口返回数据渲染,可展示多条;
  • 代码可拓展,可封装;
  • 点击节点可查看备注;

<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=tbreak;        }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树形结构内容请搜索以前的文章或继续浏览下面的相关文章,希望大家以后多多支持!


  • 上一条:
    用canvas显示验证码的实现
    下一条:
    详解canvas.toDataURL()报错的解决方案全都在这了
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    • 2025-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客