SPolygonItem.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. /*
  2. * ********************************************************************************************************************
  3. *
  4. * :*$@@%$*: ;: ;; ;;
  5. * :@@%! :!@@%: %! ;%%@@%$ =@@@@@@@%; @%@@@%%%%@@@@@
  6. * :@%; :$= %%$$$%$$ ;$$ ;$@= !@$
  7. * =@! %! @ $=;% !@@@%: !$$$$$$$$$$$$$$=
  8. * =@* %! @ $= % %@= =%@! %=
  9. * *$%%! @@= ;=$%%%$*: %! @ $= % =%%%%%%@$ *%: =%
  10. * %@@!: !@@@%=$@@@@%! :*@@$: %! @ $= % $* ;@ @* :%*
  11. * ;@@! ;!!!;: ;@%: =======@%========* @ $$ % $%*****$@ :@$=*********=@$
  12. * $@* ;@@@%=!: *@*
  13. * =@$ ;;;!=%@@@@=! =@!
  14. * %@$: =@%: :*@@@* %@= Copyright (c) 2016-2019. 北京上格云技术有限公司
  15. * ;%@@$=$@@%* *@@@$=%@@%;
  16. * ::;:: ::;:: All rights reserved.
  17. *
  18. * ********************************************************************************************************************
  19. */
  20. import {
  21. SGraphItem,
  22. SGraphPointListInsert,
  23. SGraphPointListDelete,
  24. SGraphPointListUpdate
  25. } from "@saga-web/graph/lib";
  26. import { SMouseEvent, SUndoStack } from "@saga-web/base/";
  27. import {
  28. SColor,
  29. SLineCapStyle,
  30. SPainter,
  31. SPoint,
  32. SRect,
  33. SLine,
  34. SPolygonUtil
  35. } from "@saga-web/draw";
  36. import { SItemStatus } from "..";
  37. import { SMathUtil } from "../utils/SMathUtil";
  38. /**
  39. * 编辑多边形
  40. *
  41. * @author 韩耀龙
  42. */
  43. export class SPolygonItem extends SGraphItem {
  44. /** X坐标最小值 */
  45. private minX = Number.MAX_SAFE_INTEGER;
  46. /** X坐标最大值 */
  47. private maxX = Number.MIN_SAFE_INTEGER;
  48. /** Y坐标最小值 */
  49. private minY = Number.MAX_SAFE_INTEGER;
  50. /** Y坐标最大值 */
  51. private maxY = Number.MIN_SAFE_INTEGER;
  52. /** 轮廓线坐标 */
  53. private pointList: SPoint[] = [];
  54. // 获取当前状态
  55. get getPointList(): SPoint[] {
  56. return this.pointList;
  57. }
  58. // 编辑当前状态
  59. set setPointList(arr: SPoint[]) {
  60. this.pointList = arr;
  61. this.update();
  62. }
  63. /** 是否闭合 */
  64. closeFlag: boolean = false;
  65. // 当前状态
  66. _status: number = SItemStatus.Normal;
  67. // 获取当前状态
  68. get getStatus(): number {
  69. return this._status;
  70. }
  71. // 编辑当前状态
  72. set setStatus(value: number) {
  73. this._status = value;
  74. // 如果状态为show则需清栈
  75. if (value == SItemStatus.Normal) {
  76. if (this.undoStack) {
  77. this.undoStack.clear();
  78. }
  79. }
  80. this.update();
  81. }
  82. /** 边框颜色 */
  83. borderColor: SColor = new SColor("#0091FF");
  84. /** 填充颜色 */
  85. brushColor: SColor = new SColor("#1EE887");
  86. /** border宽 只可输入像素宽*/
  87. borderLine: number = 4;
  88. /** 鼠标移动点 */
  89. private lastPoint: SPoint | null = null;
  90. /** 当前鼠标获取顶点对应索引 */
  91. private curIndex: number = -1;
  92. /** 当前鼠标获取顶点对应坐标 */
  93. private curPoint: null | SPoint = null;
  94. /** 灵敏像素 */
  95. private len: number = 10;
  96. /** 场景像素 内部将灵敏像素换算为场景实际距离 */
  97. private scenceLen: number = 15;
  98. /** 场景像素 */
  99. private isAlt: boolean = false;
  100. /** undo/redo堆栈 */
  101. private undoStack: SUndoStack | null = new SUndoStack();
  102. /**
  103. * 构造函数
  104. *
  105. * @param parent 指向父对象
  106. */
  107. constructor(parent: SGraphItem | null) {
  108. super(parent);
  109. }
  110. //////////////////
  111. // 以下为对pointList 数组的操作方法
  112. /**
  113. * 储存新的多边形顶点
  114. *
  115. * @param x 点位得x坐标
  116. * @param y 点位得y坐标
  117. * @param i 储存所在索引
  118. * @return SPoint。添加的顶点
  119. */
  120. insertPoint(x: number, y: number, i: number | null = null): SPoint {
  121. const point = new SPoint(x, y);
  122. if (i == null) {
  123. this.pointList.push(point);
  124. } else {
  125. this.pointList.splice(i, 0, point);
  126. }
  127. this.update();
  128. return point;
  129. }
  130. /**
  131. * 删除点位
  132. *
  133. * @param i 删除点所在的索引
  134. * @return SPoint|null。索引不在数组范围则返回null
  135. */
  136. deletePoint(i: number | null = null): SPoint | null {
  137. let point = null;
  138. if (i != null) {
  139. if (i >= this.pointList.length || i < 0) {
  140. point = null;
  141. } else {
  142. point = new SPoint(this.pointList[i].x, this.pointList[i].y);
  143. this.pointList.splice(i, 1);
  144. }
  145. } else {
  146. if (this.pointList.length) {
  147. point = this.pointList[this.pointList.length - 1];
  148. this.pointList.pop();
  149. } else {
  150. point = null;
  151. }
  152. }
  153. this.update();
  154. return point;
  155. }
  156. /**
  157. * 多边形顶点的移动位置
  158. *
  159. * @param x 点位得x坐标
  160. * @param y 点位得y坐标
  161. * @param i 点位得i坐标
  162. * @return 移动对应得点。如果索引无法找到移动顶点,则返回null
  163. */
  164. movePoint(x: number, y: number, i: number): SPoint | null {
  165. let point = null;
  166. if (i >= this.pointList.length || i < 0) {
  167. return null;
  168. }
  169. if (this.pointList.length) {
  170. this.pointList[i].x = x;
  171. this.pointList[i].y = y;
  172. }
  173. point = this.pointList[i];
  174. return point;
  175. }
  176. /**
  177. * 打印出多边形数组
  178. *
  179. * @return 顶点数组
  180. */
  181. PrintPointList(): SPoint[] {
  182. return this.pointList;
  183. }
  184. ////////////
  185. // 以下为三种状态下的绘制多边形方法
  186. /**
  187. * 展示状态 --绘制多边形数组
  188. *
  189. * @param painter 绘制类
  190. * @param pointList 绘制多边形数组
  191. */
  192. protected drawShowPolygon(painter: SPainter, pointList: SPoint[]): void {
  193. painter.pen.lineCapStyle = SLineCapStyle.Square;
  194. painter.pen.color = this.borderColor;
  195. painter.pen.lineWidth = painter.toPx(this.borderLine);
  196. painter.brush.color = this.brushColor;
  197. painter.drawPolygon([...pointList]);
  198. }
  199. /**
  200. * 创建状态 --绘制多边形数组
  201. *
  202. * @param painter 绘制类
  203. * @param pointList 绘制多边形数组
  204. */
  205. protected drawCreatePolygon(painter: SPainter, pointList: SPoint[]): void {
  206. painter.pen.lineCapStyle = SLineCapStyle.Square;
  207. painter.pen.color = this.borderColor;
  208. painter.pen.lineWidth = painter.toPx(this.borderLine);
  209. if (this.lastPoint && pointList.length) {
  210. painter.drawLine(
  211. pointList[pointList.length - 1].x,
  212. pointList[pointList.length - 1].y,
  213. this.lastPoint.x,
  214. this.lastPoint.y
  215. );
  216. }
  217. painter.drawPolyline(pointList);
  218. painter.pen.color = SColor.Transparent;
  219. painter.brush.color = this.brushColor;
  220. painter.pen.lineWidth = painter.toPx(this.borderLine);
  221. if (this.lastPoint) {
  222. painter.drawPolygon([...pointList, this.lastPoint]);
  223. } else {
  224. painter.drawPolygon(pointList);
  225. }
  226. }
  227. /**
  228. *
  229. * 编辑状态 --绘制多边形数组
  230. *
  231. * @param painter 绘制类
  232. * @param pointList 绘制多边形数组
  233. */
  234. protected drawEditPolygon(painter: SPainter, pointList: SPoint[]): void {
  235. // 展示多边形
  236. this.drawShowPolygon(painter, pointList);
  237. // 绘制顶点块
  238. painter.pen.color = SColor.Black;
  239. painter.brush.color = SColor.White;
  240. pointList.forEach((item, index) => {
  241. painter.drawCircle(item.x, item.y, painter.toPx(this.len / 2));
  242. });
  243. }
  244. /**
  245. * 编辑状态操作多边形数组
  246. *
  247. * @param event 鼠标事件
  248. *
  249. *
  250. */
  251. protected editPolygonPoint(event: SMouseEvent): void {
  252. // 判断是否为删除状态 isAlt = true为删除状态
  253. if (this.isAlt) {
  254. // 1 判断是否点击在多边形顶点
  255. let lenIndex = -1; // 当前点击到的点位索引;
  256. let curenLen = this.scenceLen; // 当前的灵敏度
  257. this.pointList.forEach((item, index) => {
  258. let dis = SMathUtil.pointDistance(
  259. event.x,
  260. event.y,
  261. item.x,
  262. item.y
  263. );
  264. if (dis < curenLen) {
  265. curenLen = dis;
  266. lenIndex = index;
  267. }
  268. });
  269. // 若点击到,对该索引对应的点做删除
  270. if (lenIndex != -1) {
  271. if (this.pointList.length <= 3) {
  272. return;
  273. }
  274. const delePoint = new SPoint(
  275. this.pointList[lenIndex].x,
  276. this.pointList[lenIndex].y
  277. );
  278. this.deletePoint(lenIndex);
  279. // 记录顶点操作记录压入堆栈
  280. this.recordAction(SGraphPointListDelete, [
  281. this.pointList,
  282. delePoint,
  283. lenIndex
  284. ]);
  285. }
  286. } else {
  287. // 1 判断是否点击在多边形顶点
  288. this.curIndex = -1;
  289. this.curPoint = null;
  290. let lenIndex = -1; // 当前点击到的点位索引;
  291. let curenLen = this.scenceLen; // 当前的灵敏度
  292. this.pointList.forEach((item, index) => {
  293. let dis = SMathUtil.pointDistance(
  294. event.x,
  295. event.y,
  296. item.x,
  297. item.y
  298. );
  299. if (dis < curenLen) {
  300. curenLen = dis;
  301. lenIndex = index;
  302. }
  303. });
  304. this.curIndex = lenIndex;
  305. // 2判断是否点击在多边形得边上
  306. if (-1 == lenIndex) {
  307. let len = SMathUtil.pointToLine(
  308. new SPoint(event.x, event.y),
  309. new SLine(this.pointList[0], this.pointList[1])
  310. ),
  311. index = 0;
  312. if (this.pointList.length > 2) {
  313. for (let i = 1; i < this.pointList.length; i++) {
  314. let dis = SMathUtil.pointToLine(
  315. new SPoint(event.x, event.y),
  316. new SLine(this.pointList[i], this.pointList[i + 1])
  317. );
  318. if (i + 1 == this.pointList.length) {
  319. dis = SMathUtil.pointToLine(
  320. new SPoint(event.x, event.y),
  321. new SLine(this.pointList[i], this.pointList[0])
  322. );
  323. }
  324. if (dis.MinDis < len.MinDis) {
  325. len = dis;
  326. index = i;
  327. }
  328. }
  329. }
  330. // 如果再线上则为新增点
  331. if (len.Point) {
  332. if (len.MinDis <= this.scenceLen) {
  333. this.pointList.splice(index + 1, 0, len.Point);
  334. // 记录新增顶点操作记录压入堆栈
  335. this.recordAction(SGraphPointListInsert, [
  336. this.pointList,
  337. len.Point,
  338. index + 1
  339. ]);
  340. }
  341. }
  342. } else {
  343. // 当捕捉到顶点后 ,记录当前点的xy坐标,用于undo、redo操作
  344. this.curPoint = new SPoint(
  345. this.pointList[this.curIndex].x,
  346. this.pointList[this.curIndex].y
  347. );
  348. }
  349. // 刷新视图
  350. this.update();
  351. }
  352. }
  353. /////////////////////
  354. // undo、redo相关操作
  355. /**
  356. * 记录相关动作并推入栈中
  357. * @param SGraphCommand 相关命令类
  358. * @param any 对应传入参数
  359. */
  360. protected recordAction(SGraphCommand: any, any: any[]): void {
  361. // 记录相关命令并推入堆栈中
  362. const sgraphcommand = new SGraphCommand(this, ...any);
  363. if (this.undoStack) {
  364. this.undoStack.push(sgraphcommand);
  365. }
  366. }
  367. /**
  368. * 执行取消操作执行
  369. */
  370. undo(): void {
  371. if (this._status == SItemStatus.Normal) {
  372. return;
  373. }
  374. if (this.undoStack) {
  375. this.undoStack.undo();
  376. }
  377. }
  378. /**
  379. * 执行重做操作执行
  380. */
  381. redo(): void {
  382. if (this._status == SItemStatus.Normal) {
  383. return;
  384. }
  385. if (this.undoStack) {
  386. this.undoStack.redo();
  387. }
  388. }
  389. ///////////////////////////////
  390. // 以下为鼠标事件
  391. /**
  392. * 鼠标双击事件
  393. *
  394. * @param event 事件参数
  395. * @return boolean
  396. */
  397. onDoubleClick(event: SMouseEvent): boolean {
  398. // 如果位show状态 双击改对象则需改为编辑状态
  399. if (SItemStatus.Normal == this._status) {
  400. this._status = SItemStatus.Edit;
  401. this.grabItem(this);
  402. } else if (SItemStatus.Edit == this._status) {
  403. this._status = SItemStatus.Normal;
  404. this.releaseItem();
  405. if (this.undoStack) {
  406. this.undoStack.clear();
  407. }
  408. }
  409. this.update();
  410. return true;
  411. } // Function onDoubleClick()
  412. /**
  413. * 键盘事件
  414. *
  415. * @param event 事件参数
  416. * @return boolean
  417. */
  418. onKeyDown(event: KeyboardEvent): boolean {
  419. if (this._status == SItemStatus.Normal) {
  420. return false;
  421. } else if (this._status == SItemStatus.Create) {
  422. if (event.code == "Enter") {
  423. // 当顶点大于二个时才又条件执行闭合操作并清空堆栈
  424. if (this.pointList.length > 2) {
  425. this._status = SItemStatus.Normal;
  426. //3 传递完成事件状态
  427. this.$emit("finishCreated");
  428. //1 grabItem 置为null
  429. this.releaseItem();
  430. //2 清空undo
  431. if (this.undoStack) {
  432. this.undoStack.clear();
  433. }
  434. }
  435. }
  436. } else if (this._status == SItemStatus.Edit) {
  437. if (event.key == "Alt") {
  438. this.isAlt = true;
  439. }
  440. }
  441. this.update();
  442. return true;
  443. } // Function onKeyDown()
  444. /**
  445. * 键盘键抬起事件
  446. *
  447. * @param event 事件参数
  448. * @return boolean
  449. */
  450. onKeyUp(event: KeyboardEvent): void {
  451. if (this._status == SItemStatus.Edit) {
  452. if (event.key == "Alt") {
  453. this.isAlt = false;
  454. }
  455. }
  456. this.update();
  457. } // Function onKeyUp()
  458. /**
  459. * 鼠标按下事件
  460. *
  461. * @param event 事件参数
  462. * @return boolean
  463. */
  464. onMouseDown(event: SMouseEvent): boolean {
  465. // 如果状态为编辑状态则添加点
  466. if (this._status == SItemStatus.Create) {
  467. // 新增顶点
  468. this.insertPoint(event.x, event.y);
  469. // 记录新增顶点操作记录压入堆栈
  470. let pos = new SPoint(event.x, event.y);
  471. this.recordAction(SGraphPointListInsert, [this.pointList, pos]);
  472. } else if (this._status == SItemStatus.Edit) {
  473. // 对多边形数组做编辑操作
  474. this.editPolygonPoint(event);
  475. } else {
  476. // return super.onMouseDown(event)
  477. }
  478. return true;
  479. } // Function onMouseDown()
  480. /**
  481. * 鼠标移入事件
  482. *
  483. * @param event 事件参数
  484. * @return boolean
  485. */
  486. onMouseEnter(event: SMouseEvent): boolean {
  487. return true;
  488. } // Function onMouseEnter()
  489. /**
  490. * 鼠标移出事件
  491. *
  492. * @param event 事件参数
  493. * @return boolean
  494. */
  495. onMouseLeave(event: SMouseEvent): boolean {
  496. return true;
  497. } // Function onMouseLeave()
  498. /**
  499. * 鼠标移动事件
  500. *
  501. * @param event 事件参数
  502. * @return boolean
  503. */
  504. onMouseMove(event: SMouseEvent): boolean {
  505. if (this._status == SItemStatus.Create) {
  506. this.lastPoint = new SPoint();
  507. this.lastPoint.x = event.x;
  508. this.lastPoint.y = event.y;
  509. this.update();
  510. } else if (this._status == SItemStatus.Edit) {
  511. if (-1 != this.curIndex) {
  512. this.pointList[this.curIndex].x = event.x;
  513. this.pointList[this.curIndex].y = event.y;
  514. }
  515. this.update();
  516. } else {
  517. // return super.onMouseMove(event)
  518. }
  519. return true;
  520. } // Function onMouseMove()
  521. /**
  522. * 鼠标抬起事件
  523. *
  524. * @param event 事件参数
  525. * @return boolean
  526. */
  527. onMouseUp(event: SMouseEvent): boolean {
  528. if (this._status == SItemStatus.Edit) {
  529. if (-1 != this.curIndex) {
  530. const pos = new SPoint(
  531. this.pointList[this.curIndex].x,
  532. this.pointList[this.curIndex].y
  533. );
  534. this.recordAction(SGraphPointListUpdate, [
  535. this.pointList,
  536. this.curPoint,
  537. pos,
  538. this.curIndex
  539. ]);
  540. }
  541. this.curIndex = -1;
  542. this.curPoint = null;
  543. } else {
  544. // return super.onMouseUp(event)
  545. }
  546. return true;
  547. } // Function onMouseUp()
  548. /**
  549. * 适配事件
  550. *
  551. * @param event 事件参数
  552. * @return boolean
  553. */
  554. onResize(event: SMouseEvent): boolean {
  555. return true;
  556. } // Function onResize()
  557. /**
  558. * 取消操作
  559. *
  560. */
  561. cancelOperate(): void {
  562. // 当状态为展示状态
  563. if (this._status == SItemStatus.Create) {
  564. // 闭合多边形
  565. this.parent = null;
  566. } else if (this._status == SItemStatus.Edit) {
  567. // 编辑状态
  568. this._status = SItemStatus.Normal;
  569. }
  570. this.update();
  571. } // Function cancelOperate()
  572. /**
  573. * Item对象边界区域
  574. *
  575. * @return SRect
  576. */
  577. boundingRect(): SRect {
  578. if (this.pointList.length) {
  579. this.minX = this.pointList[0].x;
  580. this.maxX = this.pointList[0].x;
  581. this.minY = this.pointList[0].y;
  582. this.maxY = this.pointList[0].y;
  583. this.pointList.forEach(it => {
  584. let x = it.x,
  585. y = it.y;
  586. if (x < this.minX) {
  587. this.minX = x;
  588. }
  589. if (y < this.minY) {
  590. this.minY = y;
  591. }
  592. if (x > this.maxX) {
  593. this.maxX = x;
  594. }
  595. if (y > this.maxY) {
  596. this.maxY = y;
  597. }
  598. });
  599. }
  600. return new SRect(
  601. this.minX,
  602. this.minY,
  603. this.maxX - this.minX,
  604. this.maxY - this.minY
  605. );
  606. } // Function boundingRect()
  607. /**
  608. * 判断点是否在区域内
  609. *
  610. * @param x
  611. * @param y
  612. */
  613. contains(x: number, y: number): boolean {
  614. let arr = this.pointList;
  615. return !(arr.length < 3 || !SPolygonUtil.pointIn(x, y, arr));
  616. } // Function contains()
  617. /**
  618. * Item绘制操作
  619. *
  620. * @param painter painter对象
  621. */
  622. onDraw(painter: SPainter): void {
  623. this.scenceLen = painter.toPx(this.len);
  624. // 当状态为展示状态
  625. if (this._status == SItemStatus.Normal) {
  626. // 闭合多边形
  627. this.drawShowPolygon(painter, this.pointList);
  628. } else if (this._status == SItemStatus.Create) {
  629. // 创建状态
  630. this.drawCreatePolygon(painter, this.pointList);
  631. } else {
  632. // 编辑状态
  633. this.drawEditPolygon(painter, this.pointList);
  634. }
  635. } // Function onDraw()
  636. }