@neo4j-nvl/react 1.0.0-fa3fa1ba → 1.1.0-3b6cdaa4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/StaticPictureWrapper.test.d.ts +1 -0
- package/lib/__tests__/StaticPictureWrapper.test.js +85 -0
- package/lib/basic-wrapper/BasicNvlWrapper.d.ts +1 -1
- package/lib/static-picture-wrapper/StaticPictureWrapper.d.ts +21 -4
- package/lib/static-picture-wrapper/StaticPictureWrapper.js +32 -7
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { render, waitFor } from '@testing-library/react';
|
|
4
|
+
import { StaticPictureWrapper } from '../static-picture-wrapper/StaticPictureWrapper';
|
|
5
|
+
jest.mock('@neo4j-nvl/base', () => {
|
|
6
|
+
const mockNVL = jest
|
|
7
|
+
.fn()
|
|
8
|
+
.mockImplementation((_element, _nodes, _rels, _nvlOptions, callbacks) => {
|
|
9
|
+
setTimeout(() => {
|
|
10
|
+
callbacks.onLayoutDone?.();
|
|
11
|
+
callbacks.onZoomTransitionDone?.();
|
|
12
|
+
}, 0);
|
|
13
|
+
return {
|
|
14
|
+
getNodes: jest.fn().mockReturnValue([
|
|
15
|
+
{ id: '0', x: 0, y: 0 },
|
|
16
|
+
{ id: '1', x: 100, y: 100 }
|
|
17
|
+
]),
|
|
18
|
+
fit: jest.fn(),
|
|
19
|
+
getImageDataUrl: jest.fn().mockReturnValue('data:image/png;base64,...mock-png...'),
|
|
20
|
+
getSvgDataUrl: jest.fn().mockResolvedValue('data:image/svg+xml;charset=utf-8,...mock-svg...'),
|
|
21
|
+
destroy: jest.fn()
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
return mockNVL;
|
|
25
|
+
});
|
|
26
|
+
const NVLMock = jest.requireMock('@neo4j-nvl/base');
|
|
27
|
+
describe('StaticPictureWrapper - format prop support', () => {
|
|
28
|
+
const mockNodes = [
|
|
29
|
+
{ id: '0', caption: 'Node 0' },
|
|
30
|
+
{ id: '1', caption: 'Node 1' }
|
|
31
|
+
];
|
|
32
|
+
const mockRels = [{ id: '01', from: '0', to: '1', caption: 'KNOWS' }];
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
NVLMock.mockClear();
|
|
35
|
+
});
|
|
36
|
+
const getMockNVLInstance = (index = 0) => {
|
|
37
|
+
return NVLMock.mock.results[index]?.value;
|
|
38
|
+
};
|
|
39
|
+
it('Should pass nodes, relationships and options to NVL constructor', () => {
|
|
40
|
+
const mockOptions = {
|
|
41
|
+
layout: 'hierarchical',
|
|
42
|
+
styling: {
|
|
43
|
+
defaultNodeColor: 'red'
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
render(_jsx(StaticPictureWrapper, { nodes: mockNodes, rels: mockRels, format: "png", nvlOptions: mockOptions, width: 250, height: 250 }));
|
|
47
|
+
expect(NVLMock).toHaveBeenCalledWith(expect.any(HTMLDivElement), mockNodes, mockRels, mockOptions, expect.any(Object));
|
|
48
|
+
});
|
|
49
|
+
it('Should use getSvgDataUrl for SVG format', async () => {
|
|
50
|
+
render(_jsx(StaticPictureWrapper, { nodes: mockNodes, rels: mockRels, format: "svg" }));
|
|
51
|
+
await waitFor(() => {
|
|
52
|
+
const mockInstance = getMockNVLInstance();
|
|
53
|
+
expect(mockInstance.getSvgDataUrl).toHaveBeenCalled();
|
|
54
|
+
expect(mockInstance.getImageDataUrl).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('Should use getImageDataUrl for PNG format', async () => {
|
|
58
|
+
render(_jsx(StaticPictureWrapper, { nodes: mockNodes, rels: mockRels, format: "png" }));
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
const mockInstance = getMockNVLInstance();
|
|
61
|
+
expect(mockInstance.getSvgDataUrl).not.toHaveBeenCalled();
|
|
62
|
+
expect(mockInstance.getImageDataUrl).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it('Should triggerer re-render when format prop changes', async () => {
|
|
66
|
+
const { rerender } = render(_jsx(StaticPictureWrapper, { nodes: mockNodes, rels: mockRels, format: "png" }));
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
const mockInstance = getMockNVLInstance(0);
|
|
69
|
+
expect(mockInstance.getSvgDataUrl).not.toHaveBeenCalled();
|
|
70
|
+
expect(mockInstance.getImageDataUrl).toHaveBeenCalledTimes(1);
|
|
71
|
+
});
|
|
72
|
+
rerender(_jsx(StaticPictureWrapper, { nodes: mockNodes, rels: mockRels, format: "svg" }));
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
const mockInstance = getMockNVLInstance(1);
|
|
75
|
+
expect(mockInstance.getSvgDataUrl).toHaveBeenCalledTimes(1);
|
|
76
|
+
expect(mockInstance.getImageDataUrl).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('Should clean up NVL instance on unmount', () => {
|
|
80
|
+
const { unmount } = render(_jsx(StaticPictureWrapper, { nodes: mockNodes, rels: mockRels, format: "png" }));
|
|
81
|
+
unmount();
|
|
82
|
+
const mockInstance = getMockNVLInstance();
|
|
83
|
+
expect(mockInstance.destroy).toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -45,4 +45,4 @@ export interface BasicReactWrapperProps {
|
|
|
45
45
|
*
|
|
46
46
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/react-wrappers/#_basic_react_wrapper Basic React wrapper documentation page}.
|
|
47
47
|
*/
|
|
48
|
-
export declare const BasicNvlWrapper: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<BasicReactWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & import("react").RefAttributes<Partial<Pick<NVL, "restart" | "destroy" | "addAndUpdateElementsInGraph" | "getSelectedNodes" | "getSelectedRelationships" | "removeNodesWithIds" | "removeRelationshipsWithIds" | "getNodes" | "getRelationships" | "getNodeById" | "getRelationshipById" | "getPositionById" | "getCurrentOptions" | "deselectAll" | "fit" | "resetZoom" | "setRenderer" | "setDisableWebGL" | "pinNode" | "unPinNode" | "setLayout" | "setLayoutOptions" | "getNodesOnScreen" | "getNodePositions" | "setNodePositions" | "isLayoutMoving" | "saveToFile" | "saveToSvg" | "getImageDataUrl" | "saveFullGraphToLargeFile" | "getZoomLimits" | "setZoom" | "getScale" | "getPan" | "getHits" | "getContainer">>>>>;
|
|
48
|
+
export declare const BasicNvlWrapper: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Omit<BasicReactWrapperProps & HTMLProps<HTMLDivElement>, "ref"> & import("react").RefAttributes<Partial<Pick<NVL, "restart" | "destroy" | "addAndUpdateElementsInGraph" | "getSelectedNodes" | "getSelectedRelationships" | "removeNodesWithIds" | "removeRelationshipsWithIds" | "getNodes" | "getRelationships" | "getNodeById" | "getRelationshipById" | "getPositionById" | "getCurrentOptions" | "deselectAll" | "fit" | "resetZoom" | "setRenderer" | "setDisableWebGL" | "pinNode" | "unPinNode" | "setLayout" | "setLayoutOptions" | "getNodesOnScreen" | "getNodePositions" | "setNodePositions" | "isLayoutMoving" | "saveToFile" | "saveToSvg" | "getImageDataUrl" | "getSvgDataUrl" | "saveFullGraphToLargeFile" | "getZoomLimits" | "setZoom" | "getScale" | "getPan" | "getHits" | "getContainer">>>>>;
|
|
@@ -9,14 +9,31 @@ export type StaticPictureWrapperProps = {
|
|
|
9
9
|
rels: Relationship[];
|
|
10
10
|
/** Options to customize the NVL instance. */
|
|
11
11
|
nvlOptions?: NvlOptions;
|
|
12
|
-
/** The width of the static picture. */
|
|
12
|
+
/** The width of the static picture in pixels. */
|
|
13
13
|
width?: number;
|
|
14
|
-
/** The height of the static picture. */
|
|
14
|
+
/** The height of the static picture in pixels. */
|
|
15
15
|
height?: number;
|
|
16
|
+
/** The format of the static picture: 'png' or 'svg'.
|
|
17
|
+
* Defaults to 'png'. */
|
|
18
|
+
format?: 'png' | 'svg';
|
|
16
19
|
};
|
|
17
20
|
/**
|
|
18
21
|
* A React component that creates a static picture of a graph using NVL.
|
|
19
22
|
* This component is useful for generating static visualizations of graphs without requiring user interaction.
|
|
20
|
-
*
|
|
23
|
+
* The component automatically fits all nodes in the viewport before capturing the image
|
|
24
|
+
* NVL is automatically destroyed after capturing the image to free up resources.
|
|
25
|
+
*
|
|
26
|
+
* @param props - The component properties
|
|
27
|
+
* @returns An img element displaying the graph visualization, or null during rendering
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <StaticPictureWrapper
|
|
32
|
+
* nodes={nodes}
|
|
33
|
+
* rels={relationships}
|
|
34
|
+
* width={500}
|
|
35
|
+
* height={500}
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
21
38
|
*/
|
|
22
|
-
export declare const StaticPictureWrapper: ({ nodes, rels, nvlOptions, width, height }: StaticPictureWrapperProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
39
|
+
export declare const StaticPictureWrapper: ({ nodes, rels, nvlOptions, width, height, format }: StaticPictureWrapperProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -4,9 +4,23 @@ import { useEffect, useState } from 'react';
|
|
|
4
4
|
/**
|
|
5
5
|
* A React component that creates a static picture of a graph using NVL.
|
|
6
6
|
* This component is useful for generating static visualizations of graphs without requiring user interaction.
|
|
7
|
-
*
|
|
7
|
+
* The component automatically fits all nodes in the viewport before capturing the image
|
|
8
|
+
* NVL is automatically destroyed after capturing the image to free up resources.
|
|
9
|
+
*
|
|
10
|
+
* @param props - The component properties
|
|
11
|
+
* @returns An img element displaying the graph visualization, or null during rendering
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <StaticPictureWrapper
|
|
16
|
+
* nodes={nodes}
|
|
17
|
+
* rels={relationships}
|
|
18
|
+
* width={500}
|
|
19
|
+
* height={500}
|
|
20
|
+
* />
|
|
21
|
+
* ```
|
|
8
22
|
*/
|
|
9
|
-
export const StaticPictureWrapper = ({ nodes, rels, nvlOptions = {}, width = 500, height = 500 }) => {
|
|
23
|
+
export const StaticPictureWrapper = ({ nodes, rels, nvlOptions = {}, width = 500, height = 500, format = 'png' }) => {
|
|
10
24
|
const [imgSrc, setImgSrc] = useState();
|
|
11
25
|
useEffect(() => {
|
|
12
26
|
const div = document.createElement('div');
|
|
@@ -17,16 +31,27 @@ export const StaticPictureWrapper = ({ nodes, rels, nvlOptions = {}, width = 500
|
|
|
17
31
|
myNvl.fit(myNvl.getNodes().map((n) => n.id));
|
|
18
32
|
},
|
|
19
33
|
onZoomTransitionDone: () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
const fetchDataUrl = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const dataUrl = format === 'svg' ? await myNvl.getSvgDataUrl() : myNvl.getImageDataUrl();
|
|
37
|
+
setImgSrc(dataUrl);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error('Failed to generate image data URL', err);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
myNvl.destroy();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
void fetchDataUrl();
|
|
24
49
|
}
|
|
25
50
|
});
|
|
26
51
|
return () => {
|
|
27
52
|
myNvl?.destroy();
|
|
28
53
|
};
|
|
29
54
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
-
}, [nodes, rels, width, height]);
|
|
55
|
+
}, [nodes, rels, width, height, format]);
|
|
31
56
|
return imgSrc !== undefined ? _jsx("img", { src: imgSrc, width: width, height: height, alt: "Graph" }) : null;
|
|
32
57
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0-3b6cdaa4",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"homepage": "https://neo4j.com/docs/nvl/current/",
|
|
6
6
|
"license": "SEE LICENSE IN 'LICENSE.txt'",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"react-dom": "19.2.1"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@neo4j-nvl/base": "1.
|
|
40
|
-
"@neo4j-nvl/interaction-handlers": "1.
|
|
39
|
+
"@neo4j-nvl/base": "1.1.0-3b6cdaa4",
|
|
40
|
+
"@neo4j-nvl/interaction-handlers": "1.1.0-3b6cdaa4",
|
|
41
41
|
"lodash": "4.17.23"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": "18.0.0 || ^19.0.0",
|
|
45
45
|
"react-dom": "18.0.0 || ^19.0.0"
|
|
46
46
|
},
|
|
47
|
-
"stableVersion": "1.
|
|
47
|
+
"stableVersion": "1.1.0"
|
|
48
48
|
}
|