DrawZoneItem.ts 21 KB

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