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

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

树形分支是后端接口返回数据渲染,可展示多条;

代码可拓展,可封装;

点击节点可查看备注;

dab163c13ded671b76d6dc3f6939d59b.png

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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值