css canvas 绘制流程图(1)

//1.引入js

//画流程图
//画所有的图形:画图和画对应的箭头
function drawFlowChart(context, canvas, flowdata, initTop, initSpaceH) {
  //1、判断是否有需要平均计算x的数据
  flowdata.forEach(function (row) {
    if (row.isAverage) {
      row.data = calChartX(canvas.width, row.data, row.y);
    }
  });
  //2、先要画完所有的图形
  flowdata.forEach(function (row, rowIndex) {
    row.y = row.y ? row.y : (rowIndex == 0 ? initTop + initSpaceH : initTop + initSpaceH * rowIndex);
    row = drawRowChart(context, row);  //画图形
  });
  //3、添加要指向的对象,必须要在画完所有图形之后
  flowdata.forEach(function (row) {
    row.data.forEach(function (item) {
      if (item.arrowArr && item.arrowArr.length) {
        item.arrowArr.forEach(function (mItem) {
          mItem = addToObj(mItem, flowdata);
        })
      }
    })
  });
  //4、给所有图形画上对应的画箭头,必须要在前两步完成之后
  flowdata.forEach(function (row, rowIndex) {
    row.data.forEach(function (item) {
      if (item.arrowArr && item.arrowArr.length) {
        drawSingleArrow(context, item);//画箭头
      }
    })
  });
  //5、给所有元素添加点击和悬浮事件
  addMethod(canvas, flowdata)
}
//当一行有n个图形并且需要平均排列时用此方法计算每个图形的x
function calChartX(canvasW, data, dataY) {
  var startW = 80;
  var stepW = 120;
  var CondW = 30;
  var count = 0;
  for (var i = 0; i < data.length; i++) {
    if (data[i].type == 'Step') {
      if (data[i].direction != undefined) {
        stepW = -120;
      }
      count += stepW;
    } else if (data[i].type == 'Start' || data[i].type == 'End') {
      count += startW;
    } else if (data[i].type == 'Condition') {
      count += CondW;
    }
  }
  //spaceW 计算一行中每个图形的平均间距
  var spaceW = parseInt((canvasW - count) / (data.length + 1));
  //计算坐标x
  var prevDiv = [], curW = 0;
  for (var i = 0; i < data.length; i++) {
    if (data[i].type == 'Step') {
      prevDiv.push(stepW);
      curW = stepW / 2;
    } else if (data[i].type == 'Start' || data[i].type == 'End') {
      prevDiv.push(startW);
      curW = startW / 2;
    } else if (data[i].type == 'Condition') {
      prevDiv.push(CondW);
      curW = CondW / 2;
    }
    var preLength = 0;
    for (var j = 0; j < i; j++) {
      preLength += prevDiv[j];
    }
    var x = spaceW * (i + 1) + preLength + curW;
    var y = data[i].y;
    data[i]['x'] = x;
    data[i]['y'] = y ? y : dataY;
  }
  return data;
}
//生成每列对应的图形
function drawRowChart(context, row) {
  row.data.forEach(function (item, index) {
    var s = null;
    item.y = item.y ? item.y : row.y;
    if (item.type == 'Step') {
      s = new Step(context, item.x, item.y, item);
    } else if (item.type == 'Condition') {
      s = new Condition(context, item.x, item.y, item);
    } else if (item.type == 'End') {
      s = new End(context, item.x, item.y, item);
    } else if (item.type == 'Start') {
      s = new Start(context, item.x, item.y, item);
    }
    item.chartObj = s;
  })
  return row;
}
//绘制单个的图形
function drawSingleChart(context, item) {
  var s = '';
  if (item.type == 'Step') {
    s = new Step(context, item.x, item.y, item);
  } else if (item.type == 'Condition') {
    s = new Condition(context, item.x, item.y, item);
  } else if (item.type == 'End') {
    s = new End(context, item.x, item.y, item);
  } else if (item.type == 'Start') {
    s = new Start(context, item.x, item.y, item);
  }
  item.chartObj = s;
  return item;
}
//每个对象的坐标范围
function calRange(obj) {
  var newObj = {
    minX: obj.x - obj.w / 2,
    maxX: obj.x + obj.w / 2,
    minY: obj.y - obj.h / 2,
    maxY: obj.y + obj.h / 2
  }
  return newObj;
}
//处理每一个箭头需要指向的对象
function addToObj(arrObj, flowData) {
  flowData.forEach(function (rows) {
    rows.data.forEach(function (item) {
      if (item.name == arrObj.to) {
        arrObj.to = item.chartObj;
      }
    })

  })
  return arrObj;
}
//话每个图形的箭头指向
function drawSingleArrow(context, data) {
  var step1 = data.chartObj;
  if (data.arrowArr && data.arrowArr.length) {
    data.arrowArr.forEach(function (item) {
      step1[item.arrow](item.to, context);
    })
  }
}
//清除单个图形
function repaintSingleChart(context, item) {
  var range = item.chartObj.range;
  //清除之前画的图形
  context.clearRect(range.minX - 1, range.minY - 1, item.chartObj.w + 2, item.chartObj.h + 3);

}
//给所有图形添加事件
function addMethod(canvas, flowData) {
  //给所有图形添加点击事件
  canvas.onclick = function (ev) {
    var ev = ev || window.event;
    var ua = navigator.userAgent.toLowerCase();
    var isIE = ua.indexOf("compatible") > -1 && ua.indexOf("msie") > -1 && !ua.indexOf("opera") > -1; //IE浏览器
    var isEdge = ua.indexOf("Edge") > -1;
    var isIE11 = ua.toLowerCase().match(/rv:([\d.]+)\) like gecko/);
    var curx = (isIE || isEdge || isIE11) ? ev.offsetX : ev.layerX;
    var cury = (isIE || isEdge || isIE11) ? ev.offsetY : ev.layerY;
    flowData.forEach(function (row, listIndex) {
      row.data.forEach(function (item) {
        var range = item.chartObj.range;
        if (curx >= range.minX && curx <= range.maxX && cury >= range.minY && cury <= range.maxY) {
          var clickMethod = null;
          if (row.method && row.method.onclick) { //如果每行定义了事件
            //判断每个元素是否有单独定义事件,如果有,取改元素定义的事件,如果没有取每行定义的事件
            if (item.method && item.method.onclick) {
              clickMethod = item.method.onclick
            } else {
              clickMethod = row.method.onclick
            }
          } else {
            if (item.method && item.method.onclick) {
              clickMethod = item.method.onclick
            } else {
              clickMethod = null;
            }
          }
          if (clickMethod instanceof Function) {
            clickMethod(item);
          }
        }
      })
    });
  };
  var timer = null;
  //给所有图形添加mousemove事件
  canvas.onmousemove = function (ev) {
    var ev = ev || window.event;
    var ua = navigator.userAgent.toLowerCase();
    var isIE = ua.indexOf("compatible") > -1 && ua.indexOf("msie") > -1 && !ua.indexOf("opera") > -1; //IE浏览器
    var isEdge = ua.indexOf("Edge") > -1;
    var isIE11 = ua.toLowerCase().match(/rv:([\d.]+)\) like gecko/);
    var curx = (isIE || isEdge || isIE11) ? ev.offsetX : ev.layerX;
    var cury = (isIE || isEdge || isIE11) ? ev.offsetY : ev.layerY;
    clearTimeout(timer);
    flowData.forEach(function (row, listIndex) {
      row.data.forEach(function (item) {
        var range = item.chartObj.range;
        if (curx >= range.minX && curx <= range.maxX && cury >= range.minY && cury <= range.maxY) {
          var clickMethod = null;
          if (row.method && row.method.onmousemove) { //如果每行定义了事件
            //判断每个元素是否有单独定义事件,如果有,取改元素定义的事件,如果没有取每行定义的事件
            if (item.method && item.method.onmousemove) {
              clickMethod = item.method.onmousemove
            } else {
              clickMethod = row.method.onmousemove
            }
          } else {
            if (item.method && item.method.onmousemove) {
              clickMethod = item.method.onmousemove
            } else {
              clickMethod = null;
            }
          }
          if (clickMethod instanceof Function) {
            timer = setTimeout(function () {
              clickMethod(item);
            }, 1000)
          }
        }
      })
    });
  }
  //给所有图形添加mouseleave事件
  canvas.onmouseleave = function (ev) {
    var ev = ev || window.event;
    var ua = navigator.userAgent.toLowerCase();
    var isIE = ua.indexOf("compatible") > -1 && ua.indexOf("msie") > -1 && !ua.indexOf("opera") > -1; //IE浏览器
    var isEdge = ua.indexOf("Edge") > -1;
    var isIE11 = ua.toLowerCase().match(/rv:([\d.]+)\) like gecko/);
    var curx = (isIE || isEdge || isIE11) ? ev.offsetX : ev.layerX;
    var cury = (isIE || isEdge || isIE11) ? ev.offsetY : ev.layerY;
    clearTimeout(timer);
    flowData.forEach(function (row) {
      row.data.forEach(function (item) {
        var range = item.chartObj.range;
        if (curx >= range.minX && curx <= range.maxX && cury >= range.minY && cury <= range.maxY) {
          var clickMethod = null;
          if (row.method && row.method.onmouseleave) { //如果每行定义了事件
            //判断每个元素是否有单独定义事件,如果有,取改元素定义的事件,如果没有取每行定义的事件
            if (item.method && item.method.onmouseleave) {
              clickMethod = item.method.onmouseleave
            } else {
              clickMethod = row.method.onmouseleave
            }
          } else {
            if (item.method && item.method.onmouseleave) {
              clickMethod = item.method.onmouseleave
            } else {
              clickMethod = null;
            }
          }
          if (clickMethod instanceof Function) {
            clickMethod(item);
          }
        }
      })
    });
  }
}
/////////////////////////////////////////基本画图形start////////////////////////////////////////////////////////
//画圆角矩形
function drawRoundRect(context, x, y, w, h, item, radius) {
  radius = radius || 20;
  context.beginPath();
  context.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);
  context.lineTo(w - radius + x, y);
  context.arc(w - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);
  context.lineTo(w + x, h + y - radius);
  context.arc(w - radius + x, h - radius + y, radius, 0, Math.PI * 1 / 2);
  context.lineTo(radius + x, h + y);
  context.arc(radius + x, h - radius + y, radius, Math.PI * 1 / 2, Math.PI);
  context.closePath();
  context.fillStyle = item.color ? (item.color.bgColor ? item.color.bgColor : 'rgba(91,155,213,0.5)') : 'white'; //背景颜色
  context.fill();
  context.lineWidth = 2;
  context.strokeStyle = item.color ? (item.color.borderColor ? item.color.borderColor : '#5B9BD5') : '#5B9BD5'; //边框颜色
  context.font = 'normal 12px 微软雅黑';
  context.fillStyle = item.color ? (item.color.fontColor ? item.color.fontColor : '#5B9BD5') : '#5B9BD5'; //文字颜色
  context.textAlign = "center";
  // context.fillText(item.text, x + w / 2, y + h / 2 + 6);
  // context.stroke();
  if (item.direction != undefined) {
    var x = x + w / 2;
    var y = y + 10;
    let letterSpacing = 3; // 设置字间距
    for (let i = 0; i < item.text.length; i++) {
      const str = item.text.slice(i, i + 1).toString();
      context.save();
      context.textBaseline = 'top';
      context.fillText(str, x, y);
      context.restore();
      y += context.measureText(str).width + letterSpacing; // 计算文字宽度
    }
    var str = item.num;
    context.save();
    context.textBaseline = 'top';
    context.fillText(str, x, y);
    context.restore();
    y += context.measureText('人').width + letterSpacing; // 计算文字宽度
    var str = item.unit;
    context.save();
    context.textBaseline = 'top';
    context.fillText(str, x, y);
    context.restore();
    y += context.measureText(str).width + letterSpacing; // 计算文字宽度
    context.stroke();
  } else {
    context.fillText(item.text, x + w / 2, y + h / 2 + 6);
    context.stroke();
  }

}
//画菱形
function drawRhombus(context, x, y, l, h, item) {
  context.beginPath();
  context.moveTo(x, y + h);
  context.lineTo(x - l * 2 - 40, y);
  context.lineTo(x, y - h);
  context.lineTo(x + l * 2 + 40, y);
  context.closePath();
  context.strokeStyle = item.color ? (item.color.borderColor ? item.color.borderColor : '#5B9BD5') : '#5B9BD5'; //边框颜色
  context.fillStyle = item.color ? (item.color.bgColor ? item.color.bgColor : 'rgba(91,155,213,0.5)') : 'white'; //背景颜色
  context.fill();
  context.fillStyle = item.color ? (item.color.fontColor ? item.color.fontColor : '#5B9BD5') : '#5B9BD5'; //文字颜色
  context.font = 'normal 14px 微软雅黑';
  context.fillText(item.text, x, y + 3);
  context.stroke();
}
//计算文本的宽高
function textSize(fontSize, fontFamily, text) {
  var span = document.createElement("span");
  span.className = "text";
  var result = {};
  result.width = span.offsetWidth;
  result.height = span.offsetHeight;
  span.style.visibility = "hidden";
  span.style.fontSize = fontSize;
  span.style.fontFamily = fontFamily;
  span.style.display = "inline-block";
  document.body.appendChild(span);
  if (typeof span.textContent != "undefined") {
    span.textContent = text;
  } else {
    span.innerText = text;
  }
  result.width = parseFloat(window.getComputedStyle(span).width) - result.width;
  result.height = parseFloat(window.getComputedStyle(span).height) - result.height;
  var list = document.body.getElementsByClassName('text')[0];
  document.body.removeChild(list);
  return result;
}
//Start 圆角矩形对象
function Start(context, x, y, item, h, w) {
  this.flag = 'start';
  var textStyle = textSize('14px', '微软雅黑', item.text);
  w = parseInt(textStyle.width) + 30;

  this.h = h || 40;
  this.w = w || 2 * this.h;
  this.x = x;
  this.y = y;
  this.text = item.text;
  this.range = calRange(this);
  drawRoundRect(context, x - this.w / 2, y - this.h / 2, this.w, this.h, item);
}
//End 圆角矩形对象
function End(context, x, y, item, h, w) {
  this.flag = 'end';
  var textStyle = textSize('14px', '微软雅黑', item.text);
  w = parseInt(textStyle.width) + 30;
  this.h = h || 40;
  this.w = w || 2 * this.h;
  this.x = x;
  this.y = y;
  this.text = item.text;
  this.range = calRange(this);
  drawRoundRect(context, x - this.w / 2, y - this.h / 2, this.w, this.h, item, 20);
}
//Step 矩形对象
function Step(context, x, y, item) {
  this.flag = "step";
  var textStyle = textSize('14px', '微软雅黑', item.text);
  // var w = parseInt(textStyle.width) + 30;
  var w = parseInt(textStyle.width) + 30;
  this.h = 40;
  this.w = w;
  this.x = x;
  this.y = y;
  this.text = item.text;
  this.color = item.color;
  this.direction = item.direction;
  this.range = calRange(this);
  // context.strokeStyle= item.color ? (item.color.borderColor ? item.color.borderColor: '#5B9BD5'):'#5B9BD5' ; //边框颜色
  // context.strokeRect(x - this.w / 2, y - this.h / 2, this.w, this.h);
  // context.fillStyle = item.color ? (item.color.bgColor ? item.color.bgColor: 'rgba(91,155,213,0.5)'):'white' ; //背景颜色
  // context.fillRect(x - this.w / 2, y - this.h / 2,this.w,this.h);
  // context.fillStyle = item.color ? (item.color.fontColor ? item.color.fontColor: '#5B9BD5'):'#5B9BD5' ; //文字颜色
  // context.textAlign = 'center';
  // context.font = 'normal 14px 微软雅黑';
  // if(item.text){context.fillText(item.text,x, y+4);}
  // drawRoundRect(context, x - this.w / 2, y - this.h / 2, this.w, this.h, item, 5);
  if (item.direction != undefined) {
    this.w = 40;
    this.h = w+20;
    drawRoundRect(context, x - this.w / 2, y - this.w / 2, this.w, this.h, item, 5);
  } else {
    drawRoundRect(context, x - this.w / 2, y - this.h / 2, this.w, this.h, item, 5);
  }
}
//Condition 菱形对象
function Condition(context, x, y, item) {
  this.flag = "condition";
  var textStyle = textSize('14px', '微软雅黑', item.text);
  var w = parseInt(textStyle.width) / 4;
  this.l = w;
  this.h = 40;
  this.w = w;
  this.x = x;
  this.y = y;
  this.text = item.text;
  this.range = calRange(this);
  drawRhombus(context, x, y, this.l, this.h, item);
}

