Vue canvas画图画线例子,数据回显与隔离,点拖拽修改

组件

<template>
  <div
    style="display: flex; height: 342px; width: 760px; border: 1px solid #000"
  >
    <div
      style="position: relative; height: 100%; width: 608px; min-width: 608px"
    >
      <canvas
        id="mycanvas"
        ref="mycanvas"
        :width="canvasWidth"
        :height="canvasHeight"
        @mousedown="canvasDown($event)"
        @mousemove="canvasMove($event)"
        @mouseup="canvasUp($event)"
        @dblclick="doubleclick()"
      ></canvas>
    </div>
    <div
      style="
        display: flex;
        flex-direction: column;
        padding: 10px;
        border: 1px solid #cbd2f7;
      "
    >
      <el-button
        size="mini"
        style="margin-left: 0; margin-top: 10px"
        @click="beginDraw = !beginDraw"
        :type="beginDraw == false ? 'primary' : ''"
        >{{ beginDraw ? "结束绘制" : "开始绘制" }}</el-button
      >
      <el-button
        size="mini"
        @click="clearAll"
        style="margin-left: 0; margin-top: 10px"
        >清空绘制区域</el-button
      >
      <div>
        <el-select
          size="mini"
          placeholder="请选择绘制类型"
          v-model="roiType"
          @change="clearAll"
          style="margin-top: 10px"
        >
          <el-option label="区域" value="1" v-if="myRoiType == 1"></el-option>
          <el-option label="线条" value="2" v-if="myRoiType == 2"></el-option>
          <el-option
            label="单区域+单向单拌线"
            value="3"
            v-if="myRoiType == 3"
          ></el-option>
        </el-select>
        <el-select
          size="mini"
          placeholder="请选择线条方向类型"
          v-model="directionType"
          v-show="roiType != '1'"
          :disabled="directionTypeDisabled"
          style="margin-top: 10px"
          @change="clearAll"
        >
          <el-option
            v-for="item in directionTypeOptions"
            :key="item.value"
            :label="item.label"
            :value="item.value"
          ></el-option>
        </el-select>
        <div style="display: flex">
          <el-button
            style="width: 50%; margin-top: 10px"
            size="mini"
            :type="drawType == 1 ? 'primary' : ''"
            @click="drawType = 1"
            v-show="roiType == 3"
            >区域</el-button
          >
          <el-button
            size="mini"
            :type="drawType == 2 ? 'primary' : ''"
            @click="drawType = 2"
            v-show="roiType == 3"
            style="width: 50%; margin-top: 10px"
            >直线</el-button
          >
        </div>
        <!-- </el-button-group> -->
      </div>

      <div class="staticLabels">
        <div
          class="staticLabels_item"
          v-for="(item, index) in allDrawList"
          @mouseenter="canvasMouseenter(item)"
          @mouseleave="canvasMouseleave()"
          v-if="(index >= 0 && !isMultiLine) || (index > 0 && isMultiLine)"
        >
          <span class="staticLabels_item_span">
            {{ item.areaName }}
          </span>
          <i
            class="el-icon-close"
            @click="canvasDeleteOne(item)"
            style="cursor: pointer"
            title="删除"
          ></i>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    canvasKey: {
      type: String,
      default: () => "first",
    },
    // 画图数据
    myDrawList: {
      type: Array,
      default: () => [],
    },
    myRoiType: {
      type: String,
      default: () => "1",
    },
  },
  data() {
    return {
      isMultiLine: false, //是否多拌线
      localMyDrawList: [],
      finalArr: [],

      roiType: "1",
      roiTypeOptions: [
        {
          value: "1",
          label: "区域",
        },
        {
          value: "2",
          label: "线条",
        },
        {
          value: "3",
          label: "单区域+单向单拌线",
        },
      ],
      directionTypeDisabled: false,
      directionType: "1",
      directionTypeOptions: [
        {
          value: "1",
          label: "单向单拌线",
        },
        {
          value: "2",
          label: "双向单拌线",
        },
        {
          value: "3",
          label: "单向多拌线",
        },
        {
          value: "4",
          label: "双向多拌线",
        },
      ],
      //
      canvasId: 1,
      drawType: "1", // 绘制类型 1-多边形 2-线条
      arrowType: "1", // 箭头类型 1-单向 2-双向
      all_line_coordinates: [
        // {
        //   canvasId: 1,
        //   areaName: '直线1',
        //   directionType:1,
        //   points_coordinates: [{ cor_x: 100, cor_y: 100 }],
        // }
      ], //所有线条的信息
      MaxAreaPointsNum: 10, //多边形的最大顶点数
      MaxLinePointsNum: 5, //线条的最大顶点数
      MaxAreaNum: 10, //多边形的最大数量
      MaxLineNum: 10, //线条的最大数量
      //
      isdraw: false, //是否在画图形
      ctx: null, //canvas对象
      coordinates: [], //当前图形的坐标信息
      all_coordinates: [
        // {
        //   canvasId: 1,
        //   areaName: '区域1',
        //   points_coordinates: [{ cor_x: 100, cor_y: 100 }],
        // },
      ], //所有多边形的信息
      isdrag: false, //是否拖拽点
      isdragType: 1, //拖拽类型 1-拖动多边形 2-拖动线条
      drag_index: [-1, -1], // 拖拽索引
      beginDraw: false, //开始作画
      colorList: [
        // "rgba(88,87,86,.4)",
        // "rgba(252,230,202,.4)",
        // "rgba(0,199,140,.4)",
        // "rgba(227,23,13,.4)",
        // "rgba(153,51,250,.4)",
        // "rgba(199,97,20,.4)",
        // "rgba(250,240,230,.4)",
        // "rgba(188,143,143,.4)",
        // "rgba(0,255,0,.4)",
        // "rgba(244,164,95,.4)",

        // "rgba(128,42,42,.4)",
        // "rgba(64,224,205,.4)",
        // "rgba(237,145,33,.4)",
        // "rgba(34,139,34,.4)",
        // "rgba(255,125,64,.4)",
        // "rgba(107,142,35,.4)",
        // "rgba(227,207,87,.4)",
        // "rgba(3,168,158,.4)",
        // "rgba(255,255,255,.4)",
        // "rgba(255,255,0,.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",

        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",
        "rgba(16, 128, 219, 0.4)",

        "rgba(255,0,0,.6)", //上色
      ],
      canvasWidth: 608, //画布宽度
      canvasHeight: 342, //画布高度
      //
      cor_index: 0, //当前多边形的索引 未开发
      isFirst: true,
    };
  },
  watch: {
    canvasKey: {
      handler(val) {
        this.clearAll();
        this.showMyDrawList();
      },
      deep: true,
      immediate: true,
    },
    myRoiType: {
      handler(val) {
        if (val) {
          this.clearAll();
          this.roiType = val;
        //   this.showMyDrawList();
        }
      },
      // deep:true,
      immediate: true,
    },
    // 区域类型:1-区域;2-线条;3-区域+单向线条
    roiType: {
      handler(val) {
        if (val) {
          this.MaxAreaNum = 10;
          this.MaxLineNum = 10;
          this.directionTypeDisabled = false;
          if (val == 1) {
            this.drawType = 1;
          } else if (val == 2) {
            this.drawType = 2;
            this.MaxLineNum = 1;
          }
          // 3-单区域+单向线条
          else if (val == 3) {
            this.MaxAreaNum = 1;
            this.MaxLineNum = 1;
            this.arrowType = 1;
            this.directionType = "1";
            this.directionTypeDisabled = true;
          }
        }
        this.handleMultiLine();
      },
      deep: true,
      immediate: true,
    },
    //线条方向类型:1-单向单拌线,2-双向单拌线,3-单向多拌线,4-双向多拌线
    directionType: {
      handler(val) {
        if (val) {
          // 1-单向单拌线
          if (val == 1) {
            this.arrowType = 1;
            this.MaxLineNum = 1;
          }
          // 2-双向单拌线
          else if (val == 2) {
            this.arrowType = 2;
            this.MaxLineNum = 1;
          }
          // 3-单向多拌线
          else if (val == 3) {
            this.arrowType = 1;
            this.MaxLineNum = 2;
          }
          // 4-双向多拌线
          else if (val == 4) {
            this.arrowType = 2;
            this.MaxLineNum = 2;
          }
        }
        this.handleMultiLine();
      },
      deep: true,
      // immediate: true,
    },
    allDrawList: {
      handler(val) {
        // 处理数据-------------------------------------------------
        var guardAreas = [];
        val &&
          val.length > 0 &&
          val.forEach((item, index) => {
            var areaName = item.areaName;
            // 线段
            if (item.directionType) {
              var directionType = item.directionType;
              var linePoints = [];
              item.points_coordinates &&
                item.points_coordinates.length > 0 &&
                item.points_coordinates.forEach((item2, index2) => {
                  linePoints.push({
                    x: (item2.cor_x / this.canvasWidth).toFixed(4),
                    y: (item2.cor_y / this.canvasHeight).toFixed(4),
                  });
                });
              var obj = {
                areaName: areaName,
                directionType: directionType,
                linePoints: linePoints,
              };
              guardAreas.push(obj);
            }
            // 区域
            else {
              var directionType = item.directionType;
              var points = [];
              item.points_coordinates &&
                item.points_coordinates.length > 0 &&
                item.points_coordinates.forEach((item2, index2) => {
                  points.push({
                    x: (item2.cor_x / this.canvasWidth).toFixed(4),
                    y: (item2.cor_y / this.canvasHeight).toFixed(4),
                  });
                });
              // 闭合区域 补起点
              if (
                points[0].x !== points[points.length - 1].x ||
                points[0].y !== points[points.length - 1].y
              ) {
                points.push({
                  x: points[0].x,
                  y: points[0].y,
                });
              }
              var obj = {
                areaName: areaName,
                points: points,
              };
              guardAreas.push(obj);
            }
          });
        var finalObj = {
          videoCode: this.canvasKey,
          guardAreas: guardAreas,
        };
        // console.log('回传数据',finalObj);
        var myDrawList = JSON.parse(JSON.stringify(this.localMyDrawList));
        var index = myDrawList.findIndex(
          (v) => v.videoCode == finalObj.videoCode
        );
        if (index != -1) {
          myDrawList.splice(index, 1, finalObj);
        } else {
          myDrawList.push(finalObj);
        }
        // this.$emit("drawListChange", myDrawList);
        // console.log("数据回传", myDrawList);
        this.finalArr = [].concat(myDrawList);
        this.localMyDrawList = [].concat(myDrawList);
        // 处理数据----------------------------------------------
        if (val.length == 0) {
          this.canvasId = 1;
        }
      },
      deep: true,
      immediate: true,
    },

    myDrawList: {
      handler(val) {
        this.clearAll();
        if (val) {
          this.localMyDrawList = [].concat(val);
          this.showMyDrawList();
        }
      },
      deep: true,
      immediate: true,
    },

    MaxLineNum: {
      handler(val) {
        this.handleMultiLine();
      },
      // deep: true,
      immediate: true,
    },
  },
  computed: {
    allDrawList: {
      get() {
        return this.all_coordinates.concat(this.all_line_coordinates);
      },
    },
  },
  mounted() {
    this.initDraw();
    document.getElementById("mycanvas").oncontextmenu = function (e) {
      e.preventDefault(); //阻止默认右键菜单
    };
  },
  methods: {
    handleMultiLine() {
      if (this.MaxLineNum == 2 && this.roiType == 2) {
        this.isMultiLine = true;
      } else {
        this.isMultiLine = false;
      }
    },
    // 回显
    showMyDrawList() {
      var val = this.localMyDrawList || [];
      if (this.canvasKey) {
        // console.log("画图回显 localMyDrawList", val);
        val.forEach((itemOut, index) => {
          if (itemOut.videoCode == this.canvasKey) {
            // console.log("当前", itemOut, itemOut.guardAreas);
            itemOut.guardAreas &&
              itemOut.guardAreas.length &&
              itemOut.guardAreas.forEach((item, index) => {
                // 线段
                if (item.directionType) {
                  this.directionType = item.directionType;
                  // console.log("回显切换", item.directionType);
                  if (item.directionType == 1 || item.directionType == 3)
                    this.arrowType = 1;
                  else this.arrowType = 2;
                  var areaName = item.areaName;
                  var directionType = item.directionType;
                  var canvasId = Number(item.areaName.substring(2));
                  var points_coordinates = [];
                  item.linePoints.forEach((points, index) => {
                    // 回显的时候不需要最后一个点,因为他跟第一个点重合
                    if (
                      index != 0 &&
                      points.x == item.linePoints[0].x &&
                      points.y == item.linePoints[0].y
                    ) {
                      return;
                    }
                    points_coordinates.push({
                      // cor_x: (points.x * this.canvasWidth).toFixed(0),
                      // cor_y: (points.y * this.canvasHeight).toFixed(0),
                      cor_x: Math.ceil(points.x * this.canvasWidth),
                      cor_y: Math.ceil(points.y * this.canvasHeight),
                    });
                  });
                  var obj = {
                    canvasId: canvasId,
                    areaName: areaName,
                    directionType: directionType,
                    points_coordinates: points_coordinates,
                  };
                  this.canvasId = ++canvasId;
                  this.all_line_coordinates.push(obj);
                }
                // 区域
                else {
                  var areaName = item.areaName;
                  var canvasId = Number(item.areaName.substring(2));
                  var points_coordinates = [];
                  item.points.forEach((points, index) => {
                    // 回显的时候不需要最后一个点,因为他跟第一个点重合
                    if (
                      index != 0 &&
                      points.x == item.points[0].x &&
                      points.y == item.points[0].y
                    ) {
                      return;
                    }
                    points_coordinates.push({
                      // cor_x: (points.x * this.canvasWidth).toFixed(0),
                      // cor_y: (points.y * this.canvasHeight).toFixed(0),
                      cor_x: Math.ceil(points.x * this.canvasWidth),
                      cor_y: Math.ceil(points.y * this.canvasHeight),
                    });
                  });
                  var obj = {
                    canvasId: canvasId,
                    areaName: areaName,
                    points_coordinates: points_coordinates,
                  };
                  this.canvasId = ++canvasId;
                  this.all_coordinates.push(obj);
                }
              });
            this.$nextTick(() => {
              // console.log("回显函数");
              this.drawAll();
            });
          }
        });
      }
    },
    getData() {
      return this.finalArr;
    },
    // 删除一个图形
    canvasDeleteOne(data) {
      if (this.isMultiLine) {
        this.all_line_coordinates = [];
        this.drawAll();
      } else {
        if (data.directionType) {
          var index = this.all_line_coordinates.findIndex(
            (v) => v.canvasId == data.canvasId
          );
          if (index != -1) this.all_line_coordinates.splice(index, 1);
        } else {
          var index = this.all_coordinates.findIndex(
            (v) => v.canvasId == data.canvasId
          );
          if (index != -1) this.all_coordinates.splice(index, 1);
        }
        this.drawAll();
      }
    },
    // 鼠标悬浮,给指定图形上色
    canvasMouseenter(data) {
      this.drawAll(data);
    },
    // 离开取消上色
    canvasMouseleave() {
      this.drawAll();
    },
    // 绘制所有图形
    drawAll(data) {
      // console.log(this.all_coordinates, this.all_line_coordinates);
      this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      if (
        this.all_coordinates.length != 0 ||
        this.all_line_coordinates.length != 0
      ) {
        this.drawlines(data);
        this.drawcircles();
        this.fillarea(data);
      }
    },
    // ----------------------------------------------------------
    // 显示箭头
    showArrow(x1, y1, x2, y2) {
      var xm = (x1 + x2) / 2;
      var ym = (y1 + y2) / 2;
      var d = 30; //箭头长度 距离
      if (this.arrowType == 1) {
        var { x, y } = this.getArrowPoint(x1, y1, x2, y2, d);
        this.drawArrow(this.ctx, xm, ym, x, y, 30, 10, 1, "#f36");
      } else {
        var { x3, y3, x4, y4 } = this.getArrowPoint(x1, y1, x2, y2, d);
        this.drawArrow(this.ctx, xm, ym, x3, y3, 30, 10, 1, "#f36");
        this.drawArrow(this.ctx, xm, ym, x4, y4, 30, 10, 1, "#f36");
      }
    },
    // 获取箭头坐标点
    getArrowPoint(x1, y1, x2, y2, d) {
      var x0 = (x1 + x2) / 2;
      var y0 = (y1 + y2) / 2;
      var k1 = (y2 - y1) / (x2 - x1);
      var k2 = -1 / k1;
      var num = Math.sqrt(d ** 2 / (1 + k2 ** 2));
      var x3 = num + x0;
      var y3 = k2 * (x3 - x0) + y0;
      var x4 = 0 - num + x0;
      var y4 = k2 * (x4 - x0) + y0;
      // 平行于x轴
      if (y2 == y1) {
        x3 = x0;
        x4 = x0;
        y3 = y0 + d;
        y4 = y0 - d;
      }
      // 平行于y轴
      else if (x2 == x1) {
        x3 = x0 + d;
        x4 = x0 - d;
        y3 = y0;
        y4 = y0;
      }
      x3 = Math.floor(x3);
      y3 = Math.floor(y3);
      x4 = Math.floor(x4);
      y4 = Math.floor(y4);
      var x, y;
      // 返回向上
      if (x2 > x1) {
        y = y3 < y0 ? y3 : y4;
        x = y == y3 ? x3 : x4;
      }
      // 返回向下
      else {
        y = y3 < y0 ? y4 : y3;
        x = y == y3 ? x3 : x4;
      }
      if (this.arrowType == 1) {
        return { x, y };
      } else {
        return { x3, y3, x4, y4 };
      }
    },
    // ----------------------------------------------------------
    /**
     * @param  dot {{x,y}} 需要判断的点
     * @param  coordinates {{x,y}[]} 多边形点坐标的数组,为保证图形能够闭合,起点和终点必须相等。
     *        比如三角形需要四个点表示,第一个点和最后一个点必须相同。
     */
    // 判断点是否点击图形
    judge(dot, coordinates) {
      var x = dot.x,
        y = dot.y;
      var crossNum = 0;
      // 点在线段的左侧数目
      var leftCount = 0;
      // 点在线段的右侧数目
      var rightCount = 0;
      for (var i = 0; i < coordinates.length - 1; i++) {
        var start = coordinates[i];
        var end = coordinates[i + 1];

        // 起点、终点斜率不存在的情况
        if (start.x === end.x) {
          // 因为射线向右水平,此处说明不相交
          if (x > start.x) continue;

          // 从左侧贯穿
          if (end.y > start.y && y >= start.y && y <= end.y) {
            leftCount++;
            crossNum++;
          }
          // 从右侧贯穿
          if (end.y < start.y && y >= end.y && y <= start.y) {
            rightCount++;
            crossNum++;
          }
          continue;
        }
        // 斜率存在的情况,计算斜率
        var k = (end.y - start.y) / (end.x - start.x);
        // 交点的x坐标
        var x0 = (y - start.y) / k + start.x;
        // 因为射线向右水平,此处说明不相交
        if (x > x0) continue;

        if (end.x > start.x && x0 >= start.x && x0 <= end.x) {
          crossNum++;
          if (k >= 0) leftCount++;
          else rightCount++;
        }
        if (end.x < start.x && x0 >= end.x && x0 <= start.x) {
          crossNum++;
          if (k >= 0) rightCount++;
          else leftCount++;
        }
      }
      return leftCount - rightCount !== 0;
    },
    // 判断点是否在直线上
    judgeLine(x1, y1, x2, y2, x, y) {
      var crossProduct = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1);
      // 如果不等于0,说明不共线,直接返回false
      if (crossProduct !== 0) {
        return false;
      }
      // 否则,检查c点是否在ab线段的范围内
      return (
        Math.min(x1, x2) <= x &&
        x <= Math.max(x1, x2) &&
        Math.min(y1, y2) <= y &&
        y <= Math.max(y1, y2)
      );
    },
    initDraw() {
      //初始化画布对象
      const canvas = document.querySelector("#mycanvas");
      this.ctx = canvas.getContext("2d");
      // this.ctx.strokeStyle = "rgb(0, 195, 155)";
      this.ctx.strokeStyle = "#f36";
    },
    clearAll() {
      console.log("clearAll");
      this.all_coordinates = [];
      this.all_line_coordinates = [];
      this.coordinates = [];
      this.isdraw = false;
      this.canvasId = 1;
      this.$nextTick(() => {
        this.ctx &&
          this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      });
    },
    // 判断是否在拖拽点
    isdragpoint(x, y) {
      if (
        this.all_coordinates.length == 0 &&
        this.all_line_coordinates.length == 0
      ) {
        return false;
      }
      for (var i = 0; i < this.all_coordinates.length; i++) {
        for (
          var j = 0;
          j < this.all_coordinates[i].points_coordinates.length;
          j++
        ) {
          var px = this.all_coordinates[i].points_coordinates[j].cor_x;
          var py = this.all_coordinates[i].points_coordinates[j].cor_y;
          //   允许偏移量5
          if (Math.abs(x - px) <= 5 && Math.abs(y - py) <= 5) {
            this.drag_index[0] = i;
            this.drag_index[1] = j;
            this.isdragType = 1;
            return true;
          }
        }
      }
      for (var i = 0; i < this.all_line_coordinates.length; i++) {
        for (
          var j = 0;
          j < this.all_line_coordinates[i].points_coordinates.length;
          j++
        ) {
          var px = this.all_line_coordinates[i].points_coordinates[j].cor_x;
          var py = this.all_line_coordinates[i].points_coordinates[j].cor_y;
          //   允许偏移量5
          if (Math.abs(x - px) <= 5 && Math.abs(y - py) <= 5) {
            this.drag_index[0] = i;
            this.drag_index[1] = j;
            this.isdragType = 2;
            return true;
          }
        }
      }
      return false;
    },
    // 鼠标按下事件
    canvasDown(e) {
      var x = e.offsetX;
      var y = e.offsetY;
      // 鼠标右键
      if (e.button == 2) {
        if (!this.beginDraw) {
          return;
        }
        if (this.coordinates.length) {
          var last_x = this.coordinates[this.coordinates.length - 1].cor_x;
          var last_y = this.coordinates[this.coordinates.length - 1].cor_y;
          if (last_x == x && last_y == y) {
            // this.$message.error("不能重复点击");
            // 同一个点 不记录
            this.doubleclick();
            return; //同步放开 二选一
          } else {
            this.coordinates.push({ cor_x: x, cor_y: y });
            this.doubleclick();
            return;
          }
        }
        // 点击顶点
        if (this.isdragpoint(x, y)) {
          // 开启弹窗
          console.log("开启弹窗");
          if (this.isdragType == 1) {
            this.all_coordinates.splice(this.drag_index[0], 1);
          } else {
            this.all_line_coordinates.splice(this.drag_index[0], 1);
          }
          this.drawAll();
          return;
        }
        // 点击图像
        if (this.all_coordinates.length) {
          let dot = { x: x, y: y };
          var flag = false;
          var arr = JSON.parse(
            JSON.stringify(
              this.all_coordinates.map((v) => v.points_coordinates)
            )
          );
          for (var i = 0; i < arr.length; i++) {
            arr[i].push(arr[i][0]);
            arr[i].forEach((item) => {
              (item.x = item.cor_x), (item.y = item.cor_y);
            });
            if (this.judge(dot, arr[i])) {
              flag = true;
              console.log("点击到了多边形上", i);
              this.all_coordinates.splice(i, 1);
              this.drawAll();
              break;
            }
          }
          if (flag) {
            return;
          }
        }
        // 点击直线 人手很难做到
        if (this.all_line_coordinates.length) {
          var flag = false;
          for (var i = 0; i < this.all_line_coordinates.length; i++) {
            for (
              var j = 0;
              j < this.all_line_coordinates[i].points_coordinates.length - 1;
              j++
            ) {
              var x1 = this.all_line_coordinates[i].points_coordinates[j].cor_x;
              var y1 = this.all_line_coordinates[i].points_coordinates[j].cor_y;
              var x2 =
                this.all_line_coordinates[i].points_coordinates[j + 1].cor_x;
              var y2 =
                this.all_line_coordinates[i].points_coordinates[j + 1].cor_y;
              if (this.judgeLine(x1, y1, x2, y2, x, y)) {
                //判断是否点击到了线上
                console.log("点击到了线上", i);
                this.all_line_coordinates.splice(i, 1);
                this.drawAll();
                flag = true;
                break;
              }
            }
          }
          if (flag) {
            return;
          }
        }
      }
      // 鼠标左键
      else if (e.button == 0) {
        if (this.isdragpoint(x, y)) {
          this.isdrag = true; //开启拖拽
          return;
        }

        if (!this.beginDraw) {
          return;
        }
        // 同一个点 不记录
        if (this.coordinates.length) {
          var last_x = this.coordinates[this.coordinates.length - 1].cor_x;
          var last_y = this.coordinates[this.coordinates.length - 1].cor_y;
          if (last_x == x && last_y == y) {
            // this.$message.error("不能重复点击");
            return; //同步放开 二选一
          }
        }

        //获取鼠标按下的坐标,放入数组中

        if (this.drawType == 1) {
          if (this.all_coordinates.length == this.MaxAreaNum) {
            this.$message.error("最多只能画" + this.MaxAreaNum + "个多边形");
            return;
          }
          if (this.coordinates.length + 1 == this.MaxAreaPointsNum) {
            this.$message.error(
              "多边形最多只能画" + this.MaxAreaPointsNum + "个点"
            );
            this.coordinates.push({ cor_x: x, cor_y: y });
            // this.coordinates.push({ cor_x: x, cor_y: y });
            this.doubleclick();
            return;
          }
        } else if (this.drawType == 2) {
          if (this.all_line_coordinates.length == this.MaxLineNum) {
            this.$message.error("最多只能画" + this.MaxLineNum + "条线条");
            return;
          }
          if (this.coordinates.length + 1 == this.MaxLinePointsNum) {
            this.$message.error(
              "线条最多只能画" + this.MaxLinePointsNum + "个点"
            );
            this.coordinates.push({ cor_x: x, cor_y: y });
            // this.coordinates.push({ cor_x: x, cor_y: y });
            this.doubleclick();
            return;
          }
        }
        this.coordinates.push({ cor_x: x, cor_y: y });
        this.isdraw = true; //正在画多边形
      }
    },
    // 鼠标松开事件
    canvasUp(e) {
      // if (!this.beginDraw) {
      //   return;
      // }
      if (this.isdrag) {
        this.isdrag = false; //关闭拖拽点状态
      }
      this.drag_index = [-1, -1];
      this.drawcircle(); //松开画点
    },
    //鼠标移动事件
    canvasMove(e) {
      //没开始画或者结束画之后不进行操作
      var x = e.offsetX;
      var y = e.offsetY;

      const canvas = document.querySelector("#mycanvas");
      if (this.isdragpoint(x, y)) {
        canvas.style.cursor = "pointer";
      } else {
        canvas.style.cursor = "default";
      }

      if (this.isdrag) {
        if (this.isdragType == 1) {
          this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
          this.all_coordinates[this.drag_index[0]].points_coordinates[
            this.drag_index[1]
          ].cor_x = x;
          this.all_coordinates[this.drag_index[0]].points_coordinates[
            this.drag_index[1]
          ].cor_y = y;
          this.drawlines();
          this.drawcircles();
          this.fillarea();
        } else {
          this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
          this.all_line_coordinates[this.drag_index[0]].points_coordinates[
            this.drag_index[1]
          ].cor_x = x;
          this.all_line_coordinates[this.drag_index[0]].points_coordinates[
            this.drag_index[1]
          ].cor_y = y;
          this.drawlines();
          this.drawcircles();
          this.fillarea();
        }
      }

      if (!this.beginDraw) {
        return;
      }

      if (this.coordinates.length == 0 || !this.isdraw) {
        return 0;
      }

      this.drawAll();
      this.drawline(); //把之前的点连线
      this.drawcircle();

      //获取上一个点
      var last_x = this.coordinates[this.coordinates.length - 1].cor_x;
      var last_y = this.coordinates[this.coordinates.length - 1].cor_y;

      if (this.drawType == "2") {
        // 显示箭头
        this.showArrow(last_x, last_y, x, y);
      }

      //获取鼠标移动时的点,画线,实现线段跟踪效果。
      this.ctx.beginPath();
      this.ctx.moveTo(last_x, last_y);
      this.ctx.lineTo(x, y);

      if (this.drawType == "1") {
        // 连接起点 更直观
        var x0 = this.coordinates[0].cor_x;
        var y0 = this.coordinates[0].cor_y;
        this.ctx.lineTo(x0, y0);
      }

      this.ctx.stroke();
      this.ctx.closePath();
    },
    // 鼠标双击事件
    doubleclick() {
      if (!this.beginDraw) {
        return;
      }
      // this.coordinates.pop(); // 同步注释 二选一

      this.isdraw = false;
      if (this.drawType == 1) {
        // 限制图形最少三个点
        if (this.coordinates.length < 3) {
          this.$message.error("绘制区域请至少绘制三个点");
          this.coordinates = [];
          this.drawAll();
          return;
        }
        this.all_coordinates.push({
          canvasId: this.canvasId,
          areaName: "区域" + this.canvasId++,
          points_coordinates: this.coordinates,
        });
      } else {
        // 限制图形最少两个点
        if (this.coordinates.length < 2) {
          this.$message.error("绘制直线请至少绘制两个点");
          this.coordinates = [];
          this.drawAll();
          return;
        }
        var name = "";
        if (this.isMultiLine) {
          name = "区域1";
        } else {
          name = "直线" + this.canvasId++;
        }
        this.all_line_coordinates.push({
          canvasId: this.canvasId,
          // areaName: "直线" + this.canvasId++,
          areaName: name,
          directionType: this.directionType,
          points_coordinates: this.coordinates,
        });
      }
      this.drawAll();
      this.ctx.closePath();
      // console.log(this.coordinates);
      this.coordinates = [];
      // 外部调用智能切换
      if (this.roiType == 3) {
        if (this.drawType == 1 && this.all_line_coordinates.length == 0) {
          this.drawType = 2;
        } else if (this.drawType == 2 && this.all_coordinates.length == 0) {
          this.drawType = 1;
        }
      }
      if (!this.isMultiLine) this.beginDraw = false;
      if (this.isMultiLine && this.all_line_coordinates.length == 2)
        this.beginDraw = false;
    },
    rightClick(e) {
      console.log("右键", e);
    },
    drawlines(data) {
      //把所有多边形画出来
      for (var i = 0; i < this.all_coordinates.length; i++) {
        this.ctx.strokeStyle = "#f36";
        var cors = this.all_coordinates[i].points_coordinates;
        //前后坐标连线
        for (var j = 0; j < cors.length - 1; j++) {
          this.ctx.beginPath();
          var x0 = cors[j].cor_x;
          var y0 = cors[j].cor_y;
          var x1 = cors[j + 1].cor_x;
          var y1 = cors[j + 1].cor_y;
          this.ctx.moveTo(x0, y0);
          this.ctx.lineTo(x1, y1);
          this.ctx.stroke();
          this.ctx.closePath();
        }
        //最后一个与第一个连线
        var begin_x = cors[0].cor_x;
        var begin_y = cors[0].cor_y;
        var end_x = cors[cors.length - 1].cor_x;
        var end_y = cors[cors.length - 1].cor_y;
        this.ctx.beginPath();
        this.ctx.moveTo(begin_x, begin_y);
        this.ctx.lineTo(end_x, end_y);
        this.ctx.stroke();
        this.ctx.closePath();
      }
      //把所有线段画出来
      for (var i = 0; i < this.all_line_coordinates.length; i++) {
        this.ctx.strokeStyle = "#f36";
        // 悬浮上色
        // this.ctx.strokeStyle = this.colorList[(data.canvasId - 1) % 20];
        if (data && this.all_line_coordinates[i].canvasId == data.canvasId) {
          // console.log("线段上色", i, data);
          // this.ctx.strokeStyle = this.colorList[20];
          this.ctx.strokeStyle = "white";
        }
        // 悬浮上色
        var cors = this.all_line_coordinates[i].points_coordinates;
        //前后坐标连线
        for (var j = 0; j < cors.length - 1; j++) {
          this.ctx.beginPath();
          var x0 = cors[j].cor_x;
          var y0 = cors[j].cor_y;
          var x1 = cors[j + 1].cor_x;
          var y1 = cors[j + 1].cor_y;

          this.showArrow(x0, y0, x1, y1);

          this.ctx.moveTo(x0, y0);
          this.ctx.lineTo(x1, y1);
          this.ctx.stroke();
          this.ctx.closePath();
        }
      }
    },
    drawline() {
      this.ctx.strokeStyle = "#f36";
      //把当前绘制的多边形之前的坐标线段绘制出来
      for (var i = 0; i < this.coordinates.length - 1; i++) {
        this.ctx.beginPath();
        var x0 = this.coordinates[i].cor_x;
        var y0 = this.coordinates[i].cor_y;
        var x1 = this.coordinates[i + 1].cor_x;
        var y1 = this.coordinates[i + 1].cor_y;

        if (this.drawType == 2) {
          this.showArrow(x0, y0, x1, y1);
        }

        this.ctx.moveTo(x0, y0);
        this.ctx.lineTo(x1, y1);
        this.ctx.stroke();
        this.ctx.closePath();
      }
    },
    drawcircle() {
      //为当前的多边形端点画圆
      this.ctx.fillStyle = "rgb(0, 195, 155)";
      for (var i = 0; i < this.coordinates.length; i++) {
        var x = this.coordinates[i].cor_x;
        var y = this.coordinates[i].cor_y;
        this.ctx.beginPath();
        this.ctx.moveTo(x, y);
        this.ctx.arc(x, y, 5, 0, Math.PI * 2);
        this.ctx.fill();
        this.ctx.closePath();
      }
    },
    drawcircles() {
      //为所有的多边形端点画圆
      this.ctx.fillStyle = "rgb(0, 195, 155)";
      for (var i = 0; i < this.all_coordinates.length; i++) {
        var cors = this.all_coordinates[i].points_coordinates;
        for (var j = 0; j < cors.length; j++) {
          var x = cors[j].cor_x;
          var y = cors[j].cor_y;
          this.ctx.beginPath();
          this.ctx.moveTo(x, y);
          this.ctx.arc(x, y, 5, 0, Math.PI * 2);
          this.ctx.fill();
          this.ctx.closePath();
        }
      }
      for (var i = 0; i < this.all_line_coordinates.length; i++) {
        var cors = this.all_line_coordinates[i].points_coordinates;
        for (var j = 0; j < cors.length; j++) {
          var x = cors[j].cor_x;
          var y = cors[j].cor_y;
          this.ctx.beginPath();
          this.ctx.moveTo(x, y);
          this.ctx.arc(x, y, 5, 0, Math.PI * 2);
          this.ctx.fill();
          this.ctx.closePath();
        }
      }
    },
    fillarea(data) {
      // this.ctx.fillStyle = "rgba(0, 195, 155,0.4)";
      for (var i = 0; i < this.all_coordinates.length; i++) {
        this.ctx.fillStyle = "rgba(16, 128, 219, 0.4)";
        // 悬浮上色
        // this.ctx.fillStyle = this.colorList[(data.canvasId - 1) % 20];
        if (data && this.all_coordinates[i].canvasId == data.canvasId) {
          this.ctx.fillStyle = this.colorList[20];
        }
        // 悬浮上色
        var cors = this.all_coordinates[i].points_coordinates;
        var x0 = cors[0].cor_x;
        var y0 = cors[0].cor_y;
        this.ctx.beginPath();
        this.ctx.moveTo(x0, y0);
        for (var j = 1; j < cors.length; j++) {
          var x = cors[j].cor_x;
          var y = cors[j].cor_y;
          this.ctx.lineTo(x, y);
        }
        this.ctx.fill();
        this.ctx.closePath();
      }
    },
    /*
        ctx:Canvas绘图环境
        fromX, fromY:起点坐标(也可以换成p1,只不过它是一个数组)
        toX, toY:终点坐标 (也可以换成p2,只不过它是一个数组)
        theta:三角斜边一直线夹角
        headlen:三角斜边长度
        width:箭头线宽度
        color:箭头颜色
      */
    // 绘制箭头
    drawArrow(ctx, fromX, fromY, toX, toY, theta, headlen, width, color) {
      theta = typeof theta != "undefined" ? theta : 30;
      headlen = typeof theta != "undefined" ? headlen : 10;
      width = typeof width != "undefined" ? width : 1;
      color = typeof color != "color" ? color : "#000";

      // 计算各角度和对应的P2,P3坐标
      var angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
        angle1 = ((angle + theta) * Math.PI) / 180,
        angle2 = ((angle - theta) * Math.PI) / 180,
        topX = headlen * Math.cos(angle1),
        topY = headlen * Math.sin(angle1),
        botX = headlen * Math.cos(angle2),
        botY = headlen * Math.sin(angle2);

      // ctx.save();
      ctx.beginPath();

      var arrowX = fromX - topX,
        arrowY = fromY - topY;

      ctx.moveTo(arrowX, arrowY);
      ctx.moveTo(fromX, fromY);
      ctx.lineTo(toX, toY);
      arrowX = toX + topX;
      arrowY = toY + topY;
      ctx.moveTo(arrowX, arrowY);
      ctx.lineTo(toX, toY);
      arrowX = toX + botX;
      arrowY = toY + botY;
      ctx.lineTo(arrowX, arrowY);
      // ctx.strokeStyle = color;
      ctx.lineWidth = width;
      ctx.stroke();
      // ctx.restore();
    },
  },
};
</script>

