EditPolylineItem.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. import { SColor, SLine, SPainter, SPoint, SRect } from "@persagy-web/draw";
  2. import { SKeyCode, SMouseEvent, SUndoStack } from "@persagy-web/base";
  3. import { SItemStatus } from "@persagy-web/big";
  4. import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
  5. import {
  6. SGraphItem,
  7. SGraphPointListDelete,
  8. SGraphPointListInsert,
  9. SGraphPointListUpdate,
  10. SLineStyle
  11. } from "@persagy-web/graph/lib";
  12. ;
  13. ;
  14. /**
  15. * 直线item
  16. *
  17. */
  18. export class EditPolylineItem extends SGraphItem {
  19. /** X坐标最小值 */
  20. private minX = Number.MAX_SAFE_INTEGER;
  21. /** X坐标最大值 */
  22. private maxX = Number.MIN_SAFE_INTEGER;
  23. /** Y坐标最小值 */
  24. private minY = Number.MAX_SAFE_INTEGER;
  25. /** Y坐标最大值 */
  26. private maxY = Number.MIN_SAFE_INTEGER;
  27. /** 折点信息 */
  28. pointList: SPoint[] = [];
  29. /** 是否绘制完成 */
  30. _status: SItemStatus = SItemStatus.Normal;
  31. get status(): SItemStatus {
  32. return this._status;
  33. }
  34. set status(v: SItemStatus) {
  35. this._status = v;
  36. this.undoStack.clear();
  37. this.update();
  38. }
  39. /** 鼠标移动时的点 */
  40. private lastPoint: SPoint | null = null;
  41. /** 线条颜色 */
  42. _strokeColor: SColor = SColor.Black;
  43. get strokeColor(): SColor {
  44. return this._strokeColor;
  45. }
  46. set strokeColor(v: SColor) {
  47. this._strokeColor = v;
  48. this.update();
  49. }
  50. /** 填充色 */
  51. _fillColor: SColor = new SColor("#2196f3");
  52. get fillColor(): SColor {
  53. return this._fillColor;
  54. }
  55. set fillColor(v: SColor) {
  56. this._fillColor = v;
  57. this.update();
  58. }
  59. /** 边框样式 */
  60. _lineStyle: SLineStyle = SLineStyle.Solid;
  61. get lineStyle(): SLineStyle {
  62. return this._lineStyle;
  63. }
  64. set lineStyle(v: SLineStyle) {
  65. this._lineStyle = v;
  66. this.update();
  67. }
  68. /** 线条宽度 */
  69. _lineWidth: number = 1;
  70. get lineWidth(): number {
  71. return this._lineWidth;
  72. }
  73. set lineWidth(v: number) {
  74. this._lineWidth = v;
  75. this.update();
  76. }
  77. /** 是否垂直水平绘制 */
  78. private _verAndLeve: Boolean = false;
  79. get verAndLeve(): Boolean {
  80. return this._verAndLeve;
  81. }
  82. set verAndLeve(bool: Boolean) {
  83. this._verAndLeve = bool;
  84. this.update();
  85. }
  86. /** 全局灵敏度 */
  87. dis: number = 10;
  88. /** 灵敏度转换为场景长度 */
  89. private sceneDis: number = 10;
  90. /** 当前点索引 */
  91. private curIndex: number = -1;
  92. /** 当前点索引 */
  93. private curPoint: SPoint | null = null;
  94. /** undo/redo堆栈 */
  95. private undoStack: SUndoStack = new SUndoStack();
  96. /**
  97. * 构造函数
  98. *
  99. * @param parent 父级
  100. * @param list 坐标集合
  101. */
  102. constructor(parent: null | SGraphItem, list: SPoint[]);
  103. /**
  104. * 构造函数
  105. *
  106. * @param parent 父级
  107. * @param list 第一个坐标
  108. */
  109. constructor(parent: null | SGraphItem, list: SPoint);
  110. /**
  111. * 构造函数
  112. *
  113. * @param parent 父级
  114. * @param list 第一个坐标|坐标集合
  115. */
  116. constructor(parent: null | SGraphItem, list: SPoint | SPoint[]) {
  117. super(parent);
  118. if (list instanceof SPoint) { // 实例在 SPoint 对象中
  119. this.pointList.push(list);
  120. } else {
  121. this.pointList = list;
  122. }
  123. } // Constructor
  124. /**
  125. * 添加点至数组中
  126. *
  127. * @param p 添加的点
  128. * @param index 添加到的索引
  129. */
  130. private addPoint(p: SPoint, index?: number): void {
  131. if (index && this.canHandle(index)) {
  132. this.pointList.splice(index, 0, p);
  133. this.recordAction(SGraphPointListInsert, [
  134. this.pointList,
  135. p,
  136. index
  137. ]);
  138. } else {
  139. this.pointList.push(p);
  140. this.recordAction(SGraphPointListInsert, [this.pointList, p]);
  141. }
  142. this.update();
  143. } // Function addPoint()
  144. /**
  145. * 是否可以添加点到数组中
  146. *
  147. * @param index 要添加到的索引
  148. * @return boolean 是否可添加
  149. */
  150. private canHandle(index: number): boolean {
  151. return index >= 0 && index <= this.pointList.length;
  152. } // Function canHandle()
  153. /**
  154. * 根据索引删除点
  155. *
  156. * @param index 删除点
  157. */
  158. deletePoint(index: number): void {
  159. if (this.canHandle(index) && this.pointList.length > 2) {
  160. const p = new SPoint(
  161. this.pointList[this.curIndex].x,
  162. this.pointList[this.curIndex].y
  163. );
  164. this.pointList.splice(index, 1);
  165. this.recordAction(SGraphPointListDelete, [
  166. this.pointList,
  167. p,
  168. index
  169. ]);
  170. this.curIndex = -1;
  171. this.curPoint = null;
  172. this.update();
  173. }
  174. } // Function deletePoint
  175. /**
  176. * 鼠标按下事件
  177. *
  178. * @param event 鼠标事件
  179. * @return boolean 是否处理事件
  180. */
  181. onMouseDown(event: SMouseEvent): boolean {
  182. this.curIndex = -1;
  183. this.curPoint = null;
  184. if (event.shiftKey || this.verAndLeve) {
  185. event = this.compare(event);
  186. }
  187. if (event.buttons == 1) {
  188. if (this.status == SItemStatus.Create) {
  189. this.addPoint(new SPoint(event.x, event.y));
  190. return true;
  191. } else if (this.status == SItemStatus.Edit) {
  192. // 查询鼠标最近的索引
  193. this.findNearestPoint(new SPoint(event.x, event.y));
  194. // 增加点
  195. if (this.curIndex < 0) {
  196. this.findAddPos(new SPoint(event.x, event.y));
  197. }
  198. // 删除点
  199. if (event.altKey && this.canHandle(this.curIndex)) {
  200. this.deletePoint(this.curIndex);
  201. }
  202. this.update();
  203. return true;
  204. } else {
  205. return super.onMouseDown(event);
  206. }
  207. }
  208. return super.onMouseDown(event);
  209. } // Function onMouseDown()
  210. /**
  211. * 鼠标移动事件
  212. *
  213. * @param event 鼠标事件
  214. * @return boolean 是否处理事件
  215. */
  216. onMouseMove(event: SMouseEvent): boolean {
  217. if (event.shiftKey || this.verAndLeve) {
  218. event = this.compare(event);
  219. }
  220. if (this.status == SItemStatus.Create) {
  221. if (this.lastPoint) {
  222. this.lastPoint.x = event.x;
  223. this.lastPoint.y = event.y;
  224. } else {
  225. this.lastPoint = new SPoint(event.x, event.y);
  226. }
  227. this.update();
  228. return true;
  229. } else if (this.status == SItemStatus.Edit) {
  230. if (event.buttons == 1) {
  231. if (this.canHandle(this.curIndex)) {
  232. this.pointList[this.curIndex].x = event.x;
  233. this.pointList[this.curIndex].y = event.y;
  234. }
  235. }
  236. this.update();
  237. return true;
  238. } else {
  239. return super.onMouseMove(event);
  240. }
  241. } // Function onMouseMove()
  242. /**
  243. * 鼠标抬起事件
  244. *
  245. * @param event 事件参数
  246. * @return 是否处理
  247. */
  248. onMouseUp(event: SMouseEvent): boolean {
  249. if (this.status == SItemStatus.Edit) {
  250. if (this.curIndex > -1) {
  251. const p = new SPoint(
  252. this.pointList[this.curIndex].x,
  253. this.pointList[this.curIndex].y
  254. );
  255. this.recordAction(SGraphPointListUpdate, [
  256. this.pointList,
  257. this.curPoint,
  258. p,
  259. this.curIndex
  260. ]);
  261. }
  262. } else if (this.status == SItemStatus.Normal) {
  263. this.moveToOrigin(this.x, this.y);
  264. return super.onMouseUp(event);
  265. }
  266. return true;
  267. } // Function onMouseUp()
  268. /**
  269. * 鼠标双击事件
  270. *
  271. * @param event 事件参数
  272. * @return boolean
  273. */
  274. onDoubleClick(event: SMouseEvent): boolean {
  275. if (this.status == SItemStatus.Normal) {
  276. this.status = SItemStatus.Edit;
  277. this.grabItem(this);
  278. } else if (this.status == SItemStatus.Edit) {
  279. this.status = SItemStatus.Normal;
  280. this.releaseItem();
  281. } else if (this.status == SItemStatus.Create) {
  282. if (this.pointList.length > 1) {
  283. this.status = SItemStatus.Normal;
  284. this.releaseItem();
  285. this.$emit("finishCreated");
  286. }
  287. }
  288. this.$emit("onDoubleClick", event);
  289. return true;
  290. } // Function onDoubleClick()
  291. /***
  292. * 键盘按键弹起事件
  293. *
  294. * @param event 事件参数
  295. */
  296. onKeyUp(event: KeyboardEvent): void {
  297. if (event.keyCode == SKeyCode.Enter) {
  298. if (this.pointList.length > 1) {
  299. if (this.status == SItemStatus.Create) {
  300. this.$emit("finishCreated");
  301. }
  302. this.status = SItemStatus.Normal;
  303. this.releaseItem();
  304. }
  305. }
  306. // delete删除点
  307. if (
  308. event.keyCode == SKeyCode.Delete &&
  309. this.status == SItemStatus.Edit
  310. ) {
  311. this.deletePoint(this.curIndex);
  312. }
  313. } // Function onKeyUp()
  314. /**
  315. * 移动后处理所有坐标,并肩原点置为场景原点
  316. *
  317. * @param x x坐标
  318. * @param y y坐标
  319. */
  320. moveToOrigin(x: number, y: number): void {
  321. super.moveToOrigin(x, y);
  322. this.pointList = this.pointList.map(t => {
  323. t.x = t.x + x;
  324. t.y = t.y + y;
  325. return t;
  326. });
  327. this.x = 0;
  328. this.y = 0;
  329. } // Function moveToOrigin()
  330. /**
  331. * 获取点击点与点集中距离最近点
  332. *
  333. * @param p 鼠标点击点
  334. */
  335. findNearestPoint(p: SPoint): void {
  336. let len = this.sceneDis;
  337. for (let i = 0; i < this.pointList.length; i++) {
  338. let dis = SMathUtil.pointDistance(
  339. p.x,
  340. p.y,
  341. this.pointList[i].x,
  342. this.pointList[i].y
  343. );
  344. if (dis < len) {
  345. len = dis;
  346. this.curIndex = i;
  347. this.curPoint = new SPoint(
  348. this.pointList[this.curIndex].x,
  349. this.pointList[this.curIndex].y
  350. );
  351. }
  352. }
  353. } // Function findNearestPoint()
  354. /**
  355. * 计算增加点的位置
  356. *
  357. * @param p 鼠标点击点
  358. */
  359. findAddPos(p: SPoint): void {
  360. let len = SMathUtil.pointToLine(
  361. p,
  362. new SLine(this.pointList[0], this.pointList[1])
  363. ),
  364. index = 0;
  365. if (this.pointList.length > 2) {
  366. for (let i = 1; i < this.pointList.length - 1; i++) {
  367. let dis = SMathUtil.pointToLine(
  368. p,
  369. new SLine(this.pointList[i], this.pointList[i + 1])
  370. );
  371. if (dis.MinDis < len.MinDis) {
  372. len = dis;
  373. index = i;
  374. }
  375. }
  376. }
  377. if (len.MinDis < this.sceneDis) {
  378. if (len.Point) {
  379. this.addPoint(len.Point, index + 1);
  380. }
  381. }
  382. } // Function findAddPos()
  383. /**
  384. * shift垂直水平创建或编辑
  385. *
  386. * @param event 事件
  387. */
  388. compare(event: SMouseEvent): SMouseEvent {
  389. if (this.pointList.length) {
  390. let last = new SPoint(event.x, event.y);
  391. if (this.status == SItemStatus.Create) {
  392. last = this.pointList[this.pointList.length - 1];
  393. } else if (this.status == SItemStatus.Edit) {
  394. if (this.curIndex > 1) {
  395. last = this.pointList[this.curIndex - 1];
  396. }
  397. }
  398. const dx = Math.abs(event.x - last.x);
  399. const dy = Math.abs(event.y - last.y);
  400. if (dy > dx) {
  401. event.x = last.x;
  402. } else {
  403. event.y = last.y;
  404. }
  405. }
  406. return event;
  407. } // Function compare()
  408. /**
  409. * 记录相关动作并推入栈中
  410. *
  411. * @param SGraphCommand 相关命令类
  412. * @param any 对应传入参数
  413. */
  414. protected recordAction(SGraphCommand: any, any: any[]): void {
  415. // 记录相关命令并推入堆栈中
  416. const command = new SGraphCommand(this.scene, this, ...any);
  417. this.undoStack.push(command);
  418. } // Function recordAction()
  419. /**
  420. * Item对象边界区域
  421. *
  422. * @return 边界区域
  423. */
  424. boundingRect(): SRect {
  425. if (this.pointList.length) {
  426. this.minX = this.pointList[0].x;
  427. this.maxX = this.pointList[0].x;
  428. this.minY = this.pointList[0].y;
  429. this.maxY = this.pointList[0].y;
  430. this.pointList.forEach(it => {
  431. let x = it.x,
  432. y = it.y;
  433. if (x < this.minX) {
  434. this.minX = x;
  435. }
  436. if (y < this.minY) {
  437. this.minY = y;
  438. }
  439. if (x > this.maxX) {
  440. this.maxX = x;
  441. }
  442. if (y > this.maxY) {
  443. this.maxY = y;
  444. }
  445. });
  446. }
  447. return new SRect(
  448. this.minX,
  449. this.minY,
  450. this.maxX - this.minX,
  451. this.maxY - this.minY
  452. );
  453. } // Function boundingRect()
  454. /**
  455. * 判断点是否在区域内
  456. *
  457. * @param x
  458. * @param y
  459. * @return true-是
  460. */
  461. contains(x: number, y: number): boolean {
  462. let p = new SPoint(x, y);
  463. for (let i = 1; i < this.pointList.length; i++) {
  464. let PTL = SMathUtil.pointToLine(
  465. p,
  466. new SLine(
  467. this.pointList[i - 1].x,
  468. this.pointList[i - 1].y,
  469. this.pointList[i].x,
  470. this.pointList[i].y
  471. )
  472. );
  473. if (PTL.MinDis < this.sceneDis) {
  474. return true;
  475. }
  476. }
  477. return false;
  478. } // Function contains()
  479. /**
  480. * 撤销操作
  481. *
  482. */
  483. undo(): void {
  484. if (this._status != SItemStatus.Normal) {
  485. this.undoStack.undo();
  486. }
  487. } // Function undo()
  488. /**
  489. * 重做操作
  490. *
  491. */
  492. redo(): void {
  493. if (this._status != SItemStatus.Normal) {
  494. this.undoStack.redo();
  495. }
  496. } // Function redo()
  497. /**
  498. * 取消操作执行
  499. *
  500. */
  501. cancelOperate(): void {
  502. if (this.status == SItemStatus.Create) {
  503. this.parent = null;
  504. this.releaseItem();
  505. } else if (this.status == SItemStatus.Edit) {
  506. this.status = SItemStatus.Normal;
  507. this.releaseItem();
  508. }
  509. } // Function cancelOperate()
  510. /**
  511. * 绘制基本图形
  512. */
  513. drawBaseLine(painter: SPainter): void {
  514. // 绘制基本图形
  515. painter.pen.color = this.strokeColor;
  516. if (this.lineStyle == SLineStyle.Dashed) {
  517. painter.pen.lineDash = [
  518. painter.toPx(this.lineWidth * 3),
  519. painter.toPx(this.lineWidth * 7)
  520. ];
  521. } else if (this.lineStyle == SLineStyle.Dotted) {
  522. painter.pen.lineDash = [
  523. painter.toPx(this.lineWidth),
  524. painter.toPx(this.lineWidth)
  525. ];
  526. }
  527. painter.drawPolyline(this.pointList);
  528. } // Function drawBaseLine()
  529. /**
  530. * Item绘制操作
  531. *
  532. * @param painter painter对象
  533. */
  534. onDraw(painter: SPainter): void {
  535. // 缓存场景长度
  536. this.sceneDis = painter.toPx(this.dis);
  537. // 创建状态
  538. painter.pen.lineWidth = painter.toPx(this.lineWidth);
  539. if (this.status == SItemStatus.Create && this.lastPoint) {
  540. // 绘制基本图形
  541. this.drawBaseLine(painter);
  542. painter.drawLine(
  543. this.pointList[this.pointList.length - 1],
  544. this.lastPoint
  545. );
  546. // 编辑状态
  547. this.pointList.forEach((t, i): void => {
  548. painter.brush.color = SColor.White;
  549. if (i == this.curIndex) {
  550. painter.brush.color = this.fillColor;
  551. }
  552. painter.drawCircle(t.x, t.y, painter.toPx(5));
  553. });
  554. } else if (this.status == SItemStatus.Edit) {
  555. // 绘制基本图形
  556. this.drawBaseLine(painter);
  557. // 编辑状态
  558. this.pointList.forEach((t, i): void => {
  559. painter.brush.color = SColor.White;
  560. if (i == this.curIndex) {
  561. painter.brush.color = this.fillColor;
  562. }
  563. painter.drawCircle(t.x, t.y, painter.toPx(5));
  564. });
  565. } else {
  566. // 查看状态
  567. if (this.selected) {
  568. painter.pen.lineWidth = painter.toPx(this.lineWidth * 2);
  569. painter.shadow.shadowBlur = 10;
  570. painter.shadow.shadowColor = new SColor(`#00000033`);
  571. painter.shadow.shadowOffsetX = 5;
  572. painter.shadow.shadowOffsetY = 5;
  573. }
  574. // 绘制基本图形
  575. this.drawBaseLine(painter);
  576. }
  577. }
  578. } // Class SPolylineItem