/////////////////////////////////////////基本画图形end////////////////////////////////////////////////////////



////////////////////////////////////////////画箭头/////////////////////////////////////////////////////////////
//箭头从start圆角矩形bottom——>top
Start.prototype.drawBottomToTop = function (obj, context) {
  if (obj.flag == "step") {
    var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.h / 2);
    arrow.drawBottomToTop(context);
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.l);
    arrow.drawBottomToTop(context);
  }
}

//箭头从step矩形bottom——>right
Step.prototype.drawBottomToRight = function (obj, context) {
  var arrow = null;
  if (obj.flag == "step") {
    arrow = new Arrow(this.x, this.y + this.h / 2, obj.x + obj.w / 2, obj.y);
  } else if (obj.flag == "condition") {
    arrow = new Arrow(this.x, this.y + this.h / 2, obj.x + obj.l * 2 + 40, obj.y);
  } else if (obj.flag == "start" || obj.flag == "end") {
    arrow = new Arrow(this.x, this.y + this.h / 2, obj.x + obj.w / 2, obj.y);
  }
  arrow.drawBottomToRight(context);
}
//箭头从step矩形bottom——>left
Step.prototype.drawBottomToLeft = function (obj, context) {
  var arrow = null;
  if (obj.flag == "step") {
    arrow = new Arrow(this.x, this.y + this.h / 2, obj.x - obj.w / 2, obj.y);
  } else if (obj.flag == "condition") {
    arrow = new Arrow(this.x, this.y + this.h / 2, obj.x - obj.l * 2 - 40, obj.y);
  } else if (obj.flag == "start" || obj.flag == "end") {
    arrow = new Arrow(this.x, this.y + this.h / 2, obj.x - obj.w / 2, obj.y);
  }
  arrow.drawBottomToRight(context);
}
//箭头从step矩形bottom——>top
Step.prototype.drawBottomToTop = function (obj, context) {
  if (obj.flag == "step") {
    if (obj.direction != undefined) {
      var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.w / 2, obj.color.lineColor);
      arrow.drawBottomToTop(context);
    } else {
      var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.h / 2, obj.color.lineColor);
      arrow.drawBottomToTop(context);
    }
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x, this.y + this.h / 2, obj.x, obj.y - obj.h);
    arrow.drawBottomToTop(context);
  }
}
//箭头从step矩形right——>left
Step.prototype.drawRightToLeft = function (obj, context) {
  var arrow = null;
  if (obj.flag == "step") {
    arrow = new Arrow(this.x + this.w / 2, this.y, obj.x - obj.w / 2, obj.y);
  } else if (obj.flag == "condition") {
    arrow = new Arrow(this.x + this.w / 2, this.y, obj.x - obj.l * 2, obj.y);
  } else if (obj.flag == "start" || obj.flag == "end") {
    arrow = new Arrow(this.x + this.w / 2, this.y, obj.x - obj.w / 2, obj.y);
  }
  arrow.drawLeftToRightOrRightToLeft(context);
}