<style lang="scss" scoped>
#mycanvas {
  /* border: 1px solid red; */
  // background-color: #2d303b;
}
.staticLabels {
  flex-grow: 1;
  overflow: auto;
  margin-top: 10px;
  background: rgba(247, 250, 255, 0.8);
  border-radius: 2px;
  border: 1px solid #eaecf6;
  padding-left: 5px;
  font-size: 12px;
  line-height: 24px;
  .staticLabels_item {
    float: left;
    height: 24px;
    background: #ecf3fe;
    border-radius: 2px;
    border: 1px solid #2c7be5;
    padding: 0px 4px;
    margin-right: 6px;
    margin-top: 5px;
    user-select: none;
  }
}
</style>

使用

<template>
  <div>
    <Canvas
      ref="drawArea"
      :canvasKey="canvasKey"
      :myDrawList="myDrawList"
      :myRoiType="myRoiType"
    />
    <el-select v-model="canvasKey" style="width: 100px" size="mini">
      <el-option value="first" label="数据1"></el-option>
      <el-option value="second" label="数据2"></el-option>
      <el-option value="third" label="数据3"></el-option>
    </el-select>
    <el-select
      v-model="myRoiType"
      style="width: 100px; margin: 10px"
      size="mini"
    >
      <el-option value="1" label="区域"></el-option>
      <el-option value="2" label="线条"></el-option>
      <el-option value="3" label="区域加线条"></el-option>
    </el-select>
    <el-button type="primary" @click="saveFunc" size="mini">保存</el-button>
  </div>
</template>

<script>
import Cookies from "js-cookie";
import Canvas from "./canvas.vue";
export default {
  data() {
    return {
      canvasKey: "first",
      myDrawList: [],
      myRoiType: "1",
    };
  },

  components: { Canvas },
  created() {
    this.myDrawList = JSON.parse(Cookies.get("myDrawList") || "[]");
    console.log("取值", this.myDrawList);
  },
  methods: {
    saveFunc() {
      var arr = this.$refs.drawArea.getData();
      Cookies.set("myDrawList", JSON.stringify(arr));
      console.log("保存", arr);
    },
  },
};
</script>
<style scoped></style>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/965057.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【自动化办公】批量图片PDF自定义指定多个区域识别重命名,批量识别铁路货物运单区域内容改名,基于WPF和飞桨ocr深度学习模型的解决方案

项目背景介绍 铁路货运企业需要对物流单进行长期存档&#xff0c;以便后续查询和审计。不同的物流单可能包含不同的关键信息&#xff0c;通过自定义指定多个区域进行识别重命名&#xff0c;可以使存档的图片文件名具有统一的规范和明确的含义。比如&#xff0c;将包含货物运单…

洛谷网站: P3029 [USACO11NOV] Cow Lineup S 题解

题目传送门&#xff1a; P3029 [USACO11NOV] Cow Lineup S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前言&#xff1a; 这道题的核心问题是在一条直线上分布着不同品种的牛&#xff0c;要找出一个连续区间&#xff0c;使得这个区间内包含所有不同品种的牛&#xff0c;…

