Vue canvas画图画线例子,数据回显与隔离,点拖拽修改
组件
<template><divstyle="display: flex; height: 342px; width: 760px; border: 1px solid #000"><divstyle="position: relative; height: 100%; width: 608px; min-width: 608px"><canvasid="mycanvas"ref="mycanvas":width="canvasWidth":height="canvasHeight"@mousedown="canvasDown($event)"@mousemove="canvasMove($event)"@mouseup="canvasUp($event)"@dblclick="doubleclick()"></canvas></div><divstyle="display: flex;flex-direction: column;padding: 10px;border: 1px solid #cbd2f7;"><el-buttonsize="mini"style="margin-left: 0; margin-top: 10px"@click="beginDraw = !beginDraw":type="beginDraw == false ? 'primary' : ''">{{ beginDraw ? "结束绘制" : "开始绘制" }}</el-button><el-buttonsize="mini"@click="clearAll"style="margin-left: 0; margin-top: 10px">清空绘制区域</el-button><div><el-selectsize="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-optionlabel="单区域+单向单拌线"value="3"v-if="myRoiType == 3"></el-option></el-select><el-selectsize="mini"placeholder="请选择线条方向类型"v-model="directionType"v-show="roiType != '1'":disabled="directionTypeDisabled"style="margin-top: 10px"@change="clearAll"><el-optionv-for="item in directionTypeOptions":key="item.value":label="item.label":value="item.value"></el-option></el-select><div style="display: flex"><el-buttonstyle="width: 50%; margin-top: 10px"size="mini":type="drawType == 1 ? 'primary' : ''"@click="drawType = 1"v-show="roiType == 3">区域</el-button><el-buttonsize="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"><divclass="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><iclass="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,说明不共线,直接返回falseif (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;// 允许偏移量5if (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;// 允许偏移量5if (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><Canvasref="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-selectv-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>
相关文章:

Vue canvas画图画线例子,数据回显与隔离,点拖拽修改
组件 <template><divstyle"display: flex; height: 342px; width: 760px; border: 1px solid #000"><divstyle"position: relative; height: 100%; width: 608px; min-width: 608px"><canvasid"mycanvas"ref"mycanva…...
Python实现CAN FD 通信(基于PCAN开发CAN FD测试工具)
目录 一.背景 二. 硬件环境 1.CAN通信设备之PCAN 2.物理架构图 三. 资料分析 四. 二次开发 五. 应用 六. 总结 一.背景 在汽车电子领域中CAN是一种被广泛应用的通信协议,CAN 是controller area network 的缩写(以下称为can),是iso国际标准化的串行通信协议。 关于…...
LeetCode--347. 前 K 个高频元素/Golang中的堆(container/heap)
例题链接-前k个高频元素 前言 以前都是用的C写算法题,最近也想熟悉一下golang的数据结构,故来一篇题解堆分析。 正文 这里重点不在分析题目,在于golang中的 container/heap 对于内部实现逻辑有兴趣的可以去看看源码。 这里先给出题解的代…...

关于大数据
在大数据背景下存在的问题: 非结构化、半结构化数据:NoSQL数据库只负责存储;程序处理时涉及到数据移动,速度慢 是否存在一套整体解决方案? 可以存储并处理海量结构化、半结构化、非结构化数据 处理海量数据的速…...

