canvas繪製三次貝賽爾曲線及應用-曲線圖

微信小程序開發交流微信羣 ↓ 掃碼入羣,備註微信小程序
在這裏插入圖片描述

承接微信小程序開發。掃碼加微信。


效果圖

在這裏插入圖片描述

之後會更新成組件

利用三次貝賽爾曲線,將折線圖變曲線圖

前提-搭框架

let curve = {
  mW: 360, //canvas寬
  mH: 250, //canvas高
  mCenter: 180, //中心點
  hCenter: 125, //中心點
  curveText: [ //x軸
    ['自主型', '(AU)'],
    ['技術型', '(TF)'],
    ['挑戰型', '(CH)'],
    ['管理型', '(GM)'],
    ['服務型', '(SV)'],
    ['創造型', '(FC)'],
    ['安全型', '(SE)'],
    ['生活型', '(LS)'],
  ],
  curveColText: ['100', '80', '60', '40', '20', '0'], //y軸
  curveData: [60, 80, 48, 72, 65, 48, 72, 65],
  points: []
};
Page({
  data: {
    curve: {
      display: 'block'
    },
  },
  onLoad() {
    let curveCtx = wx.createCanvasContext('curveCanvas');
    this.drawCurve(curveCtx);
  },
  // 曲線
  drawCurve(curveCtx) {
    const that = this;
    this.drawLineBg(curveCtx); //畫橫縱座標框架
    this.drawLineColText(curve.curveColText, curveCtx); //繪製縱座標文字
    this.drawLineRowText(curve.curveText, curveCtx); //繪製橫座標文字
    this.drawCurveCtx(curve.curveData, curveCtx); //繪製曲線
    curveCtx.draw();
  },
});

第一步-繪製橫座標

// 繪製橫座標
  drawLineBg(lineCtx) {
    lineCtx.setStrokeStyle("#eeeeee");
    for (let i = 0; i < 6; i++) {
      lineCtx.moveTo(curve.mCenter - 160, 50 + 30 * i);
      lineCtx.lineTo(curve.mCenter + 160, 50 + 30 * i);
      lineCtx.stroke();
    }
  },

在這裏插入圖片描述

第二步-繪製縱座標文字

// 繪製縱座標文字
  drawLineColText(mData, lineCtx) {
    lineCtx.beginPath();
    lineCtx.setFillStyle("#999999");
    for (let i = 0; i < 6; i++) {
      lineCtx.fillText(mData[i], 10, 55 + 30 * i);
    }
  },

在這裏插入圖片描述

第三步-繪製橫座標文字

// 繪製橫座標文字
  drawLineRowText(mData, lineCtx) {
    lineCtx.setFillStyle("#555555");
    lineCtx.setFontSize(12); //設置字體
    for (let i = 0; i < 8; i++) {
      lineCtx.fillText(mData[i][0], 10 + i * 45, 220);
      lineCtx.fillText(mData[i][1], 15 + i * 45, 235);
    }
  },

在這裏插入圖片描述

第四步-繪製曲線

