/* * ********************************************************************************************************************* * * !! * .F88X * X8888Y * .}888888N; * i888888N; .:! .I$WI: * R888888I .'N88~ i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8& * .R888888I .;N8888~ .X8' "8I.!,/8" !%NY8`"8I8~~8>,88I * +888888N; .8888888Y "&&8Y.}8, * ./888888N; .R888888Y .'}~ .>}'.`+> i}! "i' +/' .'i~ !11,.:">, .~]! .i}i * ~888888%: .I888888l .]88~`1/iY88Ii+1'.R$8$8]"888888888> Y8$ W8E X8E W8888'188Il}Y88$* * 18888888 E8888881 .]W%8$`R8X'&8%++N8i,8N%N8+l8%` .}8N:.R$RE%N88N%N$K$R 188,FE$8%~Y88I * .E888888I .i8888888' .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~ * 8888888I .,N888888~ ~88i"8W,!N8*.I88.}888%F,i$88"F88" 888:E8X.>88!i88>`888*.}Fl1]*}1YKi' * i888888N' I888Y ]88;/EX*IFKFK88X K8R .l8W 88Y ~88}'88E&%8W.X8N``]88!.$8K .:W8I * .i888888N; I8Y .&8$ .X88! i881.:%888>I88 ;88] +88+.';;;;:.Y88X 18N.,88l .+88/ * .:R888888I * .&888888I Copyright (c) 2016-2020. 博锐尚格科技股份有限公司 * ~8888' * .!88~ All rights reserved. * * ********************************************************************************************************************* */ import { SColor, SLine, SPainter, SPoint, SRect, SPath } from "@persagy-web/draw"; import { SKeyCode, SMouseEvent, SUndoStack } from "@persagy-web/base"; import { SItemStatus } from "@persagy-web/big"; import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil"; import { SGraphPointListDelete, SGraphPointListInsert, SGraphPointListUpdate, SLineStyle, SGraphItem } from "@persagy-web/graph/"; import { SGraphEdit } from ".."; import { Marker } from "../type/Marker"; /** * 折线编辑类 * * @author 韩耀龙 */ export default class SBaseCirclePolylineEdit extends SGraphEdit { /** 传入数据 */ _data: Marker | null = null; get data(): Marker | null { return this._data; } // Get data set data(v: Marker | null) { this._data = v; this.initData(); this.update(); } // Set data /** X 坐标最小值 */ private minX = Number.MAX_SAFE_INTEGER; /** X 坐标最大值 */ private maxX = Number.MIN_SAFE_INTEGER; /** Y 坐标最小值 */ private minY = Number.MAX_SAFE_INTEGER; /** Y 坐标最大值 */ private maxY = Number.MIN_SAFE_INTEGER; /** 折点信息 */ pointList: SPoint[] = []; /** 是否绘制完成 */ _status: SItemStatus = SItemStatus.Normal; get status(): SItemStatus { return this._status; } set status(v: SItemStatus) { this._status = v; this.undoStack.clear(); this.update(); } /** 鼠标移动时的点 */ private lastPoint: SPoint | null = null; /** 线条颜色 */ _strokeColor: SColor = SColor.Black; get strokeColor(): SColor { return this._strokeColor; } set strokeColor(v: SColor) { this._strokeColor = v; this.update(); } /** 填充色 */ _fillColor: SColor = new SColor("#2196f3"); get fillColor(): SColor { return this._fillColor; } set fillColor(v: SColor) { this._fillColor = v; this.update(); } /** 边框样式 */ _lineStyle: SLineStyle = SLineStyle.Solid; get lineStyle(): SLineStyle { return this._lineStyle; } set lineStyle(v: SLineStyle) { this._lineStyle = v; this.update(); } /** 线条宽度 */ _lineWidth: number = 1; get lineWidth(): number { return this._lineWidth; } set lineWidth(v: number) { this._lineWidth = v; this.update(); } /** 是否垂直水平绘制 */ private _verAndLeve: boolean = false; get verAndLeve(): boolean { return this._verAndLeve; } set verAndLeve(bool: boolean) { this._verAndLeve = bool; this.update(); } /** 全局灵敏度 */ dis: number = 10; /** 灵敏度转换为场景长度 */ private sceneDis: number = 10; /** 当前点索引 */ private curIndex: number = -1; /** 当前点 */ private curPoint: SPoint | null = null; /** undo / redo 堆栈 */ private undoStack: SUndoStack = new SUndoStack(); /** 圆角半径 */ private _radius: number = 5; get radius(): number { return this._radius; } set radius(v: number) { if (v === this._radius) { // 与旧值相等 return; } this._radius = v; this.update(); } /** 圆角半径是否需要转像素值 */ radiusIsPx: boolean = false; /** path 对象 */ path: SPath = new SPath(); /** * 构造函数 * * @param parent 父级 * @param data 折线数据 */ constructor(parent: null | SGraphItem, data: Marker | null = null) { super(parent); if (data) { // 传入数据则存储 this.data = data; } } // Constructor /** * 初始化 data 数据 */ initData(): void { const data = this.data; // 没有存储数据不处理 if (!data) return; this.name = data.name; this.moveTo(data.pos.x, data.pos.y); this.showSelect = false; if (data && data.style) { // 数据中存在样式属性 if (data.style.outLine) { // 存在轮廓线 let setPointList: SPoint[]; setPointList = data.style.outLine.map(i => { return new SPoint(i.x, i.y); }); this.pointList = setPointList; } this.moveable = data.style.isMove ? data.style.isMove : false; if (data.style && data.style.default) { // 存在默认样式 // 颜色 if (data.style.default.strokeColor) { this.strokeColor = new SColor( data.style.default.strokeColor ); } // 线宽 if (data.style.default.lineWidth) { this.lineWidth = data.style.default.lineWidth; } // 线样式 if (data.style.default.lineStyle) { this.lineStyle = data.style.default.lineStyle; } } } } // Function initData() /** * 添加点至数组中 * * @param p 添加的点 * @param index 添加到的索引 */ private addPoint(p: SPoint, index?: number): void { if (index && this.canHandle(index)) { // 传入索引且索引有效 this.pointList.splice(index, 0, p); this.recordAction(SGraphPointListInsert, [ this.pointList, p, index ]); } else { // 否则 this.pointList.push(p); this.recordAction(SGraphPointListInsert, [this.pointList, p]); } this.update(); } /** * 是否可以添加点到数组中 * * @param index 要添加到的索引 * @return 是否可添加 */ private canHandle(index: number): boolean { return index >= 0 && index <= this.pointList.length; } /** * 根据索引删除点 * * @param index 删除点 */ deletePoint(index: number): void { if (this.canHandle(index) && this.pointList.length > 2) { // 索引值有效且本对象的点数据个数大于2个 const p = new SPoint( this.pointList[this.curIndex].x, this.pointList[this.curIndex].y ); this.pointList.splice(index, 1); this.recordAction(SGraphPointListDelete, [ this.pointList, p, index ]); this.curIndex = -1; this.curPoint = null; this.update(); } } /** * 鼠标按下事件 * * @param event 鼠标事件 * @return 是否处理事件 */ onMouseDown(event: SMouseEvent): boolean { this.curIndex = -1; this.curPoint = null; if (event.shiftKey || this.verAndLeve) { // 按下了 shift 键 或者模拟 shift 键功能开启 event = this.compare(event); } if (event.buttons == 1) { // 按下的是左键 if (this.status == SItemStatus.Create) { // 处于创建态 this.addPoint(new SPoint(event.x, event.y)); return true; } else if (this.status == SItemStatus.Edit) { // 处于编辑态 // 查询鼠标最近的索引 this.findNearestPoint(new SPoint(event.x, event.y)); // 增加点 if (this.curIndex < 0) { this.findAddPos(new SPoint(event.x, event.y)); } // 删除点 if (event.altKey && this.canHandle(this.curIndex)) { this.deletePoint(this.curIndex); } this.update(); return true; } else { return super.onMouseDown(event); } } return super.onMouseDown(event); } /** * 鼠标移动事件 * * @param event 鼠标事件 * @return 是否处理事件 */ onMouseMove(event: SMouseEvent): boolean { if (event.shiftKey || this.verAndLeve) { // 按下了 shift 键 或者模拟 shift 键功能开启 event = this.compare(event); } if (this.status == SItemStatus.Create) { // 处于创建态 if (this.lastPoint) { // 最后一个点存在 this.lastPoint.x = event.x; this.lastPoint.y = event.y; } else { // 否则 this.lastPoint = new SPoint(event.x, event.y); } this.update(); return true; } else if (this.status == SItemStatus.Edit) { // 处于编辑态 if (event.buttons == 1) { // 按下的是左键 if (this.canHandle(this.curIndex)) { // 索引值有效 this.pointList[this.curIndex].x = event.x; this.pointList[this.curIndex].y = event.y; } } this.update(); return true; } else { // 否则 return super.onMouseMove(event); } } /** * 鼠标移动事件 * * @param event 鼠标事件 * @return 是否处理事件 */ onMouseUp(event: SMouseEvent): boolean { if (this.status == SItemStatus.Edit) { // 处于编辑态 if (this.curIndex > -1) { // 索引值有效 const p = new SPoint( this.pointList[this.curIndex].x, this.pointList[this.curIndex].y ); this.recordAction(SGraphPointListUpdate, [ this.pointList, this.curPoint, p, this.curIndex ]); } } else if (this.status == SItemStatus.Normal) { // 处于正常态 super.onMouseUp(event); return true; } return true; } /** * 鼠标双击事件 * * @param event 事件参数 * @return 是否处理事件 */ onDoubleClick(event: SMouseEvent): boolean { if (this.status == SItemStatus.Normal) { // 处于正常态 双击改为编辑状态 this.status = SItemStatus.Edit; // @ts-ignore this.grabItem(this); } else if (this.status == SItemStatus.Edit) { // 处于编辑状态 双击则改为正常态 this.status = SItemStatus.Normal; this.releaseItem(); } else if (this.status == SItemStatus.Create) { // 处于创建状态 if (this.pointList.length > 1) { // 本对象点数据中已经给存在的点个数大于1个 this.status = SItemStatus.Normal; this.releaseItem(); this.$emit("finishCreated"); } } this.$emit("onDoubleClick", event); return true; } /** * 键盘按键弹起事件 * * @param event 事件参数 */ onKeyUp(event: KeyboardEvent): void { if (event.keyCode == SKeyCode.Enter) { // 按键为 enter 键 if (this.pointList.length > 1) { // 点数据个数大于一个 if (this.status == SItemStatus.Create) { // 处于创建态 this.$emit("finishCreated"); } this.status = SItemStatus.Normal; this.releaseItem(); } } if ( event.keyCode == SKeyCode.Delete && this.status == SItemStatus.Edit ) { // 处于编辑态且按键为 delete 键, 则删除点 this.deletePoint(this.curIndex); } } /** * 移动后处理所有坐标,并肩原点置为场景原点 */ moveToOrigin(): void { // 遍历本身点数据坐标 this.pointList = this.pointList.map(t => { t.x = t.x + this.x; t.y = t.y + this.y; return t; }); this.x = 0; this.y = 0; } /** * 获取点击点与点集中距离最近点 * * @param p 鼠标点击点 */ findNearestPoint(p: SPoint): void { let len = this.sceneDis; // 遍历本身点数据列表 for (let i = 0; i < this.pointList.length; i++) { let dis = SMathUtil.pointDistance( p.x, p.y, this.pointList[i].x, this.pointList[i].y ); if (dis < len) { // 点击点到对象中的点距离在吸附距离之内 len = dis; this.curIndex = i; this.curPoint = new SPoint( this.pointList[this.curIndex].x, this.pointList[this.curIndex].y ); } } } /** * 计算增加点的位置 * * @param p 鼠标点击点 */ findAddPos(p: SPoint): void { let len = SMathUtil.pointToLine( p, new SLine(this.pointList[0], this.pointList[1]) ), index = 0; // 点数据个数大于2 if (this.pointList.length > 2) { // 遍历点数据列表 for (let i = 1; i < this.pointList.length - 1; i++) { let dis = SMathUtil.pointToLine( p, new SLine(this.pointList[i], this.pointList[i + 1]) ); // 点击点到对象中的点距离在吸附距离之内 if (dis.MinDis < len.MinDis) { len = dis; index = i; } } } if (len.MinDis < this.sceneDis) { // 如果点击点到第条线距离在吸附距离之内 if (len.Point) { // 存在吸附的点 this.addPoint(len.Point, index + 1); } } } /** * shift 垂直水平创建或编辑 * * @param event 事件 * @return 处理后的鼠标事件 */ compare(event: SMouseEvent): SMouseEvent { if (this.pointList.length) { // 点数据列表有效 let last = new SPoint(event.x, event.y); if (this.status == SItemStatus.Create) { // 处于创建态 last = this.pointList[this.pointList.length - 1]; } else if (this.status == SItemStatus.Edit) { // 处于编辑态 if (this.curIndex > 1) { // 当前选中点的索引不是前两个点 last = this.pointList[this.curIndex - 1]; } } const dx = Math.abs(event.x - last.x); const dy = Math.abs(event.y - last.y); if (dy > dx) { // y 轴方向差值量大 event.x = last.x; } else { // x 轴方向差值量大 event.y = last.y; } } return event; } /** * 记录相关动作并推入栈中 * * @param SGraphCommand 相关命令类 * @param any 对应传入参数 */ protected recordAction(SGraphCommand: any, any: any[]): void { // 记录相关命令并推入堆栈中 const command = new SGraphCommand(this.scene, this, ...any); this.undoStack.push(command); } /** * Item 对象边界区域 * * @return 对象边界区域 */ boundingRect(): SRect { if (this.pointList.length) { // 当前折线有效 this.minX = this.pointList[0].x; this.maxX = this.pointList[0].x; this.minY = this.pointList[0].y; this.maxY = this.pointList[0].y; this.pointList.forEach(it => { let x = it.x, y = it.y; // 计算 x 的最小值 if (x < this.minX) { this.minX = x; } // 计算 y 的最小值 if (y < this.minY) { this.minY = y; } // 计算 x 的最大值 if (x > this.maxX) { this.maxX = x; } // 计算 y 的最大值 if (y > this.maxY) { this.maxY = y; } }); } return new SRect( this.minX, this.minY, this.maxX - this.minX, this.maxY - this.minY ); } /** * 判断点是否在区域内 * * @param x x 坐标 * @param y y 坐标 * @return 是否在区域内 */ contains(x: number, y: number): boolean { let p = new SPoint(x, y); // 遍历点集列表 for (let i = 1; i < this.pointList.length; i++) { let PTL = SMathUtil.pointToLine( p, new SLine( this.pointList[i - 1].x, this.pointList[i - 1].y, this.pointList[i].x, this.pointList[i].y ) ); // 点击点距离折线的距离在吸附范围内 if (PTL.MinDis < this.sceneDis) { return true; } } return false; } /** * 撤销操作 */ undo(): void { // 处于非正常态 if (this._status != SItemStatus.Normal) { this.undoStack.undo(); } } /** * 重做操作 */ redo(): void { // 处于非正常态 if (this._status != SItemStatus.Normal) { this.undoStack.redo(); } } /** * 取消操作执行 */ cancelOperate(): void { if (this.status == SItemStatus.Create) { // 处于创建态 this.parent = null; this.releaseItem(); } else if (this.status == SItemStatus.Edit) { // 处于编辑态 this.status = SItemStatus.Normal; this.releaseItem(); } } /** * 根据点集生成 path 对象 * * @param list 需要生成的点击 * @param r 拐点处圆角半径 */ generatePath(list: SPoint[], r: number = 0): void { const len = list.length; // 当前这小有效 if (len) { this.path = new SPath(); this.path.moveTo(list[0].x, list[0].y); // 遍历点集列表 for (let i = 1; i < len - 1; i++) { const temp = list[i]; const next = list[i + 1]; this.path.arcTo(temp.x, temp.y, next.x, next.y, r); } const last = list[len - 1]; this.path.lineTo(last.x, last.y); } } /** * 绘制基本图形 * * @param painter 绘制对象 */ drawBaseLine(painter: SPainter): void { // 绘制基本图形 painter.pen.color = this.strokeColor; if (this.lineStyle == SLineStyle.Dashed) { // 类型为虚线 painter.pen.lineDash = [ painter.toPx(this.lineWidth * 3), painter.toPx(this.lineWidth * 7) ]; } else if (this.lineStyle == SLineStyle.Dotted) { // 类型为点线 painter.pen.lineDash = [ painter.toPx(this.lineWidth), painter.toPx(this.lineWidth) ]; } const temp = JSON.parse(JSON.stringify(this.pointList)); // 处于创建态且最后一个点存在 if (this.status == SItemStatus.Create && this.lastPoint) { temp.push(this.lastPoint); } if (temp.length > 2) { // 当前点集中点数超过2个点,生成 path 路径 let radius = this.radius; if (this.radiusIsPx) { // 如果圆角半径需要转像素值 radius = painter.toPx(this.radius); } this.generatePath(temp, radius); painter.drawPath(this.path); } else { // 否则绘制折线 painter.drawPolyline(temp); } } /** * 返回对象储存的相关数据 * * @return 对象储存的相关数据 */ toData(): any { this.moveToOrigin(); // 数据不存在,不做转换 if (!this.data) return; // 遍历点集列表 const Line = this.pointList.map(pos => { return { x: pos.x, y: pos.y }; }); this.data.style.outLine = Line; this.data.style.default.lineWidth = this.lineWidth; this.data.style.default.lineStyle = this.lineStyle; this.data.style.default.strokeColor = this.strokeColor.value; return this.data; } /** * Item 绘制操作 * * @param painter 绘制对象 */ onDraw(painter: SPainter): void { // 缓存场景长度 this.sceneDis = painter.toPx(this.dis); painter.pen.lineWidth = painter.toPx(this.lineWidth); // 处于创建状态且最后一个点存在 if (this.status == SItemStatus.Create && this.lastPoint) { // 绘制基本图形 this.drawBaseLine(painter); // 编辑状态 this.pointList.forEach((t, i): void => { painter.brush.color = SColor.White; if (i == this.curIndex) { painter.brush.color = this.fillColor; } painter.drawCircle(t.x, t.y, painter.toPx(5)); }); } else if (this.status == SItemStatus.Edit) { // 处于编辑状态 // 绘制基本图形 this.drawBaseLine(painter); // 遍历点集列表 this.pointList.forEach((t, i): void => { painter.brush.color = SColor.White; // 该点是当前选中的点 if (i == this.curIndex) { painter.brush.color = this.fillColor; } painter.drawCircle(t.x, t.y, painter.toPx(5)); }); } else { // 查看状态,是否选中 if (this.selected) { painter.pen.lineWidth = painter.toPx(this.lineWidth * 2); painter.shadow.shadowBlur = 10; painter.shadow.shadowColor = new SColor(`#00000033`); painter.shadow.shadowOffsetX = 5; painter.shadow.shadowOffsetY = 5; } // 绘制基本图形 this.drawBaseLine(painter); } } }