diff --git a/src/common/graph/graph.ts b/src/common/graph/graph.ts
index 855cfe14c89585ef6aa66a858aa256f0fcf67a96..80715515d0b428e1784b30565e4348ac9e9b5a73 100644
--- a/src/common/graph/graph.ts
+++ b/src/common/graph/graph.ts
@@ -4,9 +4,12 @@ import { Link, LinkData, SimLinkData } from "./link";
 import { NodeType, NodeTypeData } from "./nodetype";
 import { SerializableItem } from "../serializableitem";
 
-export interface Coordinate {
+export interface Coordinate2D {
     x: number;
     y: number;
+}
+
+export interface Coordinate3D extends Coordinate2D {
     z: number;
 }
 
diff --git a/src/display/renderer.tsx b/src/display/renderer.tsx
index 2ad9ef2480ff478429bc8e5b878fd0e741d59a33..523b028183cf0f28d0de0486825b659612bb35aa 100644
--- a/src/display/renderer.tsx
+++ b/src/display/renderer.tsx
@@ -10,7 +10,7 @@ import React from "react";
 import PropTypes, { InferType } from "prop-types";
 import SpriteText from "three-spritetext";
 import { Object3D, Sprite } from "three";
-import { Graph, Coordinate } from "../common/graph/graph";
+import { Graph, Coordinate3D } from "../common/graph/graph";
 import { Node } from "../common/graph/node";
 import { Link } from "../common/graph/link";
 
@@ -287,7 +287,7 @@ export class GraphRenderer extends React.PureComponent<
         this.displayNodeSelection(node);
     }
 