drawCurveCtx(mData, lineCtx) {
    curve.points = [];
    for (let i = 0; i < 8; i++) {
      curve.points.push({
        x: 29.5 + i * 45,
        y: 200 - mData[i] / 100 * 150
      });
    }
    this.drawCurvePath(curve.points, lineCtx);
  },
  // 繪製曲線背景
  drawCurvePath(path, ctx) {
    var point = getControlPoint(path);
    ctx.beginPath();
    const grd = ctx.createLinearGradient(150, 0, 150, 200);
    grd.addColorStop(0, 'rgba(51,136,255,0.5)');
    grd.addColorStop(0.7, 'rgba(51,136,255,0.5)');
    grd.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.setFillStyle(grd);
    ctx.setGlobalAlpha(0.5);
    ctx.beginPath();
    ctx.moveTo(29, 200);
    ctx.lineTo(curve.points[0].x, curve.points[0].y);
    var int = 0;
    for (var i = 0; i < curve.points.length; i++) {
      if (i == 0) {
        ctx.quadraticCurveTo(point[0].x, point[0].y, curve.points[1].x, curve.points[1].y);
        int = int + 1;
      } else if (i < curve.points.length - 2) {
        ctx.bezierCurveTo(point[int].x, point[int].y, point[int + 1].x, point[int + 1].y, curve.points[i + 1].x, curve.points[i + 1].y);
        int += 2;
      } else if (i == curve.points.length - 2) {
        ctx.quadraticCurveTo(point[point.length - 1].x, point[point.length - 1].y, curve.points[curve.points.length - 1].x, curve.points[curve.points.length - 1].y);
      }
    }
    ctx.lineTo(curve.points[curve.points.length - 1].x, 200);
    ctx.fill();
    ctx.closePath();
    this.drawCurveSign(point, ctx)
  },
  // 繪製點加線
  drawCurveSign(point, ctx) {
    // 繪製線
    ctx.beginPath();
    ctx.setStrokeStyle("#3388FF");
    ctx.setGlobalAlpha(1);
    ctx.setLineWidth(1.5);
    var int = 0;
    ctx.moveTo(curve.points[0].x, curve.points[0].y);
    for (var i = 0; i < curve.points.length; i++) {
      if (i == 0) {
        ctx.quadraticCurveTo(point[0].x, point[0].y, curve.points[1].x, curve.points[1].y);
        int = int + 1;
      } else if (i < curve.points.length - 2) {
        ctx.bezierCurveTo(point[int].x, point[int].y, point[int + 1].x, point[int + 1].y, curve.points[i + 1].x, curve.points[i + 1].y);
        int += 2;
      } else if (i == curve.points.length - 2) {
        ctx.quadraticCurveTo(point[point.length - 1].x, point[point.length - 1].y, curve.points[curve.points.length - 1].x, curve.points[curve.points.length - 1].y);
      }
    }
    ctx.stroke();
    // 繪製點
    ctx.beginPath();
    ctx.setGlobalAlpha(1);
    for (let i = 0; i < curve.points.length; i++) {
      ctx.beginPath();
      ctx.arc(curve.points[i].x, curve.points[i].y, 5, 0, 2 * Math.PI);
      ctx.setFillStyle("#3388FF");
      ctx.fill();
      ctx.closePath();
    }
  },

在這裏插入圖片描述

// 折線變曲線
let Vector2 = function (x, y) {
  this.x = x;
  this.y = y;
};
Vector2.prototype = {
  "length": function () {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  },
  "normalize": function () {
    let inv = 1 / this.length() == Infinity ? 0 : 1 / this.length();
    return new Vector2(this.x * inv, this.y * inv);
  },
  "add": function (v) {
    return new Vector2(this.x + v.x, this.y + v.y);
  },
  "multiply": function (f) {
    return new Vector2(this.x * f, this.y * f);
  },
  "dot": function (v) {
    return this.x * v.x + this.y * v.y;
  },
  "angle": function (v) {
    return Math.acos(this.dot(v) / (this.length() * v.length())) * 180 / Math.PI;
  }
};
function getControlPoint(path) {
  let rt = 0.3;
  let count = path.length - 2;
  let arr = [];
  for (let i = 0; i < count; i++) {
    let a = path[i];
    let b = path[i + 1];
    let c = path[i + 2];
    let v1 = new Vector2(a.x - b.x, a.y - b.y);
    let v2 = new Vector2(c.x - b.x, c.y - b.y);
    let v1Len = v1.length();
    let v2Len = v2.length();
    let centerV = v1.normalize().add(v2.normalize()).normalize();
    let ncp1 = new Vector2(centerV.y, centerV.x * -1);
    let ncp2 = new Vector2(centerV.y * -1, centerV.x);
    if (ncp1.angle(v1) < 90) {
      let p1 = ncp1.multiply(v1Len * rt).add(b);
      let p2 = ncp2.multiply(v2Len * rt).add(b);
      arr.push(p1, p2);
    } else {
      let p1 = ncp1.multiply(v2Len * rt).add(b);
      let p2 = ncp2.multiply(v1Len * rt).add(b);
      arr.push(p2, p1);
    }
  }
  return arr;
};

全部代碼

let curve = {
  mW: 360, //canvas寬
  mH: 250, //canvas高
  mCenter: 180, //中心點
  hCenter: 125, //中心點
  curveText: [ //x軸
    ['自主型', '(AU)'],
    ['技術型', '(TF)'],
    ['挑戰型', '(CH)'],
    ['管理型', '(GM)'],
    ['服務型', '(SV)'],
    ['創造型', '(FC)'],
    ['安全型', '(SE)'],
    ['生活型', '(LS)'],
  ],
  curveColText: ['100', '80', '60', '40', '20', '0'], //y軸
  curveData: [60, 80, 48, 72, 65, 48, 72, 65],
  points: []
};
Page({
  data: {
    curve: {
      display: 'block'
    },
  },
  onLoad() {
    let curveCtx = wx.createCanvasContext('curveCanvas');
    this.drawCurve(curveCtx);
  },
  // ***************************曲線開始-職業傾向維度解析********************************
  // 曲線
  drawCurve(curveCtx) {
    const that = this;
    this.drawLineBg(curveCtx); //畫橫縱座標框架
    this.drawLineColText(curve.curveColText, curveCtx); //繪製縱座標文字
    this.drawLineRowText(curve.curveText, curveCtx); //繪製橫座標文字
    this.drawCurveCtx(curve.curveData, curveCtx); //繪製曲線
    curveCtx.draw();
  },
  drawCurveCtx(mData, lineCtx) {
    curve.points = [];
    for (let i = 0; i < 8; i++) {
      curve.points.push({
        x: 29.5 + i * 45,
        y: 200 - mData[i] / 100 * 150
      });
    }
    this.drawCurvePath(curve.points, lineCtx);
  },
  // 繪製曲線背景
  drawCurvePath(path, ctx) {
    var point = getControlPoint(path);
    ctx.beginPath();
    const grd = ctx.createLinearGradient(150, 0, 150, 200);
    grd.addColorStop(0, 'rgba(51,136,255,0.5)');
    grd.addColorStop(0.7, 'rgba(51,136,255,0.5)');
    grd.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.setFillStyle(grd);
    ctx.setGlobalAlpha(0.5);
    ctx.beginPath();
    ctx.moveTo(29, 200);
    ctx.lineTo(curve.points[0].x, curve.points[0].y);
    var int = 0;
    for (var i = 0; i < curve.points.length; i++) {
      if (i == 0) {
        ctx.quadraticCurveTo(point[0].x, point[0].y, curve.points[1].x, curve.points[1].y);
        int = int + 1;
      } else if (i < curve.points.length - 2) {
        ctx.bezierCurveTo(point[int].x, point[int].y, point[int + 1].x, point[int + 1].y, curve.points[i + 1].x, curve.points[i + 1].y);
        int += 2;
      } else if (i == curve.points.length - 2) {
        ctx.quadraticCurveTo(point[point.length - 1].x, point[point.length - 1].y, curve.points[curve.points.length - 1].x, curve.points[curve.points.length - 1].y);
      }
    }
    ctx.lineTo(curve.points[curve.points.length - 1].x, 200);
    ctx.fill();
    ctx.closePath();
    this.drawCurveSign(point, ctx)
  },
  // 繪製點加線
  drawCurveSign(point, ctx) {
    // 繪製線
    ctx.beginPath();
    ctx.setStrokeStyle("#3388FF");
    ctx.setGlobalAlpha(1);
    ctx.setLineWidth(1.5);
    var int = 0;
    ctx.moveTo(curve.points[0].x, curve.points[0].y);
    for (var i = 0; i < curve.points.length; i++) {
      if (i == 0) {
        ctx.quadraticCurveTo(point[0].x, point[0].y, curve.points[1].x, curve.points[1].y);
        int = int + 1;
      } else if (i < curve.points.length - 2) {
        ctx.bezierCurveTo(point[int].x, point[int].y, point[int + 1].x, point[int + 1].y, curve.points[i + 1].x, curve.points[i + 1].y);
        int += 2;
      } else if (i == curve.points.length - 2) {
        ctx.quadraticCurveTo(point[point.length - 1].x, point[point.length - 1].y, curve.points[curve.points.length - 1].x, curve.points[curve.points.length - 1].y);
      }
    }
    ctx.stroke();
    // 繪製點
    ctx.beginPath();
    ctx.setGlobalAlpha(1);
    for (let i = 0; i < curve.points.length; i++) {
      ctx.beginPath();
      ctx.arc(curve.points[i].x, curve.points[i].y, 5, 0, 2 * Math.PI);
      ctx.setFillStyle("#3388FF");
      ctx.fill();
      ctx.closePath();
    }
  },
  // 畫橫座標
  drawLineBg(lineCtx) {
    lineCtx.setStrokeStyle("#eeeeee");
    for (let i = 0; i < 6; i++) {
      lineCtx.moveTo(curve.mCenter - 160, 50 + 30 * i);
      lineCtx.lineTo(curve.mCenter + 160, 50 + 30 * i);
      lineCtx.stroke();
    }
  },
  // 繪製橫座標文字
  drawLineRowText(mData, lineCtx) {
    lineCtx.setFillStyle("#555555");
    lineCtx.setFontSize(12); //設置字體
    for (let i = 0; i < 8; i++) {
      lineCtx.fillText(mData[i][0], 10 + i * 45, 220);
      lineCtx.fillText(mData[i][1], 15 + i * 45, 235);
    }
  },
  // 繪製縱座標文字
  drawLineColText(mData, lineCtx) {
    lineCtx.beginPath();
    lineCtx.setFillStyle("#999999");
    for (let i = 0; i < 6; i++) {
      lineCtx.fillText(mData[i], 10, 55 + 30 * i);
    }
  },
});
// 折線變曲線
let Vector2 = function(x, y) {
  this.x = x;
  this.y = y;
};
Vector2.prototype = {
  "length": function() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  },
  "normalize": function() {
    let inv = 1 / this.length() == Infinity ? 0 : 1 / this.length();
    return new Vector2(this.x * inv, this.y * inv);
  },
  "add": function(v) {
    return new Vector2(this.x + v.x, this.y + v.y);
  },
  "multiply": function(f) {
    return new Vector2(this.x * f, this.y * f);
  },
  "dot": function(v) {
    return this.x * v.x + this.y * v.y;
  },
  "angle": function(v) {
    return Math.acos(this.dot(v) / (this.length() * v.length())) * 180 / Math.PI;
  }
};

function getControlPoint(path) {
  let rt = 0.3;
  let count = path.length - 2;
  let arr = [];
  for (let i = 0; i < count; i++) {
    let a = path[i];
    let b = path[i + 1];
    let c = path[i + 2];
    let v1 = new Vector2(a.x - b.x, a.y - b.y);
    let v2 = new Vector2(c.x - b.x, c.y - b.y);
    let v1Len = v1.length();
    let v2Len = v2.length();
    let centerV = v1.normalize().add(v2.normalize()).normalize();
    let ncp1 = new Vector2(centerV.y, centerV.x * -1);
    let ncp2 = new Vector2(centerV.y * -1, centerV.x);
    if (ncp1.angle(v1) < 90) {
      let p1 = ncp1.multiply(v1Len * rt).add(b);
      let p2 = ncp2.multiply(v2Len * rt).add(b);
      arr.push(p1, p2);
    } else {
      let p1 = ncp1.multiply(v2Len * rt).add(b);
      let p2 = ncp2.multiply(v1Len * rt).add(b);
      arr.push(p2, p1);
    }
  }
  return arr;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章