9-收纳的知识
[ComponentOf(typeof(xxx))]组件描述,表示是哪个实体的组件 [EntitySystemOf(typeof(xxx))] 系统描述 [Event(SceneType.Demo)] 定义事件,在指定场景的指定事件发生后触发 [ChildOf(typeof(ComputersComponent))] 标明是谁的子实体 [ResponseType(na…...

堆的实现——堆的应用(堆排序)
文章目录 1.堆的实现2.堆的应用--堆排序 大家在学堆的时候,需要有二叉树的基础知识,大家可以看我的二叉树文章:二叉树 1.堆的实现 如果有⼀个关键码的集合 K {k0 , k1 , k2 , …,kn−1 } ,把它的所有元素按完全⼆叉树…...

机器学习6-全连接神经网络2
机器学习6-全连接神经网络2-梯度算法改进 梯度下降算法存在的问题动量法与自适应梯度动量法一、动量法的核心思想二、动量法的数学表示三、动量法的作用四、动量法的应用五、示例 自适应梯度与RMSProp 权值初始化随机权值初始化Xavier初始化HE初始化(MSRA) 
基于 SpringBoot 的电影购票系统
基于SpringBoot的电影购票系统是一个集成了现代化Web开发技术的在线电影票务平台。以下是对该系统的详细介绍: 一、系统背景与意义 随着电影行业的快速发展和观众对观影体验的不断追求,电影票务管理面临着越来越多的挑战。传统的票务管理方式存在效率低…...
C++SLT(三)——list
目录 一、list的介绍二、list的使用list的定义方式 三、list的插入和删除push_back和pop_backpush_front和pop_frontinserterase 四、list的迭代器使用五、list的元素获取六、list的大小控制七、list的操作函数sort和reversemergeremoveremove_ifuniqueassignswap 一、list的介…...

C++ Primer 算术运算符
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...

数据结构-堆和PriorityQueue
1.堆(Heap) 1.1堆的概念 堆是一种非常重要的数据结构,通常被实现为一种特殊的完全二叉树 如果有一个关键码的集合K{k0,k1,k2,...,kn-1},把它所有的元素按照完全二叉树的顺序存储在一个一维数组中,如果满足ki<k2i…...

【玩转 Postman 接口测试与开发2_017】第13章:在 Postman 中实现契约测试(Contract Testing)与 API 接口验证(下)
《API Testing and Development with Postman》最新第二版封面 文章目录 第十三章 契约测试与 API 接口验证8 导入官方契约测试集合9 契约测试集合的详细配置9.1 env-apiKey 的创建与设置9.2 env-workspaceId 的设置9.3 Mock 服务器及 env-server 的配置9.4 API 测试实例的配置…...

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

React图标库: 使用React Icons实现定制化图标效果
React图标库: 使用React Icons实现定制化图标效果 图标库介绍 是一个专门为React应用设计的图标库,它包含了丰富的图标集合,覆盖了常用的图标类型,如FontAwesome、Material Design等。React Icons可以让开发者在React应用中轻松地添加、定制各…...

Python sider-ai-api库 — 访问Claude、llama、ChatGPT、gemini、o1等大模型API
目前国内少有调用ChatGPT、Claude、Gemini等国外大模型API的库。 Python库sider_ai_api 提供了调用这些大模型的一个完整解决方案, 使得开发者能调用 sider.ai 的API,实现大模型的访问。 Sider是谷歌浏览器和Edge的插件,能调用ChatGPT、Clau…...
DeepSeek、哪吒和数据库:厚积薄发的力量
以下有部分来源于AI,毕竟我认为AI还不能替代,他只能是辅助 快速迭代是应用程序不是工程 在这个追求快速迭代、小步快跑的时代,我们似乎总是被 “快” 的节奏裹挟着前进。但当我们静下心来,审视 DeepSeek 的发展、饺子导演创作哪吒…...

DDD - 微服务架构模型_领域驱动设计(DDD)分层架构 vs 整洁架构(洋葱架构) vs 六边形架构(端口-适配器架构)
文章目录 引言1. 概述2. 领域驱动设计(DDD)分层架构模型2.1 DDD的核心概念2.2 DDD架构分层解析 3. 整洁架构:洋葱架构与依赖倒置3.1 整洁架构的核心思想3.2 整洁架构的层次结构 4. 六边形架构:解耦核心业务与外部系统4.1 六边形架…...
第 1 天:UE5 C++ 开发环境搭建,全流程指南
🎯 目标:搭建 Unreal Engine 5(UE5)C 开发环境,配置 Visual Studio 并成功运行 C 代码! 1️⃣ Unreal Engine 5 安装 🔹 下载与安装 Unreal Engine 5 步骤: 注册并安装 Epic Game…...
【华为OD-E卷 - 109 磁盘容量排序 100分(python、java、c++、js、c)】
【华为OD-E卷 - 磁盘容量排序 100分(python、java、c、js、c)】 题目 磁盘的容量单位常用的有M,G,T这三个等级, 它们之间的换算关系为1T 1024G,1G 1024M, 现在给定n块磁盘的容量,…...
【大数据技术】编写Python代码实现词频统计(python+hadoop+mapreduce+yarn)
编写Python代码实现词频统计(python+hadoop+mapreduce+yarn) 搭建完全分布式高可用大数据集群(VMware+CentOS+FinalShell) 搭建完全分布式高可用大数据集群(Hadoop+MapReduce+Yarn) 本机PyCharm连接CentOS虚拟机 在阅读本文前,请确保已经阅读过以上三篇文章,成功搭建了…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...