-    onNodeDragEnd(node: VisualGraphNode, translate: Coordinate) {
+    onNodeDragEnd(node: VisualGraphNode, translate: Coordinate3D) {
         // NodeDrag is handled like NodeClick if distance is very short
         if (
             Math.hypot(translate.x, translate.y, translate.z) <
@@ -322,7 +322,7 @@ export class GraphRenderer extends React.PureComponent<
         );
     }
 
-    updateLinkPosition(line: Line2, start: Coordinate, end: Coordinate) {
+    updateLinkPosition(line: Line2, start: Coordinate3D, end: Coordinate3D) {
         if (!(line instanceof Line2)) {
             return false;
         }
@@ -373,11 +373,12 @@ export class GraphRenderer extends React.PureComponent<
                 onLinkHover={(link: VisualGraphLink) => this.onLinkHover(link)}
                 linkPositionUpdate={(
                     line: Line2,
-                    coords: { start: Coordinate; end: Coordinate }
+                    coords: { start: Coordinate3D; end: Coordinate3D }
                 ) => this.updateLinkPosition(line, coords.start, coords.end)}
-                onNodeDragEnd={(node: VisualGraphNode, translate: Coordinate) =>
-                    this.onNodeDragEnd(node, translate)
-                }
+                onNodeDragEnd={(
+                    node: VisualGraphNode,
+                    translate: Coordinate3D
+                ) => this.onNodeDragEnd(node, translate)}
             />
         );
     }
diff --git a/src/editor/components/historynavigator.tsx b/src/editor/components/historynavigator.tsx
index f9de09cf07ee1e07e48bf16312a67d1f962b6969..4fd76f9bbd775f1d3c9c83cd8fc87abd648777c1 100644
--- a/src/editor/components/historynavigator.tsx
+++ b/src/editor/components/historynavigator.tsx
@@ -37,6 +37,8 @@ export class HistoryNavigator extends React.Component<propTypes> {
      * Saves current data of history object.
      */
     handleSave() {
+        this.props.history.createCheckpoint("Saved graph.");
+
         saveGraphJson(
             this.props.spaceId,
             this.props.history.currentCheckpoint.data
@@ -46,6 +48,7 @@ export class HistoryNavigator extends React.Component<propTypes> {
         alert(
             "Saved! (Though not for sure, currently not checking for success of failure)"
         );
+        this.forceUpdate();
     }
 
     /**
diff --git a/src/editor/components/instructions.tsx b/src/editor/components/instructions.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1e604ee67c8e82b4e0178b03f7b4ade433cf856a
--- /dev/null
+++ b/src/editor/components/instructions.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+
+interface InstructionsProps {
+    connectOnDragEnabled: boolean;
+}
+
+function Instructions({ connectOnDragEnabled }: InstructionsProps) {
+    return (
+        <ul className="instructions">
+            <li>Click background to create node</li>
+            <li>
+                SHIFT+Click and drag on background to add nodes to selection
+            </li>
+            <li>CTRL+Click background to clear selection</li>
+            <li>Click node to select and edit</li>
+            <li>SHIFT+Click node to add or remove from selection</li>
+            <li>CTRL+Click another node to connect</li>
+            <li>Right-Click node to delete</li>
+            <li>Right-Click link to delete</li>
+            {connectOnDragEnabled ? (
+                <li>Drag node close to other node to connect</li>
+            ) : (
+                ""
+            )}
+            <li>DELETE to delete selected nodes</li>
+            <li>ESCAPE to clear selection</li>
+        </ul>
+    );
+}
+
+export default Instructions;
diff --git a/src/editor/components/nodedetails.tsx b/src/editor/components/nodedetails.tsx
index 37d5a96029b54cb101b84aef38979e1806c14d7f..9db2e920eeb2edc5f0fb6c96aa7345b178676e24 100644
--- a/src/editor/components/nodedetails.tsx
+++ b/src/editor/components/nodedetails.tsx
@@ -88,7 +88,6 @@ export class NodeDetails extends React.Component<propTypes> {
         this.props.onChange();
 
         // Save change, but debounce, so it doesn't trigger too quickly
-        this.props.onChange();
         // this.debounce(
         //     (property: string) => {
         //         // this.props.selectedNodes[0].graph.storeCurrentData( TODO: Reimplement
diff --git a/src/editor/components/selectlayer.tsx b/src/editor/components/selectlayer.tsx
index d638abceef0198b4745204a04d9c608c85d9cb4e..9b7872d5d1a5d87e9e43d43765272318a054b85a 100644
--- a/src/editor/components/selectlayer.tsx
+++ b/src/editor/components/selectlayer.tsx
@@ -6,7 +6,7 @@ import "./selectlayer.css";
 type propTypes = {
     children: any;
     allNodes: Node[];
-    isEnable: () => boolean;
+    isEnabled: boolean;
     screen2GraphCoords: (x: number, y: number) => any;
     onBoxSelect: (nodes: Node[]) => void;
 };
@@ -50,7 +50,7 @@ export class SelectLayer extends React.Component<propTypes> {
             return false;
         }
 
-        if (!this.props.isEnable()) {
+        if (!this.props.isEnabled) {
             this.initialSelectPoint = undefined;
             this.layerBox.current.className = "";
             return false;
@@ -80,7 +80,7 @@ export class SelectLayer extends React.Component<propTypes> {
     }
 
     boxSelectOnPointerDown(e: any) {
-        if (!this.props.isEnable()) {
+        if (!this.props.isEnabled) {
             return;
         }
 
diff --git a/src/editor/components/settings.tsx b/src/editor/components/settings.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8abfef101dde6aa177c9ed5efca62361f66befd1
--- /dev/null
+++ b/src/editor/components/settings.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+
+interface SettingsProps {
+    labelVisibility: boolean;
+    onLabelVisibilityChange: (state: boolean) => void;
+
+    connectOnDrag: boolean;
+    onConnectOnDragChange: (state: boolean) => void;
+}
+
+function Settings({
+    labelVisibility,
+    onLabelVisibilityChange,
+    connectOnDrag,
+    onConnectOnDragChange,
+}: SettingsProps) {
+    return (
+        <div className={"SettingsMenu"}>
+            <h3>Settings</h3>
+            <input
+                id="node-label-visibility"
+                type={"checkbox"}
+                checked={labelVisibility}
+                onChange={(event) => {
+                    onLabelVisibilityChange(event.target.checked);
+                }}
+            />
+            <label htmlFor="node-label-visibility">Node labels</label>
+            <br />
+            <input
+                id="connect-on-drag"
+                type={"checkbox"}
+                checked={connectOnDrag}
+                onChange={(event) => {
+                    onConnectOnDragChange(event.target.checked);
+                }}
+            />
+            <label htmlFor="connect-on-drag">Connect nodes when dragged</label>
+        </div>
+    );
+}
+
+export default Settings;
diff --git a/src/editor/editor.tsx b/src/editor/editor.tsx
index b50a9f516d68d37500b912e7d5994ff09214e2ff..be8e6e115ec3e4c2ce2b7f6f313436539ee5ea09 100644
--- a/src/editor/editor.tsx
+++ b/src/editor/editor.tsx
@@ -4,16 +4,17 @@ import { loadGraphJson } from "../common/datasets";
 import { NodeDetails } from "./components/nodedetails";
 import { SpaceSelect } from "./components/spaceselect";
 import "./editor.css";
-import { ForceGraph2D } from "react-force-graph";
+
 import { Node } from "../common/graph/node";
-import { HistoryNavigator } from "./components/historynavigator";
-import { GraphElement } from "../common/graph/graphelement";
-import { Link } from "../common/graph/link";
+
 import { NodeTypesEditor } from "./components/nodetypeseditor";
 import { SpaceManager } from "./components/spacemanager";
 import { SelectLayer } from "./components/selectlayer";
 import { GraphData } from "../common/graph/graph";
 import { NodeType } from "../common/graph/nodetype";
+import { GraphRenderer2D } from "./renderer";
+import Instructions from "./components/instructions";
+import Settings from "./components/settings";
 
 type propTypes = {
     spaceId: string;
@@ -35,9 +36,9 @@ type stateTypes = {
     connectOnDrag: boolean;
 
     /**
-     * Collection of all currently selected nodes. Can also be undefined or empty.
+     * Current width of graph object. Used to specifically adjust and correct the graph size.
      */
-    selectedNodes: Node[];
+    graphWidth: number;
 
     /**
      * True for each key, that is currently considered pressed. If key has not been pressed yet, it will not exist as dict-key.
@@ -45,40 +46,17 @@ type stateTypes = {
     keys: { [name: string]: boolean };
 
     /**
-     * Current width of graph object. Used to specifically adjust and correct the graph size.
+     * Collection of all currently selected nodes. Can also be undefined or empty.
      */
-    graphWidth: number;
-};
-
-/**
- * Coordinate structure used for the force-graph.
- */
-type graphCoordinates = {
-    x: number;
-    y: number;
-};
-/**
- * Easy to access format for translated positions of a click event.
- */
-type clickPosition = {
-    graph: graphCoordinates;
-    window: graphCoordinates;
+    selectedNodes: Node[];
 };
 
 /**
  * Knowledge space graph editor. Allows easy editing of the graph structure.
  */
 export class Editor extends React.PureComponent<propTypes, stateTypes> {
-    private maxDistanceToConnect = 15;
-    private defaultWarmupTicks = 100;
-    private warmupTicks = 100;
-    private renderer: React.RefObject<any>;
     private graphContainer: React.RefObject<HTMLDivElement>;
-
-    /**
-     * True, if the graph was the target of the most recent click event.
-     */
-    private graphInFocus = false;
+    private rendererRef: React.RefObject<GraphRenderer2D>;
 
     constructor(props: propTypes) {
         super(props);
@@ -86,24 +64,18 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
         // Making sure, all functions retain the proper this-bind
         this.loadGraph = this.loadGraph.bind(this);
         this.loadSpace = this.loadSpace.bind(this);
-        this.extractPositions = this.extractPositions.bind(this);
-        this.handleNodeClick = this.handleNodeClick.bind(this);
-        this.onGraphDataChange = this.onGraphDataChange.bind(this);
-        this.handleEngineStop = this.handleEngineStop.bind(this);
-        this.handleKeyDown = this.handleKeyDown.bind(this);
-        this.handleKeyUp = this.handleKeyUp.bind(this);
         this.forceUpdate = this.forceUpdate.bind(this);
-        this.handleNodeCanvasObject = this.handleNodeCanvasObject.bind(this);
-        this.handleLinkCanvasObject = this.handleLinkCanvasObject.bind(this);
-        this.handleBackgroundClick = this.handleBackgroundClick.bind(this);
-        this.handleNodeDrag = this.handleNodeDrag.bind(this);
-        this.handleElementRightClick = this.handleElementRightClick.bind(this);
-        this.selectNode = this.selectNode.bind(this);
-        this.handleResize = this.handleResize.bind(this);
-        this.handleBoxSelect = this.handleBoxSelect.bind(this);
         this.handleNodeTypeSelect = this.handleNodeTypeSelect.bind(this);
+        this.handleBoxSelect = this.handleBoxSelect.bind(this);
+        this.selectNodes = this.selectNodes.bind(this);
+
+        document.addEventListener("keydown", (e) => {
+            this.keyPressed(e.key);
+        });
+        document.addEventListener("keyup", (e) => {
+            this.keyReleased(e.key);
+        });
 
-        this.renderer = React.createRef();
         this.graphContainer = React.createRef();
 
         // Set as new state
@@ -111,12 +83,24 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
             graph: undefined,
             visibleLabels: true,
             connectOnDrag: false,
-            selectedNodes: [], // TODO: Why was undefined allowed here?
-            keys: {},
             graphWidth: 1000,
+            selectedNodes: [],
+            keys: {},
         };
     }
 
+    keyPressed(key: string) {
+        const keys = this.state.keys;
+        keys[key] = true;
+        this.setState({ keys: keys });
+    }
+
+    keyReleased(key: string) {
+        const keys = this.state.keys;
+        keys[key] = false;
+        this.setState({ keys: keys });
+    }
+
     /**
      * Tries to load initial graph after webpage finished loading.
      */
@@ -149,98 +133,22 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
         const graph = new DynamicGraph();
         graph.fromSerializedObject(data);
 
-        this.warmupTicks = this.defaultWarmupTicks; // Should only run for each newly initialized graph, but then never again
-
         // Set as new state
         console.log(graph);
         this.setState({
             graph: graph,
         });
 
-        graph.onChangeCallbacks.push(this.onGraphDataChange);
+        //graph.onChangeCallbacks.push(this.onGraphDataChange);
 
         // Subscribe to global events
-        document.onkeydown = this.handleKeyDown;
-        document.onkeyup = this.handleKeyUp;
-        document.onmousedown = this.handleMouseDown;
-        window.onresize = this.handleResize;
+        window.addEventListener("resize", () => this.handleResize());
 
         this.handleResize();
 
         return true;
     }
 
-    /**
-     * Processes page wide key down events. Stores corresponding key as pressed in state.
-     *
-     * Also triggers actions corresponding to shortcuts.
-     */
-    private handleKeyDown(event: KeyboardEvent) {
-        const key: string = event.key;
-
-        const keys = this.state.keys;
-        keys[key] = true;
-
-        this.setState({
-            keys: keys,
-        });
-
-        this.handleShortcutEvents(key);
-    }
-
-    /**
-     * Triggers actions that correspond with certain shortcuts.
-     *
-     * @param key Newly pressed key.
-     */
-    private handleShortcutEvents(key: string) {
-        if (key === "Escape") {
-            this.selectNode(undefined);
-        } else if (
-            key === "Delete" &&
-            this.graphInFocus // Only delete if 2d-graph is the focused element
-        ) {
-            this.deleteSelectedNodes();
-        }
-    }
-
-    /**
-     * Deletes all nodes currently selected. Handles store points accordingly of the number of deleted nodes.
-     */
-    private deleteSelectedNodes() {
-        const selectedNodes = this.state.selectedNodes;
-
-        if (selectedNodes.length == 1) {
-            selectedNodes[0].delete();
-            selectedNodes.pop();
-            this.selectNodes(selectedNodes);
-        } else {
-            selectedNodes.forEach((node: Node) => node.delete());
-            this.deselect();
-        }
-    }
-
-    /**
-     * Processes page wide mouse down events.
-     */
-    private handleMouseDown() {
-        this.graphInFocus = false;
-    }
-
-    /**
-     * Processes page wide key up events. Stores corresponding key as not-pressed in state.
-     */
-    private handleKeyUp(event: KeyboardEvent) {
-        const key: string = event.key;
-
-        const keys = this.state.keys;
-        keys[key] = false;
-
-        this.setState({
-            keys: keys,
-        });
-    }
-
     /**
      * Processes resize window event. Focusses on resizing the graph accordingly.
      */
@@ -251,96 +159,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
         });
     }
 
