SGraphView.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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) 2009-2020. 博锐尚格科技股份有限公司
  21. * ~8888'
  22. * .!88~ All rights reserved.
  23. *
  24. * *********************************************************************************************************************
  25. */
  26. import { SMouseEvent, SNetUtil } from "@persagy-web/base";
  27. import {
  28. SCanvasPaintEngine,
  29. SCanvasView,
  30. SPainter,
  31. SPoint,
  32. SRect,
  33. SSvgPaintEngine
  34. } from "@persagy-web/draw";
  35. import { SGraphScene } from "./SGraphScene";
  36. import { SGraphItem } from "./SGraphItem";
  37. import { v1 as uuidv1 } from "uuid";
  38. import { SColor } from "@persagy-web/draw";
  39. /**
  40. * Graph 图形引擎视图类
  41. *
  42. * @author 庞利祥 <sybotan@126.com>
  43. */
  44. export class SGraphView extends SCanvasView {
  45. /** 场景对象 */
  46. private _scene: SGraphScene | null = null;
  47. get scene(): SGraphScene | null {
  48. return this._scene;
  49. }
  50. set scene(v: SGraphScene | null) {
  51. if (this._scene != null) {
  52. this._scene.view = null;
  53. }
  54. this._scene = v;
  55. if (this._scene != null) {
  56. this._scene.view = this;
  57. }
  58. this.update();
  59. }
  60. /** 背景色 */
  61. backgroundColor: SColor = SColor.Transparent;
  62. /** 旋转角度 */
  63. rotate: number = 0;
  64. /**
  65. * 构造函数
  66. *
  67. * @param id 画布对象 ID
  68. */
  69. constructor(id: string) {
  70. super(id);
  71. }
  72. /**
  73. * 保存场景 SVG 文件
  74. *
  75. * @param name 文件名
  76. * @param width svg 宽度
  77. * @param height svg 高度
  78. */
  79. saveSceneSvg(name: string, width: number, height: number): void {
  80. let url = URL.createObjectURL(
  81. new Blob([this.sceneSvgData(width, height)], {
  82. type: "text/xml,charset=UTF-8"
  83. })
  84. );
  85. SNetUtil.downLoad(name, url);
  86. }
  87. /**
  88. * 场景 SVG 图形的数据
  89. *
  90. * @param width svg 宽度
  91. * @param height svg 高度
  92. * @return URL 地址
  93. */
  94. sceneSvgData(width: number, height: number): string {
  95. if (null == this.scene) {
  96. return "";
  97. }
  98. let engine = new SSvgPaintEngine(width, height);
  99. let painter = new SPainter(engine);
  100. // 保存视图缩放比例与原点位置
  101. let s0 = this.scale;
  102. let x0 = this.origin.x;
  103. let y0 = this.origin.y;
  104. // 场景中无对象
  105. let rect = this.scene.allItemRect();
  106. this.fitRectToSize(width, height, rect);
  107. this.onDraw(painter);
  108. // 恢复视图缩放比例与原点位置
  109. this.scale = s0;
  110. this.origin.x = x0;
  111. this.origin.y = y0;
  112. return engine.toSvg();
  113. }
  114. /**
  115. * 适配视图到视图
  116. *
  117. * @param scale 适配所占绘制区域百分比
  118. */
  119. fitSceneToView(scale: number = 0.8): void {
  120. if (null == this.scene) {
  121. return;
  122. }
  123. // 场景中无对象
  124. let rect = this.scene.allItemRect();
  125. this.fitRectToSize(this.width, this.height, rect, scale);
  126. }
  127. /**
  128. * 适配选中的对象在视图中可见
  129. *
  130. * @param scale 适配所占绘制区域百分比
  131. */
  132. fitSelectedToView(scale: number = 0.8): void {
  133. if (null == this.scene) {
  134. return;
  135. }
  136. // 场景中无对象
  137. let rect = this.scene.selectedItemRect();
  138. this.fitRectToSize(this.width, this.height, rect, scale);
  139. }
  140. /**
  141. * 适配任意对象在视图中可见
  142. *
  143. * @param itemList 所有要参与适配的 item
  144. * @param scale 适配所占绘制区域百分比
  145. */
  146. fitItemToView(itemList: SGraphItem[], scale: number = 0.8): void {
  147. if (null == this.scene) {
  148. return;
  149. }
  150. let rect: SRect | null = null;
  151. // 依次取 item 列中的所有 item。将所有 item 的边界做并焦处理。
  152. for (let item of itemList) {
  153. if (rect == null) {
  154. rect = item.boundingRect().translated(item.pos.x, item.pos.y);
  155. } else {
  156. rect.union(
  157. item.boundingRect().translated(item.pos.x, item.pos.y)
  158. );
  159. }
  160. }
  161. // 场景中无对象
  162. this.fitRectToSize(this.width, this.height, rect, scale);
  163. }
  164. /**
  165. * 将场景中的 x, y 坐标转换成视图坐标。
  166. *
  167. * @param x 场景中的横坐标
  168. * @param y 场景中的纵坐标
  169. * @return 视图坐标
  170. */
  171. mapFromScene(x: number, y: number): SPoint;
  172. /**
  173. * 将场景中的 x, y 坐标转换成视图坐标。
  174. *
  175. * @param pos 场景中的坐标
  176. * @return 视图坐标
  177. */
  178. mapFromScene(pos: SPoint): SPoint;
  179. /**
  180. * 将场景中的 x, y 坐标转换成视图坐标(重载实现)。
  181. *
  182. * @param x 场景中的横坐标
  183. * @param y 场景中的纵坐标
  184. * @return 视图坐标
  185. */
  186. mapFromScene(x: number | SPoint, y?: number): SPoint {
  187. if (x instanceof SPoint) {
  188. // 如果传入的是 SPoint 对象
  189. return new SPoint(
  190. x.x * this.scale + this.origin.x,
  191. x.y * this.scale + this.origin.y
  192. );
  193. }
  194. // @ts-ignore
  195. return new SPoint(
  196. x * this.scale + this.origin.x,
  197. (y == undefined ? 0 : y) * this.scale + this.origin.y
  198. );
  199. }
  200. /**
  201. * 将视图的 x, y 坐标转换成场景坐标。
  202. *
  203. * @param x 视图横坐标
  204. * @param y 视图纵坐标
  205. * @return 场景坐标
  206. */
  207. mapToScene(x: number, y: number): SPoint;
  208. /**
  209. * 将视图的 x, y 坐标转换成场景坐标。
  210. *
  211. * @param pos 视图坐标
  212. * @return 场景坐标
  213. */
  214. mapToScene(pos: SPoint): SPoint;
  215. /**
  216. * 将视图的 x, y 坐标转换成场景坐标。(不推荐在外部调用)
  217. *
  218. * @param x 视图的横坐标或 SPoint 对象
  219. * @param y 视图的纵坐标
  220. * @return 场景坐标
  221. */
  222. mapToScene(x: number | SPoint, y?: number): SPoint {
  223. if (x instanceof SPoint) {
  224. // 如果传入的是 SPoint 对象
  225. return new SPoint(
  226. (x.x - this.origin.x) / this.scale,
  227. (x.y - this.origin.y) / this.scale
  228. );
  229. }
  230. return new SPoint(
  231. (x - this.origin.x) / this.scale,
  232. ((y == undefined ? 0 : y) - this.origin.y) / this.scale
  233. );
  234. }
  235. /**
  236. * 保存指定大小图像,并且适配到中央而不影响原图
  237. *
  238. * @param name 名称
  239. * @param type 图像类型
  240. * @param width 要保存图形的宽
  241. * @param height 要保存图形的高
  242. * @param bg 生成图的背景色
  243. */
  244. saveImageSize(
  245. name: string,
  246. type: string,
  247. width: number,
  248. height: number,
  249. bg: string = "#ffffff"
  250. ): void {
  251. const can = document.createElement("CANVAS") as HTMLCanvasElement;
  252. let vi: any = this.generateView(can, width, height, bg);
  253. vi.saveImage(name, type);
  254. // @ts-ignore
  255. this.scene.view = this;
  256. can.remove();
  257. vi = null;
  258. }
  259. /**
  260. * 保存指定大小图像,并且适配到中央而不影响原图所使用的图像的 URL
  261. *
  262. * @param type 图像类型
  263. * @param width 要保存图形的宽
  264. * @param height 要保存图形的高
  265. * @param bg 生成图的背景色
  266. * @return 图像的 URL
  267. */
  268. imageSizeUrl(
  269. type: string,
  270. width: number,
  271. height: number,
  272. bg: string = "#FFFFFF"
  273. ): string {
  274. const can = document.createElement("CANVAS") as HTMLCanvasElement;
  275. let vi: any = this.generateView(can, width, height, bg);
  276. const str = vi.canvasView.toDataURL(`image/${type}`);
  277. can.remove();
  278. vi = null;
  279. return str;
  280. }
  281. /**
  282. * 生成新的view
  283. *
  284. * @param can canvas dom
  285. * @param width 要保存图形的宽
  286. * @param height 要保存图形的高
  287. * @param bg 生成图的背景色
  288. * @return 图像的 URL
  289. */
  290. private generateView(
  291. can: HTMLCanvasElement,
  292. width: number,
  293. height: number,
  294. bg: string = "#FFFFFF"
  295. ): SGraphView {
  296. can.width = width;
  297. can.height = height;
  298. can.id = uuidv1();
  299. const body = document.getElementsByTagName("body")[0];
  300. body.appendChild(can);
  301. let engine = new SCanvasPaintEngine(
  302. can.getContext("2d") as CanvasRenderingContext2D
  303. );
  304. let painter = new SPainter(engine);
  305. let vi = new SGraphView(can.id);
  306. vi.scene = this.scene;
  307. vi.fitSceneToView();
  308. vi.backgroundColor = new SColor(bg);
  309. vi.onDraw(painter);
  310. return vi;
  311. }
  312. /**
  313. * 绘制视图
  314. *
  315. * @param painter 绘制对象
  316. */
  317. protected onDraw(painter: SPainter): void {
  318. painter.save();
  319. painter.clearRect(0, 0, this.width, this.height);
  320. painter.restore();
  321. // 如果未设备场景
  322. if (this.scene == null) {
  323. return;
  324. }
  325. // 绘制背景
  326. painter.save();
  327. this.drawBackground(painter);
  328. painter.restore();
  329. // 绘制场景
  330. painter.save();
  331. painter.translate(this.origin.x, this.origin.y);
  332. painter.scale(this.scale, this.scale);
  333. painter.rotate(this.rotate);
  334. this.scene.drawScene(painter, new SRect());
  335. painter.restore();
  336. // 绘制前景
  337. painter.save();
  338. this.drawForeground(painter);
  339. painter.restore();
  340. }
  341. /**
  342. * 绘制场景背景
  343. *
  344. * @param painter 绘制对象
  345. */
  346. protected drawBackground(painter: SPainter): void {
  347. painter.brush.color = this.backgroundColor;
  348. painter.pen.color = SColor.Transparent;
  349. painter.drawRect(0, 0, this.width, this.height);
  350. // DO NOTHING
  351. }
  352. /**
  353. * 绘制场景前景
  354. *
  355. * @param painter 绘制对象
  356. */
  357. protected drawForeground(painter: SPainter): void {
  358. // DO NOTHING
  359. }
  360. /**
  361. * 鼠标双击事件
  362. *
  363. * @param event 事件参数
  364. */
  365. protected onDoubleClick(event: MouseEvent): void {
  366. if (this.scene != null) {
  367. let ce = this.toSceneMotionEvent(event);
  368. this.scene.onDoubleClick(ce);
  369. }
  370. }
  371. /**
  372. * 鼠标按下事件
  373. *
  374. * @param event 事件参数
  375. */
  376. protected onMouseDown(event: MouseEvent): void {
  377. super.onMouseDown(event);
  378. if (this.scene != null) {
  379. let ce = this.toSceneMotionEvent(event);
  380. this.scene.onMouseDown(ce);
  381. }
  382. }
  383. /**
  384. * 鼠标移动事件
  385. *
  386. * @param event 事件参数
  387. */
  388. protected onMouseMove(event: MouseEvent): void {
  389. super.onMouseMove(event);
  390. if (this.scene != null) {
  391. let ce = this.toSceneMotionEvent(event);
  392. this.scene.onMouseMove(ce);
  393. }
  394. }
  395. /**
  396. * 鼠标松开事件
  397. *
  398. * @param event 事件参数
  399. */
  400. protected onMouseUp(event: MouseEvent): void {
  401. super.onMouseUp(event);
  402. if (this.scene != null) {
  403. let ce = this.toSceneMotionEvent(event);
  404. this.scene.onMouseUp(ce);
  405. }
  406. }
  407. /**
  408. * 上下文菜单事件
  409. *
  410. * @param event 事件参数
  411. */
  412. protected onContextMenu(event: MouseEvent): void {
  413. if (this.scene != null) {
  414. let ce = this.toSceneMotionEvent(event);
  415. this.scene.onContextMenu(ce);
  416. }
  417. }
  418. /**
  419. * 按键按下事件
  420. *
  421. * @param event 事件参数
  422. */
  423. protected onKeyDown(event: KeyboardEvent): void {
  424. if (this.scene != null) {
  425. this.scene.onKeyDown(event);
  426. }
  427. }
  428. /**
  429. * 按键松开事件
  430. *
  431. * @param event 事件参数
  432. */
  433. protected onKeyUp(event: KeyboardEvent): void {
  434. if (this.scene != null) {
  435. this.scene.onKeyUp(event);
  436. }
  437. }
  438. /**
  439. * 适配场景在视图中可见
  440. *
  441. * @param width 宽度
  442. * @param height 高度
  443. * @param rect 对象的矩阵大小
  444. * @param scale 适配后所占屏幕百分比(0 - 1 之间的数)
  445. */
  446. private fitRectToSize(
  447. width: number,
  448. height: number,
  449. rect: SRect | null,
  450. scale: number = 0.8
  451. ): void {
  452. // 未设置场景
  453. if (null == rect || !rect.isValid()) {
  454. return;
  455. }
  456. this.scale = Math.min(width / rect.width, height / rect.height) * scale;
  457. // 计算场景中心点
  458. let center = rect.center();
  459. this.origin.x = width / 2.0 - center.x * this.scale;
  460. this.origin.y = height / 2.0 - center.y * this.scale;
  461. }
  462. /**
  463. * MouseEvent 事件转换成场景 SMouseEvent 事件
  464. *
  465. * @param event 事件参数
  466. * @return MouseEvent 事件转换成场景 SMouseEvent 事件
  467. */
  468. private toSceneMotionEvent(event: MouseEvent): SMouseEvent {
  469. let se = new SMouseEvent(event);
  470. se.matrix.translate(this.origin.x, this.origin.y);
  471. se.matrix.scale(this.scale, this.scale);
  472. se.matrix.rotate(this.rotate);
  473. const mp = new SPoint(event.offsetX, event.offsetY).matrixTransform(
  474. se.matrix.inversed()
  475. );
  476. se.x = mp.x;
  477. se.y = mp.y;
  478. return se;
  479. }
  480. }