Skip to content
Snippets Groups Projects
Commit 08947221 authored by Leonardo Christino's avatar Leonardo Christino
Browse files

Merge branch 'DEV-178' into 'main'

feat(nodelink): popup persists on click

See merge request !63
parents 07c7af58 5b543ca2
No related branches found
Tags v1.11.0
1 merge request!63feat(nodelink): popup persists on click
Pipeline #127232 passed
import { GraphType, LinkType, NodeType } from '../Types';
import { tailwindColors } from 'config';
import { ReactEventHandler, useEffect, useMemo, useRef, useState } from 'react';
import { ReactEventHandler, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { Application, Circle, Container, FederatedPointerEvent, Graphics, IPointData } from 'pixi.js';
import { binaryColor, nodeColor as nodeColor } from './utils';
import { select, zoom as d3zoom, drag as d3drag } from 'd3';
......@@ -10,10 +10,12 @@ import { GraphQueryResult, GraphQueryResultFromBackendPayload } from '@graphpola
import { useAppDispatch, useML } from '@graphpolaris/shared/lib/data-access';
import { ML, setShortestPathSource, setShortestPathTarget } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
import { parseQueryResult } from './query2NL';
import { NLPopup } from './NLPopup';
type Props = {
onClick: (node: NodeType) => void;
onHover: (data: { node: NodeType; pos: IPointData } | undefined) => void;
onClick: (node: NodeType, pos: IPointData) => void;
// onHover: (data: { node: NodeType; pos: IPointData }) => void;
// onUnHover: (data: { node: NodeType; pos: IPointData }) => void;
highlightNodes: NodeType[];
currentShortestPathEdges?: LinkType[];
highlightedLinks?: LinkType[];
......@@ -29,6 +31,9 @@ const links = new Container();
//////////////////
export const NLPixi = (props: Props) => {
const [quickPopup, setQuickPopup] = useState<{ node: NodeType; pos: IPointData } | undefined>();
const [popups, setPopups] = useState<{ node: NodeType; pos: IPointData }[]>([]);
const nodeMap = useRef(new Map<string, Graphics>());
const linkMap = useRef(new Map<string, Graphics>());
const viewport = useRef<Viewport>();
......@@ -39,6 +44,71 @@ export const NLPixi = (props: Props) => {
const onlyClicked = useRef(false);
const dispatch = useAppDispatch();
const imperative = useRef<any>(null);
useImperativeHandle(imperative, () => ({
onDragStart(node: NodeType, gfx: Graphics) {
if (viewport.current) viewport.current.pause = true;
dragging.current = { node, gfx };
onlyClicked.current = true;
},
onDragMove(movementX: number, movementY: number) {
if (dragging.current) {
onlyClicked.current = false;
// if (popups.length > 0) setPopups([]);
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 (!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();
}
},
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));
} else {
console.log('clicked', popups);
setPopups([...popups, { node: dragging.current.node, pos: toGlobal(dragging.current.node) }]);
}
props.onClick(dragging.current.node, toGlobal(dragging.current.node));
}
this.onHover(dragging.current.node);
dragging.current = null;
}
},
onHover(node: NodeType) {
if (viewport?.current && !viewport?.current?.pause && node && popups.filter((p) => p.node.id === node.id).length === 0) {
setQuickPopup({ node, pos: toGlobal(node) });
}
},
onUnHover() {
setQuickPopup(undefined);
},
onPan() {
setPopups([]);
},
}));
// useEffect(() => {
// app.renderer.resize(props.windowSize.width, props.windowSize.height);
// app.render();
......@@ -73,57 +143,28 @@ export const NLPixi = (props: Props) => {
}
}, [ref]);
function onDragStart(event: FederatedPointerEvent, node: NodeType, gfx: Graphics) {
// store a reference to the data
// the reason for this is because of multitouch
// we want to track the movement of this particular touch
event.stopPropagation();
console.log('dragstart', node);
onHover(event, undefined);
if (viewport.current) viewport.current.pause = true;
dragging.current = { node, gfx };
onlyClicked.current = true;
}
function onHover(event: FederatedPointerEvent, node: NodeType | undefined) {
event.stopPropagation();
if (viewport?.current && !viewport?.current?.pause && node) {
function toGlobal(node: NodeType): IPointData {
if (viewport?.current) {
const rect = ref.current?.getBoundingClientRect();
const x = (rect?.x || 0) + (node.x || 0);
const y = (rect?.y || 0) + (node.y || 0);
props.onHover({ node, pos: viewport.current.toScreen(x, y) });
} else {
props.onHover(undefined);
}
return viewport.current.toScreen(x, y);
} else return { x: 0, y: 0 };
}
function onDragEnd(event: FederatedPointerEvent) {
if (dragging.current) {
event.stopPropagation();
dragging.current.node.fx = null;
dragging.current.node.fy = null;
if (viewport.current) viewport.current.pause = false;
if (onlyClicked.current) {
onlyClicked.current = false;
props.onClick(dragging.current.node);
}
onHover(event, dragging.current.node);
dragging.current = null;
}
function onDragStart(event: FederatedPointerEvent, node: NodeType, gfx: Graphics) {
event.stopPropagation();
imperative.current.onDragStart(node, gfx);
}
function onDragMove(event: FederatedPointerEvent) {
if (dragging.current) {
onlyClicked.current = false;
event.stopPropagation();
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 += event.movementX / (viewport.current?.scaled || 1);
dragging.current.node.fy += event.movementY / (viewport.current?.scaled || 1);
force.simulation.alpha(0.1).restart();
}
event.stopPropagation();
imperative.current.onDragMove(event.movementX, event.movementY);
}
function onDragEnd(event: FederatedPointerEvent) {
event.stopPropagation();
imperative.current.onDragEnd();
}
const updateNode = (node: NodeType) => {
......@@ -151,8 +192,14 @@ export const NLPixi = (props: Props) => {
gfx.off('mouseover');
gfx.off('mousedown');
gfx.on('mouseover', (e) => onHover(e, node));
gfx.on('mouseout', (e) => onHover(e, undefined));
gfx.on('mouseover', (e) => {
e.stopPropagation();
imperative.current.onHover(node);
});
gfx.on('mouseout', (e) => {
e.stopPropagation();
imperative.current.onUnHover();
});
gfx.on('mousedown', (e) => onDragStart(e, node, gfx));
// if (!item.position) {
......@@ -394,6 +441,10 @@ export const NLPixi = (props: Props) => {
viewport.current.addChild(links);
viewport.current.addChild(nodes);
viewport.current.on('drag-start', (event) => {
imperative.current.onPan();
});
// app.stage.addChild(links);
// app.stage.addChild(nodes);
app.stage.eventMode = 'dynamic';
......@@ -410,5 +461,13 @@ export const NLPixi = (props: Props) => {
isSetup.current = true;
};
return <div className="h-full w-full overflow-hidden" ref={ref}></div>;
return (
<>
{popups.map((popup) => (
<NLPopup onClose={() => {}} data={popup} key={popup.node.id} />
))}
{quickPopup && <NLPopup onClose={() => {}} data={quickPopup} />}
<div className="h-full w-full overflow-hidden" ref={ref}></div>
</>
);
};
......@@ -46,7 +46,6 @@ export const NodeLinkVis = React.memo((props: Props) => {
const [graph, setGraph] = useImmer<GraphType | undefined>(undefined);
const [highlightNodes, setHighlightNodes] = useState<NodeType[]>([]);
const [highlightedLinks, setHighlightedLinks] = useState<LinkType[]>([]);
const [popup, setPopup] = useState<undefined | { node: NodeType; pos: PIXI.IPointData }>(undefined);
const graphQueryResult = useGraphQueryResult();
const ml = useML();
......@@ -67,6 +66,7 @@ export const NodeLinkVis = React.memo((props: Props) => {
const onClickedNode = (node: NodeType, ml: ML) => {
console.log('shortestPath', graph, ml.shortestPath.enabled);
if (graph) {
if (ml.shortestPath.enabled) {
console.log('shortestPath');
......@@ -103,18 +103,13 @@ export const NodeLinkVis = React.memo((props: Props) => {
return (
<>
<div className="h-full w-full overflow-hidden" ref={ref}>
{!!popup && <NLPopup onClose={() => setPopup(undefined)} data={popup} />}
<NLPixi
graph={graph}
highlightNodes={highlightNodes}
highlightedLinks={highlightedLinks}
onClick={(node) => {
console.log(ml.shortestPath);
onClick={(node, pos) => {
onClickedNode(node, ml);
}}
onHover={(data) => {
setPopup(data);
}}
/>
{/* <VisConfigPanelComponent> */}
......
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