//箭头从Condition菱形Bottom——>top
Condition.prototype.drawBottomToTop = function (obj, context) {
  if (obj.flag == "step") {
    var arrow = new Arrow(this.x, this.y + this.h, obj.x, obj.y - obj.h / 2);
    arrow.drawBottomToTop(context);
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x, this.y + this.l, obj.x, obj.y - obj.l);
    arrow.drawBottomToTop(context);
  }
}
//箭头从Condition菱形right——>top
Condition.prototype.drawRightToTop = function (obj, context) {
  if (obj.flag == "step") {
    var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x, obj.y - obj.h / 2);
    arrow.drawLeftOrRightToTop(context);
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x, obj.y - obj.l);
    arrow.drawLeftOrRightToTop(context);
  }
}
//箭头从Condition菱形left——>top
Condition.prototype.drawLeftToTop = function (obj, context) {
  if (obj.flag == "step") {
    var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x, obj.y - obj.h / 2);
    arrow.drawLeftOrRightToTop(context);
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x, obj.y - obj.l);
    arrow.drawLeftOrRightToTop(context);
  }
}
//箭头从Condition菱形right——>left
Condition.prototype.drawRightToLeft = function (obj, context) {
  if (obj.flag == "step") {
    var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x - this.w / 2, obj.y);
    arrow.drawLeftToRightOrRightToLeft(context);
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x + this.l * 2, this.y, obj.x - this.l * 2, obj.y);
    arrow.drawLeftToRightOrRightToLeft(context);
  }
}
//箭头从Condition菱形left——>right
Condition.prototype.drawLeftToRight = function (obj, context) {
  if (obj.flag == "step") {
    var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x + this.w / 2, obj.y);
    arrow.drawLeftToRightOrRightToLeft(context);
  } else if (obj.flag == "condition") {
    var arrow = new Arrow(this.x - this.l * 2, this.y, obj.x + this.l * 2, obj.y);
    arrow.drawLeftToRightOrRightToLeft(context);
  }
}


