@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
275 lines • 13.1 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { tokens } from '@neo4j-ndl/base';
import { IconButtonArray, useNeedleTheme } from '@neo4j-ndl/react';
import { InteractiveNvlWrapper, } from '@neo4j-nvl/react';
import { default as cx } from 'classnames';
import React, { useEffect, useId, useMemo, useState } from 'react';
import { BoxSelectButton, DownloadButton, LassoSelectButton, SingleSelectButton, ToggleSidePanelButton, ZoomInButton, ZoomOutButton, ZoomToFitButton, } from './graph-visualization-buttons';
import { GraphVisualizationContext, } from './graph-visualization-context';
import { GraphVisualizationSidepanel } from './graph-visualization-sidepanel';
import { mapToNvlGraph } from './map-to-nvl-graph';
import { SingleSelectionSidepanelContents } from './sidepanel-components/single-selection-sidepanel-contents';
import { useManagedNodeState } from './use-managed-node-state';
import { useSemicontrolledState } from './use-semi-controlled-state';
const PLACEMENTS = {
'bottom-left': 'ndl-graph-visualization-interaction-island ndl-bottom-left',
'bottom-right': 'ndl-graph-visualization-interaction-island ndl-bottom-right',
'top-left': 'ndl-graph-visualization-interaction-island ndl-top-left',
'top-right': 'ndl-graph-visualization-interaction-island ndl-top-right',
};
const InteractionIsland = ({ children, className, placement, }) => {
return _jsx("div", { className: cx(PLACEMENTS[placement], className), children: children });
};
export const DEFAULT_NVL_OPTIONS = {
disableTelemetry: true,
disableWebGL: true,
maxZoom: 3,
minZoom: 0.05,
relationshipThreshold: 0.55,
useWebGL: false,
};
const DEFAULT_COMPONENTS = {
bottomLeftIsland: null,
bottomRightIsland: (_jsxs(IconButtonArray, { orientation: "vertical", isFloating: true, children: [_jsx(ZoomInButton, {}), " ", _jsx(ZoomOutButton, {}), " ", _jsx(ZoomToFitButton, {})] })),
topLeftIsland: null,
topRightIsland: (_jsxs("div", { className: "ndl-graph-visualization-default-download-group", children: [_jsx(DownloadButton, {}), " ", _jsx(ToggleSidePanelButton, {})] })),
};
/**
* A comprehensive graph visualization component for rendering Neo4j-style graphs with interactive features.
*
* @remarks
* This component provides a complete graph visualization solution with built-in interaction modes,
* selection handling, zoom controls, and an optional sidepanel. It wraps the NVL (Neo4j Visualization Library)
* with additional UI controls and state management.
*
* The component supports various interaction modes including single selection, box selection, and lasso selection.
* It also provides customizable UI islands for placing controls at different corners of the visualization.
*
* @example
* Basic usage:
* ```tsx
* <GraphVisualization
* nodes={nodes}
* rels={relationships}
* selected={selection}
* setSelected={setSelection}
* />
* ```
*
* @example
* With custom controls:
* ```tsx
* <GraphVisualization
* nodes={nodes}
* rels={relationships}
* topRightIsland={<CustomControls />}
* sidepanel={{
* contents: <CustomSidepanel />,
* isSidePanelOpen: true,
* setIsSidePanelOpen: setSidepanelOpen
* sidePanelWidth: 400,
* onSidePanelResize: setSidepanelWidth,
* }}
* />
* ```
*
* @example
* With search highlighting:
* ```tsx
* <GraphVisualization
* nodes={nodes}
* rels={relationships}
* // undefined = no highlighting (default behavior)
* // empty array = no search matches (dims all nodes)
* // array with IDs = highlight only those items
* highlightedNodeIds={searchResults.nodeIds}
* highlightedRelationshipIds={searchResults.relationshipIds}
* />
* ```
*
* @param props - {@link GraphVisualizationProps}
* @returns A React component that renders an interactive graph visualization
*
* @alpha
*/
export function GraphVisualization(_a) {
var _b, _c;
var { nvlRef: rawNvlRef, nvlCallbacks, nvlOptions, sidepanel: rawSidepanel, nodes: rawNodes, rels: rawRels, highlightedNodeIds, highlightedRelationshipIds, topLeftIsland = DEFAULT_COMPONENTS.topLeftIsland, topRightIsland = DEFAULT_COMPONENTS.topRightIsland, bottomLeftIsland = DEFAULT_COMPONENTS.bottomLeftIsland, bottomRightIsland = DEFAULT_COMPONENTS.bottomRightIsland, gesture = 'single', setGesture, selected: rawSelected, setSelected: rawSetSelected, interactionMode: rawInteractionMode, setInteractionMode: rawSetInteractionMode, mouseEventCallbacks = {}, className, style, htmlAttributes, ref, as } = _a, restProps = __rest(_a, ["nvlRef", "nvlCallbacks", "nvlOptions", "sidepanel", "nodes", "rels", "highlightedNodeIds", "highlightedRelationshipIds", "topLeftIsland", "topRightIsland", "bottomLeftIsland", "bottomRightIsland", "gesture", "setGesture", "selected", "setSelected", "interactionMode", "setInteractionMode", "mouseEventCallbacks", "className", "style", "htmlAttributes", "ref", "as"]);
const nvlRef = useMemo(() => rawNvlRef !== null && rawNvlRef !== void 0 ? rawNvlRef : React.createRef(), [rawNvlRef]);
const instanceId = useId();
// Respect NDL theme changes
const { theme } = useNeedleTheme();
const { bg, border } = tokens.theme[theme].palette.neutral;
// key required for nvl react responsiveness
const [key, setKey] = useState(0);
useEffect(() => {
setKey((prevKey) => prevKey + 1);
}, [theme]);
// Semi-control interaction mode and selection state
const [interactionMode, setInteractionMode] = useSemicontrolledState(rawInteractionMode !== null && rawInteractionMode !== void 0 ? rawInteractionMode : 'select', rawSetInteractionMode);
const [selected, setSelected] = useSemicontrolledState(rawSelected !== null && rawSelected !== void 0 ? rawSelected : { nodeIds: [], relationshipIds: [] }, rawSetSelected);
const nvlGraph = useMemo(() => mapToNvlGraph(rawNodes, rawRels), [rawNodes, rawRels]);
const { nodesWithState, relsWithState, wrappedMouseEventCallbacks } = useManagedNodeState({
gesture,
highlightedNodeIds,
highlightedRelationshipIds,
interactionMode,
mouseEventCallbacks,
nvlGraph,
selected,
setInteractionMode,
setSelected,
});
const [isSidePanelOpen, setIsSidePanelOpen] = useSemicontrolledState((_b = rawSidepanel === null || rawSidepanel === void 0 ? void 0 : rawSidepanel.isSidePanelOpen) !== null && _b !== void 0 ? _b : true, rawSidepanel === null || rawSidepanel === void 0 ? void 0 : rawSidepanel.setIsSidePanelOpen);
const [sidePanelWidth, setSidePanelWidth] = useSemicontrolledState((_c = rawSidepanel === null || rawSidepanel === void 0 ? void 0 : rawSidepanel.sidePanelWidth) !== null && _c !== void 0 ? _c : 400, rawSidepanel === null || rawSidepanel === void 0 ? void 0 : rawSidepanel.onSidePanelResize);
const sidepanel = useMemo(() => {
if (rawSidepanel === undefined) {
return {
children: _jsx(SingleSelectionSidepanelContents, {}),
isSidePanelOpen,
onSidePanelResize: setSidePanelWidth,
setIsSidePanelOpen,
sidePanelWidth,
};
}
return rawSidepanel;
}, [
rawSidepanel,
isSidePanelOpen,
setIsSidePanelOpen,
sidePanelWidth,
setSidePanelWidth,
]);
const Component = as !== null && as !== void 0 ? as : 'div';
return (_jsx(Component, Object.assign({ ref: ref, className: cx('ndl-graph-visualization-container', className), style: style }, htmlAttributes, { children: _jsxs(GraphVisualizationContext.Provider, { value: {
gesture,
interactionMode,
nvlGraph,
nvlInstance: nvlRef,
selected,
setGesture,
sidepanel,
}, children: [_jsxs("div", { className: "ndl-graph-visualization", children: [_jsx(InteractiveNvlWrapper, Object.assign({ layout: "d3Force", nodes: nodesWithState, rels: relsWithState, nvlOptions: Object.assign(Object.assign(Object.assign({}, DEFAULT_NVL_OPTIONS), { instanceId, styling: {
defaultRelationshipColor: border.strongest,
dropShadowColor: border.weak,
selectedInnerBorderColor: bg.default,
} }), nvlOptions), nvlCallbacks: Object.assign({ onLayoutComputing(isComputing) {
var _a;
if (!isComputing) {
(_a = nvlRef.current) === null || _a === void 0 ? void 0 : _a.fit(nvlRef.current.getNodes().map((neighbors) => neighbors.id), { noPan: true });
}
} }, nvlCallbacks), mouseEventCallbacks: wrappedMouseEventCallbacks, ref: nvlRef }, restProps), key), topLeftIsland !== null && (_jsx(InteractionIsland, { placement: "top-left", children: topLeftIsland })), topRightIsland !== null && (_jsx(InteractionIsland, { placement: "top-right", children: topRightIsland })), bottomLeftIsland !== null && (_jsx(InteractionIsland, { placement: "bottom-left", children: bottomLeftIsland })), bottomRightIsland !== null && (_jsx(InteractionIsland, { placement: "bottom-right", children: bottomRightIsland }))] }), sidepanel && _jsx(GraphVisualizationSidepanel, { sidepanel: sidepanel })] }) })));
}
/**
* Zoom in button for use within a GraphVisualization component.
*
* @remarks
* Increases zoom level by 1.3x
*
* @alpha
*/
GraphVisualization.ZoomInButton = ZoomInButton;
/**
* Zoom out button for use within a GraphVisualization component.
*
* @remarks
* Decreases zoom level by 0.7x
*
* @alpha
*/
GraphVisualization.ZoomOutButton = ZoomOutButton;
/**
* Zoom to fit button for use within a GraphVisualization component.
*
* @remarks
* Automatically adjusts zoom and pan to show all nodes and relationships
*
* @alpha
*/
GraphVisualization.ZoomToFitButton = ZoomToFitButton;
/**
* Sidepanel toggle button for use within a GraphVisualization component.
*
* @remarks
* Shows or hides the sidepanel containing node and relationship details.
* Requires a sidepanel to be configured in the GraphVisualization component.
*
* @throws Error when used without a configured sidepanel
*
* @alpha
*/
GraphVisualization.ToggleSidePanelButton = ToggleSidePanelButton;
/**
* Download button for use within a GraphVisualization component.
*
* @remarks
* Opens a dropdown menu with download options. Currently supports PNG format.
* Captures the current visualization state including zoom level.
*
* @alpha
*/
GraphVisualization.DownloadButton = DownloadButton;
/**
* Box selection button for use within a GraphVisualization component.
*
* @remarks
* Enables box selection mode where dragging creates a rectangular selection area.
* Disabled unless the `setGesture` callback is passed.
* Shows active state when enabled and supports keyboard shortcut 'B'.
*
* @alpha
*/
GraphVisualization.BoxSelectButton = BoxSelectButton;
/**
* Lasso selection button for use within a GraphVisualization component.
*
* @remarks
* Enables lasso selection mode where dragging creates a free-form selection area.
* Disabled unless the `setGesture` callback is passed.
* Shows active state when enabled and supports keyboard shortcut 'L'.
*
* @alpha
*/
GraphVisualization.LassoSelectButton = LassoSelectButton;
/**
* Single node selection button for use within a GraphVisualization component.
*
* @remarks
* Enables single selection mode where clicking selects individual nodes or relationships.
* Shows active state when enabled and supports keyboard shortcut 'S'.
*
* @alpha
*/
GraphVisualization.SingleSelectButton = SingleSelectButton;
//# sourceMappingURL=graph-visualization.js.map