From 3af06eb52a7c7496f7b2d630bca1b170b71707b6 Mon Sep 17 00:00:00 2001 From: Maximilian Giller <m.giller@tu-bs.de> Date: Sun, 26 Jun 2022 18:58:45 +0200 Subject: [PATCH] Moved some variables to state and tried to implemented custom node and link rendering, but doesn't get called for some reason --- src/editor/js/components/editor.tsx | 171 +++++++++-- src/editor/js/state.ts | 435 ++++++---------------------- 2 files changed, 235 insertions(+), 371 deletions(-) diff --git a/src/editor/js/components/editor.tsx b/src/editor/js/components/editor.tsx index 30a4587..e6fc0ff 100644 --- a/src/editor/js/components/editor.tsx +++ b/src/editor/js/components/editor.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { State } from "../state"; import * as Interactions from "../interactions"; import { Graph } from "../structures/graph/graph"; import { loadGraphJson } from "../../../datasets"; @@ -9,19 +8,20 @@ import "./editor.css"; import ReactForceGraph2d from "react-force-graph-2d"; import { Node } from "../structures/graph/node"; import { HistoryNavigator } from "./historynavigator"; +import { GraphElement } from "../structures/graph/graphelement"; type propTypes = any; type stateTypes = { graph: Graph; + visibleLabels: boolean; + selectedNode: Node; + keys: { [name: string]: boolean }; }; export class Editor extends React.PureComponent<propTypes, stateTypes> { private defaultWarmupTicks = 100; private warmupTicks = 100; - private keyStates: { [name: string]: boolean } = {}; - private selectedNode: Node; - constructor(props: propTypes) { super(props); this.loadGraph = this.loadGraph.bind(this); @@ -32,10 +32,16 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyUp = this.handleKeyUp.bind(this); this.forceUpdate = this.forceUpdate.bind(this); + this.isHighlighted = this.isHighlighted.bind(this); + this.handleNodeCanvasObject = this.handleNodeCanvasObject.bind(this); + this.handleLinkCanvasObject = this.handleLinkCanvasObject.bind(this); // Set as new state this.state = { graph: undefined, + visibleLabels: true, + selectedNode: undefined, + keys: {}, }; Interactions.initInteractions(); @@ -72,9 +78,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { return false; } - // .linkColor((link: any) => Editor.globalState.linkColor(link)) - // .nodeColor((node: any) => Editor.globalState.nodeColor(node)) - // .onNodeClick((node: any) => Editor.globalState.onNodeClick(node)) // .onNodeDragEnd((node: any, translate: any) => // Editor.globalState.onNodeDragEnd(node, translate) // ) @@ -91,18 +94,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { // this.extractPositions(event) // ) // ) - // .nodeCanvasObjectMode((node: any) => - // Editor.globalState.nodeCanvasObjectMode(node) - // ) - // .nodeCanvasObject((node: any, ctx: any, globalScale: any) => - // Editor.globalState.nodeCanvasObject(node, ctx, globalScale) - // ) - // .linkCanvasObjectMode((link: any) => - // Editor.globalState.linkCanvasObjectMode(link) - // ) - // .linkCanvasObject((link: any, ctx: any, globalScale: any) => - // Editor.globalState.linkCanvasObject(link, ctx, globalScale) - // ) // .onLinkClick((link: any) => Editor.globalState.onLinkClick(link)); // Set as new state @@ -122,22 +113,62 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { private handleKeyDown(event: KeyboardEvent) { const key: string = event.key; - this.keyStates[key] = true; + + const keys = this.state.keys; + keys[key] = true; + + this.setState({ + keys: keys, + }); } private handleKeyUp(event: KeyboardEvent) { const key: string = event.key; - this.keyStates[key] = false; + + const keys = this.state.keys; + keys[key] = false; + + this.setState({ + keys: keys, + }); } /** * Propagates the changed state of the graph. */ private onHistoryChange() { - this.selectedNode = undefined; + this.setState({ + selectedNode: undefined, + }); this.forceUpdate(); } + /** + * Should a given element be highlighted in rendering or not. + * @param element Element that should, or should not be highlighted. + * @returns True, if element should be highlighted. + */ + private isHighlighted(element: GraphElement): boolean { + if (this.state.selectedNode == undefined) { + // Default to false if nothing selected. + return false; + } + + if (element.node) { + // Is node + return element.equals(this.state.selectedNode); + } else if (element.link) { + // Is link + // Is it one of the adjacent links? + const found = this.state.selectedNode.links.find( + this.state.selectedNode.equals + ); + return found !== undefined; + } else { + return false; + } + } + /** * Calculates the corresponding coordinates for a click event for easier further processing. * @param event The corresponding click event. @@ -157,14 +188,100 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { } private handleNodeClick(node: Node) { - if (this.keyStates["Control"]) { + if (this.state.keys["Control"]) { node.delete(); } else { - this.selectedNode = node; + this.setState({ + selectedNode: node, + }); } this.forceUpdate(); } + private handleNodeCanvasObject(node: any, ctx: any, globalScale: any) { + // add ring just for highlighted nodes + if (this.isHighlighted(node)) { + ctx.beginPath(); + ctx.arc(node.x, node.y, 4 * 0.6, 0, 2 * Math.PI, false); + ctx.fillStyle = "red"; + ctx.fill(); + } + + // Draw image + const imageSize = 12; + if (node.icon !== undefined) { + const img = new Image(); + img.src = node.icon.link; + + ctx.drawImage( + img, + node.x - imageSize / 2, + node.y - imageSize / 2, + imageSize, + imageSize + ); + } + + // Draw label + if (this.state.visibleLabels) { + const label = node.label; + const fontSize = 11 / globalScale; + ctx.font = `${fontSize}px Sans-Serif`; + const textWidth = ctx.measureText(label).width; + const bckgDimensions = [textWidth, fontSize].map( + (n) => n + fontSize * 0.2 + ); // some padding + + const nodeHeightOffset = imageSize / 3 + bckgDimensions[1]; + ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; + ctx.fillRect( + node.x - bckgDimensions[0] / 2, + node.y - bckgDimensions[1] / 2 + nodeHeightOffset, + ...bckgDimensions + ); + + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = "white"; + ctx.fillText(label, node.x, node.y + nodeHeightOffset); + } + + // TODO: Render label as always visible + } + + private handleLinkCanvasObject(link: any, ctx: any): any { + // Links already initialized? + if (link.source.x === undefined) { + return undefined; + } + + // Draw gradient link + const gradient = ctx.createLinearGradient( + link.source.x, + link.source.y, + link.target.x, + link.target.y + ); + // Have reversed colors + // Color at source node referencing the target node and vice versa + gradient.addColorStop("0", link.target.type.color); + gradient.addColorStop("1", link.source.type.color); + + ctx.beginPath(); + ctx.moveTo(link.source.x, link.source.y); + ctx.lineTo(link.target.x, link.target.y); + ctx.strokeStyle = gradient; + ctx.stroke(); + + // Only render strokes on last link + // var lastLink = graph.data[Graph.GRAPH_LINKS][graph.data[Graph.GRAPH_LINKS].length - 1]; + // if (link === lastLink) { + // ctx.stroke(); + // } + + return undefined; + } + private handleEngineStop() { // Only do something on first stop for each graph if (this.warmupTicks <= 0) { @@ -192,7 +309,7 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { /> <hr /> <NodeDetails - selectedNode={this.selectedNode} + selectedNode={this.state.selectedNode} allTypes={ this.state.graph ? this.state.graph.types : [] } @@ -207,6 +324,10 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { cooldownTicks={0} warmupTicks={this.warmupTicks} onEngineStop={this.handleEngineStop} + nodeCanvasObject={this.handleNodeCanvasObject} + nodeCanvasObjectMode={"after"} + linkCanvasObject={this.handleLinkCanvasObject} + linkCanvasObjectMode={"replace"} /> ) : undefined} </div> diff --git a/src/editor/js/state.ts b/src/editor/js/state.ts index 04710bd..335c8ee 100644 --- a/src/editor/js/state.ts +++ b/src/editor/js/state.ts @@ -1,346 +1,89 @@ -import Tool from "./tools/tool"; -import UndoTool from "./tools/undotool"; -import RedoTool from "./tools/redotool"; -import SelectTool from "./tools/selecttool"; -import DeleteTool from "./tools/deletetool"; -import AddNodeTool from "./tools/addnodetool"; -import ConnectTool from "./tools/connecttool"; -import SettingsTool from "./tools/settingstool"; -import SaveTool from "./tools/savetool"; -import Toolbar from "./toolbar"; -import { DRAG_THRESHOLD_2D } from "../../config"; -import { GraphElement } from "./structures/graph/graphelement"; -import { Node } from "./structures/graph/node"; - -export const TOOLS = { - undo: new UndoTool("undo"), - redo: new RedoTool("redo"), - select: new SelectTool("select"), - delete: new DeleteTool("delete"), - addnode: new AddNodeTool("addnode"), - connect: new ConnectTool("connect"), - settings: new SettingsTool("settings"), - save: new SaveTool("save"), -}; - -export const CONTEXT = { - node: "node", - link: "link", - mixed: "mixed", - nothing: "nothing", -}; - -export class State extends Tool { - display: Toolbar; - tool: Tool; - previousTool: Tool; - selectedItem: GraphElement; - selectedItems: Set<GraphElement>; - itemsContext: any; - labelVisible: boolean; - keyStates: any; - - constructor() { - super("State"); - - this.display = new Toolbar(TOOLS); - - this.tool = undefined; - this.setTool(TOOLS.select); - - // Shared variables - this.selectedItem = undefined; - this.selectedItems = new Set(); - this.itemsContext = CONTEXT.nothing; - this.labelVisible = true; - - this.keyStates = {}; - } - - setTool(tool: Tool) { - if (this.tool === tool) { - return; - } - - if (this.tool !== undefined) { - this.tool.deactivateTool(tool); - } - - this.previousTool = this.tool; - this.tool = tool; - this.display.setSelectedTool(tool); - - if (this.tool !== undefined) { - this.tool.activateTool(); - } - } - - setSelectedItem(item: GraphElement) { - this.selectedItem = item; - } - - addSelectedItem(item: GraphElement) { - this.selectedItems.add(item); - } - - addSelectedItems(items: GraphElement) { - Object.values(items).forEach((item) => { - this.selectedItems.add(item); - }); - } - - removeSelectedItem(item: GraphElement) { - this.selectedItems.delete(item); - } - - clearSelectedItems() { - this.selectedItems.clear(); - this.itemsContext = CONTEXT.nothing; - } - - onNodeClick(node: GraphElement) { - this.tool.onNodeClick(node); - } - - onNodeDragEnd(node: any, translate: any) { - // Handle as click event, if drag distance under a certain threshold - const distanceDragged = Math.sqrt( - Math.pow(translate.x, 2) + Math.pow(translate.y, 2) - ); - if (distanceDragged < DRAG_THRESHOLD_2D) { - this.onNodeClick(node); - return; - } else { - this.tool.onNodeDragEnd(node, translate); - } - } - - onLinkClick(link: any) { - this.tool.onLinkClick(link); - } - - linkColor(link: any) { - return "red"; - } - - nodeColor(node: any) { - return "black"; - } - - onKeyDown(key: any) { - const id = this.getKeyId(key); - const previous = this.keyStates[id]; - - this.keyStates[id] = true; - - if (previous !== true) { - this.tool.onKeyDown(key); - } - } - - onKeyUp(key: any) { - const id = this.getKeyId(key); - const previous = this.keyStates[id]; - - this.keyStates[id] = false; - - if (previous !== false) { - this.tool.onKeyUp(key); - } - } - - getKeyId(key: any) { - return key.keyCode; - } - - nodeCanvasObject(node: any, ctx: any, globalScale: any) { - const toolValue = this.tool.nodeCanvasObject(node, ctx); - - if (toolValue !== undefined) { - return toolValue; - } - - // TODO: Clean up function - - // add ring just for highlighted nodes - if (this.selectedItem === node || this.selectedItems.has(node)) { - ctx.beginPath(); - ctx.arc(node.x, node.y, 4 * 0.6, 0, 2 * Math.PI, false); - ctx.fillStyle = this.selectedItem === node ? "red" : "white"; - ctx.fill(); - } - - // Draw image - const imageSize = 12; - if (node.icon !== undefined) { - const img = new Image(); - img.src = node.icon.link; - - ctx.drawImage( - img, - node.x - imageSize / 2, - node.y - imageSize / 2, - imageSize, - imageSize - ); - } - - // Draw label - if (this.labelVisible) { - const label = node.label; - const fontSize = 11 / globalScale; - ctx.font = `${fontSize}px Sans-Serif`; - const textWidth = ctx.measureText(label).width; - const bckgDimensions = [textWidth, fontSize].map( - (n) => n + fontSize * 0.2 - ); // some padding - - const nodeHeightOffset = imageSize / 3 + bckgDimensions[1]; - ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; - ctx.fillRect( - node.x - bckgDimensions[0] / 2, - node.y - bckgDimensions[1] / 2 + nodeHeightOffset, - ...bckgDimensions - ); - - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = "white"; - ctx.fillText(label, node.x, node.y + nodeHeightOffset); - } - - // TODO: Render label as always visible - } - - nodePointerAreaPaint(node: any, color: any, ctx: any) { - const toolValue = this.tool.nodePointerAreaPaint(node, color, ctx); - - if (toolValue !== undefined) { - return toolValue; - } - - const imageSize = 12; - ctx.fillStyle = color; - ctx.fillRect( - node.x - imageSize / 2, - node.y - imageSize / 2, - imageSize, - imageSize - ); // draw square as pointer trap - } - - nodeCanvasObjectMode(node: any) { - const toolValue = this.tool.nodeCanvasObjectMode(node); - - if (toolValue !== undefined) { - return toolValue; - } - - return "after"; - } - - linkCanvasObjectMode(link: any) { - const toolValue = this.tool.linkCanvasObjectMode(link); - - if (toolValue !== undefined) { - return toolValue; - } - - return "replace"; - } - - linkCanvasObject(link: any, ctx: any, globalScale: any) { - const toolValue = this.tool.linkCanvasObject(link, ctx); - - if (toolValue !== undefined) { - return toolValue; - } - - // Links already initialized? - if (link.source.x === undefined) { - return undefined; - } - - // Draw gradient link - const gradient = ctx.createLinearGradient( - link.source.x, - link.source.y, - link.target.x, - link.target.y - ); - // Have reversed colors - // Color at source node referencing the target node and vice versa - gradient.addColorStop("0", link.target.type.color); - gradient.addColorStop("1", link.source.type.color); - - ctx.beginPath(); - ctx.moveTo(link.source.x, link.source.y); - ctx.lineTo(link.target.x, link.target.y); - ctx.strokeStyle = gradient; - ctx.stroke(); - - // Only render strokes on last link - // var lastLink = graph.data[Graph.GRAPH_LINKS][graph.data[Graph.GRAPH_LINKS].length - 1]; - // if (link === lastLink) { - // ctx.stroke(); - // } - - return undefined; - } - - linkWidth(link: any) { - const toolValue = this.tool.linkWidth(link); - - if (toolValue !== undefined) { - return toolValue; - } - - return this.isLinkHighlighted(link) ? 2 : 1; - } - - linkDirectionalParticles() { - const toolValue = this.tool.linkDirectionalParticles(); - - if (toolValue !== undefined) { - return toolValue; - } - - return 4; - } - - linkDirectionalParticleWidth(link: any) { - const toolValue = this.tool.linkDirectionalParticleWidth(link); - - if (toolValue !== undefined) { - return toolValue; - } - - return this.isLinkHighlighted(link) ? 4 : 0; - } - - onBackgroundClick(event: any, positions: any) { - this.tool.onBackgroundClick(event, positions); - } - - redraw() { - this.display.setSelectedTool(this.tool); - } - - isLinkHighlighted(link: any) { - return false; // Code after that is causing issues - - if (this.selectedItem === link) { - return true; - } - - if (this.selectedItem.node) { - return (this.selectedItem as Node).links.includes(link); - } - - return false; - } - - setLabelVisibility(visibility: boolean) { - this.labelVisible = visibility; - } -} +// export const TOOLS = { +// undo: new UndoTool("undo"), +// redo: new RedoTool("redo"), +// select: new SelectTool("select"), +// delete: new DeleteTool("delete"), +// addnode: new AddNodeTool("addnode"), +// connect: new ConnectTool("connect"), +// settings: new SettingsTool("settings"), +// save: new SaveTool("save"), +// }; +// export class State extends Tool { +// onNodeDragEnd(node: any, translate: any) { +// // Handle as click event, if drag distance under a certain threshold +// const distanceDragged = Math.sqrt( +// Math.pow(translate.x, 2) + Math.pow(translate.y, 2) +// ); +// if (distanceDragged < DRAG_THRESHOLD_2D) { +// this.onNodeClick(node); +// return; +// } else { +// this.tool.onNodeDragEnd(node, translate); +// } +// } + +// onKeyDown(key: any) { +// const previous = this.keyStates[id]; +// if (previous !== true) { +// this.tool.onKeyDown(key); +// } +// } + +// onKeyUp(key: any) { +// const previous = this.keyStates[id]; +// if (previous !== false) { +// this.tool.onKeyUp(key); +// } +// } + +// nodePointerAreaPaint(node: any, color: any, ctx: any) { +// const toolValue = this.tool.nodePointerAreaPaint(node, color, ctx); + +// if (toolValue !== undefined) { +// return toolValue; +// } + +// const imageSize = 12; +// ctx.fillStyle = color; +// ctx.fillRect( +// node.x - imageSize / 2, +// node.y - imageSize / 2, +// imageSize, +// imageSize +// ); // draw square as pointer trap +// } + +// linkWidth(link: any) { +// const toolValue = this.tool.linkWidth(link); + +// if (toolValue !== undefined) { +// return toolValue; +// } + +// return this.isLinkHighlighted(link) ? 2 : 1; +// } + +// linkDirectionalParticles() { +// const toolValue = this.tool.linkDirectionalParticles(); + +// if (toolValue !== undefined) { +// return toolValue; +// } + +// return 4; +// } + +// linkDirectionalParticleWidth(link: any) { +// const toolValue = this.tool.linkDirectionalParticleWidth(link); + +// if (toolValue !== undefined) { +// return toolValue; +// } + +// return this.isLinkHighlighted(link) ? 4 : 0; +// } + +// onBackgroundClick(event: any, positions: any) { +// this.tool.onBackgroundClick(event, positions); +// } +// } -- GitLab