基于canvas实现一个多分支流程图组件

综述:使用canvas实现了一个多分支流向图,总结下主要的实现思路,有需要的朋友可直接使用,假设如果不使用canvas来绘制,使用svg来设计又该如何实现呢,思考下?

1.效果展示

2.实现思路解析

  • 需求分析

一个流程图,主要由横线,空心圆,空心圆中的实心圆三部分组成,并且随着状态的变化能控制其颜色

可以支持多分支,多分支中还包括奇数个分支和偶数个分支,实现思路上两者稍微不同

  • 核心思路说明

通过配置的数组解析绘制,如果是非数组元素,则就是画直线和圆圈,实心圆,并分别计算各自的位置(x,y)坐标,依次类推。

如果是一个数组的元素,则说明是一个多分支的元素,需要向上和向下开分支,绘制多条直线和圆圈,实心圆,依次类推即可,并分别计算各自的位置(x,y)坐标,依次类推。

3.遇到的问题总结

  • canvas是基于矢量绘制的图像,存在模糊的问题(参考连接),细节见js代码种的最后一个模块
  • 对于绘制直线的颜色控制逻辑,没有理清楚,废了些时间

4.代码展示

  • javascript代码(组件实现代码),render方法过于冗余,但是好处是比较好理解,后期应该优化下
/**
 *流向图组件,mouyao
 */
