diff --git a/display/graph.js b/display/graph.js
index a906eddd46905ed612ff1758d3a7be2418070782..e54a14fe2a058a6bba0f85a87414caf5e048d6d1 100644
--- a/display/graph.js
+++ b/display/graph.js
@@ -29,6 +29,7 @@ class Graph {
         this.highlightLinks = new Set();
         this.hoverNode = null;
         this.edgeColors = {};
+        this.nodeColors = {};
         this.idToNode = {};
 
         this.firstTick = true;
@@ -49,6 +50,7 @@ class Graph {
         this.gData = await loadGraphJson(spaceId);
         this.graph = ForceGraph3D({
             extraRenderers: [new CSS2DRenderer(), new CSS3DRenderer()],
+            rendererConfig: { antialias: true },
         })(document.getElementById("3d-graph"))
             .graphData(this.gData)
             .nodeLabel("id")
@@ -64,7 +66,10 @@ class Graph {
                 this.updateHighlight();
             })
             .onLinkHover((link) => this.onLinkHover(link))
-            .linkColor((link) => this.getLinkColor(link))
+            //.linkColor((link) => this.getLinkColor(link))
+            .linkPositionUpdate((line, { start, end }) =>
+                this.updateLinkPosition(line, start, end)
+            )
             .linkOpacity(0.8)
             .nodeThreeObjectExtend(false)
             .nodeThreeObject((node) => this.drawNode(node))
@@ -79,11 +84,17 @@ class Graph {
      */
     initializeModel() {
         if (this.firstTick) {
-            this.mapEdgeColors();
+            // Initialize data structures
+            this.mapLinkColors();
+            this.mapNodeColors();
             this.updateNodeData();
             this.updateNodeMap();
             this.addBackground();
 
+            // Can only be called after link colors have been mapped.
+            this.graph.linkThreeObject((link) => this.drawLink(link));
+
+            // Add overlay components
             loadComponents();
 
             // Catch resize events
@@ -131,7 +142,15 @@ class Graph {
         this.graph
             .graphData()
             .links.forEach((link) => linkClasses.push(link.type));
-        return [...new Set(linkClasses)];
+        return [...new Set(linkClasses)].map((c) => String(c));
+    }
+
+    getNodeClasses() {
+        const nodeClasses = [];
+        this.graph
+            .graphData()
+            .nodes.forEach((node) => nodeClasses.push(node.type));
+        return [...new Set(nodeClasses)];
     }
 
     onNodeHover(node) {
@@ -308,7 +327,7 @@ class Graph {
     /**
      * Maps the colors of the color palette to the different edge types
      */
-    mapEdgeColors() {
+    mapLinkColors() {
         const linkClasses = this.getLinkClasses();
         for (let i = 0; i < linkClasses.length; i++) {
             this.edgeColors[linkClasses[i]] =
@@ -316,6 +335,65 @@ class Graph {
         }
     }
 
+    mapNodeColors() {
+        const nodeClasses = this.getNodeClasses();
+        this.nodeColors["undefined"] = Config.COLOR_PALETTE[0]; // Default color
+        for (let i = 0; i < nodeClasses.length; i++) {
+            this.nodeColors[nodeClasses[i]] =
+                Config.COLOR_PALETTE[i % Config.COLOR_PALETTE.length];
+        }
+    }
+
+    drawLink(link) {
+        const colors = new Float32Array(
+            [].concat(
+                ...[link.target, link.source]
+                    .map((node) => this.nodeColors[node.type])
+                    .map((color) => color.replace(/[^\d,]/g, "").split(",")) // Extract rgb() color components
+                    .map((rgb) => rgb.map((v) => v / 255))
+            )
+        );
+
+        const material = new THREE.LineBasicMaterial({
+            vertexColors: THREE.VertexColors,
+        });
+        const geometry = new THREE.BufferGeometry();
+        geometry.setAttribute(
+            "position",
+            new THREE.BufferAttribute(new Float32Array(2 * 3), 3)
+        );
+        geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
+
+        return new THREE.Line(geometry, material);
+    }
+
+    updateLinkPosition(line, start, end) {
+        const startR = 4;
+        const endR = 4;
+        // const startR = Graph.nodeRelSize();
+        // const endR = Graph.nodeRelSize();
+        const lineLen = Math.sqrt(
+            ["x", "y", "z"]
+                .map((dim) => Math.pow((end[dim] || 0) - (start[dim] || 0), 2))
+                .reduce((acc, v) => acc + v, 0)
+        );
+
+        const linePos = line.geometry.getAttribute("position");
+
+        // calculate coordinate on the node's surface instead of center
+        linePos.set(
+            [startR / lineLen, 1 - endR / lineLen]
+                .map((t) =>
+                    ["x", "y", "z"].map(
+                        (dim) => start[dim] + (end[dim] - start[dim]) * t
+                    )
+                )
+                .flat()
+        );
+        linePos.needsUpdate = true;
+        return true;
+    }
+
     drawNode(node) {
         // Draw node as label + image
         const nodeDiv = document.createElement("div");
@@ -399,9 +477,8 @@ var infooverlay;
 
 // Only execute, if corresponding dom is present
 if (document.getElementById("3d-graph") !== null) {
-    G = new Graph(space_id);    // space_id defined globaly through knowledge-space.php
+    G = new Graph(space_id); // space_id defined globaly through knowledge-space.php
     linkoverlay = new LinkSelectionOverlay(G);
     infooverlay = new NodeInfoOverlay(G);
     G.infooverlay = infooverlay;
 }
-