SPolygonItem.ts 21 KB

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