K8S Deployment 实现 蓝绿 发布

一、何为蓝绿发布 蓝绿发布&#xff08;Blue - Green Deployment&#xff09;是一种软件部署策略&#xff0c;旨在最大程度减少应用程序停机时间&#xff0c;确保新老版本系统平稳过渡。以下为详细介绍&#xff1a; 1.1、基本概念 存在两个完全相同的生产环境&#xff0c;通…

CVPR | CNN融合注意力机制,芜湖起飞!

**标题&#xff1a;**On the Integration of Self-Attention and Convolution **论文链接&#xff1a;**https://arxiv.org/pdf/2111.14556 **代码链接&#xff1a;**https://github.com/LeapLabTHU/ACmix 创新点 1. 揭示卷积和自注意力的内在联系 文章通过重新分解卷积和自…

ollama部署deepseek实操记录

1. 安装 ollama 1.1 下载并安装 官网 https://ollama.com/ Linux安装命令 https://ollama.com/download/linux curl -fsSL https://ollama.com/install.sh | sh安装成功截图 3. 开放外网访问 1、首先停止ollama服务&#xff1a;systemctl stop ollama 2、修改ollama的servic…

鸟哥Linux私房菜笔记(三)

鸟哥Linux私房菜笔记&#xff08;三&#xff09; 该第三部分和第四部分主要为原书的第十一章&#xff08;正则表达式与文件格式化处理&#xff09;&#xff0c;第十二章学习shell脚本&#xff0c;第十六章&#xff08;进程管理与SElinux初探部分&#xff09;&#xff0c;第十七…

