SPolylineItem.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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.buttons == 1) {
  164. if (this.status == SItemStatus.Create) {
  165. this.addPoint(new SPoint(event.x, event.y));
  166. return true;
  167. } else if (this.status == SItemStatus.Edit) {
  168. // 查询鼠标最近的索引
  169. this.findNearestPoint(new SPoint(event.x, event.y));
  170. // 增加点
  171. if (this.curIndex < 0) {
  172. this.findAddPos(new SPoint(event.x, event.y));
  173. }
  174. // 删除点
  175. if (event.altKey && this.canHandle(this.curIndex)) {
  176. this.deletePoint(this.curIndex);
  177. }
  178. this.update();
  179. return true;
  180. } else {
  181. return super.onMouseDown(event);
  182. }
  183. }
  184. return super.onMouseDown(event);
  185. } // Function onMouseDown()
  186. /**
  187. * 鼠标移动事件
  188. *
  189. * @param event 鼠标事件
  190. * @return boolean 是否处理事件
  191. * */
  192. onMouseMove(event: SMouseEvent): boolean {
  193. if (this.status == SItemStatus.Create) {
  194. if (this.lastPoint) {
  195. this.lastPoint.x = event.x;
  196. this.lastPoint.y = event.y;
  197. } else {
  198. this.lastPoint = new SPoint(event.x, event.y);
  199. }
  200. this.update();
  201. return true;
  202. } else if (this.status == SItemStatus.Edit) {
  203. if (event.buttons == 1) {
  204. if (this.canHandle(this.curIndex)) {
  205. this.pointList[this.curIndex].x = event.x;
  206. this.pointList[this.curIndex].y = event.y;
  207. }
  208. }
  209. this.update();
  210. return true;
  211. } else {
  212. return super.onMouseMove(event);
  213. }
  214. } // Function onMouseMove()
  215. /**
  216. * 鼠标移动事件
  217. *
  218. * @param event 鼠标事件
  219. * @return boolean 是否处理事件
  220. * */
  221. onMouseUp(event: SMouseEvent): boolean {
  222. if (this.status == SItemStatus.Edit) {
  223. if (this.curIndex > -1) {
  224. const p = new SPoint(
  225. this.pointList[this.curIndex].x,
  226. this.pointList[this.curIndex].y
  227. );
  228. this.recordAction(SGraphPointListUpdate, [
  229. this.pointList,
  230. this.curPoint,
  231. p,
  232. this.curIndex
  233. ]);
  234. }
  235. } else if (this.status == SItemStatus.Normal) {
  236. this.moveToOrigin(this.x, this.y);
  237. return super.onMouseUp(event);
  238. }
  239. return true;
  240. } // Function onMouseUp()
  241. /**
  242. * 鼠标双击事件
  243. *
  244. * @param event 事件参数
  245. * @return boolean
  246. */
  247. onDoubleClick(event: SMouseEvent): boolean {
  248. if (this.status == SItemStatus.Normal) {
  249. this.status = SItemStatus.Edit;
  250. this.grabItem(this);
  251. } else if (this.status == SItemStatus.Edit) {
  252. this.status = SItemStatus.Normal;
  253. this.releaseItem();
  254. } else if (this.status == SItemStatus.Create) {
  255. if (this.pointList.length > 2) {
  256. this.status = SItemStatus.Normal;
  257. this.releaseItem();
  258. this.$emit("finishCreated");
  259. }
  260. }
  261. this.$emit("onDoubleClick", event);
  262. return true;
  263. } // Function onDoubleClick()
  264. /***
  265. * 键盘按键弹起事件
  266. *
  267. * @param event 事件参数
  268. */
  269. onKeyUp(event: KeyboardEvent): void {
  270. if (event.keyCode == SKeyCode.Enter) {
  271. if (this.pointList.length > 2) {
  272. if (this.status == SItemStatus.Create) {
  273. this.$emit("finishCreated");
  274. }
  275. this.status = SItemStatus.Normal;
  276. this.releaseItem();
  277. }
  278. }
  279. // delete删除点
  280. if (
  281. event.keyCode == SKeyCode.Delete &&
  282. this.status == SItemStatus.Edit
  283. ) {
  284. this.deletePoint(this.curIndex);
  285. }
  286. } // Function onKeyUp()
  287. /**
  288. * 移动后处理所有坐标,并肩原点置为场景原点
  289. *
  290. * @param x x坐标
  291. * @param y y坐标
  292. * */
  293. moveToOrigin(x: number, y: number): void {
  294. super.moveToOrigin(x, y);
  295. this.pointList = this.pointList.map(t => {
  296. t.x = t.x + x;
  297. t.y = t.y + y;
  298. return t;
  299. });
  300. this.x = 0;
  301. this.y = 0;
  302. } // Function moveToOrigin()
  303. /**
  304. * 获取点击点与点集中距离最近点
  305. *
  306. * @param p 鼠标点击点
  307. * */
  308. findNearestPoint(p: SPoint): void {
  309. let len = this.sceneDis;
  310. for (let i = 0; i < this.pointList.length; i++) {
  311. let dis = SMathUtil.pointDistance(
  312. p.x,
  313. p.y,
  314. this.pointList[i].x,
  315. this.pointList[i].y
  316. );
  317. if (dis < len) {
  318. len = dis;
  319. this.curIndex = i;
  320. this.curPoint = new SPoint(
  321. this.pointList[this.curIndex].x,
  322. this.pointList[this.curIndex].y
  323. );
  324. }
  325. }
  326. } // Function findNearestPoint()
  327. /**
  328. * 计算增加点的位置
  329. *
  330. * @param p 鼠标点击点
  331. * */
  332. findAddPos(p: SPoint): void {
  333. let len = SMathUtil.pointToLine(
  334. p,
  335. new SLine(this.pointList[0], this.pointList[1])
  336. ),
  337. index = 0;
  338. if (this.pointList.length > 2) {
  339. for (let i = 1; i < this.pointList.length - 1; i++) {
  340. let dis = SMathUtil.pointToLine(
  341. p,
  342. new SLine(this.pointList[i], this.pointList[i + 1])
  343. );
  344. if (dis.MinDis < len.MinDis) {
  345. len = dis;
  346. index = i;
  347. }
  348. }
  349. }
  350. if (len.MinDis < this.sceneDis) {
  351. if (len.Point) {
  352. this.addPoint(len.Point, index + 1);
  353. }
  354. }
  355. } // Function findAddPos()
  356. /**
  357. * 记录相关动作并推入栈中
  358. *
  359. * @param SGraphCommand 相关命令类
  360. * @param any 对应传入参数
  361. */
  362. protected recordAction(SGraphCommand: any, any: any[]): void {
  363. // 记录相关命令并推入堆栈中
  364. const command = new SGraphCommand(this.scene, this, ...any);
  365. this.undoStack.push(command);
  366. } // Function recordAction()
  367. /**
  368. * Item对象边界区域
  369. *
  370. * @return SRect 外接矩阵
  371. * */
  372. boundingRect(): SRect {
  373. if (this.pointList.length) {
  374. this.minX = this.pointList[0].x;
  375. this.maxX = this.pointList[0].x;
  376. this.minY = this.pointList[0].y;
  377. this.maxY = this.pointList[0].y;
  378. this.pointList.forEach(it => {
  379. let x = it.x,
  380. y = it.y;
  381. if (x < this.minX) {
  382. this.minX = x;
  383. }
  384. if (y < this.minY) {
  385. this.minY = y;
  386. }
  387. if (x > this.maxX) {
  388. this.maxX = x;
  389. }
  390. if (y > this.maxY) {
  391. this.maxY = y;
  392. }
  393. });
  394. }
  395. return new SRect(
  396. this.minX,
  397. this.minY,
  398. this.maxX - this.minX,
  399. this.maxY - this.minY
  400. );
  401. } // Function boundingRect()
  402. /**
  403. * 判断点是否在区域内
  404. *
  405. * @param x
  406. * @param y
  407. * @return true-是
  408. */
  409. contains(x: number, y: number): boolean {
  410. let p = new SPoint(x, y);
  411. for (let i = 1; i < this.pointList.length; i++) {
  412. let PTL = SMathUtil.pointToLine(
  413. p,
  414. new SLine(
  415. this.pointList[i - 1].x,
  416. this.pointList[i - 1].y,
  417. this.pointList[i].x,
  418. this.pointList[i].y
  419. )
  420. );
  421. if (PTL.MinDis < this.sceneDis) {
  422. return true;
  423. }
  424. }
  425. return false;
  426. } // Function contains()
  427. /**
  428. * 撤销操作
  429. *
  430. */
  431. undo(): void {
  432. if (this._status != SItemStatus.Normal) {
  433. this.undoStack.undo();
  434. }
  435. } // Function undo()
  436. /**
  437. * 重做操作
  438. *
  439. */
  440. redo(): void {
  441. if (this._status != SItemStatus.Normal) {
  442. this.undoStack.redo();
  443. }
  444. } // Function redo()
  445. /**
  446. * 取消操作执行
  447. *
  448. * */
  449. cancelOperate(): void {
  450. if (this.status == SItemStatus.Create) {
  451. this.parent = null;
  452. this.releaseItem();
  453. } else if (this.status == SItemStatus.Edit) {
  454. this.status = SItemStatus.Normal;
  455. this.releaseItem();
  456. }
  457. } // Function cancelOperate()
  458. /**
  459. * 绘制基本图形
  460. * */
  461. drawBaseLine(painter: SPainter): void {
  462. // 绘制基本图形
  463. painter.pen.color = this.strokeColor;
  464. painter.drawPolyline(this.pointList);
  465. } // Function drawBaseLine()
  466. /**
  467. * Item绘制操作
  468. *
  469. * @param painter painter对象
  470. */
  471. onDraw(painter: SPainter): void {
  472. // 缓存场景长度
  473. this.sceneDis = painter.toPx(this.dis);
  474. // 创建状态
  475. painter.pen.lineWidth = painter.toPx(this.lineWidth);
  476. if (this.status == SItemStatus.Create && this.lastPoint) {
  477. // 绘制基本图形
  478. this.drawBaseLine(painter);
  479. painter.drawLine(
  480. this.pointList[this.pointList.length - 1],
  481. this.lastPoint
  482. );
  483. // 编辑状态
  484. this.pointList.forEach((t, i): void => {
  485. painter.brush.color = SColor.White;
  486. if (i == this.curIndex) {
  487. painter.brush.color = this.fillColor;
  488. }
  489. painter.drawCircle(t.x, t.y, painter.toPx(5));
  490. });
  491. } else if (this.status == SItemStatus.Edit) {
  492. // 绘制基本图形
  493. this.drawBaseLine(painter);
  494. // 编辑状态
  495. this.pointList.forEach((t, i): void => {
  496. painter.brush.color = SColor.White;
  497. if (i == this.curIndex) {
  498. painter.brush.color = this.fillColor;
  499. }
  500. painter.drawCircle(t.x, t.y, painter.toPx(5));
  501. });
  502. } else {
  503. // 查看状态
  504. if (this.selected) {
  505. painter.pen.lineWidth = painter.toPx(this.lineWidth * 2);
  506. painter.shadow.shadowBlur = 10;
  507. painter.shadow.shadowColor = new SColor(`#00000033`);
  508. painter.shadow.shadowOffsetX = 5;
  509. painter.shadow.shadowOffsetY = 5;
  510. }
  511. // 绘制基本图形
  512. this.drawBaseLine(painter);
  513. }
  514. } // Function onDraw()
  515. } // Class SPolylineItem