SPolylineItem.ts 17 KB

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