SBasePolylineEdit.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. /*
  2. * *********************************************************************************************************************
  3. *
  4. * !!
  5. * .F88X
  6. * X8888Y
  7. * .}888888N;
  8. * i888888N; .:! .I$WI:
  9. * R888888I .'N88~ i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
  10. * .R888888I .;N8888~ .X8' "8I.!,/8" !%NY8`"8I8~~8>,88I
  11. * +888888N; .8888888Y "&&8Y.}8,
  12. * ./888888N; .R888888Y .'}~ .>}'.`+> i}! "i' +/' .'i~ !11,.:">, .~]! .i}i
  13. * ~888888%: .I888888l .]88~`1/iY88Ii+1'.R$8$8]"888888888> Y8$ W8E X8E W8888'188Il}Y88$*
  14. * 18888888 E8888881 .]W%8$`R8X'&8%++N8i,8N%N8+l8%` .}8N:.R$RE%N88N%N$K$R 188,FE$8%~Y88I
  15. * .E888888I .i8888888' .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
  16. * 8888888I .,N888888~ ~88i"8W,!N8*.I88.}888%F,i$88"F88" 888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
  17. * i888888N' I888Y ]88;/EX*IFKFK88X K8R .l8W 88Y ~88}'88E&%8W.X8N``]88!.$8K .:W8I
  18. * .i888888N; I8Y .&8$ .X88! i881.:%888>I88 ;88] +88+.';;;;:.Y88X 18N.,88l .+88/
  19. * .:R888888I
  20. * .&888888I Copyright (c) 2016-2020. 博锐尚格科技股份有限公司
  21. * ~8888'
  22. * .!88~ All rights reserved.
  23. *
  24. * *********************************************************************************************************************
  25. */
  26. import { SColor, SLine, SPainter, SPoint, SRect } from "@persagy-web/draw";
  27. import { SKeyCode, SMouseEvent, SUndoStack } from "@persagy-web/base";
  28. import { SItemStatus } from "@persagy-web/big";;
  29. import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";;
  30. import {
  31. SGraphPointListDelete,
  32. SGraphPointListInsert,
  33. SGraphPointListUpdate,
  34. SLineStyle,
  35. SGraphItem
  36. } from "@persagy-web/graph/";
  37. import { SGraphEdit } from ".."
  38. import { Marker } from "../type/Marker";
  39. /**
  40. * 折线编辑类
  41. *
  42. * @author 韩耀龙 <han_yao_long@163.com>
  43. */
  44. export class SBasePolylineEdit extends SGraphEdit {
  45. /** 传入数据 */
  46. _data: Marker | null = null;
  47. get data(): Marker | null {
  48. return this._data;
  49. } // Get data
  50. set data(v: Marker | null) {
  51. this._data = v;
  52. this.initData()
  53. this.update();
  54. } // Set data
  55. /** X 坐标最小值 */
  56. private minX = Number.MAX_SAFE_INTEGER;
  57. /** X 坐标最大值 */
  58. private maxX = Number.MIN_SAFE_INTEGER;
  59. /** Y 坐标最小值 */
  60. private minY = Number.MAX_SAFE_INTEGER;
  61. /** Y 坐标最大值 */
  62. private maxY = Number.MIN_SAFE_INTEGER;
  63. /** 折点信息 */
  64. pointList: SPoint[] = [];
  65. /** 是否绘制完成 */
  66. _status: SItemStatus = SItemStatus.Normal;
  67. get status(): SItemStatus {
  68. return this._status;
  69. }
  70. set status(v: SItemStatus) {
  71. this._status = v;
  72. this.undoStack.clear();
  73. this.update();
  74. }
  75. /** 鼠标移动时的点 */
  76. private lastPoint: SPoint | null = null;
  77. /** 线条颜色 */
  78. _strokeColor: SColor = SColor.Black;
  79. get strokeColor(): SColor {
  80. return this._strokeColor;
  81. }
  82. set strokeColor(v: SColor) {
  83. this._strokeColor = v;
  84. this.update();
  85. }
  86. /** 填充色 */
  87. _fillColor: SColor = new SColor("#2196f3");
  88. get fillColor(): SColor {
  89. return this._fillColor;
  90. }
  91. set fillColor(v: SColor) {
  92. this._fillColor = v;
  93. this.update();
  94. }
  95. /** 边框样式 */
  96. _lineStyle: SLineStyle = SLineStyle.Solid;
  97. get lineStyle(): SLineStyle {
  98. return this._lineStyle;
  99. }
  100. set lineStyle(v: SLineStyle) {
  101. this._lineStyle = v;
  102. this.update();
  103. }
  104. /** 线条宽度 */
  105. _lineWidth: number = 1;
  106. get lineWidth(): number {
  107. return this._lineWidth;
  108. }
  109. set lineWidth(v: number) {
  110. this._lineWidth = v;
  111. this.update();
  112. }
  113. /** 是否垂直水平绘制 */
  114. private _verAndLeve: Boolean = false;
  115. get verAndLeve(): Boolean {
  116. return this._verAndLeve;
  117. }
  118. set verAndLeve(bool: Boolean) {
  119. this._verAndLeve = bool;
  120. this.update();
  121. }
  122. /** 全局灵敏度 */
  123. dis: number = 10;
  124. /** 灵敏度转换为场景长度 */
  125. private sceneDis: number = 10;
  126. /** 当前点索引 */
  127. private curIndex: number = -1;
  128. /** 当前点 */
  129. private curPoint: SPoint | null = null;
  130. /** undo / redo 堆栈 */
  131. private undoStack: SUndoStack = new SUndoStack();
  132. /**
  133. * 构造函数
  134. *
  135. * @param parent 父级
  136. * @param data 折线数据
  137. */
  138. constructor(parent: null | SGraphItem, data: Marker | null = null) {
  139. super(parent);
  140. if (data) {
  141. this.data = data;
  142. }
  143. } // Constructor
  144. /**
  145. * 初始化 data 数据
  146. */
  147. initData() {
  148. const data = this.data;
  149. if (!data) return;
  150. this.name = data.name;
  151. this.moveTo(data.pos.x, data.pos.y);
  152. this.showSelect = false;
  153. if (data && data.style) {
  154. if (data.style.outLine) {
  155. let setPointList: SPoint[];
  156. setPointList = data.style.outLine.map(i => {
  157. return new SPoint(i.x, i.y)
  158. });
  159. this.pointList = setPointList;
  160. }
  161. this.moveable = data.style.isMove ? data.style.isMove : false;
  162. if (data.style && data.style.default) {
  163. // 颜色
  164. if (data.style.default.strokeColor) {
  165. this.strokeColor = new SColor(data.style.default.strokeColor)
  166. }
  167. // 线宽
  168. if (data.style.default.lineWidth) {
  169. this.lineWidth = data.style.default.lineWidth
  170. }
  171. // 线样式
  172. if (data.style.default.lineStyle) {
  173. this.lineStyle = data.style.default.lineStyle
  174. }
  175. }
  176. }
  177. }// Function initData()
  178. /**
  179. * 添加点至数组中
  180. *
  181. * @param p 添加的点
  182. * @param index 添加到的索引
  183. */
  184. private addPoint(p: SPoint, index?: number): void {
  185. if (index && this.canHandle(index)) {
  186. this.pointList.splice(index, 0, p);
  187. this.recordAction(SGraphPointListInsert, [
  188. this.pointList,
  189. p,
  190. index
  191. ]);
  192. } else {
  193. this.pointList.push(p);
  194. this.recordAction(SGraphPointListInsert, [this.pointList, p]);
  195. }
  196. this.update();
  197. }
  198. /**
  199. * 是否可以添加点到数组中
  200. *
  201. * @param index 要添加到的索引
  202. * @return 是否可添加
  203. */
  204. private canHandle(index: number): boolean {
  205. return index >= 0 && index <= this.pointList.length;
  206. }
  207. /**
  208. * 根据索引删除点
  209. *
  210. * @param index 删除点
  211. */
  212. deletePoint(index: number): void {
  213. if (this.canHandle(index) && this.pointList.length > 2) {
  214. const p = new SPoint(
  215. this.pointList[this.curIndex].x,
  216. this.pointList[this.curIndex].y
  217. );
  218. this.pointList.splice(index, 1);
  219. this.recordAction(SGraphPointListDelete, [
  220. this.pointList,
  221. p,
  222. index
  223. ]);
  224. this.curIndex = -1;
  225. this.curPoint = null;
  226. this.update();
  227. }
  228. }
  229. /**
  230. * 鼠标按下事件
  231. *
  232. * @param event 鼠标事件
  233. * @return 是否处理事件
  234. */
  235. onMouseDown(event: SMouseEvent): boolean {
  236. this.curIndex = -1;
  237. this.curPoint = null;
  238. if (event.shiftKey || this.verAndLeve) {
  239. event = this.compare(event);
  240. }
  241. if (event.buttons == 1) {
  242. if (this.status == SItemStatus.Create) {
  243. this.addPoint(new SPoint(event.x, event.y));
  244. return true;
  245. } else if (this.status == SItemStatus.Edit) {
  246. // 查询鼠标最近的索引
  247. this.findNearestPoint(new SPoint(event.x, event.y));
  248. // 增加点
  249. if (this.curIndex < 0) {
  250. this.findAddPos(new SPoint(event.x, event.y));
  251. }
  252. // 删除点
  253. if (event.altKey && this.canHandle(this.curIndex)) {
  254. this.deletePoint(this.curIndex);
  255. }
  256. this.update();
  257. return true;
  258. } else {
  259. return super.onMouseDown(event);
  260. }
  261. }
  262. return super.onMouseDown(event);
  263. }
  264. /**
  265. * 鼠标移动事件
  266. *
  267. * @param event 鼠标事件
  268. * @return 是否处理事件
  269. */
  270. onMouseMove(event: SMouseEvent): boolean {
  271. if (event.shiftKey || this.verAndLeve) {
  272. event = this.compare(event);
  273. }
  274. if (this.status == SItemStatus.Create) {
  275. if (this.lastPoint) {
  276. this.lastPoint.x = event.x;
  277. this.lastPoint.y = event.y;
  278. } else {
  279. this.lastPoint = new SPoint(event.x, event.y);
  280. }
  281. this.update();
  282. return true;
  283. } else if (this.status == SItemStatus.Edit) {
  284. if (event.buttons == 1) {
  285. if (this.canHandle(this.curIndex)) {
  286. this.pointList[this.curIndex].x = event.x;
  287. this.pointList[this.curIndex].y = event.y;
  288. }
  289. }
  290. this.update();
  291. return true;
  292. } else {
  293. return super.onMouseMove(event);
  294. }
  295. }
  296. /**
  297. * 鼠标移动事件
  298. *
  299. * @param event 鼠标事件
  300. * @return 是否处理事件
  301. */
  302. onMouseUp(event: SMouseEvent): boolean {
  303. if (this.status == SItemStatus.Edit) {
  304. if (this.curIndex > -1) {
  305. const p = new SPoint(
  306. this.pointList[this.curIndex].x,
  307. this.pointList[this.curIndex].y
  308. );
  309. this.recordAction(SGraphPointListUpdate, [
  310. this.pointList,
  311. this.curPoint,
  312. p,
  313. this.curIndex
  314. ]);
  315. }
  316. } else if (this.status == SItemStatus.Normal) {
  317. super.onMouseUp(event)
  318. return true;
  319. }
  320. return true;
  321. }
  322. /**
  323. * 鼠标双击事件
  324. *
  325. * @param event 事件参数
  326. * @return 是否处理事件
  327. */
  328. onDoubleClick(event: SMouseEvent): boolean {
  329. // 如果为show状态 双击改对象则需改为编辑状态
  330. if (this.status == SItemStatus.Normal) {
  331. this.status = SItemStatus.Edit;
  332. this.grabItem(this);
  333. } else if (this.status == SItemStatus.Edit) { // 编辑状态
  334. this.status = SItemStatus.Normal;
  335. this.releaseItem();
  336. } else if (this.status == SItemStatus.Create) { // 创建状态
  337. if (this.pointList.length > 1) {
  338. this.status = SItemStatus.Normal;
  339. this.releaseItem();
  340. this.$emit("finishCreated");
  341. }
  342. }
  343. this.$emit("onDoubleClick", event);
  344. return true;
  345. }
  346. /**
  347. * 键盘按键弹起事件
  348. *
  349. * @param event 事件参数
  350. */
  351. onKeyUp(event: KeyboardEvent): void {
  352. if (event.keyCode == SKeyCode.Enter) {
  353. if (this.pointList.length > 1) {
  354. if (this.status == SItemStatus.Create) {
  355. this.$emit("finishCreated");
  356. }
  357. this.status = SItemStatus.Normal;
  358. this.releaseItem();
  359. }
  360. }
  361. // delete删除点
  362. if (
  363. event.keyCode == SKeyCode.Delete &&
  364. this.status == SItemStatus.Edit
  365. ) {
  366. this.deletePoint(this.curIndex);
  367. }
  368. }
  369. /**
  370. * 移动后处理所有坐标,并肩原点置为场景原点
  371. */
  372. moveToOrigin(): void {
  373. this.pointList = this.pointList.map(t => {
  374. t.x = t.x + this.x;
  375. t.y = t.y + this.y;
  376. return t;
  377. });
  378. this.x = 0;
  379. this.y = 0;
  380. }
  381. /**
  382. * 获取点击点与点集中距离最近点
  383. *
  384. * @param p 鼠标点击点
  385. */
  386. findNearestPoint(p: SPoint): void {
  387. let len = this.sceneDis;
  388. for (let i = 0; i < this.pointList.length; i++) {
  389. let dis = SMathUtil.pointDistance(
  390. p.x,
  391. p.y,
  392. this.pointList[i].x,
  393. this.pointList[i].y
  394. );
  395. if (dis < len) {
  396. len = dis;
  397. this.curIndex = i;
  398. this.curPoint = new SPoint(
  399. this.pointList[this.curIndex].x,
  400. this.pointList[this.curIndex].y
  401. );
  402. }
  403. }
  404. }
  405. /**
  406. * 计算增加点的位置
  407. *
  408. * @param p 鼠标点击点
  409. */
  410. findAddPos(p: SPoint): void {
  411. let len = SMathUtil.pointToLine(
  412. p,
  413. new SLine(this.pointList[0], this.pointList[1])
  414. ),
  415. index = 0;
  416. if (this.pointList.length > 2) {
  417. for (let i = 1; i < this.pointList.length - 1; i++) {
  418. let dis = SMathUtil.pointToLine(
  419. p,
  420. new SLine(this.pointList[i], this.pointList[i + 1])
  421. );
  422. if (dis.MinDis < len.MinDis) {
  423. len = dis;
  424. index = i;
  425. }
  426. }
  427. }
  428. if (len.MinDis < this.sceneDis) {
  429. if (len.Point) {
  430. this.addPoint(len.Point, index + 1);
  431. }
  432. }
  433. }
  434. /**
  435. * shift 垂直水平创建或编辑
  436. *
  437. * @param event 事件
  438. * @return 处理后的鼠标事件
  439. */
  440. compare(event: SMouseEvent): SMouseEvent {
  441. if (this.pointList.length) {
  442. let last = new SPoint(event.x, event.y);
  443. if (this.status == SItemStatus.Create) {
  444. last = this.pointList[this.pointList.length - 1];
  445. } else if (this.status == SItemStatus.Edit) {
  446. if (this.curIndex > 1) {
  447. last = this.pointList[this.curIndex - 1];
  448. }
  449. }
  450. const dx = Math.abs(event.x - last.x);
  451. const dy = Math.abs(event.y - last.y);
  452. if (dy > dx) {
  453. event.x = last.x;
  454. } else {
  455. event.y = last.y;
  456. }
  457. }
  458. return event;
  459. }
  460. /**
  461. * 记录相关动作并推入栈中
  462. *
  463. * @param SGraphCommand 相关命令类
  464. * @param any 对应传入参数
  465. */
  466. protected recordAction(SGraphCommand: any, any: any[]): void {
  467. // 记录相关命令并推入堆栈中
  468. const command = new SGraphCommand(this.scene, this, ...any);
  469. this.undoStack.push(command);
  470. }
  471. /**
  472. * Item 对象边界区域
  473. *
  474. * @return 对象边界区域
  475. */
  476. boundingRect(): SRect {
  477. if (this.pointList.length) {
  478. this.minX = this.pointList[0].x;
  479. this.maxX = this.pointList[0].x;
  480. this.minY = this.pointList[0].y;
  481. this.maxY = this.pointList[0].y;
  482. this.pointList.forEach(it => {
  483. let x = it.x,
  484. y = it.y;
  485. if (x < this.minX) {
  486. this.minX = x;
  487. }
  488. if (y < this.minY) {
  489. this.minY = y;
  490. }
  491. if (x > this.maxX) {
  492. this.maxX = x;
  493. }
  494. if (y > this.maxY) {
  495. this.maxY = y;
  496. }
  497. });
  498. }
  499. return new SRect(
  500. this.minX,
  501. this.minY,
  502. this.maxX - this.minX,
  503. this.maxY - this.minY
  504. );
  505. }
  506. /**
  507. * 判断点是否在区域内
  508. *
  509. * @param x x 坐标
  510. * @param y y 坐标
  511. * @return 是否在区域内
  512. */
  513. contains(x: number, y: number): boolean {
  514. let p = new SPoint(x, y);
  515. for (let i = 1; i < this.pointList.length; i++) {
  516. let PTL = SMathUtil.pointToLine(
  517. p,
  518. new SLine(
  519. this.pointList[i - 1].x,
  520. this.pointList[i - 1].y,
  521. this.pointList[i].x,
  522. this.pointList[i].y
  523. )
  524. );
  525. if (PTL.MinDis < this.sceneDis) {
  526. return true;
  527. }
  528. }
  529. return false;
  530. }
  531. /**
  532. * 撤销操作
  533. */
  534. undo(): void {
  535. if (this._status != SItemStatus.Normal) {
  536. this.undoStack.undo();
  537. }
  538. }
  539. /**
  540. * 重做操作
  541. */
  542. redo(): void {
  543. if (this._status != SItemStatus.Normal) {
  544. this.undoStack.redo();
  545. }
  546. }
  547. /**
  548. * 取消操作执行
  549. */
  550. cancelOperate(): void {
  551. if (this.status == SItemStatus.Create) {
  552. this.parent = null;
  553. this.releaseItem();
  554. } else if (this.status == SItemStatus.Edit) {
  555. this.status = SItemStatus.Normal;
  556. this.releaseItem();
  557. }
  558. }
  559. /**
  560. * 绘制基本图形
  561. *
  562. * @param painter 绘制对象
  563. */
  564. drawBaseLine(painter: SPainter): void {
  565. // 绘制基本图形
  566. painter.pen.color = this.strokeColor;
  567. if (this.lineStyle == SLineStyle.Dashed) {
  568. painter.pen.lineDash = [
  569. painter.toPx(this.lineWidth * 3),
  570. painter.toPx(this.lineWidth * 7)
  571. ];
  572. } else if (this.lineStyle == SLineStyle.Dotted) {
  573. painter.pen.lineDash = [
  574. painter.toPx(this.lineWidth),
  575. painter.toPx(this.lineWidth)
  576. ];
  577. }
  578. painter.drawPolyline(this.pointList);
  579. }
  580. /**
  581. * 返回对象储存的相关数据
  582. *
  583. * @return 对象储存的相关数据
  584. */
  585. toData(): any {
  586. this.moveToOrigin();
  587. if (!this.data) return;
  588. const Line = this.pointList.map(pos => {
  589. return {
  590. x: pos.x,
  591. y: pos.y
  592. }
  593. });
  594. this.data.style.outLine = Line;
  595. this.data.style.default.lineWidth = this.lineWidth;
  596. this.data.style.default.lineStyle = this.lineStyle;
  597. this.data.style.default.strokeColor = this.strokeColor.value;
  598. return this.data
  599. }
  600. /**
  601. * Item 绘制操作
  602. *
  603. * @param painter 绘制对象
  604. */
  605. onDraw(painter: SPainter): void {
  606. // 缓存场景长度
  607. this.sceneDis = painter.toPx(this.dis);
  608. // 创建状态
  609. painter.pen.lineWidth = painter.toPx(this.lineWidth);
  610. if (this.status == SItemStatus.Create && this.lastPoint) {
  611. // 绘制基本图形
  612. this.drawBaseLine(painter);
  613. painter.drawLine(
  614. this.pointList[this.pointList.length - 1],
  615. this.lastPoint
  616. );
  617. // 编辑状态
  618. this.pointList.forEach((t, i): void => {
  619. painter.brush.color = SColor.White;
  620. if (i == this.curIndex) {
  621. painter.brush.color = this.fillColor;
  622. }
  623. painter.drawCircle(t.x, t.y, painter.toPx(5));
  624. });
  625. } else if (this.status == SItemStatus.Edit) {
  626. // 绘制基本图形
  627. this.drawBaseLine(painter);
  628. // 编辑状态
  629. this.pointList.forEach((t, i): void => {
  630. painter.brush.color = SColor.White;
  631. if (i == this.curIndex) {
  632. painter.brush.color = this.fillColor;
  633. }
  634. painter.drawCircle(t.x, t.y, painter.toPx(5));
  635. });
  636. } else {
  637. // 查看状态,是否选中
  638. if (this.selected) {
  639. painter.pen.lineWidth = painter.toPx(this.lineWidth * 2);
  640. painter.shadow.shadowBlur = 10;
  641. painter.shadow.shadowColor = new SColor(`#00000033`);
  642. painter.shadow.shadowOffsetX = 5;
  643. painter.shadow.shadowOffsetY = 5;
  644. }
  645. // 绘制基本图形
  646. this.drawBaseLine(painter);
  647. }
  648. }
  649. }