var opsDirectionMap = function(option){
    this.const(option);
    this.init();
};
/*
*配置项引入
*/
opsDirectionMap.prototype.const=function(option){
    this.r=option.r||4;//节点半径
    this.config=option;
    this.data = option.data||[];
    this.mLeft = option.mLeft||-20;//起点距左边距离
    this.space = option.space||18*this.r;//节点之间距离
    this.angle =2*this.r;//分支上下之间的高度
    this.nodeArr = []; //存储所有的圆点的信息和坐标
};
/*
*配置项引入
*/
opsDirectionMap.prototype.init =function(){
    var myCanvas=document.getElementById(this.config.placeId);
    this.resolveVagueProblem(myCanvas);
    this.render(myCanvas);
};
/*
*判定是否数组
*/
opsDirectionMap.prototype.isArrayFn =function(o) {
    return Object.prototype.toString.call(o) === '[object Array]';
};
/*
*根据当前节点的执行状态,渲染圆点前的线条的颜色
*/
opsDirectionMap.prototype.drawDashLine =function(ctx, x1, y1, x2, y2,data,index){
    ctx.lineWidth=1;
    ctx.beginPath();
    var x=(x2-x1)/2;
    if(index>0&&!this.isArrayFn(this.data[index-1])){
        ctx.moveTo(x1,y1);
        ctx.lineTo(x1+x ,y1);
        ctx.moveTo(x1+x,y1);
        ctx.lineTo(x1+x ,y2);
        ctx.moveTo(x1+x,y2);
        ctx.lineTo(x2 ,y2);
    }else if(index>0&&this.isArrayFn(this.data[index-1])){
        ctx.moveTo(x1,y1);
        ctx.lineTo(x1+x ,y1);
        ctx.moveTo(x1+x,y1);
        ctx.lineTo(x1+x ,y2);
        ctx.moveTo(x1+x,y2);
        ctx.lineTo(x2 ,y2);
    }else{
        if(index!==0){ //删除第一个圆点的连接线
            ctx.moveTo(x1,y1);
            ctx.lineTo(x2 ,y2);
        }
    }
    if(data.isExcuted===true){
        ctx.strokeStyle="#009aff";
    }else if(data.isExcuted===false&&index!==0&&this.data[index-1].isExcuted===true&&!this.isArrayFn(this.data[index-1])){
        ctx.strokeStyle="#009aff";
    }else if(data.isExcuted===false&&this.isArrayFn(this.data[index-1])){
        //如果上一个元素是数组
        var arr=[];
        this.data[index-1].some(function(item){
            if(item.isExcuted===true){
                arr.push(true);
            }
        });
        if(arr.length===(this.data[index-1]).length){
            ctx.strokeStyle="#009aff";
        }else{
            ctx.strokeStyle="#959595";
        }
    }else{
        ctx.strokeStyle="#959595";
    }
    ctx.stroke();
};
/*
*绘制线条,圆点,圆心,和说明文字
*/
opsDirectionMap.prototype.render =function(canvas){
        var this_ = this;
        this_.canvas = canvas;
        var ctx = canvas.getContext("2d");//上下文
        this_.ctx = ctx;
        var x = this_.mLeft; //每个操作的对象的坐标
        //var y = canvas.height/2;
        //x偏移量:this_.r*Math.sin((90-itemY)*Math.PI/180)
        //y偏移量:this_.r*Math.cos((90-itemY)*Math.PI/180)
        var y =50;
        this_.data.forEach(function(item, index){
            if(!(item instanceof Array)){
                x = index == 0?x:(x + this_.space);
                if((index-1)>=0 && this_.data[index-1] instanceof Array){
                    var arr = this_.data[index-1];
                  
                    if(arr.length % 2==0){
                        var itemY = this_.angle;
                        for(var i=0;i<arr.length/2;i++){
                            this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y-Math.tan(itemY*Math.PI/180)*this_.space+this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index);
                            itemY = itemY + this_.angle;
                        }
                        var itemY = this_.angle;
                        for(var i=0;i<arr.length/2;i++){
                            this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y+Math.tan(itemY*Math.PI/180)*this_.space-this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index);
                            itemY = itemY + this_.angle;
                        }
                    }else{
                        var itemY = 0;
                        for(var i=0;i<parseInt(arr.length/2)+1;i++){
                            console.log(arr[i]);
                            this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y-Math.tan(itemY*Math.PI/180)*this_.space+this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index);
                            itemY = itemY + this_.angle;
                        }
                        var itemY = this_.angle;
                        for(var i=0;i<parseInt(arr.length/2);i++){
                            this_.drawDashLine(ctx, x - this_.space - this_.r+this_.r*Math.sin((90-itemY)*Math.PI/180), y+Math.tan(itemY*Math.PI/180)*this_.space-this_.r*Math.cos((90-itemY)*Math.PI/180), x, y,item,index);
                            itemY = itemY + this_.angle;
                        }
                    }
                }

                if(index == 0){
                    ctx.moveTo(x,y);
                    x = x + this_.space;
                }
                //绘制非数组直线
                if(!((index-1)>=0 && this_.data[index-1] instanceof Array)){
                    this_.drawDashLine(ctx,x-this_.space, y, x, y,item,index);
                }
                ctx.moveTo(x + 2*this_.r,y);

                //绘制节点,画圆
                ctx.arc(x + this_.r,y,this_.r,0,2*Math.PI);
                this_.nodeArr.push({x:x + this_.r,y:y,data:item});
                ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                ctx.fill();

                //节点标题note
                ctx.textAlign ="center";
                ctx.textBaseline = "middle";
                ctx.font = "bold 10px 宋体";//字体大小
                ctx.fillStyle =item.noteColor;//字体颜色
                //节点的名称设置
                ctx.fillText(item.noteName,x + this_.r,y-this_.r-10);
                ctx.fillStyle = "black";//字体颜色
              
                x = x + 2*this_.r;
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(x,y);
            }else{//数组
                if(!(this_.data[index-1] instanceof Array)){//上一级不是数组
                    var itemY = 0;
                    if(item.length%2==0){//偶数
                        itemY = this_.angle;
                        var dataArr = item.slice(0,item.length/2).reverse();
                        for(var i=0;i<dataArr.length;i++){
                            this_.drawDashLine(ctx, x, y, x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);
                            ctx.beginPath();
                            ctx.arc(x + this_.space, y-Math.tan(itemY*Math.PI/180)*(this_.space),this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x + this_.space,y:y-Math.tan(itemY*Math.PI/180)*(this_.space),data:dataArr[i]});

                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle =dataArr[i].isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x + this_.space, y-Math.tan(itemY*Math.PI/180)*(this_.space)-this_.r-10);
                            ctx.fill();
                            ctx.moveTo(x+this_.r,y);
                            itemY = itemY + this_.angle;
                        }
                        itemY = this_.angle;
                        var dataArr = item.slice(item.length/2,item.length);
                        for(var i=0;i<dataArr.length;i++){
                            this_.drawDashLine(ctx, x, y, x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);
                            ctx.beginPath();
                            ctx.arc(x + this_.space, y+Math.tan(itemY*Math.PI/180)*(this_.space),this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x + this_.space,y:y+Math.tan(itemY*Math.PI/180)*(this_.space),data:dataArr[i]});

                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle =dataArr[i].isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x + this_.space, y+Math.tan(itemY*Math.PI/180)*(this_.space)+this_.r+10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                    }else{//奇数
                        var dataArr = item.slice(0,parseInt(item.length/2)+1).reverse();
                        for(var i=0;i<dataArr.length;i++){
                            this_.drawDashLine(ctx, x, y, x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);

                            ctx.beginPath();
                            ctx.arc(x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x + this_.space,y:y-Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]});

                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;;//填充颜色
                            ctx.fill();
                            ctx.stroke();
                            
                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x + this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space-this_.r-10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                        itemY = this_.angle;
                        var dataArr = item.slice(parseInt(item.length/2)+1,item.length);
                        for(var i=0;i<dataArr.length;i++){
                            this_.drawDashLine(ctx, x, y, x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);
                            ctx.beginPath();
                            ctx.arc(x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x + this_.space,y:y+Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]});
                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x + this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space+this_.r+10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                    }
                    ctx.stroke();

                    ctx.beginPath();
                    x = x+this_.space+this_.r;
                    ctx.moveTo(x,y);
                }else{//上一级是数组
                    if(item.length%2==0){//偶数
                        var itemY = this_.angle;
                        var dataArr = item.slice(0,item.length/2).reverse();
                        for(var i=0;i<dataArr.length;i++){
                            ctx.moveTo(x,y-Math.tan(itemY*Math.PI/180)*this_.space);
                            this_.drawDashLine(ctx, x, y-Math.tan(itemY*Math.PI/180)*this_.space,
                                x+this_.r+ this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);
                            ctx.beginPath();
                            ctx.arc(x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x+ this_.space+this_.r,y:y-Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]});

                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space-this_.r-10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                        var itemY = this_.angle;
                        var dataArr = item.slice(item.length/2,item.length);
                        for(var i=0;i<dataArr.length;i++){
                            ctx.moveTo(x,y+Math.tan(itemY*Math.PI/180)*this_.space);
                            this_.drawDashLine(ctx, x, y+Math.tan(itemY*Math.PI/180)*this_.space,
                                x+this_.r+ this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);
                            ctx.beginPath();
                            ctx.arc(x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x+ this_.space+this_.r,y:y+Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]});

                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space+this_.r+10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                    }else{//奇数
                        var itemY = 0;
                        var dataArr = item.slice(0,parseInt(item.length/2)+1).reverse();
                        for(var i=0;i<dataArr.length;i++){
                            ctx.moveTo(x,y-Math.tan(itemY*Math.PI/180)*this_.space);
                            this_.drawDashLine(ctx, x, y-Math.tan(itemY*Math.PI/180)*this_.space,
                                x+this_.r+ this_.space, y-Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);

                            ctx.beginPath();
                            ctx.arc(x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x+ this_.space+this_.r,y:y-Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]});

                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y-Math.tan(itemY*Math.PI/180)*this_.space-this_.r-10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                        var itemY = this_.angle;
                        var dataArr = item.slice(parseInt(item.length/2)+1,item.length);
                        for(var i=0;i<dataArr.length;i++){
                            ctx.moveTo(x,y+Math.tan(itemY*Math.PI/180)*this_.space);
                            this_.drawDashLine(ctx, x, y+Math.tan(itemY*Math.PI/180)*this_.space,
                                x+this_.r+ this_.space, y+Math.tan(itemY*Math.PI/180)*this_.space,dataArr[i],index);
                            
                            ctx.beginPath();
                            ctx.arc(x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space,this_.r,0,2*Math.PI);
                            this_.nodeArr.push({x:x+ this_.space+this_.r,y:y+Math.tan(itemY*Math.PI/180)*this_.space,data:dataArr[i]});
                            //节点信息
                            ctx.textAlign ="center";
                            ctx.textBaseline = "middle";
                            ctx.font = "bold 10px 宋体";//字体大小
                            ctx.fillStyle = item.isExcuted===true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;//填充颜色
                            ctx.fill();
                            ctx.stroke();

                            ctx.beginPath();
                            ctx.fillStyle = dataArr[i].noteColor;//字体颜色
                            ctx.fillText(dataArr[i].noteName,x+ this_.space+this_.r, y+Math.tan(itemY*Math.PI/180)*this_.space+this_.r+10);
                            ctx.fill();
                            itemY = itemY + this_.angle;
                        }
                    }
                    ctx.stroke();
                    ctx.beginPath();
                    x = x+this_.space+2*this_.r;
                    ctx.moveTo(x,y);
                }
            }
        });
};
/*
*因为canvas绘制的是矢量图,会出现模糊问题,使用下边的方法解决
* 参考链接:https://zhuanlan.zhihu.com/p/31426945
*/
opsDirectionMap.prototype.resolveVagueProblem=function(myCanvas) {
    var getPixelRatio = function (context) {
        var backingStore = context.backingStorePixelRatio ||
            context.webkitBackingStorePixelRatio ||
            context.mozBackingStorePixelRatio ||
            context.msBackingStorePixelRatio ||
            context.oBackingStorePixelRatio ||
            context.backingStorePixelRatio || 1;
        return (window.devicePixelRatio || 1) / backingStore;
    };
    //画文字
    myCanvas.style.border = "1px solid silver";
    var context = myCanvas.getContext("2d");
    var ratio = getPixelRatio(context);
    myCanvas.style.width = myCanvas.width + 'px';
    myCanvas.style.height =myCanvas.height+ 'px';
    myCanvas.width = myCanvas.width * ratio;
    myCanvas.height = myCanvas.height * ratio;
    context.scale(ratio,ratio); 
};
  • html调用代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