WordPressAI自动生成发布文章免费插件,SEO,定时任务,生成长尾关键词、根据网站主题内容全自动化后台生成发布文章

一款可以自动发布文章的WordPress插件 wordpress 自动生成文章发布插件下载地址: 点击下载 插件基础功能免费无限制 1、插件后台可输入想要的文章主题或文章构成思路 2、ai自动生成并发布 3、可自定义发布后的文章状态和分类 4、可根据已有的长尾关键词生成文章 5、可对a…

R语言 | 使用 ComplexHeatmap 绘制热图,分区并给对角线分区加黑边框

目的&#xff1a;画热图&#xff0c;分区&#xff0c;给对角线分区添加黑色边框 建议直接看0和4。 0. 准备数据 # 安装并加载必要的包 #install.packages("ComplexHeatmap") # 如果尚未安装 library(ComplexHeatmap)# 使用 iris 数据集 #data(iris)# 选择数值列&a…

机器学习基本概念(附代码)

一、算法与模型的关系 在机器学习领域&#xff0c;算法和模型是两个核心概念。算法是一种偏抽象的概念&#xff0c;它定义了计算机解决问题的步骤和流程。而模型则是更加具体的概念&#xff0c;通过代码实现特定算法来完成任务。数学上&#xff0c;一个简单的模型可以表示为 y…

