亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > HTML5 > 正文

canvas繪制樹形結構可視圖形的實現

2024-09-05 07:23:21
字體:
來源:轉載
供稿:網友

如下圖,最近項目中需要這么個樹形結構可視化數據圖形,找了好多可視化插件,沒有找到可用的,所以就自己畫了一個,代碼如下。

  • 樹形分支是后端接口返回數據渲染,可展示多條;
  • 代碼可拓展,可封裝;
  • 點擊節點可查看備注;

<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樹形結構內容請搜索武林網以前的文章或繼續瀏覽下面的相關文章,希望大家以后多多支持武林網!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久久久久69| 欧美性生活大片免费观看网址| 欧美日韩国产限制| 日韩精品一区二区三区第95| 国产一区二区三区在线观看视频| 91久久夜色精品国产网站| 国产精品福利无圣光在线一区| 一区二区亚洲欧洲国产日韩| 黄色一区二区在线观看| 亚洲最大的av网站| 国产综合在线观看视频| 亚洲国产精品成人av| 欧美国产第二页| 欧美一级片在线播放| 亚洲欧美制服另类日韩| 国产亚洲视频在线观看| www.色综合| 国产精品久久久久久久久久| 亚洲欧美综合另类中字| 亚洲缚视频在线观看| 在线免费看av不卡| 日韩在线激情视频| 色无极影院亚洲| 久久久久久久久久国产| 68精品国产免费久久久久久婷婷| 九色91av视频| 欧美午夜丰满在线18影院| 国产午夜精品理论片a级探花| 日日狠狠久久偷偷四色综合免费| 国产精品久久网| 久久激情五月丁香伊人| 国产精品丝袜一区二区三区| 色综合色综合久久综合频道88| 国产日韩欧美夫妻视频在线观看| 国产精品美女免费视频| 亚洲精品永久免费精品| 欧美电影免费观看网站| 国产精品wwwwww| 国产美女高潮久久白浆| 精品久久久久久亚洲精品| 91久久精品久久国产性色也91| 亚洲精品国产精品自产a区红杏吧| 久久精品一本久久99精品| 国产精品久久久久久五月尺| 欧美在线观看视频| 成人日韩在线电影| 精品中文字幕乱| 欧美精品生活片| 日韩成人av在线播放| 中文字幕不卡在线视频极品| 国产精品成人一区| 亚洲摸下面视频| 欧美xxxx14xxxxx性爽| 中文字幕在线看视频国产欧美| 亚洲一区二区三区四区视频| 欧美极品少妇xxxxⅹ免费视频| 黑人巨大精品欧美一区二区一视频| 欧美日韩一区二区三区| 久久精品人人爽| 欧美一级免费看| 欧美国产欧美亚洲国产日韩mv天天看完整| 国产精品成人va在线观看| 国产精品嫩草影院久久久| 亚洲午夜精品视频| 国产成人精品久久二区二区91| 粉嫩老牛aⅴ一区二区三区| 亚洲开心激情网| 2019中文在线观看| 久久久久国产精品一区| 51精品国产黑色丝袜高跟鞋| 精品在线观看国产| 国产成人精品视频在线| 国产自产女人91一区在线观看| 色婷婷综合成人| 少妇久久久久久| 国产精品丝袜高跟| 国产精品成人在线| 久久伊人91精品综合网站| 国产aⅴ夜夜欢一区二区三区| 97精品一区二区三区| 欧美亚洲激情在线| 在线播放日韩av| 欧美怡红院视频一区二区三区| 91免费看国产| 国产精品一区二区久久国产| 国产精品夜间视频香蕉| 欧美最猛性xxxxx(亚洲精品)| 国产精品一区二区av影院萌芽| 日韩av网站电影| 欧美色图在线视频| 丰满岳妇乱一区二区三区| 国产精品美女主播在线观看纯欲| 中文字幕日韩专区| 91精品国产综合久久香蕉的用户体验| 国产成人avxxxxx在线看| 日韩精品福利在线| 97精品伊人久久久大香线蕉| 日韩精品亚洲元码| 久久夜色精品国产欧美乱| 午夜精品久久久久久久99热浪潮| 最近2019年手机中文字幕| 国产一区二区三区视频| 国产福利精品av综合导导航| 国产精品狼人色视频一区| 久久精品国产亚洲精品2020| 日韩视频中文字幕| 亚洲精品久久久久中文字幕欢迎你| 日韩欧美国产骚| 欧美性视频在线| 亚洲精选在线观看| 国产精品成久久久久三级| 久久精品男人天堂| 另类色图亚洲色图| 欧美丰满少妇xxxx| 欧美久久精品一级黑人c片| 亚洲福利视频在线| 亚洲国产精彩中文乱码av| 4438全国亚洲精品在线观看视频| 免费av在线一区| 久久99热精品这里久久精品| 国产精品96久久久久久又黄又硬| 久久久精品免费| 亚洲国产成人精品电影| 国产91精品黑色丝袜高跟鞋| 日韩中文第一页| 九九热这里只有精品6| 这里只有精品视频在线| 欧美资源在线观看| 欧美日韩国产精品一区二区不卡中文| 日韩精品免费观看| 国产丝袜精品第一页| 国产精品一区久久| 久久6精品影院| 日韩精品中文字幕久久臀| 原创国产精品91| 深夜福利亚洲导航| 欧美专区在线播放| 亚洲性生活视频在线观看| 国产偷国产偷亚洲清高网站| 亚洲激情自拍图| 色系列之999| 日韩国产精品一区| 精品亚洲一区二区| 国产成人精品a视频一区www| 国产日本欧美一区二区三区在线| 青青久久av北条麻妃海外网| 国产精品三级美女白浆呻吟| 日本a级片电影一区二区| 欧美日韩免费在线观看| 精品国产一区二区三区久久久| 欧美国产欧美亚洲国产日韩mv天天看完整| 日韩av大片免费看| 国产日韩欧美一二三区| 日韩中文字幕在线免费观看| 亚洲成avwww人| 日韩激情视频在线播放| 中文字幕日韩欧美在线视频| 欧美视频在线看| 久久国产精品久久久久| 在线观看视频99| 欧美成人免费播放| 日本一区二区三区四区视频| 97视频网站入口| 亚洲999一在线观看www|