-    /**
-     * Handler for background click event on force graph. Adds new node by default.
-     * @param event Click event.
-     */
-    private handleBackgroundClick(event: MouseEvent, position: clickPosition) {
-        this.graphInFocus = true;
-
-        // Is there really no node there? Trying to prevent small error, where this event is triggered, even if there is a node.
-        const nearestNode = this.state.graph.getClosestNode(
-            position.graph.x,
-            position.graph.y
-        );
-        if (nearestNode !== undefined && nearestNode.distance < 4) {
-            this.handleNodeClick(nearestNode.node);
-            return;
-        }
-
-        // Just deselect if control key is pressed
-        if (this.state.keys["Control"]) {
-            this.selectNode(undefined);
-            return;
-        }
-
-        // Add new node
-        const node = this.state.graph.createNode(
-            undefined,
-            position.graph.x,
-            position.graph.y,
-            0,
-            0
-        );
-        this.forceUpdate(); // TODO: Remove?
-
-        // Select newly created node
-        if (this.state.keys["Shift"]) {
-            // Simply add to current selection of shift is pressed
-            this.toggleNodeSelection(node);
-        } else {
-            this.selectNode(node);
-        }
-    }
-
-    /**
-     * Propagates the changed state of the graph.
-     */
-    private onGraphDataChange() {
-        const nodes: Node[] = this.state.selectedNodes.map((node: Node) =>
-            this.state.graph.node(node.id)
-        );
-        this.selectNodes(nodes);
-        this.forceUpdate(); // TODO
-    }
-
-    /**
-     * Calculates the corresponding coordinates for a click event for easier further processing.
-     * @param event The corresponding click event.
-     * @returns Coordinates in graph and coordinates in browser window.
-     */
-    private extractPositions(event: any): clickPosition {
-        return {
-            graph: this.renderer.current.screen2GraphCoords(
-                event.layerX, // TODO: Replace layerx/layery non standard properties and fix typing
-                event.layerY
-            ),
-            window: { x: event.clientX, y: event.clientY },
-        };
-    }
-
-    private deselect() {
-        this.setState({ selectedNodes: [] });
-    }
-
-    /**
-     * Selects a single node, or clears selection if given undefined.
-     * @param node Single node to select, or undefined.
-     */
-    private selectNode(node: Node) {
-        this.selectNodes([node]);
-    }
-
-    /**
-     * Selects multiple nodes, or clears selection if given undefined or empty array.
-     * @param nodes Multiple nodes to mark as selected.
-     */
-    private selectNodes(nodes: Node[]) {
-        this.setState({
-            selectedNodes: nodes,
-        });
-    }
-
     // /**
     //  * Makes sure to always offer a valid format of the selected nodes. Is either undefined or contains at least one valid node. An empty array is never returned.
     //  */