OpenCV:特征检测总结

目录 一、什么是特征检测&#xff1f; 二、OpenCV 中的常见特征检测方法 1. Harris 角点检测 2. Shi-Tomasi 角点检测 3. Canny 边缘检测 4. SIFT&#xff08;尺度不变特征变换&#xff09; 5. ORB 三、特征检测的应用场景 1. 图像匹配 2. 运动检测 3. 自动驾驶 4.…

深度学习系列--01.入门

一.深度学习概念 深度学习&#xff08;Deep Learning&#xff09;是机器学习的分支&#xff0c;是指使用多层的神经网络进行机器学习的一种手法抖音百科。它学习样本数据的内在规律和表示层次&#xff0c;最终目标是让机器能够像人一样具有分析学习能力&#xff0c;能够识别文字…

Vue3.5常用特性整理

Vue3.5 发布已近半年&#xff0c;抽空整理下常用的新增/改动特性 响应式 Props 解构 Vue3.5 中 Props 正式支持解构了&#xff0c;并添加了响应式跟踪 设置默认值 使用 JavaScript 原生的默认值语法声明 props 默认值 以前 const props withDefaults(defineProps<{ co…

Windows程序设计10:文件指针及目录的创建与删除

文章目录 前言一、文件指针是什么&#xff1f;二、设置文件指针的位置&#xff1a;随机读写&#xff0c;SetFilePointer函数1.函数说明2.函数实例 三、 目录的创建CreateDirectory四、目录的删除RemoveDirectory总结 前言 Windows程序设计10&#xff1a;文件指针及目录的创建与…

