SPolygonItem.ts 22 KB

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