@@ -362,175 +180,22 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
     //     return undefined;
     // }
 
-    private handleNodeClick(node: Node) {
-        this.graphInFocus = true;
-
-        if (this.state.keys["Control"]) {
-            // Connect to clicked node as parent while control is pressed
-            if (this.state.selectedNodes.length == 0) {
-                // Have no node connected, so select
-                this.selectNode(node);
-            } else if (!this.state.selectedNodes.includes(node)) {
-                // Already have *other* node/s selected, so connect
-                this.connectSelectionToNode(node);
-            }
-        } else if (this.state.keys["Shift"]) {
-            this.toggleNodeSelection(node);
-        } else {
-            // By default, simply select node
-            this.selectNode(node);
-        }
-        this.forceUpdate(); // TODO: Remove?
-    }
-
-    private connectSelectionToNode(node: Node) {
-        if (this.state.selectedNodes.length == 0) {
+    handleBoxSelect(selectedNodes: Node[]) {
+        if (selectedNodes !== undefined && selectedNodes.length <= 0) {
             return;
         }
 
-        if (this.state.selectedNodes.length == 1) {
-            node.connect(this.state.selectedNodes[0]);
-        } else {
-            this.state.selectedNodes.forEach((selectedNode: Node) =>
-                node.connect(selectedNode)
-            );
-        }
-    }
-
-    private toggleNodeSelection(node: Node) {
-        // Convert selection to array as basis
-        let selection = this.state.selectedNodes;
-
-        // Add/Remove node
-        if (selection.includes(node)) {
-            // Remove node from selection
-            selection = selection.filter((n: Node) => !n.equals(node));
-        } else {
-            // Add node to selection
-            selection.push(node);
-        }
-        this.selectNodes(selection);
-    }
-
-    private handleNodeCanvasObject(
-        node: Node,
-        ctx: CanvasRenderingContext2D,
-        globalScale: number
-    ) {
-        // TODO: Refactor
-
-        // add ring just for highlighted nodes
-        if (this.state.selectedNodes.includes(node)) {
-            // Outer circle
-            ctx.beginPath();
-            ctx.arc(node.x, node.y, 4 * 0.7, 0, 2 * Math.PI, false);
-            ctx.fillStyle = "white";
-            ctx.fill();
-
-            // Inner circle
-            ctx.beginPath();
-            ctx.arc(node.x, node.y, 4 * 0.3, 0, 2 * Math.PI, false);
-            ctx.fillStyle = node.type.color;
-            ctx.fill();
-        }
-
-        // Draw image
-        const imageSize = 12;
-        if (node.icon !== undefined) {
-            const img = new Image();
-            img.src = node.icon;
-
-            ctx.drawImage(
-                img,
-                node.x - imageSize / 2,
-                node.y - imageSize / 2,
-                imageSize,
-                imageSize
-            );
-        }
-
-        // Draw label
-        /**
-         * Nothing selected? => Draw all labels
-         * If this nodes is considered highlighted => Draw label
-         * If this node is a neighbor of a selected node => Draw label
-         */
-        // TODO: Reenable node label rendering
-        // const isNodeRelatedToSelection: boolean =
-        //     this.state.selectedNodes.length != 0 ||
-        //     this.isHighlighted(node) ||
-        //     this.selectedNodes.some((selectedNode: Node) =>
-        //         selectedNode.neighbors.includes(node)
-        //     );
-        //
-        // if (this.state.visibleLabels && isNodeRelatedToSelection) {
-        //     const label = node.name;
-        //     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 as any).x - bckgDimensions[0] / 2,
-        //         (node as any).y - bckgDimensions[1] / 2 + nodeHeightOffset,
-        //         ...bckgDimensions
-        //     );
-        //
-        //     ctx.textAlign = "center";
-        //     ctx.textBaseline = "middle";
-        //     ctx.fillStyle = "white";
-        //     ctx.fillText(
-        //         label,
-        //         (node as any).x,
-        //         (node as any).y + nodeHeightOffset
-        //     );
-        // }
-
-        // TODO: Render label as always visible
+        this.selectNodes(selectedNodes.concat(this.state.selectedNodes));
     }
 
-    private handleLinkCanvasObject(
-        link: Link,
-        ctx: CanvasRenderingContext2D,
-        globalScale: number
-    ) {
-        // Links already initialized?
-        if (link.source.x === undefined) {
-            return;
-        }
-
-        // 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);
-
-        let lineWidth = 0.5;
-        if (
-            this.state.selectedNodes.some((node: Node) =>
-                node.links.find(link.equals)
-            )
-        ) {
-            lineWidth = 2;
-        }
-        lineWidth /= globalScale; // Scale with zoom
-
-        ctx.beginPath();
-        ctx.moveTo(link.source.x, link.source.y);
-        ctx.lineTo(link.target.x, link.target.y);
-        ctx.strokeStyle = gradient;
-        ctx.lineWidth = lineWidth;
-        ctx.stroke();
+    /**
+     * Selects multiple nodes, or clears selection if given undefined or empty array.
+     * @param nodes Multiple nodes to mark as selected.
+     */
+    public selectNodes(nodes: Node[]) {
+        this.setState({
+            selectedNodes: nodes,
+        });
     }
 
     private handleNodeTypeSelect(type: NodeType) {
@@ -540,67 +205,6 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
         this.selectNodes(nodesWithType);
     }
 
-    private handleNodeDrag(node: Node) {
-        this.graphInFocus = true;
-
-        if (
-            !this.state.selectedNodes ||
-            !this.state.selectedNodes.includes(node)
-        ) {
-            this.selectNode(node);
-        }
-
-        // Should run connect logic?
-        if (!this.state.connectOnDrag) {
-            return;
-        }
-
-        const closest = this.state.graph.getClosestNode(node.x, node.y, node);
-
-        // Is close enough for new link?
-        if (closest.distance > this.maxDistanceToConnect) {
-            return;
-        }
-
-        // Does link already exist?
-        if (node.neighbors.includes(closest.node)) {
-            return;
-        }
-
-        // Add link
-        node.connect(closest.node);
-        this.forceUpdate();
-    }
-
-    /**
-     * Processes right-click event on graph elements by deleting them.
-     */
-    private handleElementRightClick(element: GraphElement<unknown, unknown>) {
-        this.graphInFocus = true;
-
-        element.delete();
-        this.forceUpdate(); // TODO: Necessary?
-    }
-
-    private handleEngineStop() {
-        // Only do something on first stop for each graph
-        if (this.warmupTicks <= 0) {
-            return;
-        }
-
-        this.warmupTicks = 0; // Only warm up once, so stop warming up after the first freeze
-
-        this.forceUpdate();
-    }
-
-    private handleBoxSelect(selectedNodes: Node[]) {
-        if (selectedNodes !== undefined && selectedNodes.length <= 0) {
-            return;
-        }
-
-        this.selectNodes(selectedNodes.concat(this.state.selectedNodes));
-    }
-
     render(): React.ReactNode {
         return (
             <div id="ks-editor">
@@ -608,62 +212,43 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
                 <SpaceSelect onLoadSpace={this.loadSpace} />
                 <SpaceManager />
                 <div id="content">
-                    <div id="force-graph-renderer" ref={this.graphContainer}>
-                        <SelectLayer
-                            allNodes={
-                                this.state.graph ? this.state.graph.nodes : []
-                            }
-                            screen2GraphCoords={
-                                this.renderer.current
-                                    ? this.renderer.current.screen2GraphCoords
-                                    : undefined
-                            }
-                            isEnable={() => this.state.keys["Shift"]}
-                            onBoxSelect={this.handleBoxSelect}
+                    {this.state.graph && (
+                        <div
+                            id="force-graph-renderer"
+                            ref={this.graphContainer}
                         >
-                            {this.state.graph ? (
-                                <ForceGraph2D
-                                    ref={this.renderer}
+                            <SelectLayer
+                                allNodes={
+                                    this.state.graph
+                                        ? this.state.graph.nodes
+                                        : []
+                                }
+                                screen2GraphCoords={
+                                    this.rendererRef
+                                        ? this.rendererRef.current
+                                              .screen2GraphCoords
+                                        : undefined
+                                }
+                                isEnabled={this.state.keys["Shift"]}
+                                onBoxSelect={this.handleBoxSelect}
+                            >
+                                <GraphRenderer2D
+                                    ref={this.rendererRef}
+                                    graph={this.state.graph}
                                     width={this.state.graphWidth}
-                                    graphData={this.state.graph}
-                                    onNodeClick={this.handleNodeClick}
-                                    autoPauseRedraw={false}
-                                    cooldownTicks={0}
-                                    warmupTicks={this.warmupTicks}
-                                    onEngineStop={this.handleEngineStop}
-                                    nodeCanvasObject={
-                                        this.handleNodeCanvasObject
-                                    }
-                                    nodeCanvasObjectMode={() => "after"}
-                                    linkCanvasObject={
-                                        this.handleLinkCanvasObject
-                                    }
-                                    linkCanvasObjectMode={() => "replace"}
-                                    nodeColor={(node: Node) => node.type.color}
-                                    onNodeDrag={this.handleNodeDrag}
-                                    onLinkRightClick={
-                                        this.handleElementRightClick
-                                    }
-                                    onNodeRightClick={
-                                        this.handleElementRightClick
-                                    }
-                                    onBackgroundClick={(event: any) =>
-                                        this.handleBackgroundClick(
-                                            event,
-                                            this.extractPositions(event)
-                                        )
-                                    }
+                                    onNodeSelectionChanged={this.selectNodes}
+                                    selectedNodes={this.state.selectedNodes}
                                 />
-                            ) : undefined}
-                        </SelectLayer>
-                    </div>
+                            </SelectLayer>
+                        </div>
+                    )}
                     {this.state.graph && (
                         <div id="sidepanel">
-                            <HistoryNavigator
-                                spaceId="space"
-                                history={this.state.graph.history}
-                                onChange={this.onGraphDataChange}
-                            />
+                            {/*<HistoryNavigator*/}
+                            {/*    spaceId="space"*/}
+                            {/*    history={this.state.graph.history}*/}
+                            {/*    onChange={this.onGraphDataChange}*/}
+                            {/*/>*/}
                             <hr />
                             <NodeDetails
                                 selectedNodes={this.state.selectedNodes}
@@ -682,72 +267,23 @@ export class Editor extends React.PureComponent<propTypes, stateTypes> {
                                 onSelectAll={this.handleNodeTypeSelect}
                             />
                             <hr />
-                            <h3>Settings</h3>
-                            <input
-                                id="node-labe-visibility"
-                                type={"checkbox"}
-                                checked={this.state.visibleLabels}
-                                onChange={(event) => {
-                                    const newValue = event.target.checked;
-                                    if (newValue == this.state.visibleLabels) {
-                                        return;
-                                    }
-
+                            <Settings
+                                labelVisibility={this.state.visibleLabels}
+                                onLabelVisibilityChange={(visible) =>
+                                    this.setState({ visibleLabels: visible })
+                                }
+                                connectOnDrag={this.state.connectOnDrag}
+                                onConnectOnDragChange={(connectOnDrag) =>
                                     this.setState({
-                                        visibleLabels: newValue,
-                                    });
-                                }}
+                                        connectOnDrag: connectOnDrag,
+                                    })
+                                }
                             />
-                            <label htmlFor="node-labe-visibility">
-                                Node labels
-                            </label>
-                            <br />
-                            <input
-                                id="connect-on-drag"
-                                type={"checkbox"}
-                                checked={this.state.connectOnDrag}
-                                onChange={(event) => {
-                                    const newValue = event.target.checked;
-                                    if (newValue == this.state.connectOnDrag) {
-                                        return;
-                                    }
+                            <hr />
 
-                                    this.setState({
-                                        connectOnDrag: newValue,
-                                    });
-                                }}
+                            <Instructions
+                                connectOnDragEnabled={this.state.connectOnDrag}
                             />
-                            <label htmlFor="connect-on-drag">
-                                Connect nodes when dragged
-                            </label>
-                            <hr />
-                            <ul className="instructions">
-                                <li>Click background to create node</li>
-                                <li>
-                                    SHIFT+Click and drag on background to add
-                                    nodes to selection
-                                </li>
-                                <li>
-                                    CTRL+Click background to clear selection
-                                </li>
-                                <li>Click node to select and edit</li>
-                                <li>
-                                    SHIFT+Click node to add or remove from
-                                    selection
-                                </li>
-                                <li>CTRL+Click another node to connect</li>
-                                <li>Right-Click node to delete</li>
-                                <li>Right-Click link to delete</li>
-                                {this.state.connectOnDrag ? (
-                                    <li>
-                                        Drag node close to other node to connect
-                                    </li>
-                                ) : (
-                                    ""
-                                )}
-                                <li>DELETE to delete selected nodes</li>
-                                <li>ESCAPE to clear selection</li>
-                            </ul>
                         </div>
                     )}
                 </div>
diff --git a/src/editor/renderer.tsx b/src/editor/renderer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..325b47605e085e444d61f5b13c5329e00501d2d9
--- /dev/null
+++ b/src/editor/renderer.tsx
@@ -0,0 +1,429 @@
+import React from "react";
+import PropTypes, { InferType } from "prop-types";
+import { DynamicGraph } from "./graph";
+import { Node } from "../common/graph/node";
+import { ForceGraph2D } from "react-force-graph";
+import { Link } from "../common/graph/link";
+import { GraphElement } from "../common/graph/graphelement";
+import { Coordinate2D } from "../common/graph/graph";
+
+export class GraphRenderer2D extends React.PureComponent<
+    InferType<typeof GraphRenderer2D.propTypes>,
+    InferType<typeof GraphRenderer2D.stateTypes>
+> {
+    private maxDistanceToConnect = 15;
+    private defaultWarmupTicks = 100;
+    private warmupTicks = 100;
+    private forceGraph: React.RefObject<any>; // using typeof ForceGraph3d produces an error here...
+
+    /**
+     * True, if the graph was the target of the most recent click event.
+     */
+    private graphInFocus = false; // TODO: Remove?
+    /**
+     * True for each key, that is currently considered pressed. If key has not been pressed yet, it will not exist as dict-key.
+     */
+    private keys: { [name: string]: boolean };
+
+    static propTypes = {
+        graph: PropTypes.instanceOf(DynamicGraph).isRequired,
+        width: PropTypes.number.isRequired,
+        onNodeClicked: PropTypes.func,
+        onNodeSelectionChanged: PropTypes.func,
+        /**
+         * Collection of all currently selected nodes. Can also be undefined or empty.
+         */
+        selectedNodes: PropTypes.arrayOf(PropTypes.instanceOf(Node)),
+    };
+
+    static stateTypes = {};
+
+    constructor(props: InferType<typeof GraphRenderer2D.propTypes>) {
+        super(props);
+
+        this.screen2GraphCoords = this.screen2GraphCoords.bind(this);
+        this.handleNodeCanvasObject = this.handleNodeCanvasObject.bind(this);
+        this.handleLinkCanvasObject = this.handleLinkCanvasObject.bind(this);
+
+        document.addEventListener("keydown", (e) => {
+            this.keys[e.key] = true;
+            this.handleShortcutEvents(e.key);
+        });
+        document.addEventListener("keyup", (e) => {
+            this.keys[e.key] = false;
+            this.handleShortcutEvents(e.key);
+        });
+        document.addEventListener(
+            "mousedown",
+            (e) => (this.graphInFocus = false)
+        );
+
+        this.state = {
+            selectedNodes: [], // TODO: Why was undefined allowed here?
+        };
+
+        this.forceGraph = React.createRef();
+    }
+
+    /**
+     * Deletes all nodes currently selected. Handles store points accordingly of the number of deleted nodes.
+     */
+    private deleteSelectedNodes() {
+        const selectedNodes = this.state.selectedNodes;
+
+        if (selectedNodes.length == 1) {
+            selectedNodes[0].delete();
+            selectedNodes.pop();
+            this.props.onNodeSelectionChanged(selectedNodes);
+        } else {
+            selectedNodes.forEach((node: Node) => node.delete());
+            this.props.onNodeSelectionChanged([]);
+        }
+    }
+
+    /**
+     * Triggers actions that correspond with certain shortcuts.
+     *
+     * @param key Newly pressed key.
+     */
+    private handleShortcutEvents(key: string) {
+        if (key === "Escape") {
+            this.props.onNodeSelectionChanged([]);
+        } else if (
+            key === "Delete" &&
+            this.graphInFocus // Only delete if 2d-graph is the focused element
+        ) {
+            this.deleteSelectedNodes();
+        }
+    }
+
+    private handleNodeClick(node: Node) {
+        this.graphInFocus = true;
+
+        if (this.state.keys["Control"]) {
+            // Connect to clicked node as parent while control is pressed
+            if (this.state.selectedNodes.length == 0) {
+                // Have no node connected, so select
+                this.props.onNodeSelectionChanged([node]);
+            } else if (!this.state.selectedNodes.includes(node)) {
+                // Already have *other* node/s selected, so connect
+                this.connectSelectionToNode(node);
+            }
+        } else if (this.state.keys["Shift"]) {
+            this.toggleNodeSelection(node);
+        } else {
+            // By default, simply select node
+            this.props.onNodeSelectionChanged([node]);
+        }
+        //this.forceUpdate(); // TODO: Remove?
+    }
+
+    /**
+     * Handler for background click event on force graph. Adds new node by default.
+     * @param event Click event.
+     */
+    private handleBackgroundClick(
+        event: MouseEvent,
+        position: { graph: Coordinate2D; window: Coordinate2D }
+    ) {
+        this.graphInFocus = true;
+
+        // Is there really no node there? Trying to prevent small error, where this event is triggered, even if there is a node.
+        const nearestNode = this.state.graph.getClosestNode(
+            position.graph.x,
+            position.graph.y
+        );
+        if (nearestNode !== undefined && nearestNode.distance < 4) {
+            this.handleNodeClick(nearestNode.node);
+            return;
+        }
+
+        // Just deselect if control key is pressed
+        if (this.state.keys["Control"]) {
+            this.props.onNodeSelectionChanged([]);
+            return;
+        }
+
+        // Add new node
+        const node = this.state.graph.createNode(
+            undefined,
+            position.graph.x,
+            position.graph.y,
+            0,
+            0
+        );
+        this.forceUpdate(); // TODO: Remove?
+
+        // Select newly created node
+        if (this.state.keys["Shift"]) {
+            // Simply add to current selection of shift is pressed
+            this.toggleNodeSelection(node);
+        } else {
+            this.props.onNodeSelectionChanged([node]);
+        }
+    }
+
+    /**
+     * Processes right-click event on graph elements by deleting them.
+     */
+    private handleElementRightClick(element: GraphElement<unknown, unknown>) {
+        this.graphInFocus = true;
+
+        element.delete();
+        this.forceUpdate(); // TODO: Necessary?
+    }
+
+    /**
+     * Propagates the changed state of the graph.
+     */
+    private onGraphDataChange() {
+        const nodes: Node[] = this.state.selectedNodes.map((node: Node) =>
+            this.state.graph.node(node.id)
+        );
+        this.props.onNodeSelectionChanged(nodes);
+        this.forceUpdate(); // TODO
+    }
+
+    private connectSelectionToNode(node: Node) {
+        if (this.state.selectedNodes.length == 0) {
+            return;
+        }
+
+        if (this.state.selectedNodes.length == 1) {
+            node.connect(this.state.selectedNodes[0]);
+        } else {
+            this.state.selectedNodes.forEach((selectedNode: Node) =>
+                node.connect(selectedNode)
+            );
+        }
+    }
+
+    private toggleNodeSelection(node: Node) {
+        // Convert selection to array as basis
+        let selection = this.state.selectedNodes;
+
+        // Add/Remove node
+        if (selection.includes(node)) {
+            // Remove node from selection
+            selection = selection.filter((n: Node) => !n.equals(node));
+        } else {
+            // Add node to selection
+            selection.push(node);
+        }
+        this.props.onNodeSelectionChanged(selection);
+    }
+
+    private handleEngineStop() {
+        // Only do something on first stop for each graph
+        if (this.warmupTicks <= 0) {
+            return;
+        }
+
+        this.warmupTicks = 0; // Only warm up once, so stop warming up after the first freeze
+    }
+
+    private handleNodeDrag(node: Node) {
+        this.graphInFocus = true;
+
+        if (
+            !this.state.selectedNodes ||
+            !this.state.selectedNodes.includes(node)
+        ) {
+            this.props.onNodeSelectionChanged([node]);
+        }
+
+        // Should run connect logic?
+        if (!this.state.connectOnDrag) {
+            return;
+        }
+
+        const closest = this.state.graph.getClosestNode(node.x, node.y, node);
+
+        // Is close enough for new link?
+        if (closest.distance > this.maxDistanceToConnect) {
+            return;
+        }
+
+        // Does link already exist?
+        if (node.neighbors.includes(closest.node)) {
+            return;
+        }
+
+        // Add link
+        node.connect(closest.node);
+        // this.forceUpdate(); TODO: Remove?
+    }
+
+    private handleNodeCanvasObject(
+        node: Node,
+        ctx: CanvasRenderingContext2D,
+        globalScale: number
+    ) {
+        // TODO: Refactor
+
+        // add ring just for highlighted nodes
+        if (this.state.selectedNodes.includes(node)) {
+            // Outer circle
+            ctx.beginPath();
+            ctx.arc(node.x, node.y, 4 * 0.7, 0, 2 * Math.PI, false);
+            ctx.fillStyle = "white";
+            ctx.fill();
+
+            // Inner circle
+            ctx.beginPath();
+            ctx.arc(node.x, node.y, 4 * 0.3, 0, 2 * Math.PI, false);
+            ctx.fillStyle = node.type.color;
+            ctx.fill();
+        }
+
+        // Draw image
+        const imageSize = 12;
+        if (node.icon !== undefined) {
+            const img = new Image();
+            img.src = node.icon;
+
+            ctx.drawImage(
+                img,
+                node.x - imageSize / 2,
+                node.y - imageSize / 2,
+                imageSize,
+                imageSize
+            );
+        }
+
+        // Draw label
+        /**
+         * Nothing selected? => Draw all labels
+         * If this nodes is considered highlighted => Draw label
+         * If this node is a neighbor of a selected node => Draw label
+         */
+        // TODO: Reenable node label rendering
+        // const isNodeRelatedToSelection: boolean =
+        //     this.state.selectedNodes.length != 0 ||
+        //     this.isHighlighted(node) ||
+        //     this.selectedNodes.some((selectedNode: Node) =>
+        //         selectedNode.neighbors.includes(node)
+        //     );
+        //
+        // if (this.state.visibleLabels && isNodeRelatedToSelection) {
+        //     const label = node.name;
+        //     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 as any).x - bckgDimensions[0] / 2,
+        //         (node as any).y - bckgDimensions[1] / 2 + nodeHeightOffset,
+        //         ...bckgDimensions
+        //     );
+        //
+        //     ctx.textAlign = "center";
+        //     ctx.textBaseline = "middle";
+        //     ctx.fillStyle = "white";
+        //     ctx.fillText(
+        //         label,
+        //         (node as any).x,
+        //         (node as any).y + nodeHeightOffset
+        //     );
+        // }
+
+        // TODO: Render label as always visible
+    }
+
+    private handleLinkCanvasObject(
+        link: Link,
+        ctx: CanvasRenderingContext2D,
+        globalScale: number
+    ) {
+        // Links already initialized?
+        if (link.source.x === undefined) {
+            return;
+        }
+
+        // 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);
+
+        let lineWidth = 0.5;
+        if (
+            this.props.selectedNodes.some((node: Node) =>
+                node.links.find(link.equals)
+            )
+        ) {
+            lineWidth = 2;
+        }
+        lineWidth /= globalScale; // Scale with zoom
+
+        ctx.beginPath();
+        ctx.moveTo(link.source.x, link.source.y);
+        ctx.lineTo(link.target.x, link.target.y);
+        ctx.strokeStyle = gradient;
+        ctx.lineWidth = lineWidth;
+        ctx.stroke();
+    }
+
+    public screen2GraphCoords(x: number, y: number): Coordinate2D {
+        return this.forceGraph.current.screen2GraphCoords(x, y);
+    }
+
+    /**
+     * Calculates the corresponding coordinates for a click event for easier further processing.
+     * @param event The corresponding click event.
+     * @returns Coordinates in graph and coordinates in browser window.
+     */
+    private extractPositions(event: any): {
+        graph: Coordinate2D;
+        window: Coordinate2D;
+    } {
+        return {
+            graph: this.screen2GraphCoords(
+                event.layerX, // TODO: Replace layerx/layery non standard properties and fix typing
+                event.layerY
+            ),
+            window: { x: event.clientX, y: event.clientY },
+        };
+    }
+
+    render() {
+        this.warmupTicks = this.defaultWarmupTicks;
+        return (
+            <ForceGraph2D
+                ref={this.forceGraph}
+                width={this.props.width}
+                graphData={this.props.graph}
+                onNodeClick={this.handleNodeClick}
+                autoPauseRedraw={false}
+                cooldownTicks={0}
+                warmupTicks={this.warmupTicks}
+                onEngineStop={this.handleEngineStop}
+                nodeCanvasObject={this.handleNodeCanvasObject}
+                nodeCanvasObjectMode={() => "after"}
+                linkCanvasObject={this.handleLinkCanvasObject}
+                linkCanvasObjectMode={() => "replace"}
+                nodeColor={(node: Node) => node.type.color}
+                onNodeDrag={this.handleNodeDrag}
+                onLinkRightClick={this.handleElementRightClick}
+                onNodeRightClick={this.handleElementRightClick}
+                onBackgroundClick={(event: any) =>
+                    this.handleBackgroundClick(
+                        event,
+                        this.extractPositions(event)
+                    )
+                }
+            />
+        );
+    }
+}