/////////////////////////////////////////画箭头start/////////////////////////////////////
function Arrow(x1, y1, x2, y2, color) {
  this.x1 = x1;
  this.y1 = y1;
  this.x2 = x2;
  this.y2 = y2;
  this.tmpX1 = null;
  this.tmpY1 = null;
  this.tmpX2 = null;
  this.tmpY2 = null;
  // this.color = "#5B9BD5";
  this.color = color == undefined ? "#5B9BD5" : color;
}
Arrow.prototype.setColor = function (color) {
  this.color = color;
}
/**
 *
 * @param {Object} x1起始点横坐标
 * @param {Object} y1起始点纵坐标
 * @param {Object} x2结束点横坐标
 * @param {Object} y2结束点纵坐标
 */
Arrow.prototype.setP = function (x1, y1, x2, y2) {
  this.x1 = x1;
  this.y1 = y1;
  this.x2 = x2;
  this.y2 = y2;
}
//第一个拐点
Arrow.prototype.setP1 = function (tmpX1, tmpY1) {
  this.tmpX1 = tmpX1;
  this.tmpY1 = tmpY1;
}
//第二个拐点
Arrow.prototype.setP2 = function (tmpX2, tmpY2) {
  this.tmpX2 = tmpX2;
  this.tmpY2 = tmpY2;
}
Arrow.prototype.drawBottomToTop = function (ctx) {
  if (this.x1 != this.x2) {
    this.setP1(this.x1, (this.y1 + this.y2) / 2);
    this.setP2(this.x2, (this.y1 + this.y2) / 2);
    this.draw(ctx);
  } else {
    this.draw(ctx);
  }
}
Arrow.prototype.drawLeftOrRightToTop = function (ctx) {
  this.setP1(this.x2, this.y1);
  this.draw(ctx);
}
Arrow.prototype.drawLeftToRightOrRightToLeft = function (ctx) {
  if (this.y1 != this.y2) {
    this.setP1((this.x1 + this.x2) / 2, this.y1);
    this.setP2((this.x1 + this.x2) / 2, this.y2);
    this.draw(ctx);
  } else {
    this.draw(ctx);
  }
}
Arrow.prototype.drawBottomToRight = function (ctx) {
  if (this.y1 != this.y2) {
    this.setP1(this.x1, this.y2);
    this.draw(ctx);
  } else {
    this.draw(ctx);
  }
}
Arrow.prototype.draw = function (ctx) {
  // arbitrary styling
  ctx.strokeStyle = this.color;
  ctx.fillStyle = this.color;
  // draw the line
  ctx.beginPath();
  ctx.moveTo(this.x1, this.y1);
  if (this.tmpX1 != null && this.tmpY1 != null && this.tmpX2 != null && this.tmpY2 != null) {
    ctx.lineTo(this.tmpX1, this.tmpY1);
    ctx.closePath();
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(this.tmpX1, this.tmpY1)
    ctx.lineTo(this.tmpX2, this.tmpY2);
    ctx.closePath();
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(this.tmpX2, this.tmpY2);
    ctx.lineTo(this.x2, this.y2);
    ctx.closePath();
    ctx.stroke();
    var endRadians = Math.atan((this.y2 - this.tmpY2) / (this.x2 - this.tmpX2));
    endRadians += ((this.x2 >= this.tmpX2) ? 90 : -90) * Math.PI / 180;
    this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
  } else if (this.tmpX1 != null && this.tmpY1 != null && this.tmpX2 == null && this.tmpY2 == null) {
    ctx.lineTo(this.tmpX1, this.tmpY1);
    ctx.closePath();
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(this.tmpX1, this.tmpY1)
    ctx.lineTo(this.x2, this.y2);
    ctx.closePath();
    ctx.stroke();
    var endRadians = Math.atan((this.y2 - this.tmpY1) / (this.x2 - this.tmpX1));
    endRadians += ((this.x2 >= this.tmpX1) ? 90 : -90) * Math.PI / 180;
    this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
  } else if (this.tmpX1 == null && this.tmpY1 == null && this.tmpX2 == null && this.tmpY2 == null) {
    ctx.lineTo(this.x2, this.y2);
    ctx.closePath();
    ctx.stroke();
    var endRadians = Math.atan((this.y2 - this.y1) / (this.x2 - this.x1));
    endRadians += ((this.x2 >= this.x1) ? 90 : -90) * Math.PI / 180;
    this.drawArrowhead(ctx, this.x2, this.y2, endRadians);
  }
}
Arrow.prototype.drawArrowhead = function (ctx, x, y, radians) {
  ctx.save();
  ctx.beginPath();
  ctx.translate(x, y);
  ctx.rotate(radians);
  // ctx.moveTo(0, 0);
  // ctx.lineTo(5, 10);
  // ctx.lineTo(-5, 10);
  ctx.closePath();
  ctx.restore();
  ctx.fill();
}

