|
@@ -0,0 +1,578 @@
|
|
|
+import {
|
|
|
+ SColor,
|
|
|
+ SLine,
|
|
|
+ SPainter,
|
|
|
+ SPoint,
|
|
|
+ SRect,
|
|
|
+ STextAlign
|
|
|
+} from "@saga-web/draw";
|
|
|
+import { SMouseButton, SMouseEvent, SUndoStack } from "@saga-web/base";
|
|
|
+import { SMathUtil } from "../utils/SMathUtil";
|
|
|
+import { SGraphItem, SLineStyle } from "@saga-web/graph";
|
|
|
+import { SItemStatus } from "../enums/SItemStatus";
|
|
|
+
|
|
|
+/**
|
|
|
+ * 直线 item
|
|
|
+ *
|
|
|
+ * @author 郝建龙 <haojianlong@sagacloud.cn>
|
|
|
+ */
|
|
|
+export class EditLineItem extends SGraphItem {
|
|
|
+ /** 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;
|
|
|
+
|
|
|
+ /** 线段 */
|
|
|
+ private _line: SPoint[] = [];
|
|
|
+ get line(): SPoint[] {
|
|
|
+ return this._line;
|
|
|
+ }
|
|
|
+ set line(arr: SPoint[]) {
|
|
|
+ this._line = arr;
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 是否完成绘制 */
|
|
|
+ protected _status: SItemStatus = SItemStatus.Normal;
|
|
|
+ get status(): SItemStatus {
|
|
|
+ return this._status;
|
|
|
+ }
|
|
|
+ set status(v: SItemStatus) {
|
|
|
+ this._status = v;
|
|
|
+ // this.undoStack.clear();
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 线条颜色 */
|
|
|
+ private _strokeColor: SColor = SColor.Black;
|
|
|
+ get strokeColor(): SColor {
|
|
|
+ return this._strokeColor;
|
|
|
+ }
|
|
|
+ set strokeColor(v: SColor) {
|
|
|
+ this._strokeColor = v;
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 线条样式 */
|
|
|
+ private _lineStyle: SLineStyle = SLineStyle.Solid;
|
|
|
+ get lineStyle(): SLineStyle {
|
|
|
+ return this._lineStyle;
|
|
|
+ }
|
|
|
+ set lineStyle(v: SLineStyle) {
|
|
|
+ this._lineStyle = v;
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 端点填充色 */
|
|
|
+ private _fillColor: SColor = SColor.White;
|
|
|
+ get fillColor(): SColor {
|
|
|
+ return this._fillColor;
|
|
|
+ }
|
|
|
+ set fillColor(v: SColor) {
|
|
|
+ this._fillColor = v;
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 选中端点填充色 */
|
|
|
+ private _activeFillColor: SColor = new SColor("#2196f3");
|
|
|
+ get activeFillColor(): SColor {
|
|
|
+ return this._activeFillColor;
|
|
|
+ }
|
|
|
+ set activeFillColor(v: SColor) {
|
|
|
+ this._activeFillColor = v;
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 线条宽度 */
|
|
|
+ private _lineWidth: number = 1;
|
|
|
+ get lineWidth(): number {
|
|
|
+ return this._lineWidth;
|
|
|
+ }
|
|
|
+ set lineWidth(v: number) {
|
|
|
+ this._lineWidth = v;
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 拖动灵敏度 */
|
|
|
+ dis: number = 5;
|
|
|
+
|
|
|
+ /** 拖动灵敏度 */
|
|
|
+ private sceneDis: number = 5;
|
|
|
+
|
|
|
+ /** 当前点索引 */
|
|
|
+ curIndex: number = -1;
|
|
|
+
|
|
|
+ /** 当前点坐标 */
|
|
|
+ private curPoint: SPoint | null = null;
|
|
|
+
|
|
|
+ /** undo/redo 堆栈 */
|
|
|
+ // private undoStack: SUndoStack = new SUndoStack();
|
|
|
+
|
|
|
+ /** 比例尺长度 */
|
|
|
+ private _text: string = "";
|
|
|
+ get text(): string {
|
|
|
+ return this._text;
|
|
|
+ }
|
|
|
+ set text(v: string) {
|
|
|
+ this._text = v;
|
|
|
+ }
|
|
|
+ /** 文本位置 */
|
|
|
+ textPos: SPoint | null = null;
|
|
|
+
|
|
|
+ /** 绘制时需要旋转的角度 */
|
|
|
+ private _ang: number = 0;
|
|
|
+ /** 两端长短 */
|
|
|
+ extremeLen: number = 8;
|
|
|
+
|
|
|
+ /** 线段数组 */
|
|
|
+ lineList: SLine[] = [];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ *
|
|
|
+ * @param parent 父级
|
|
|
+ */
|
|
|
+ constructor(parent: SGraphItem | null);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ *
|
|
|
+ * @param parent 父级
|
|
|
+ * @param line 坐标集合
|
|
|
+ */
|
|
|
+ constructor(parent: SGraphItem | null, line: SPoint[]);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ *
|
|
|
+ * @param parent 父级
|
|
|
+ * @param point 第一个点坐标
|
|
|
+ */
|
|
|
+ constructor(parent: SGraphItem | null, point: SPoint);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ *
|
|
|
+ * @param parent 父级
|
|
|
+ * @param line 坐标集合|第一个点坐标
|
|
|
+ */
|
|
|
+ constructor(parent: SGraphItem | null, line?: SPoint | SPoint[]) {
|
|
|
+ super(parent);
|
|
|
+ // 坐标集合存在时
|
|
|
+ if (line) {
|
|
|
+ // 坐标集合出现在 SPoint 的实例对象上
|
|
|
+ if (line instanceof SPoint) {
|
|
|
+ this.line.push(line);
|
|
|
+ } else {
|
|
|
+ // 没有在 SPoint 的实例对象上
|
|
|
+ this.line = line;
|
|
|
+ this.pointChange();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 坐标集合不存在时
|
|
|
+ this.line = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 点发生变化
|
|
|
+ */
|
|
|
+ protected pointChange(): void {
|
|
|
+ if (this.line.length > 1) {
|
|
|
+ const line = new SLine(this.line[0], this.line[1]);
|
|
|
+ const dis = SMathUtil.pointDistance(
|
|
|
+ this.line[0].x,
|
|
|
+ this.line[0].y,
|
|
|
+ this.line[1].x,
|
|
|
+ this.line[1].y
|
|
|
+ );
|
|
|
+ if (line.dx != 0) {
|
|
|
+ const tempFo = Math.atan(line.dy / line.dx);
|
|
|
+ this._ang = line.dx > 0 ? tempFo : tempFo + Math.PI;
|
|
|
+ } else {
|
|
|
+ this._ang = line.dy > 0 ? Math.PI / 2 : (3 * Math.PI) / 2;
|
|
|
+ }
|
|
|
+ this.lineList = [
|
|
|
+ new SLine(0, 0, dis, 0),
|
|
|
+ new SLine(0, this.extremeLen, 0, -this.extremeLen),
|
|
|
+ new SLine(dis, this.extremeLen, dis, -this.extremeLen)
|
|
|
+ ];
|
|
|
+ this.textPos = new SPoint(dis / 2, 0);
|
|
|
+ this.calculate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加点至数组中
|
|
|
+ *
|
|
|
+ * @param p 添加的点
|
|
|
+ */
|
|
|
+ private addPoint(p: SPoint): void {
|
|
|
+ // 坐标集合长度大于2时
|
|
|
+ if (this.line.length < 2) {
|
|
|
+ this.line.push(p);
|
|
|
+ // this.recordAction(SGraphPointListInsert, [this.line, p]);
|
|
|
+ } else {
|
|
|
+ // 坐标集合长度不大于2时
|
|
|
+ this.line[1] = p;
|
|
|
+ // this.recordAction(SGraphPointListInsert, [this.line, p, 1]);
|
|
|
+ this.status = SItemStatus.Normal;
|
|
|
+ this.releaseItem();
|
|
|
+ this.$emit("finishCreated");
|
|
|
+ this.pointChange();
|
|
|
+ }
|
|
|
+ this.update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标双击事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ * @return 是否处理事件
|
|
|
+ */
|
|
|
+ onDoubleClick(event: SMouseEvent): boolean {
|
|
|
+ // 如果为show状态 双击改对象则需改为编辑状态
|
|
|
+ if (this.status == SItemStatus.Normal) {
|
|
|
+ this.status = SItemStatus.Edit;
|
|
|
+ this.grabItem(this);
|
|
|
+ } else if (this.status == SItemStatus.Edit) {
|
|
|
+ // 处于编辑状态时
|
|
|
+ this.status = SItemStatus.Normal;
|
|
|
+ this.releaseItem();
|
|
|
+ }
|
|
|
+ this.update();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标按下事件
|
|
|
+ *
|
|
|
+ * @param event 鼠标事件
|
|
|
+ * @return 是否处理事件
|
|
|
+ */
|
|
|
+ onMouseDown(event: SMouseEvent): boolean {
|
|
|
+ this.curIndex = -1;
|
|
|
+ this.curPoint = null;
|
|
|
+ // 按下 shiftKey 键
|
|
|
+ if (event.shiftKey) {
|
|
|
+ event = this.compare(event);
|
|
|
+ }
|
|
|
+ // 按下鼠标左键
|
|
|
+ if (event.buttons == SMouseButton.LeftButton) {
|
|
|
+ if (this.status == SItemStatus.Normal) {
|
|
|
+ //标准状态时
|
|
|
+ return super.onMouseDown(event);
|
|
|
+ } else if (this.status == SItemStatus.Edit) {
|
|
|
+ // 编辑状态时
|
|
|
+ // 判断是否点击到端点上(获取端点索引值)
|
|
|
+ this.findNearestPoint(new SPoint(event.x, event.y));
|
|
|
+ } else if (this.status == SItemStatus.Create) {
|
|
|
+ // 创建状态时
|
|
|
+ this.addPoint(new SPoint(event.x, event.y));
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标抬起事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ * @return 是否处理事件
|
|
|
+ */
|
|
|
+ onMouseUp(event: SMouseEvent): boolean {
|
|
|
+ // 处于编辑状态时
|
|
|
+ if (this.status == SItemStatus.Edit) {
|
|
|
+ // 当前点索引大于 -1 时
|
|
|
+ if (this.curIndex > -1) {
|
|
|
+ const p = new SPoint(
|
|
|
+ this.line[this.curIndex].x,
|
|
|
+ this.line[this.curIndex].y
|
|
|
+ );
|
|
|
+ // this.recordAction(SGraphPointListUpdate, [
|
|
|
+ // this.line,
|
|
|
+ // this.curPoint,
|
|
|
+ // p,
|
|
|
+ // this.curIndex
|
|
|
+ // ]);
|
|
|
+ }
|
|
|
+ } else if (this.status == SItemStatus.Normal) {
|
|
|
+ // 标准状态时
|
|
|
+ this.moveToOrigin(this.x, this.y);
|
|
|
+ return super.onMouseUp(event);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.curIndex = -1;
|
|
|
+ this.curPoint = null;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标移动事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ * @return 是否处理事件
|
|
|
+ */
|
|
|
+ onMouseMove(event: SMouseEvent): boolean {
|
|
|
+ // 按下 shiftKey 键
|
|
|
+ if (event.shiftKey) {
|
|
|
+ event = this.compare(event);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处于创建状态时
|
|
|
+ if (this.status == SItemStatus.Create) {
|
|
|
+ if (this.line[0] instanceof SPoint) {
|
|
|
+ this.line[1] = new SPoint(event.x, event.y);
|
|
|
+ this.pointChange();
|
|
|
+ }
|
|
|
+ } else if (this.status == SItemStatus.Edit) {
|
|
|
+ // 处于编辑状态时
|
|
|
+ // 当前索引不等于 -1 时
|
|
|
+ if (-1 != this.curIndex) {
|
|
|
+ this.line[this.curIndex].x = event.x;
|
|
|
+ this.line[this.curIndex].y = event.y;
|
|
|
+ this.pointChange();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return super.onMouseMove(event);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.update();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取点击点与 Point[] 中的点距离最近点
|
|
|
+ *
|
|
|
+ * @param p 鼠标点击点
|
|
|
+ */
|
|
|
+ findNearestPoint(p: SPoint): void {
|
|
|
+ // 拖动灵敏度
|
|
|
+ let len = this.sceneDis;
|
|
|
+ for (let i = 0; i < this.line.length; i++) {
|
|
|
+ // 计算点到点距离
|
|
|
+ let dis = SMathUtil.pointDistance(
|
|
|
+ p.x,
|
|
|
+ p.y,
|
|
|
+ this.line[i].x,
|
|
|
+ this.line[i].y
|
|
|
+ );
|
|
|
+ // 点到点的距离大于灵敏度时
|
|
|
+ if (dis < len) {
|
|
|
+ len = dis;
|
|
|
+ this.curIndex = i;
|
|
|
+ this.curPoint = new SPoint(this.line[this.curIndex]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录相关动作并推入栈中
|
|
|
+ *
|
|
|
+ * @param SGraphCommand 相关命令类
|
|
|
+ * @param any 对应传入参数
|
|
|
+ */
|
|
|
+ // protected recordAction(SGraphCommand: any, any: any[]): void {
|
|
|
+ // // 记录相关命令并推入堆栈中 todo
|
|
|
+ // const command = new SGraphCommand(this.scene, this, ...any);
|
|
|
+ // this.undoStack.push(command);
|
|
|
+ // }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移动后处理所有坐标,并肩原点置为场景原点
|
|
|
+ *
|
|
|
+ * @param x x 坐标
|
|
|
+ * @param y y 坐标
|
|
|
+ */
|
|
|
+ moveToOrigin(x: number, y: number): void {
|
|
|
+ super.moveToOrigin(x, y);
|
|
|
+ this.line = this.line.map(
|
|
|
+ (t): SPoint => {
|
|
|
+ t.x = t.x + x;
|
|
|
+ t.y = t.y + y;
|
|
|
+ return t;
|
|
|
+ }
|
|
|
+ );
|
|
|
+ this.x = 0;
|
|
|
+ this.y = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * shift 垂直水平创建或编辑
|
|
|
+ *
|
|
|
+ * @param event 事件
|
|
|
+ * @return 处理后的事件对象
|
|
|
+ */
|
|
|
+ compare(event: SMouseEvent): SMouseEvent {
|
|
|
+ // 线段长度存在时
|
|
|
+ if (this.line.length) {
|
|
|
+ let last = new SPoint(event.x, event.y);
|
|
|
+ // 处于创建状态时
|
|
|
+ if (this.status == SItemStatus.Create) {
|
|
|
+ last = this.line[0];
|
|
|
+ } else if (this.status == SItemStatus.Edit) {
|
|
|
+ // 处于编辑状态时
|
|
|
+ // 当前索引等于 1 时
|
|
|
+ if (this.curIndex == 1) {
|
|
|
+ last = this.line[0];
|
|
|
+ } else if (this.curIndex == 0) {
|
|
|
+ // 当前索引等于 0 时
|
|
|
+ last = this.line[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const dx = Math.abs(event.x - last.x);
|
|
|
+ const dy = Math.abs(event.y - last.y);
|
|
|
+ if (dy > dx) {
|
|
|
+ event.x = last.x;
|
|
|
+ } else {
|
|
|
+ event.y = last.y;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return event;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断点是否在区域内
|
|
|
+ *
|
|
|
+ * @param x x 坐标
|
|
|
+ * @param y y 坐标
|
|
|
+ * @return 是否包含
|
|
|
+ */
|
|
|
+ contains(x: number, y: number): boolean {
|
|
|
+ // 线段长度大于 2 时
|
|
|
+ if (this.line.length == 2) {
|
|
|
+ let p = new SPoint(x, y);
|
|
|
+ // 计算点到线段垂线与线段的交点
|
|
|
+ if (
|
|
|
+ SMathUtil.pointToLine(p, new SLine(this.line[0], this.line[1]))
|
|
|
+ .MinDis < this.dis
|
|
|
+ ) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // /**
|
|
|
+ // * 撤销操作
|
|
|
+ // */
|
|
|
+ // undo(): void {
|
|
|
+ // // 处于标准状态时
|
|
|
+ // if (this.status != SItemStatus.Normal) {
|
|
|
+ // this.undoStack.undo();
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // /**
|
|
|
+ // * 重做操作
|
|
|
+ // */
|
|
|
+ // redo(): void {
|
|
|
+ // // 处于标准状态时
|
|
|
+ // if (this.status != SItemStatus.Normal) {
|
|
|
+ // this.undoStack.redo();
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取消操作 item 事件
|
|
|
+ */
|
|
|
+ cancelOperate(): void {
|
|
|
+ // 处于创建状态时
|
|
|
+ if (this.status == SItemStatus.Create) {
|
|
|
+ this.parent = null;
|
|
|
+ this.releaseItem();
|
|
|
+ } else if (this.status == SItemStatus.Edit) {
|
|
|
+ // 处于编辑状态时
|
|
|
+ this.status = SItemStatus.Normal;
|
|
|
+ this.releaseItem();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算边界值
|
|
|
+ */
|
|
|
+ calculate(): void {
|
|
|
+ // 线段长度存在时
|
|
|
+ if (this.line.length) {
|
|
|
+ this.minX = this.line[0].x;
|
|
|
+ this.maxX = this.line[0].x;
|
|
|
+ this.minY = this.line[0].y;
|
|
|
+ this.maxY = this.line[0].y;
|
|
|
+ this.line.forEach((it): void => {
|
|
|
+ let x = it.x,
|
|
|
+ y = it.y;
|
|
|
+
|
|
|
+ // 如果数据 x 坐标小于 x 坐标最小值
|
|
|
+ if (x < this.minX) {
|
|
|
+ this.minX = x;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果数据 y 坐标小于 y 坐标最小值
|
|
|
+ if (y < this.minY) {
|
|
|
+ this.minY = y;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果数据 x 坐标大于 x 坐标最小值
|
|
|
+ if (x > this.maxX) {
|
|
|
+ this.maxX = x;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果数据 y 坐标大于 y 坐标最小值
|
|
|
+ if (y > this.maxY) {
|
|
|
+ this.maxY = y;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Item 对象边界区域
|
|
|
+ *
|
|
|
+ * @return 边界区域
|
|
|
+ */
|
|
|
+ boundingRect(): SRect {
|
|
|
+ this.calculate();
|
|
|
+ return new SRect(
|
|
|
+ this.minX,
|
|
|
+ this.minY,
|
|
|
+ this.maxX - this.minX,
|
|
|
+ this.maxY - this.minY
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Item 绘制操作
|
|
|
+ *
|
|
|
+ * @param painter 绘制对象
|
|
|
+ */
|
|
|
+ onDraw(painter: SPainter): void {
|
|
|
+ // 如果线段存在
|
|
|
+ if (this.line.length) {
|
|
|
+ painter.save();
|
|
|
+ painter.pen.lineWidth = painter.toPx(this.lineWidth);
|
|
|
+ painter.translate(this.line[0].x, this.line[0].y);
|
|
|
+ painter.rotate((this._ang * 180) / Math.PI);
|
|
|
+ painter.pen.color = this.strokeColor;
|
|
|
+ painter.brush.color = this.fillColor;
|
|
|
+ this.lineList.forEach(t => {
|
|
|
+ painter.drawLine(t);
|
|
|
+ });
|
|
|
+ if (this.text && this.textPos) {
|
|
|
+ painter.font.textAlign = STextAlign.Center;
|
|
|
+ painter.brush.color = SColor.Black;
|
|
|
+ painter.pen.color = SColor.White;
|
|
|
+ painter.pen.lineWidth = painter.toPx(8);
|
|
|
+ painter.drawText(this.text, this.textPos.x, this.textPos.y);
|
|
|
+ }
|
|
|
+ painter.restore();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|