</head>
<body>
<canvas id="renderArea" width="600" height="100">浏览器不支持canvas</canvas>
<script type="text/javascript" src="ops-direction-map.js"></script>
<script>
     var demo=new opsDirectionMap({
        placeId:"renderArea",
        excutedCirclePointColor:"#009aff",//执行的节点的圆心颜色
        circlePointColor:"#ffffff",//未执行的的节点的圆心颜色
        data:[{
            noteName:'节点1',
            noteColor:'#000000', //说明文字的颜色
            isExcuted:true//如果这里为true,则其前边的线条为蓝色,圆点为实心,否为白色
        },{
            noteName:'节点2',
            noteColor:'#000000',
            isExcuted:true
        },[
            {
                noteName:'节点3-1',
                noteColor:'#000000',
                isExcuted:true
            },
            {
                noteName:'节点3-2',
                noteColor:'#000000',
                isExcuted:false
            }
        ],{
            noteName:'节点4',
            noteColor:'#000000',
            isExcuted:false
        },{
            noteName:'节点5',
            noteColor:'#000000',
            isExcuted:false
        },[
            {
                noteName:'节点6-1',
                noteColor:'#000000',
                isExcuted:false
            },{
                noteName:'节点6-2',
                noteColor:'#000000',
                isExcuted:false,
            }
        ],{
            noteName:'节点7',
            noteColor:'#000000',
            isExcuted:false
        }
        ]
    });
</script>
</body>
</html>

 

 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值