【Linux系统编程】进程间通信(管道:匿名管道、命名管道、实战练习)

知其然&#xff0c;知其所以然 什么是进程间通信&#xff1a; 进程间通信是不同进程间交换信息的一种机制。进程可能在同一台计算机上&#xff0c;也可能在网络中的不同计算机上。那我们为什么要有这种机制&#xff1a; 为什么进程间要通信&#xff1a; ①数据共享&#xff…

K8S ReplicaSet 控制器

一、理论介绍 今天我们来实验 ReplicaSet 控制器&#xff08;也叫工作负载&#xff09;。官网描述如下&#xff1a; 1、是什么&#xff1f; ReplicaSet 副本集&#xff0c; 维护一组稳定的副本 Pod 集合。 2、为什么需要&#xff1f; 解决 pod 被删除了&#xff0c;不能自我恢…

【C语言】自定义类型讲解

文章目录 一、前言二、结构体2.1 概念2.2 定义2.2.1 通常情况下的定义2.2.2 匿名结构体 2.3 结构体的自引用和嵌套2.4 结构体变量的定义与初始化2.5 结构体的内存对齐2.6 结构体传参2.7 结构体实现位段 三、枚举3.1 概念3.2 定义3.3 枚举的优点3.3.1 提高代码的可读性3.3.2 防止…

