Skip to content
Snippets Groups Projects
Commit 3af06eb5 authored by Maximilian Giller's avatar Maximilian Giller :squid:
Browse files

Moved some variables to state and tried to implemented custom node and link...

Moved some variables to state and tried to implemented custom node and link rendering, but doesn't get called for some reason
parent e5dbcded
No related branches found
No related tags found
1 merge request!2Implemented editor in the react framework
Pipeline #56692 passed
import React from "react"; import React from "react";
import { State } from "../state";
import * as Interactions from "../interactions"; import * as Interactions from "../interactions";
import { Graph } from "../structures/graph/graph"; import { Graph } from "../structures/graph/graph";
import { loadGraphJson } from "../../../datasets"; import { loadGraphJson } from "../../../datasets";
...@@ -9,19 +8,20 @@ import "./editor.css"; ...@@ -9,19 +8,20 @@ import "./editor.css";
import ReactForceGraph2d from "react-force-graph-2d"; import ReactForceGraph2d from "react-force-graph-2d";
import { Node } from "../structures/graph/node"; import { Node } from "../structures/graph/node";
import { HistoryNavigator } from "./historynavigator"; import { HistoryNavigator } from "./historynavigator";
import { GraphElement } from "../structures/graph/graphelement";
type propTypes = any; type propTypes = any;
type stateTypes = { type stateTypes = {
graph: Graph; graph: Graph;
visibleLabels: boolean;
selectedNode: Node;
keys: { [name: string]: boolean };
}; };
export class Editor extends React.PureComponent<propTypes, stateTypes> { export class Editor extends React.PureComponent<propTypes, stateTypes> {
private defaultWarmupTicks = 100; private defaultWarmupTicks = 100;
private warmupTicks = 100; private warmupTicks = 100;
private keyStates: { [name: string]: boolean } = {};
private selectedNode: Node;
constructor(props: propTypes) { constructor(props: propTypes) {
super(props); super(props);
this.loadGraph = this.loadGraph.bind(this); this.loadGraph = this.loadGraph.bind(this);
...@@ -32,10 +32,16 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -32,10 +32,16 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this); this.handleKeyUp = this.handleKeyUp.bind(this);
this.forceUpdate = this.forceUpdate.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 // Set as new state
this.state = { this.state = {
graph: undefined, graph: undefined,
visibleLabels: true,
selectedNode: undefined,
keys: {},
}; };
Interactions.initInteractions(); Interactions.initInteractions();
...@@ -72,9 +78,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -72,9 +78,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
return false; 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) => // .onNodeDragEnd((node: any, translate: any) =>
// Editor.globalState.onNodeDragEnd(node, translate) // Editor.globalState.onNodeDragEnd(node, translate)
// ) // )
...@@ -91,18 +94,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -91,18 +94,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
// this.extractPositions(event) // 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)); // .onLinkClick((link: any) => Editor.globalState.onLinkClick(link));
// Set as new state // Set as new state
...@@ -122,22 +113,62 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -122,22 +113,62 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
private handleKeyDown(event: KeyboardEvent) { private handleKeyDown(event: KeyboardEvent) {
const key: string = event.key; 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) { private handleKeyUp(event: KeyboardEvent) {
const key: string = event.key; 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. * Propagates the changed state of the graph.
*/ */
private onHistoryChange() { private onHistoryChange() {
this.selectedNode = undefined; this.setState({
selectedNode: undefined,
});
this.forceUpdate(); 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. * Calculates the corresponding coordinates for a click event for easier further processing.
* @param event The corresponding click event. * @param event The corresponding click event.
...@@ -157,14 +188,100 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -157,14 +188,100 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
} }
private handleNodeClick(node: Node) { private handleNodeClick(node: Node) {
if (this.keyStates["Control"]) { if (this.state.keys["Control"]) {
node.delete(); node.delete();
} else { } else {
this.selectedNode = node; this.setState({
selectedNode: node,
});
} }
this.forceUpdate(); 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() { private handleEngineStop() {
// Only do something on first stop for each graph // Only do something on first stop for each graph
if (this.warmupTicks <= 0) { if (this.warmupTicks <= 0) {
...@@ -192,7 +309,7 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -192,7 +309,7 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
/> />
<hr /> <hr />
<NodeDetails <NodeDetails
selectedNode={this.selectedNode} selectedNode={this.state.selectedNode}
allTypes={ allTypes={
this.state.graph ? this.state.graph.types : [] this.state.graph ? this.state.graph.types : []
} }
...@@ -207,6 +324,10 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> { ...@@ -207,6 +324,10 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
cooldownTicks={0} cooldownTicks={0}
warmupTicks={this.warmupTicks} warmupTicks={this.warmupTicks}
onEngineStop={this.handleEngineStop} onEngineStop={this.handleEngineStop}
nodeCanvasObject={this.handleNodeCanvasObject}
nodeCanvasObjectMode={"after"}
linkCanvasObject={this.handleLinkCanvasObject}
linkCanvasObjectMode={"replace"}
/> />
) : undefined} ) : undefined}
</div> </div>
......
import Tool from "./tools/tool"; // export const TOOLS = {
import UndoTool from "./tools/undotool"; // undo: new UndoTool("undo"),
import RedoTool from "./tools/redotool"; // redo: new RedoTool("redo"),
import SelectTool from "./tools/selecttool"; // select: new SelectTool("select"),
import DeleteTool from "./tools/deletetool"; // delete: new DeleteTool("delete"),
import AddNodeTool from "./tools/addnodetool"; // addnode: new AddNodeTool("addnode"),
import ConnectTool from "./tools/connecttool"; // connect: new ConnectTool("connect"),
import SettingsTool from "./tools/settingstool"; // settings: new SettingsTool("settings"),
import SaveTool from "./tools/savetool"; // save: new SaveTool("save"),
import Toolbar from "./toolbar"; // };
import { DRAG_THRESHOLD_2D } from "../../config"; // export class State extends Tool {
import { GraphElement } from "./structures/graph/graphelement"; // onNodeDragEnd(node: any, translate: any) {
import { Node } from "./structures/graph/node"; // // Handle as click event, if drag distance under a certain threshold
// const distanceDragged = Math.sqrt(
export const TOOLS = { // Math.pow(translate.x, 2) + Math.pow(translate.y, 2)
undo: new UndoTool("undo"), // );
redo: new RedoTool("redo"), // if (distanceDragged < DRAG_THRESHOLD_2D) {
select: new SelectTool("select"), // this.onNodeClick(node);
delete: new DeleteTool("delete"), // return;
addnode: new AddNodeTool("addnode"), // } else {
connect: new ConnectTool("connect"), // this.tool.onNodeDragEnd(node, translate);
settings: new SettingsTool("settings"), // }
save: new SaveTool("save"), // }
};
// onKeyDown(key: any) {
export const CONTEXT = { // const previous = this.keyStates[id];
node: "node", // if (previous !== true) {
link: "link", // this.tool.onKeyDown(key);
mixed: "mixed", // }
nothing: "nothing", // }
};
// onKeyUp(key: any) {
export class State extends Tool { // const previous = this.keyStates[id];
display: Toolbar; // if (previous !== false) {
tool: Tool; // this.tool.onKeyUp(key);
previousTool: Tool; // }
selectedItem: GraphElement; // }
selectedItems: Set<GraphElement>;
itemsContext: any; // nodePointerAreaPaint(node: any, color: any, ctx: any) {
labelVisible: boolean; // const toolValue = this.tool.nodePointerAreaPaint(node, color, ctx);
keyStates: any;
// if (toolValue !== undefined) {
constructor() { // return toolValue;
super("State"); // }
this.display = new Toolbar(TOOLS); // const imageSize = 12;
// ctx.fillStyle = color;
this.tool = undefined; // ctx.fillRect(
this.setTool(TOOLS.select); // node.x - imageSize / 2,
// node.y - imageSize / 2,
// Shared variables // imageSize,
this.selectedItem = undefined; // imageSize
this.selectedItems = new Set(); // ); // draw square as pointer trap
this.itemsContext = CONTEXT.nothing; // }
this.labelVisible = true;
// linkWidth(link: any) {
this.keyStates = {}; // const toolValue = this.tool.linkWidth(link);
}
// if (toolValue !== undefined) {
setTool(tool: Tool) { // return toolValue;
if (this.tool === tool) { // }
return;
} // return this.isLinkHighlighted(link) ? 2 : 1;
// }
if (this.tool !== undefined) {
this.tool.deactivateTool(tool); // linkDirectionalParticles() {
} // const toolValue = this.tool.linkDirectionalParticles();
this.previousTool = this.tool; // if (toolValue !== undefined) {
this.tool = tool; // return toolValue;
this.display.setSelectedTool(tool); // }
if (this.tool !== undefined) { // return 4;
this.tool.activateTool(); // }
}
} // linkDirectionalParticleWidth(link: any) {
// const toolValue = this.tool.linkDirectionalParticleWidth(link);
setSelectedItem(item: GraphElement) {
this.selectedItem = item; // if (toolValue !== undefined) {
} // return toolValue;
// }
addSelectedItem(item: GraphElement) {
this.selectedItems.add(item); // return this.isLinkHighlighted(link) ? 4 : 0;
} // }
addSelectedItems(items: GraphElement) { // onBackgroundClick(event: any, positions: any) {
Object.values(items).forEach((item) => { // this.tool.onBackgroundClick(event, positions);
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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment