diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx
index 8a7ce6f0247273932ba90cde7e8940490b7d528f..20ce1321d2d9e049ff6d4253ebbc7eeb73bd6894 100644
--- a/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx
+++ b/libs/shared/lib/vis/visualizations/nodelinkvis/components/NLPixi.tsx
@@ -1,6 +1,6 @@
 import { GraphType, GraphTypeD3, LinkType, LinkTypeD3, NodeType, NodeTypeD3 } from '../types';
 import { dataColors, visualizationColors } from 'config';
-import { ReactEventHandler, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
+import { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
 import {
   Application,
   AssetsBundle,
@@ -22,6 +22,7 @@ import { MultiGraph } from 'graphology';
 import { Viewport } from 'pixi-viewport';
 import { NodelinkVisProps } from '../nodelinkvis';
 import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip';
+import { MovedEvent } from 'pixi-viewport/dist/types';
 
 
 type Props = {
@@ -70,8 +71,6 @@ export const NLPixi = (props: Props) => {
   const mouseInCanvas = useRef<boolean>(false);
   const isSetup = useRef(false);
   const ml = useML();
-  const dragging = useRef<{ node: NodeTypeD3; gfx: Sprite } | null>(null);
-  const onlyClicked = useRef(false);
   const searchResults = useSearchResultData();
   const graph = useRef<GraphTypeD3>({ nodes: [], links: [] });
 
@@ -124,71 +123,58 @@ export const NLPixi = (props: Props) => {
 
   const imperative = useRef<any>(null);
 
+  const mouseClickThreshold = 200;  // Time between mouse up and down events that is considered a click, and not a drag.
+
   useImperativeHandle(imperative, () => ({
-    onDragStart(node: NodeTypeD3, gfx: Sprite) {
-      dragging.current = { node, gfx };
-      onlyClicked.current = true;
-
-      // todo: graphology does not support fixed nodes
-      // todo: after vis-settings panel is there, we should to also support the original d3 force to allow interactivity if needed
-      if (props.layoutAlgorithm === Layouts.FORCEATLAS2WEBWORKER) return;
-      if (viewport.current) viewport.current.pause = true;
+    onMouseDown(event: FederatedPointerEvent) {
+      (event as any).mouseDownTimeStamp = event.timeStamp;
     },
+    
+    onMouseUpNode(event: FederatedPointerEvent) {
+      // If its a short click (not a drag) on the stage but not on a node: clear the selection and remove all popups.
+      const holdDownTime = event.timeStamp - (event as any).mouseDownTimeStamp;
+      if(holdDownTime > mouseClickThreshold) {
+        return;
+      }
 
-    onDragMove(movementX: number, movementY: number) {
-      if (props.layoutAlgorithm === Layouts.FORCEATLAS2WEBWORKER) return;
+      const sprite = event.target as Sprite;
+      const node = (sprite as any).node as NodeType;
 
-      for (const idx in popups) {
-        const p = popups[idx];
-        p.pos.x += movementX / (viewport.current?.scaled || 1);
-        p.pos.y += movementY / (viewport.current?.scaled || 1);
-        popups[idx] = p;
-        setPopups([...popups]);
-      }
+      
 
-      if (dragging.current) {
-        onlyClicked.current = false;
-        if (quickPopup) setQuickPopup(undefined);
-        const idx = popups.findIndex((p) => p.node._id === dragging.current?.node._id);
-        if (idx >= 0) {
-          
-          const p = popups[idx];
-          p.pos.x += movementX / (viewport.current?.scaled || 1);
-          p.pos.y += movementY / (viewport.current?.scaled || 1);
-          popups[idx] = p;
-          setPopups([...popups]);
+      if (event.shiftKey) {
+        setPopups([...popups, { node: node, pos: toGlobal(node) }]);
+      } else {
+        setPopups([{ node: node, pos: toGlobal(node) }]);
+        for (const popup of popups) {
+          const sprite = nodeMap.current.get(popup.node._id) as Sprite;
+          sprite.texture = Assets.get(textureId(false));
         }
-
-        if (!dragging.current.node.fx) dragging.current.node.fx = dragging.current.node.x || 0;
-        if (!dragging.current.node.fy) dragging.current.node.fy = dragging.current.node.y || 0;
-        dragging.current.node.fx += movementX / (viewport.current?.scaled || 1);
-        dragging.current.node.fy += movementY / (viewport.current?.scaled || 1);
-        // force.simulation.alpha(0.1).restart();
       }
+
+      sprite.texture = Assets.get(textureId(true));
+
+      props.onClick({ node: node, pos: toGlobal(node) });
+
+      event.stopPropagation();
     },
 
-    onDragEnd() {
-      if (dragging.current) {
-        // dragging.current.node.fx = null;
-        // dragging.current.node.fy = null;
-        if (viewport.current) viewport.current.pause = false;
-        if (onlyClicked.current) {
-          onlyClicked.current = false;
-
-          if (popups.filter((d) => d.node._id === dragging.current?.node._id).length > 0) {
-            setPopups(popups.filter((p) => p.node._id !== dragging.current?.node._id));
-            props.onClick();
-          } else {
-            setPopups([...popups, { node: props.graph.nodes[dragging.current.node._id], pos: toGlobal(dragging.current.node) }]);
-            props.onClick({ node: dragging.current.node, pos: toGlobal(dragging.current.node) });
-          }
+    onMouseUpStage(event: FederatedPointerEvent) {
+      // If its a short click (not a drag) on the stage but not on a node: clear the selection and remove all popups.
+      const holdDownTime = event.timeStamp - (event as any).mouseDownTimeStamp;
+      if(holdDownTime < mouseClickThreshold) {
+        for (const popup of popups) {
+          const sprite = nodeMap.current.get(popup.node._id) as Sprite;
+          sprite.texture = Assets.get(textureId(false));
         }
-        this.onHover(dragging.current.node);
-        dragging.current = null;
-      } else {
+        setPopups([]);
+        props.onClick();
       }
     },
-    onHover(node: NodeTypeD3) {
+
+    onHover(event: FederatedPointerEvent) {
+      const sprite = event.target as Sprite;
+      const node = (sprite as any).node as NodeType;
       if (
         mouseInCanvas.current &&
         viewport?.current &&
@@ -202,10 +188,14 @@ export const NLPixi = (props: Props) => {
     onUnHover() {
       setQuickPopup(undefined);
     },
-    onPan() {
-      setPopups([]);
-      props.onClick();
-    },
+    onMoved(event: MovedEvent) {
+      for (const popup of popups) {
+        if (popup.node.x == null || popup.node.y == null) continue;
+        popup.pos.x = event.viewport.transform.position.x + (popup.node.x * event.viewport.scale.x);
+        popup.pos.y = event.viewport.transform.position.y + (popup.node.y * event.viewport.scale.y);
+      }
+      setPopups([...popups]);
+    }
   }));
 
   function resize() {
@@ -246,22 +236,7 @@ export const NLPixi = (props: Props) => {
     } else return { x: 0, y: 0 };
   }
 
-  function onDragStart(event: FederatedPointerEvent, node: NodeTypeD3, gfx: Sprite) {
-    event.stopPropagation();
-    if (imperative.current) imperative.current.onDragStart(node, gfx);
-  }
-
-  function onDragMove(event: FederatedPointerEvent) {
-    event.stopPropagation();
-    if (imperative.current) imperative.current.onDragMove(event.movementX, event.movementY);
-  }
-
-  function onDragEnd(event: FederatedPointerEvent) {
-    event.stopPropagation();
-    if (imperative.current) imperative.current.onDragEnd();
-  }
-
-  const updateNode = (node: NodeTypeD3) => {
+  const updateNode = (node: NodeType) => {
     const gfx = nodeMap.current.get(node._id);
     if (!gfx) return;
 
@@ -279,20 +254,6 @@ export const NLPixi = (props: Props) => {
 
     gfx.position.set(node.x, node.y);
 
-    gfx.off('mouseover');
-    gfx.off('mousedown');
-    gfx.on('mouseover', (e) => {
-      e.stopPropagation();
-      e.preventDefault();
-      if (imperative.current) imperative.current.onHover(node);
-    });
-    gfx.on('mouseout', (e) => {
-      e.stopPropagation();
-      e.preventDefault();
-      if (imperative.current) imperative.current.onUnHover();
-    });
-    gfx.on('mousedown', (e) => onDragStart(e, node, gfx));
-
     // if (!item.position) {
     //   item.position = new Point(node.x, node.y);
     // } else {
@@ -319,23 +280,28 @@ export const NLPixi = (props: Props) => {
     // Do not draw node if it has no position
     if (node.x === undefined || node.y === undefined) return;
 
-    let gfx: Sprite;
+    let sprite: Sprite;
     const texture = Assets.get(textureId());
-    gfx = new Sprite(texture);
+    sprite = new Sprite(texture);
 
-    gfx.tint = nodeColor(nodeMeta.type);
+    sprite.tint = nodeColor(nodeMeta.type);
     const scale = (Math.max(nodeMeta.radius || 5, 5) / 70) * 2;
-    gfx.scale.set(scale, scale);
-    gfx.anchor.set(0.5, 0.5);
-
-    nodeMap.current.set(node._id, gfx);
-    nodeLayer.addChild(gfx);
+    sprite.scale.set(scale, scale);
+    sprite.anchor.set(0.5, 0.5);
+    
+    sprite.eventMode = 'static';
+    sprite.on('mousedown', (e) => imperative.current.onMouseDown(e));
+    sprite.on('mouseup', (e) => imperative.current.onMouseUpNode(e));
+    sprite.on('mouseover', (e) => imperative.current.onHover(e));
+    sprite.on('mouseout', (e) => imperative.current.onUnHover(e));
+
+    nodeMap.current.set(node._id, sprite);
+    nodeLayer.addChild(sprite);
 
     updateNode(node);
-    gfx.name = 'node_' + node._id;
-    gfx.eventMode = 'dynamic';
+    (sprite as any).node = node;
 
-    return gfx;
+    return sprite;
   };
 
   // /** UpdateRadius works just like UpdateColors, but also applies radius*/
@@ -524,7 +490,6 @@ export const NLPixi = (props: Props) => {
   };
 
   const update = (forceClear = false) => {
-    setPopups([]);
     if (!props.graph || !ref.current) return;
 
     if (props.graph) {
@@ -613,21 +578,13 @@ export const NLPixi = (props: Props) => {
 
     viewport.current.addChild(linkGfx);
     viewport.current.addChild(nodeLayer);
-    viewport.current.on('drag-start', (event) => {
-      imperative.current.onPan();
-    });
-    viewport.current.on('moved', () => {
-      for (const popup in popups) {
-        popup.pos.x += 10;
-        popup.pos.y += 10;
-      }
-      setPopups([...popups]);
+    viewport.current.on('moved', (event) => {
+      imperative.current.onMoved(event)
     })
 
     app.stage.eventMode = 'dynamic';
-    app.stage.on('pointerup', onDragEnd);
-    app.stage.on('mousemove', onDragMove);
-    app.stage.on('mouseup', onDragEnd);
+    app.stage.on('mousedown', (e) => imperative.current.onMouseDown(e));
+    app.stage.on('mouseup', (e) => imperative.current.onMouseUpStage(e));
 
     nodeMap.current.clear();
     linkGfx.clear();
@@ -660,12 +617,18 @@ export const NLPixi = (props: Props) => {
   };
   return (
     <>
-      <Tooltip open={true}>
+      <Tooltip open={popups.length > 0}>
         <TooltipTrigger x={popups[0]?.pos.x} y={popups[0]?.pos.y} />
         <TooltipContent>
           {popups[0] != null && <NLPopup onClose={() => {}} data={popups[0]} key={popups[0].node._id} />}
         </TooltipContent>
       </Tooltip>
+      <Tooltip open={popups.length > 1}>
+        <TooltipTrigger x={popups[1]?.pos.x} y={popups[1]?.pos.y} />
+        <TooltipContent>
+          {popups[1] != null && <NLPopup onClose={() => {}} data={popups[1]} key={popups[1].node._id} />}
+        </TooltipContent>
+      </Tooltip>
       <div
         className="h-full w-full overflow-hidden"
         ref={ref}