VUE2双向绑定的原理

文章目录 VUE2双向绑定的原理1. 什么是双向绑定2. 双向绑定的原理2.1 ViewModel的重要作用2.2 双向绑定的流程 3. 双向绑定的实现3.1 data响应化处理3.2 Compile编译3.3 依赖收集 VUE2双向绑定的原理 1. 什么是双向绑定 讲双向绑定先讲单项绑定&#xff0c;啥叫单项绑定&…

入行FPGA设计工程师需要提前学习哪些内容?

FPGA作为一种灵活可编程的硬件平台&#xff0c;广泛应用于嵌入式系统、通信、数据处理等领域。很多人选择转行FPGA设计工程师&#xff0c;但对于新手来说&#xff0c;可能在学习过程中会遇到一些迷茫和困惑。为了帮助大家更好地准备&#xff0c;本文将详细介绍入行FPGA设计工程…

Mac M1 ComfyUI 中 AnyText插件安装问题汇总?

Q1&#xff1a;NameError: name ‘PreTrainedTokenizer’ is not defined ? 该项目最近更新日期为2024年12月&#xff0c;该时间段的transformers 版本由PyPI 上的 transformers 页面 可知为4.47.1. A1: transformers 版本不满足要求&#xff0c;必须降级transformors &#…

深度学习 Pytorch 神经网络的学习

本节将从梯度下降法向外拓展&#xff0c;介绍更常用的优化算法&#xff0c;实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。 对于像神经网络这样的复杂模型&#xff0c;可能会有数百个 w w w的存在&#xff0c;同时如果我们使用的是像交叉熵这样…