SGraphView.ts 13 KB

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