/* * ********************************************************************************************************************* * * !! * .F88X * X8888Y * .}888888N; * i888888N; .:! .I$WI: * R888888I .'N88~ i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8& * .R888888I .;N8888~ .X8' "8I.!,/8" !%NY8`"8I8~~8>,88I * +888888N; .8888888Y "&&8Y.}8, * ./888888N; .R888888Y .'}~ .>}'.`+> i}! "i' +/' .'i~ !11,.:">, .~]! .i}i * ~888888%: .I888888l .]88~`1/iY88Ii+1'.R$8$8]"888888888> Y8$ W8E X8E W8888'188Il}Y88$* * 18888888 E8888881 .]W%8$`R8X'&8%++N8i,8N%N8+l8%` .}8N:.R$RE%N88N%N$K$R 188,FE$8%~Y88I * .E888888I .i8888888' .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~ * 8888888I .,N888888~ ~88i"8W,!N8*.I88.}888%F,i$88"F88" 888:E8X.>88!i88>`888*.}Fl1]*}1YKi' * i888888N' I888Y ]88;/EX*IFKFK88X K8R .l8W 88Y ~88}'88E&%8W.X8N``]88!.$8K .:W8I * .i888888N; I8Y .&8$ .X88! i881.:%888>I88 ;88] +88+.';;;;:.Y88X 18N.,88l .+88/ * .:R888888I * .&888888I Copyright (c) 2009-2020. 博锐尚格科技股份有限公司 * ~8888' * .!88~ All rights reserved. * * ********************************************************************************************************************* */ import { SMouseEvent, SNetUtil } from "@persagy-web/base"; import { SCanvasPaintEngine, SCanvasView, SPainter, SPoint, SRect, SSvgPaintEngine } from "@persagy-web/draw"; import { SGraphScene } from "./SGraphScene"; import { SGraphItem } from "./SGraphItem"; import { v1 as uuidv1 } from "uuid"; import { SColor } from "@persagy-web/draw"; /** * Graph 图形引擎视图类 * * @author 庞利祥 */ export class SGraphView extends SCanvasView { /** 场景对象 */ private _scene: SGraphScene | null = null; get scene(): SGraphScene | null { return this._scene; } // Get scene set scene(v: SGraphScene | null) { if (this._scene != null) { this._scene.view = null; } this._scene = v; if (this._scene != null) { this._scene.view = this; } this.update(); } // Set scene /** 背景色 */ backgroundColor: SColor = SColor.Transparent; /** 旋转角度 */ rotate: number = 0; /** * 构造函数 * * @param id 画布对象 ID */ constructor(id: string) { super(id); } // Function constructor() /** * 保存场景 SVG 文件 * * @param name 文件名 * @param width svg 宽度 * @param height svg 高度 */ saveSceneSvg(name: string, width: number, height: number): void { let url = URL.createObjectURL( new Blob([this.sceneSvgData(width, height)], { type: "text/xml,charset=UTF-8" }) ); SNetUtil.downLoad(name, url); } // Function saveSceneSvg() /** * 场景 SVG 图形的数据 * * @param width svg 宽度 * @param height svg 高度 * @return URL 地址 */ sceneSvgData(width: number, height: number): string { if (null == this.scene) { return ""; } let engine = new SSvgPaintEngine(width, height); let painter = new SPainter(engine); // 保存视图缩放比例与原点位置 let s0 = this.scale; let x0 = this.origin.x; let y0 = this.origin.y; // 场景中无对象 let rect = this.scene.allItemRect(); this.fitRectToSize(width, height, rect); this.onDraw(painter); // 恢复视图缩放比例与原点位置 this.scale = s0; this.origin.x = x0; this.origin.y = y0; return engine.toSvg(); } // Function saveSvg() /** * 适配视图到视图 */ fitSceneToView(): void { if (null == this.scene) { return; } // 场景中无对象 let rect = this.scene.allItemRect(); this.fitRectToSize(this.width, this.height, rect); } // Function FitView() /** * 适配选中的对象在视图中可见 */ fitSelectedToView(): void { if (null == this.scene) { return; } // 场景中无对象 let rect = this.scene.selectedItemRect(); this.fitRectToSize(this.width, this.height, rect); } // Function fitSelectedToSize() /** * 适配任意对象在视图中可见 */ fitItemToView(itemList: SGraphItem[]): void { if (null == this.scene) { return; } let rect: SRect | null = null; // 依次取 item 列中的所有 item。将所有 item 的边界做并焦处理。 for (let item of itemList) { if (rect == null) { rect = item.boundingRect().translated(item.pos.x, item.pos.y); } else { rect.union( item.boundingRect().translated(item.pos.x, item.pos.y) ); } } // 场景中无对象 this.fitRectToSize(this.width, this.height, rect); } // Function fitItemToView() /** * 将场景中的 x, y 坐标转换成视图坐标。 * * @param x 场景中的横坐标 * @param y 场景中的纵坐标 * @return 视图坐标 */ mapFromScene(x: number, y: number): SPoint; /** * 将场景中的 x, y 坐标转换成视图坐标。 * * @param pos 场景中的坐标 * @return 视图坐标 */ mapFromScene(pos: SPoint): SPoint; /** * 将场景中的 x, y 坐标转换成视图坐标(重载实现)。 * * @param x 场景中的横坐标 * @param y 场景中的纵坐标 * @return 视图坐标 */ mapFromScene(x: number | SPoint, y?: number): SPoint { if (x instanceof SPoint) { // 如果传入的是 SPoint 对象 return new SPoint( x.x * this.scale + this.origin.x, x.y * this.scale + this.origin.y ); } // @ts-ignore return new SPoint( x * this.scale + this.origin.x, (y == undefined ? 0 : y) * this.scale + this.origin.y ); } // Function mapFromScene() /** * 将视图的 x, y 坐标转换成场景坐标。 * * @param x 视图横坐标 * @param y 视图纵坐标 * @return 场景坐标 */ mapToScene(x: number, y: number): SPoint; /** * 将视图的 x, y 坐标转换成场景坐标。 * * @param pos 视图坐标 * @return 场景坐标 */ mapToScene(pos: SPoint): SPoint; /** * 将视图的 x, y 坐标转换成场景坐标。(不推荐在外部调用) * * @param x 视图的横坐标或 SPoint 对象 * @param y 视图的纵坐标 * @return 场景坐标 */ mapToScene(x: number | SPoint, y?: number): SPoint { if (x instanceof SPoint) { // 如果传入的是 SPoint 对象 return new SPoint( (x.x - this.origin.x) / this.scale, (x.y - this.origin.y) / this.scale ); } return new SPoint( (x - this.origin.x) / this.scale, ((y == undefined ? 0 : y) - this.origin.y) / this.scale ); } // Function mapToScene() /** * 保存图像 * * @param name 名称 * @param type 图像类型 * @param width 要保存图形的宽 * @param height 要保存图形的高 */ saveImageSize( name: string, type: string, width: number, height: number ): void { const can = document.createElement("CANVAS") as HTMLCanvasElement; can.width = width; can.height = height; can.id = uuidv1(); const body = document.getElementsByTagName("body")[0]; body.appendChild(can); let engine = new SCanvasPaintEngine( can.getContext("2d") as CanvasRenderingContext2D ); let painter = new SPainter(engine); let vi = new SGraphView(can.id); vi.scene = this.scene; vi.fitSceneToView(); vi.backgroundColor = new SColor("#FFFFFF"); vi.onDraw(painter); vi.saveImage(name, type); // @ts-ignore this.scene.view = this; can.remove(); // @ts-ignore vi = null; } // Function saveImageSize() /** * 绘制视图 * * @param painter 绘制对象 */ protected onDraw(painter: SPainter): void { painter.save(); painter.clearRect(0, 0, this.width, this.height); painter.restore(); // 如果未设备场景 if (this.scene == null) { return; } // 绘制背景 painter.save(); this.drawBackground(painter); painter.restore(); // 绘制场景 painter.save(); painter.translate(this.origin.x, this.origin.y); painter.scale(this.scale, this.scale); painter.rotate(this.rotate); this.scene.drawScene(painter, new SRect()); painter.restore(); // 绘制前景 painter.save(); this.drawForeground(painter); painter.restore(); } // Function onDraw(); /** * 绘制场景背景 * * @param painter 绘制对象 */ protected drawBackground(painter: SPainter): void { painter.brush.color = this.backgroundColor; painter.pen.color = SColor.Transparent; painter.drawRect(0, 0, this.width, this.height); // DO NOTHING } // Function drawBackground() /** * 绘制场景前景 * * @param painter 绘制对象 */ protected drawForeground(painter: SPainter): void { // DO NOTHING } // Function drawForeground() /** * 鼠标双击事件 * * @param event 事件参数 */ protected onDoubleClick(event: MouseEvent): void { if (this.scene != null) { let ce = this.toSceneMotionEvent(event); this.scene.onDoubleClick(ce); } } // Function onDoubleClick() /** * 鼠标按下事件 * * @param event 事件参数 */ protected onMouseDown(event: MouseEvent): void { super.onMouseDown(event); if (this.scene != null) { let ce = this.toSceneMotionEvent(event); this.scene.onMouseDown(ce); } } // Function onMouseDown() /** * 鼠标移动事件 * * @param event 事件参数 */ protected onMouseMove(event: MouseEvent): void { super.onMouseMove(event); if (this.scene != null) { let ce = this.toSceneMotionEvent(event); this.scene.onMouseMove(ce); } } // Function onMouseMove() /** * 鼠标松开事件 * * @param event 事件参数 */ protected onMouseUp(event: MouseEvent): void { super.onMouseUp(event); if (this.scene != null) { let ce = this.toSceneMotionEvent(event); this.scene.onMouseUp(ce); } } // Function onMouseUp() /** * 上下文菜单事件 * * @param event 事件参数 */ protected onContextMenu(event: MouseEvent): void { if (this.scene != null) { let ce = this.toSceneMotionEvent(event); this.scene.onContextMenu(ce); } } // Function onContextMenu() /** * 按键按下事件 * * @param event 事件参数 */ protected onKeyDown(event: KeyboardEvent): void { if (this.scene != null) { this.scene.onKeyDown(event); } } // Function onKeyDown() /** * 按键松开事件 * * @param event 事件参数 */ protected onKeyUp(event: KeyboardEvent): void { if (this.scene != null) { this.scene.onKeyUp(event); } } // Function onKeyUp() /** * 适配场景在视图中可见 * * @param width 宽度 * @param height 高度 * @param rect 对象的矩阵大小 */ private fitRectToSize( width: number, height: number, rect: SRect | null ): void { // 未设置场景 if (null == rect || !rect.isValid()) { return; } this.scale = Math.min(width / rect.width, height / rect.height) * 0.8; // 计算场景中心点 let center = rect.center(); this.origin.x = width / 2.0 - center.x * this.scale; this.origin.y = height / 2.0 - center.y * this.scale; } // Function fitRectToSize() /** * MouseEvent 事件转换成场景 SMouseEvent 事件 * * @param event 事件参数 * @return MouseEvent 事件转换成场景 SMouseEvent 事件 */ private toSceneMotionEvent(event: MouseEvent): SMouseEvent { let se = new SMouseEvent(event); se.matrix.translate(this.origin.x, this.origin.y); se.matrix.scale(this.scale, this.scale); se.matrix.rotate(this.rotate); const mp = new SPoint(event.offsetX, event.offsetY).matrixTransform( se.matrix.inversed() ); se.x = mp.x; se.y = mp.y; return se; } // Function toSceneMotionEvent() } // Class SGraphView