in packages/graph-explorer/src/modules/GraphViewer/GraphViewer.tsx [96:257]
export default function GraphViewer() {
const graphRef = useRef<GraphRef | null>(null);
const [nodesSelectedIds, setNodesSelectedIds] = useAtom(
nodesSelectedRenderedIdsAtom
);
const [edgesSelectedIds, setEdgesSelectedIds] = useAtom(
edgesSelectedRenderedIdsAtom
);
const nodesOutIds = useAtomValue(nodesOutOfFocusRenderedIdsAtom);
const edgesOutIds = useAtomValue(edgesOutOfFocusRenderedIdsAtom);
const autoOpenDetails = useAutoOpenDetailsSidebar();
const onSelectedElementIdsChange = useCallback(
({ nodeIds, edgeIds }: SelectedElements) => {
setNodesSelectedIds(nodeIds as Set<RenderedVertexId>);
setEdgesSelectedIds(edgeIds as Set<RenderedEdgeId>);
if (
(nodeIds.size === 1 && edgeIds.size === 0) ||
(nodeIds.size === 0 && edgeIds.size === 1)
) {
autoOpenDetails();
}
},
[autoOpenDetails, setEdgesSelectedIds, setNodesSelectedIds]
);
const [legendOpen, setLegendOpen] = useState(false);
const { onZoomIn, onZoomOut, onSaveScreenshot } =
useGraphGlobalActions(graphRef);
const {
clearAllLayers,
contextLayerProps,
contextNodeId,
contextEdgeId,
isContextOpen,
onGraphRightClick,
onNodeRightClick,
onEdgeRightClick,
parentRef,
renderContextLayer,
} = useContextMenu();
const styles = useGraphStyles();
const getNodeBadges = useNodeBadges();
const { expandNode } = useExpandNode();
const onNodeDoubleClick: ElementEventCallback<RenderedVertex["data"]> =
useCallback(
(_, vertex) => {
const vertexId = getVertexIdFromRenderedVertexId(vertex.id);
expandNode(vertexId, vertex.types, {
limit: 10,
});
},
[expandNode]
);
const [layout, setLayout] = useState("F_COSE");
const onClearGraph = useClearGraph();
const nodes = useRenderedVertices();
const edges = useRenderedEdges();
return (
<div className="relative size-full grow" onContextMenu={onContextMenu}>
<Panel>
<PanelHeader>
<PanelTitle>Graph View</PanelTitle>
<PanelHeaderActions>
<SelectField
className="min-w-auto max-w-64"
label="Layout"
labelPlacement="inner"
options={LAYOUT_OPTIONS}
value={layout}
onValueChange={setLayout}
/>
<IconButton
tooltipText="Re-run Layout"
icon={<RefreshCwIcon />}
variant="text"
onClick={() => {
graphRef.current?.runLayout();
}}
/>
<div className="grow" />
<PanelHeaderActionButton
label="Download Screenshot"
icon={<ImageDownIcon />}
onActionClick={onSaveScreenshot}
/>
<ExportGraphButton />
<ImportGraphButton />
<PanelHeaderDivider />
<PanelHeaderActionButton
label="Zoom in"
icon={<ZoomInIcon />}
onActionClick={onZoomIn}
/>
<PanelHeaderActionButton
label="Zoom out"
icon={<ZoomOutIcon />}
onActionClick={onZoomOut}
/>
<PanelHeaderDivider />
<PanelHeaderActionButton
label="Clear canvas"
icon={<CircleSlash2 />}
color="danger"
onActionClick={onClearGraph}
/>
<PanelHeaderActionButton
label="Legend"
icon={<BadgeInfoIcon />}
onActionClick={() => setLegendOpen(open => !open)}
/>
</PanelHeaderActions>
</PanelHeader>
<PanelContent
className="bg-background-secondary relative flex h-full w-full"
ref={parentRef}
>
<Graph
ref={graphRef}
nodes={nodes}
edges={edges}
badgesEnabled={false}
getNodeBadges={getNodeBadges(nodesOutIds)}
selectedNodesIds={nodesSelectedIds}
selectedEdgesIds={edgesSelectedIds}
outOfFocusNodesIds={nodesOutIds}
outOfFocusEdgesIds={edgesOutIds}
onSelectedElementIdsChange={onSelectedElementIdsChange}
onNodeDoubleClick={onNodeDoubleClick}
onNodeRightClick={onNodeRightClick}
onEdgeRightClick={onEdgeRightClick}
onGraphRightClick={onGraphRightClick}
styles={styles}
layout={layout}
/>
{isContextOpen &&
renderContextLayer(
<div
{...contextLayerProps}
style={contextLayerProps.style}
className="z-menu"
>
<ContextMenu
graphRef={graphRef}
onClose={clearAllLayers}
affectedNodesIds={contextNodeId ? [contextNodeId] : []}
affectedEdgesIds={contextEdgeId ? [contextEdgeId] : []}
/>
</div>
)}
{legendOpen && <Legend onClose={() => setLegendOpen(false)} />}