2.demo

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>
    <div class="box">
        <canvas id="myCanvas" width="600" height="500"></canvas>
    </div>
    <script type="text/javascript" src="./drawFlowChart.js"></script>
    <script>
        var canvas = document.getElementById("myCanvas");
        var cxt = canvas.getContext('2d');
        var canWidth = cxt.canvas.clientWidth;
        var init = { top: 32, spaceH: 100 };
        var row2 = {
            y: init.top + init.spaceH,
            data: [
                {
                    type: 'Step',
                    text: '市委办局9999人',
                    name: 'step_2_1',
                    x: '',
                    y: '',
                    requestData: {},
                    color: {
                        bgColor: '#3D6667',
                        borderColor: "#4ee9a2",
                        fontColor: "#fff",
                        lineColor: '#59B6D9'
                    }
                },
                {
                    type: 'Step',
                    text: '区县级19999人',
                    name: 'step_2_2',
                    color: {
                        bgColor: '#3D6667',
                        borderColor: "#4ee9a2",
                        fontColor: "#fff",
                        lineColor: '#59B6D9'
                    },
                    arrowArr: [
                        {
                            arrow: 'drawBottomToTop',
                            to: 'step_3_1'
                        },
                        {
                            arrow: 'drawBottomToTop',
                            to: 'step_3_2'
                        },
                        {
                            arrow: 'drawBottomToTop',
                            to: 'step_3_3'
                        },
                        {
                            arrow: 'drawBottomToTop',
                            to: 'step_3_4'
                        }
                    ]
                },
                {
                    type: 'Step',
                    text: '市级园区0人',
                    name: 'step_2_3',
                    color: {
                        bgColor: '#3D6667',
                        borderColor: "#4ee9a2",
                        fontColor: "#fff",
                        lineColor: '#59B6D9'
                    }
                }
            ]
        };
        var row3 = {
            y: init.top + init.spaceH * 2,
            data: [
                {
                    type: 'Step',
                    text: '区直属1234人',
                    name: 'step_3_1',
                    direction: 1,
                    // count: 1,
                    color: {
                        bgColor: '#4B4966',
                        borderColor: "#B75E55",
                        fontColor: "#fff",
                        lineColor: '#4ee9a2'
                    }
                },
                {
                    type: 'Step',
                    text: '乡镇街道1298人',
                    name: 'step_3_2',
                    direction: 1,
                    color: {
                        bgColor: '#4B4966',
                        borderColor: "#B75E55",
                        fontColor: "#fff",
                        lineColor: '#4ee9a2'
                    },
                    // count: 1.15,
                },
                {
                    type: 'Step',
                    text: '区委办局8752人',
                    name: 'step_3_3',
                    color: {
                        bgColor: '#4B4966',
                        borderColor: "#B75E55",
                        fontColor: "#fff",
                        lineColor: '#4ee9a2'
                    },
                    direction: 1,
                    // count: 0.95
                },
                {
                    type: 'Step',
                    text: '区县级园区9870人',
                    name: 'step_3_4',
                    color: {
                        bgColor: '#4B4966',
                        borderColor: "#B75E55",
                        fontColor: "#fff",
                        lineColor: '#4ee9a2'
                    },
                    direction: 1,
                    // count: 1
                }
            ]
        };
        row2.data = calChartX(canWidth, row2.data, row2.y);
        row3.data = calChartX(canWidth, row3.data, row3.y);
        var flowData = [
            {
                row: 1,
                y: init.top,
                data: [
                    {
                        type: 'Step',
                        text: '网格员总人数3334人',
                        name: 'step_1_1',
                        color: {
                            bgColor: '#365F87',
                            borderColor: "#59B6D9",
                            fontColor: "#fff"
                        },
                        arrowArr: [
                            {
                                arrow: 'drawBottomToTop',
                                to: 'step_2_1'
                            },
                            {
                                arrow: 'drawBottomToTop',
                                to: 'step_2_2'
                            },
                            {
                                arrow: 'drawBottomToTop',
                                to: 'step_2_3'
                            }
                        ],
                        x: canWidth / 2,
                        y: ''
                    }
                ],
                method: {
                    onmousemove: null,
                    onmouseleave: null,
                    onclick: hoverSingleChart
                }
            },
            {
                row: 2,
                y: init.top + init.spaceH,
                data: row2.data,
                method: {
                    onmousemove: null,
                    onmouseleave: null,
                    onclick: hoverSingleChart
                }
            },
            {
                row: 3,
                y: init.top + init.spaceH * 2,
                data: row3.data,
                method: {
                    onmousemove: null,
                    onmouseleave: null,
                    onclick: hoverSingleChart
                },
            }

        ];
        drawFlowChart(cxt, canvas, flowData, init.top, init.spaceH);
        function hoverSingleChart(singleData) {
            console.log(singleData);
            alert('--');
        }


    </script>
</body>

</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容