workbench.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. import * as React from "react";
  2. import { GlobalState } from '../globalState';
  3. import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
  4. import { GraphNode } from './graphNode';
  5. import * as dagre from 'dagre';
  6. import { Nullable } from 'babylonjs/types';
  7. import { NodePort } from './nodePort';
  8. import { DataStorage } from 'babylonjs/Misc/dataStorage';
  9. import { IEditorData} from '../nodeLocationInfo';
  10. import { Button } from 'babylonjs-gui/2D/controls/button';
  11. import { Engine } from 'babylonjs/Engines/engine';
  12. import { Scene } from 'babylonjs/scene';
  13. import { Container, Rectangle } from 'babylonjs-gui';
  14. require("./graphCanvas.scss");
  15. export interface IGraphCanvasComponentProps {
  16. globalState: GlobalState
  17. }
  18. export type FramePortData = {
  19. }
  20. export const isFramePortData = (variableToCheck: any): variableToCheck is FramePortData => {
  21. if (variableToCheck) {
  22. return (variableToCheck as FramePortData) !== undefined;
  23. }
  24. else return false;
  25. }
  26. export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
  27. private readonly MinZoom = 0.1;
  28. private readonly MaxZoom = 4;
  29. private _hostCanvas: HTMLDivElement;
  30. private _graphCanvas: HTMLDivElement;
  31. private _selectionContainer: HTMLDivElement;
  32. private _frameContainer: HTMLDivElement;
  33. private _svgCanvas: HTMLElement;
  34. private _rootContainer: HTMLDivElement;
  35. private _nodes: GraphNode[] = [];
  36. private _guiNodes: GraphNode[] = [];
  37. private _mouseStartPointX: Nullable<number> = null;
  38. private _mouseStartPointY: Nullable<number> = null
  39. private _dropPointX = 0;
  40. private _dropPointY = 0;
  41. private _selectionStartX = 0;
  42. private _selectionStartY = 0;
  43. private _candidateLinkedHasMoved = false;
  44. private _x = 0;
  45. private _y = 0;
  46. private _zoom = 1;
  47. private _selectedNodes: GraphNode[] = [];
  48. private _selectedGuiNodes: GraphNode[] = [];
  49. private _selectedPort: Nullable<NodePort> = null;
  50. private _gridSize = 20;
  51. private _selectionBox: Nullable<HTMLDivElement> = null;
  52. private _frameCandidate: Nullable<HTMLDivElement> = null;
  53. private _altKeyIsPressed = false;
  54. private _ctrlKeyIsPressed = false;
  55. private _oldY = -1;
  56. public _frameIsMoving = false;
  57. public _isLoading = false;
  58. public get gridSize() {
  59. return this._gridSize;
  60. }
  61. public set gridSize(value: number) {
  62. this._gridSize = value;
  63. this.updateTransform();
  64. }
  65. public get globalState(){
  66. return this.props.globalState;
  67. }
  68. public get nodes() {
  69. return this._nodes;
  70. }
  71. public get zoom() {
  72. return this._zoom;
  73. }
  74. public set zoom(value: number) {
  75. if (this._zoom === value) {
  76. return;
  77. }
  78. this._zoom = value;
  79. this.updateTransform();
  80. }
  81. public get x() {
  82. return this._x;
  83. }
  84. public set x(value: number) {
  85. this._x = value;
  86. this.updateTransform();
  87. }
  88. public get y() {
  89. return this._y;
  90. }
  91. public set y(value: number) {
  92. this._y = value;
  93. this.updateTransform();
  94. }
  95. public get selectedNodes() {
  96. return this._selectedNodes;
  97. }
  98. public get selectedGuiNodes() {
  99. return this._selectedGuiNodes;
  100. }
  101. public get selectedPort() {
  102. return this._selectedPort;
  103. }
  104. public get canvasContainer() {
  105. return this._graphCanvas;
  106. }
  107. public get hostCanvas() {
  108. return this._hostCanvas;
  109. }
  110. public get svgCanvas() {
  111. return this._svgCanvas;
  112. }
  113. public get selectionContainer() {
  114. return this._selectionContainer;
  115. }
  116. public get frameContainer() {
  117. return this._frameContainer;
  118. }
  119. constructor(props: IGraphCanvasComponentProps) {
  120. super(props);
  121. props.globalState.onSelectionChangedObservable.add(selection => {
  122. if (!selection) {
  123. this.selectedGuiNodes.forEach(element => {
  124. element.isSelected = false;
  125. });
  126. this._selectedNodes = [];
  127. this._selectedGuiNodes = [];
  128. this._selectedPort = null;
  129. } else {
  130. if (selection instanceof GraphNode){
  131. if (this._ctrlKeyIsPressed) {
  132. if (this._selectedNodes.indexOf(selection) === -1) {
  133. this._selectedNodes.push(selection);
  134. this._selectedGuiNodes.push(selection);
  135. }
  136. } else {
  137. this._selectedNodes = [selection];
  138. this._selectedGuiNodes = [selection];
  139. }
  140. } else if(selection instanceof NodePort){
  141. this._selectedNodes = [];
  142. this._selectedGuiNodes = [];
  143. this._selectedPort = selection;
  144. } else {
  145. this._selectedNodes = [];
  146. this._selectedGuiNodes = [];
  147. //this._selectedPort = selection.port;
  148. }
  149. }
  150. });
  151. props.globalState.onGridSizeChanged.add(() => {
  152. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  153. });
  154. this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
  155. this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
  156. this._altKeyIsPressed = evt.altKey;
  157. this._ctrlKeyIsPressed = evt.ctrlKey;
  158. }, false);
  159. this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
  160. this._altKeyIsPressed = false;
  161. this._ctrlKeyIsPressed = false;
  162. }, false);
  163. // Store additional data to serialization object
  164. this.props.globalState.storeEditorData = (editorData, graphFrame) => {
  165. editorData.frames = [];
  166. if (graphFrame) {
  167. editorData.frames.push(graphFrame!.serialize());
  168. } else {
  169. editorData.x = this.x;
  170. editorData.y = this.y;
  171. editorData.zoom = this.zoom;
  172. }
  173. }
  174. }
  175. public getGridPosition(position: number, useCeil = false) {
  176. let gridSize = this.gridSize;
  177. if (gridSize === 0) {
  178. return position;
  179. }
  180. if (useCeil) {
  181. return gridSize * Math.ceil(position / gridSize);
  182. }
  183. return gridSize * Math.floor(position / gridSize);
  184. }
  185. public getGridPositionCeil(position: number) {
  186. let gridSize = this.gridSize;
  187. if (gridSize === 0) {
  188. return position;
  189. }
  190. return gridSize * Math.ceil(position / gridSize);
  191. }
  192. updateTransform() {
  193. this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
  194. if (DataStorage.ReadBoolean("ShowGrid", true)) {
  195. this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
  196. this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
  197. } else {
  198. this._hostCanvas.style.backgroundSize = `0`;
  199. }
  200. }
  201. onKeyUp() {
  202. this._altKeyIsPressed = false;
  203. this._ctrlKeyIsPressed = false;
  204. this._oldY = -1;
  205. }
  206. findNodeFromBlock(block: NodeMaterialBlock) {
  207. return this.nodes.filter(n => n.block === block)[0];
  208. }
  209. reset() {
  210. for (var node of this._nodes) {
  211. node.dispose();
  212. }
  213. this._nodes = [];
  214. this._graphCanvas.innerHTML = "";
  215. this._svgCanvas.innerHTML = "";
  216. }
  217. appendBlock(block: NodeMaterialBlock) {
  218. let newNode = new GraphNode(block, this.props.globalState, null);
  219. newNode.appendVisual(this._graphCanvas, this);
  220. this._nodes.push(newNode);
  221. return newNode;
  222. }
  223. distributeGraph() {
  224. this.x = 0;
  225. this.y = 0;
  226. this.zoom = 1;
  227. let graph = new dagre.graphlib.Graph();
  228. graph.setGraph({});
  229. graph.setDefaultEdgeLabel(() => ({}));
  230. graph.graph().rankdir = "LR";
  231. // Build dagre graph
  232. this._nodes.forEach(node => {
  233. graph.setNode(node.id.toString(), {
  234. id: node.id,
  235. type: "node",
  236. width: node.width,
  237. height: node.height
  238. });
  239. });
  240. this._nodes.forEach(node => {
  241. node.block.outputs.forEach(output => {
  242. if (!output.hasEndpoints) {
  243. return;
  244. }
  245. });
  246. });
  247. // Distribute
  248. dagre.layout(graph);
  249. // Update graph
  250. let dagreNodes = graph.nodes().map(node => graph.node(node));
  251. dagreNodes.forEach((dagreNode: any) => {
  252. if (!dagreNode) {
  253. return;
  254. }
  255. if (dagreNode.type === "node") {
  256. for (var node of this._nodes) {
  257. if (node.id === dagreNode.id) {
  258. node.x = dagreNode.x - dagreNode.width / 2;
  259. node.y = dagreNode.y - dagreNode.height / 2;
  260. node.cleanAccumulation();
  261. return;
  262. }
  263. }
  264. return;
  265. }
  266. });
  267. }
  268. componentDidMount() {
  269. this._hostCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas") as HTMLDivElement;
  270. this._rootContainer = this.props.globalState.hostDocument.getElementById("graph-container") as HTMLDivElement;
  271. this._graphCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
  272. this._svgCanvas = this.props.globalState.hostDocument.getElementById("graph-svg-container") as HTMLElement;
  273. this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;
  274. this._frameContainer = this.props.globalState.hostDocument.getElementById("frame-container") as HTMLDivElement;
  275. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  276. this.updateTransform();
  277. }
  278. onMove(evt: React.PointerEvent) {
  279. // Selection box
  280. /*if (this._selectionBox) {
  281. const rootRect = this.canvasContainer.getBoundingClientRect();
  282. const localX = evt.pageX - rootRect.left;
  283. const localY = evt.pageY - rootRect.top;
  284. if (localX > this._selectionStartX) {
  285. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  286. this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  287. } else {
  288. this._selectionBox.style.left = `${localX / this.zoom}px`;
  289. this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  290. }
  291. if (localY > this._selectionStartY) {
  292. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  293. this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  294. } else {
  295. this._selectionBox.style.top = `${localY / this.zoom}px`;
  296. this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  297. }
  298. this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
  299. return;
  300. }
  301. // Candidate frame box
  302. if (this._frameCandidate) {
  303. const rootRect = this.canvasContainer.getBoundingClientRect();
  304. const localX = evt.pageX - rootRect.left;
  305. const localY = evt.pageY - rootRect.top;
  306. if (localX > this._selectionStartX) {
  307. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  308. this._frameCandidate.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  309. } else {
  310. this._frameCandidate.style.left = `${localX / this.zoom}px`;
  311. this._frameCandidate.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  312. }
  313. if (localY > this._selectionStartY) {
  314. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  315. this._frameCandidate.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  316. } else {
  317. this._frameCandidate.style.top = `${localY / this.zoom}px`;
  318. this._frameCandidate.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  319. }
  320. return;
  321. }
  322. // Candidate link
  323. if (this._candidateLink) {
  324. const rootRect = this.canvasContainer.getBoundingClientRect();
  325. this._candidatePort = null;
  326. this.props.globalState.onCandidateLinkMoved.notifyObservers(new Vector2(evt.pageX, evt.pageY));
  327. this._dropPointX = (evt.pageX - rootRect.left) / this.zoom;
  328. this._dropPointY = (evt.pageY - rootRect.top) / this.zoom;
  329. this._candidateLink.update(this._dropPointX, this._dropPointY, true);
  330. this._candidateLinkedHasMoved = true;
  331. return;
  332. }
  333. // Zoom with mouse + alt
  334. if (this._altKeyIsPressed && evt.buttons === 1) {
  335. if (this._oldY < 0) {
  336. this._oldY = evt.pageY;
  337. }
  338. let zoomDelta = (evt.pageY - this._oldY) / 10;
  339. if (Math.abs(zoomDelta) > 5) {
  340. const oldZoom = this.zoom;
  341. this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
  342. const boundingRect = evt.currentTarget.getBoundingClientRect();
  343. const clientWidth = boundingRect.width;
  344. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  345. const clientX = evt.clientX - boundingRect.left;
  346. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  347. this.x = this.x - widthDiff * xFactor;
  348. this._oldY = evt.pageY;
  349. }
  350. return;
  351. }
  352. // Move canvas
  353. this._rootContainer.style.cursor = "move";
  354. */
  355. if (this._mouseStartPointX != null && this._mouseStartPointY != null) {
  356. //this.x += evt.clientX - this._mouseStartPointX;
  357. //this.y += evt.clientY - this._mouseStartPointY;
  358. var x = this._mouseStartPointX;
  359. var y = this._mouseStartPointY;
  360. this._guiNodes.forEach(element => {
  361. element._onMove(new BABYLON.Vector2(evt.clientX, evt.clientY),
  362. new BABYLON.Vector2( x, y));
  363. });
  364. this._mouseStartPointX = evt.clientX;
  365. this._mouseStartPointY = evt.clientY;
  366. //doing the dragging for the gui node.
  367. }
  368. }
  369. onDown(evt: React.PointerEvent<HTMLElement>) {
  370. this._rootContainer.setPointerCapture(evt.pointerId);
  371. // Selection?
  372. if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
  373. this._selectionBox = this.props.globalState.hostDocument.createElement("div");
  374. this._selectionBox.classList.add("selection-box");
  375. this._selectionContainer.appendChild(this._selectionBox);
  376. const rootRect = this.canvasContainer.getBoundingClientRect();
  377. this._selectionStartX = (evt.pageX - rootRect.left);
  378. this._selectionStartY = (evt.pageY - rootRect.top);
  379. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  380. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  381. this._selectionBox.style.width = "0px";
  382. this._selectionBox.style.height = "0px";
  383. return;
  384. }
  385. // Frame?
  386. if (evt.currentTarget === this._hostCanvas && evt.shiftKey) {
  387. this._frameCandidate = this.props.globalState.hostDocument.createElement("div");
  388. this._frameCandidate.classList.add("frame-box");
  389. this._frameContainer.appendChild(this._frameCandidate);
  390. const rootRect = this.canvasContainer.getBoundingClientRect();
  391. this._selectionStartX = (evt.pageX - rootRect.left);
  392. this._selectionStartY = (evt.pageY - rootRect.top);
  393. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  394. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  395. this._frameCandidate.style.width = "0px";
  396. this._frameCandidate.style.height = "0px";
  397. return;
  398. }
  399. // Port dragging
  400. if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
  401. return;
  402. }
  403. this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
  404. this._mouseStartPointX = evt.clientX;
  405. this._mouseStartPointY = evt.clientY;
  406. }
  407. onUp(evt: React.PointerEvent) {
  408. this._mouseStartPointX = null;
  409. this._mouseStartPointY = null;
  410. this._rootContainer.releasePointerCapture(evt.pointerId);
  411. this._oldY = -1;
  412. if (this._selectionBox) {
  413. this._selectionBox.parentElement!.removeChild(this._selectionBox);
  414. this._selectionBox = null;
  415. }
  416. if (this._frameCandidate) {
  417. this._frameCandidate.parentElement!.removeChild(this._frameCandidate);
  418. this._frameCandidate = null;
  419. }
  420. }
  421. onWheel(evt: React.WheelEvent) {
  422. let delta = evt.deltaY < 0 ? 0.1 : -0.1;
  423. let oldZoom = this.zoom;
  424. this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
  425. const boundingRect = evt.currentTarget.getBoundingClientRect();
  426. const clientWidth = boundingRect.width;
  427. const clientHeight = boundingRect.height;
  428. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  429. const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
  430. const clientX = evt.clientX - boundingRect.left;
  431. const clientY = evt.clientY - boundingRect.top;
  432. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  433. const yFactor = (clientY - this.y) / oldZoom / clientHeight;
  434. this.x = this.x - widthDiff * xFactor;
  435. this.y = this.y - heightDiff * yFactor;
  436. evt.stopPropagation();
  437. }
  438. zoomToFit() {
  439. // Get negative offset
  440. let minX = 0;
  441. let minY = 0;
  442. this._nodes.forEach(node => {
  443. if (node.x < minX) {
  444. minX = node.x;
  445. }
  446. if (node.y < minY) {
  447. minY = node.y;
  448. }
  449. });
  450. // Restore to 0
  451. this._nodes.forEach(node => {
  452. node.x += -minX;
  453. node.y += -minY;
  454. node.cleanAccumulation();
  455. });
  456. // Get correct zoom
  457. const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
  458. const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
  459. const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
  460. this.zoom = zoomFactor;
  461. this.x = 0;
  462. this.y = 0;
  463. }
  464. processEditorData(editorData: IEditorData) {
  465. this.x = editorData.x || 0;
  466. this.y = editorData.y || 0;
  467. this.zoom = editorData.zoom || 1;
  468. // Frames
  469. }
  470. createGUICanvas()
  471. {
  472. // Get the canvas element from the DOM.
  473. const canvas = document.getElementById("graph-canvas") as HTMLCanvasElement;
  474. // Associate a Babylon Engine to it.
  475. const engine = new BABYLON.Engine(canvas);
  476. // Create our first scene.
  477. var scene = new BABYLON.Scene(engine);
  478. // This creates and positions a free camera (non-mesh)
  479. var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
  480. // This targets the camera to scene origin
  481. camera.setTarget(BABYLON.Vector3.Zero());
  482. // This attaches the camera to the canvas
  483. camera.attachControl(true);
  484. // GUI
  485. this.globalState.guiTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
  486. engine.runRenderLoop(() => {this.updateGUIs(); scene.render()});
  487. }
  488. public addNewButton()
  489. {
  490. if(!this.globalState.guiTexture)
  491. {
  492. this.createGUICanvas();
  493. }
  494. var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Click Me");
  495. button1.width = "150px"
  496. button1.height = "40px";
  497. button1.color = "#FFFFFFFF";
  498. button1.cornerRadius = 20;
  499. button1.background = "#138016FF";
  500. button1.onPointerUpObservable.add(function() {
  501. });
  502. var fakeNodeMaterialBlock = new NodeMaterialBlock("Button");
  503. var newGuiNode = new GraphNode(fakeNodeMaterialBlock, this.globalState, button1);
  504. newGuiNode.appendVisual(this._graphCanvas, this);
  505. this._guiNodes.push(newGuiNode);
  506. this.globalState.guiTexture.addControl(button1);
  507. }
  508. public addNewSlider()
  509. {
  510. if(!this.globalState.guiTexture)
  511. {
  512. this.createGUICanvas();
  513. }
  514. var slider1 = new BABYLON.GUI.Slider("Slider");
  515. slider1.width = "150px"
  516. slider1.height = "40px";
  517. slider1.color = "#FFFFFFFF";
  518. slider1.background = "#138016FF";
  519. slider1.onPointerUpObservable.add(function() {
  520. });
  521. var fakeNodeMaterialBlock = new NodeMaterialBlock("Slider");
  522. var newGuiNode = new GraphNode(fakeNodeMaterialBlock, this.globalState, slider1);
  523. newGuiNode.appendVisual(this._graphCanvas, this);
  524. this._guiNodes.push(newGuiNode);
  525. this.globalState.guiTexture.addControl(slider1);
  526. }
  527. //private _advancedTexture: BABYLON.GUI.AdvancedDynamicTexture;
  528. updateGUIs()
  529. {
  530. this._guiNodes.forEach(element => {
  531. element.updateVisual();
  532. });
  533. }
  534. render() {
  535. //var canv = new HTMLCanvasElement;
  536. var canv = <canvas id="graph-canvas"
  537. onWheel={evt => this.onWheel(evt)}
  538. onPointerMove={evt => this.onMove(evt)}
  539. onPointerDown={evt => this.onDown(evt)}
  540. onPointerUp={evt => this.onUp(evt)}
  541. >
  542. <div id="graph-container">
  543. <div id="graph-canvas-container">
  544. </div>
  545. <div id="frame-container">
  546. </div>
  547. <svg id="graph-svg-container">
  548. </svg>
  549. <div id="selection-container">
  550. </div>
  551. </div>
  552. </canvas>
  553. return (
  554. canv
  555. );
  556. }
  557. }