@reltio/graph 1.4.1585 → 1.4.1586-mui5

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.
Files changed (164) hide show
  1. package/index.ts +1 -0
  2. package/package.json +26 -9
  3. package/public/bundle.js +205 -0
  4. package/public/bundle.js.LICENSE.txt +88 -0
  5. package/public/package.json +29 -0
  6. package/scripts/build/index.js +20 -0
  7. package/src/__tests__/GraphView.test.tsx +25 -0
  8. package/src/components/CircleLayout/CircleLayout.tsx +76 -0
  9. package/src/components/CircleLayout/__tests__/CircleLayout.test.tsx +194 -0
  10. package/src/components/GotoButton/GotoButton.tsx +23 -0
  11. package/src/components/GotoButton/__tests__/GotoButton.test.tsx +33 -0
  12. package/src/components/GotoButton/styles.ts +15 -0
  13. package/src/components/GraphArea/GraphArea.tsx +113 -0
  14. package/src/components/GraphArea/__tests__/GraphArea.test.tsx +202 -0
  15. package/src/components/GraphArea/styles.ts +30 -0
  16. package/src/components/GraphLayoutSelector/GraphLayoutSelector.tsx +43 -0
  17. package/src/components/GraphLayoutSelector/__tests__/GraphLayoutSelector.spec.tsx +44 -0
  18. package/src/components/GraphLayoutSelector/styles.ts +15 -0
  19. package/src/components/GraphPerspectiveView/GraphPerspectiveView.tsx +58 -0
  20. package/src/components/GraphPerspectiveView/__tests__/GraphPerspectiveView.test.tsx +115 -0
  21. package/src/components/GraphPerspectiveView/__tests__/useGraphLoader.test.tsx +1519 -0
  22. package/src/components/GraphPerspectiveView/__tests__/useRelationshipTable.test.tsx +1003 -0
  23. package/src/components/GraphPerspectiveView/__tests__/useSelectedEntity.test.tsx +373 -0
  24. package/src/components/GraphPerspectiveView/styles.ts +34 -0
  25. package/src/components/GraphRightSidePanel/GraphRightSidePanel.tsx +27 -0
  26. package/src/components/GraphRightSidePanel/__tests__/GraphRightSidePanel.test.tsx +18 -0
  27. package/src/components/GraphRightSidePanel/icons/relationship-icon.svg +3 -0
  28. package/src/components/GraphRightSidePanel/styles.ts +8 -0
  29. package/src/components/GraphTypeSelector/GraphTypeSelector.tsx +56 -0
  30. package/src/components/GraphTypeSelector/__tests__/GraphTypeSelector.test.tsx +109 -0
  31. package/src/components/GraphTypeSelector/styles.ts +17 -0
  32. package/src/components/HierarchyLayout/HierarchyLayout.ts +102 -0
  33. package/src/components/HierarchyLayout/__tests__/HierarchyLayout.spec.tsx +241 -0
  34. package/src/components/HierarchyLayout/__tests__/buildHierarchy.spec.ts +130 -0
  35. package/src/components/HierarchyLayout/__tests__/calculations.spec.ts +230 -0
  36. package/src/components/HierarchyLayout/buildHierarchy.ts +109 -0
  37. package/src/components/HierarchyLayout/calculations.ts +86 -0
  38. package/src/components/RightPanelEntityDetails/RightPanelEntityDetails.tsx +33 -0
  39. package/src/components/RightPanelEntityDetails/__tests__/RightPanelEntityDetails.test.tsx +39 -0
  40. package/src/components/RightPanelEntityDetails/styles.ts +12 -0
  41. package/src/components/RightPanelProfileBand/RightPanelProfileBand.tsx +33 -0
  42. package/src/components/RightPanelProfileBand/__tests__/RightPanelProfileBand.test.tsx +67 -0
  43. package/src/components/RightPanelProfileBand/styles.ts +8 -0
  44. package/src/components/RightPanelRelationship/RightPanelRelationship.tsx +25 -0
  45. package/src/components/RightPanelRelationship/__tests__/RightPanelRelationship.test.tsx +18 -0
  46. package/src/components/RightPanelRelationship/components/CollapsibleTable/CollapsibleTable.tsx +100 -0
  47. package/src/components/RightPanelRelationship/components/CollapsibleTable/TableHead.tsx +38 -0
  48. package/src/components/RightPanelRelationship/components/CollapsibleTable/__tests__/CollapsibleTable.test.tsx +169 -0
  49. package/src/components/RightPanelRelationship/components/CollapsibleTable/__tests__/TableHead.test.tsx +107 -0
  50. package/src/components/RightPanelRelationship/components/CollapsibleTable/styles.ts +38 -0
  51. package/src/components/RightPanelRelationship/components/CollapsibleTable/types.ts +20 -0
  52. package/src/components/RightPanelRelationship/components/DeleteRelationDialog/DeleteRelationDialog.tsx +34 -0
  53. package/src/components/RightPanelRelationship/components/DeleteRelationDialog/__tests__/DeleteRelationDialog.test.tsx +46 -0
  54. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/GraphAddRelationButton.tsx +43 -0
  55. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/GraphAddRelationDialog.tsx +121 -0
  56. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/__tests__/GraphAddRelationButton.test.tsx +80 -0
  57. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/__tests__/GraphAddRelationDialog.test.tsx +262 -0
  58. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/__tests__/mockdata.ts +75 -0
  59. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/styles.ts +25 -0
  60. package/src/components/RightPanelRelationship/components/GraphAddRelationDialog/useCreatableInOutRelationTypes.ts +13 -0
  61. package/src/components/RightPanelRelationship/components/RelationshipTable/RelationshipTable.tsx +126 -0
  62. package/src/components/RightPanelRelationship/components/RelationshipTable/__tests__/RelationshipTable.test.tsx +176 -0
  63. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/ControlsCellRenderer.tsx +59 -0
  64. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/DefaultCellRenderer.tsx +29 -0
  65. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/ExpandedRowRenderer.tsx +94 -0
  66. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/HeadCellRenderer.tsx +34 -0
  67. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/ProfileCellRenderer.tsx +41 -0
  68. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/__tests__/ControlsCellRenderer.test.tsx +80 -0
  69. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/__tests__/DefaultCellRenderer.test.tsx +47 -0
  70. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/__tests__/ExpandedRowRenderer.test.tsx +166 -0
  71. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/__tests__/HeadCellRenderer.test.tsx +23 -0
  72. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/__tests__/ProfileCellRenderer.test.tsx +47 -0
  73. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/components/DeleteRelationButton.tsx +32 -0
  74. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/components/__tests__/DeleteRelationButton.test.tsx +28 -0
  75. package/src/components/RightPanelRelationship/components/RelationshipTable/cell-renderers/styles.ts +113 -0
  76. package/src/components/RightPanelRelationship/components/RelationshipTable/styles.ts +36 -0
  77. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsFilters.tsx +41 -0
  78. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsFiltersDialog/RelationshipsFiltersDialog.tsx +91 -0
  79. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsFiltersDialog/__tests__/RelationshipsFilterDialog.test.tsx +192 -0
  80. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsFiltersDialog/styles.ts +34 -0
  81. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsSearchTextField/RelationshipsSearchTextField.tsx +31 -0
  82. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsSearchTextField/__tests__/RelationshipsSearchTextField.test.tsx +43 -0
  83. package/src/components/RightPanelRelationship/components/RelationshipsFilters/RelationshipsSearchTextField/styles.ts +17 -0
  84. package/src/components/RightPanelRelationship/components/RelationshipsFilters/__tests__/RelationshipsFilters.test.tsx +76 -0
  85. package/src/components/RightPanelRelationship/components/RelationshipsFilters/styles.ts +19 -0
  86. package/src/components/RightPanelRelationship/styles.ts +20 -0
  87. package/src/components/SigmaCustomRenderers/SigmaCustomRenderersContainer.tsx +16 -0
  88. package/src/components/SigmaGraphContainer/SigmaGraphContainer.tsx +55 -0
  89. package/src/components/SigmaGraphContainer/__tests__/SigmaGraphContainer.test.tsx +25 -0
  90. package/src/components/SigmaGraphResizer/SigmaGraphResizer.tsx +13 -0
  91. package/src/components/SigmaGraphResizer/__tests__/SigmaGraphResizer.test.tsx +28 -0
  92. package/src/components/TreeLayout/TreeLayout.tsx +33 -0
  93. package/src/components/TreeLayout/__tests__/TreeLayout.test.tsx +42 -0
  94. package/src/components/TreeLayout/styles.ts +10 -0
  95. package/src/components/ZoomSlider/ZoomSlider.tsx +90 -0
  96. package/src/components/ZoomSlider/__tests__/ZoomSlider.test.tsx +84 -0
  97. package/src/components/ZoomSlider/styles.ts +57 -0
  98. package/src/constants/index.ts +1 -0
  99. package/src/contexts/GraphStateContext.tsx +19 -0
  100. package/src/contexts/SigmaCustomRenderersContext.ts +20 -0
  101. package/src/helpers/relationsHelpers.ts +9 -0
  102. package/src/hooks/__tests__/useGraphLayout.test.tsx +825 -0
  103. package/src/hooks/__tests__/useGraphLayoutSelector.test.tsx +19 -0
  104. package/src/hooks/__tests__/useSelfRelationLoopsRenderer.test.ts +198 -0
  105. package/src/hooks/__tests__/useSigmaCustomRenderers.test.ts +273 -0
  106. package/src/hooks/__tests__/useTooltipRenderer.test.ts +289 -0
  107. package/src/hooks/useGraphLayout.ts +308 -0
  108. package/src/hooks/useGraphLayoutSelector.ts +19 -0
  109. package/src/hooks/useGraphLoader.ts +279 -0
  110. package/src/hooks/useGraphRightSidePanelElements.tsx +36 -0
  111. package/src/hooks/useGraphState.ts +40 -0
  112. package/src/hooks/useGraphType.ts +32 -0
  113. package/src/hooks/useRelationshipTable.ts +212 -0
  114. package/src/hooks/useRelationshipTableFilters.ts +43 -0
  115. package/src/hooks/useSelectedEntity.ts +89 -0
  116. package/src/hooks/useSelfRelationLoopsRenderer.ts +52 -0
  117. package/src/hooks/useSigmaCustomRenderers.ts +112 -0
  118. package/src/hooks/useTooltipRenderer.ts +80 -0
  119. package/src/index.tsx +37 -0
  120. package/src/rendering/canvas/__tests__/hover.spec.ts +31 -0
  121. package/src/rendering/canvas/__tests__/label.spec.ts +64 -0
  122. package/src/rendering/canvas/__tests__/nodeBackground.spec.ts +99 -0
  123. package/src/rendering/canvas/__tests__/selfRelationLoop.spec.ts +88 -0
  124. package/src/rendering/canvas/__tests__/tooltip.spec.ts +33 -0
  125. package/src/rendering/canvas/hover.ts +13 -0
  126. package/src/rendering/canvas/label.ts +33 -0
  127. package/src/rendering/canvas/nodeBackground.ts +61 -0
  128. package/src/rendering/canvas/selfRelationLoop.ts +108 -0
  129. package/src/rendering/canvas/tooltip.ts +38 -0
  130. package/src/rendering/icons/collapseIcon.inline.svg.ts +6 -0
  131. package/src/rendering/icons/expandIcon.inline.svg.ts +5 -0
  132. package/src/rendering/icons/no_photo.inline.svg.ts +13 -0
  133. package/src/rendering/webgl/edge.arrowHead.ts +87 -0
  134. package/src/rendering/webgl/edge.clamped.ts +246 -0
  135. package/src/rendering/webgl/edge.reversedArrowHead.ts +14 -0
  136. package/src/rendering/webgl/helpers/__tests__/imageHelper.spec.ts +36 -0
  137. package/src/rendering/webgl/helpers/imageHelper.ts +13 -0
  138. package/src/rendering/webgl/image.ts +115 -0
  139. package/src/rendering/webgl/node.border.ts +78 -0
  140. package/src/rendering/webgl/node.buttons.ts +241 -0
  141. package/src/rendering/webgl/node.image.ts +169 -0
  142. package/src/rendering/webgl/shaders/edge.clamped.vert.glsl.ts +55 -0
  143. package/src/rendering/webgl/shaders/node.border.frag.glsl.ts +30 -0
  144. package/src/rendering/webgl/shaders/node.border.vert.glsl.ts +37 -0
  145. package/src/rendering/webgl/shaders/node.buttons.frag.glsl.ts +32 -0
  146. package/src/rendering/webgl/shaders/node.buttons.vert.glsl.ts +49 -0
  147. package/src/rendering/webgl/shaders/node.image.frag.glsl.ts +46 -0
  148. package/src/rendering/webgl/shaders/node.image.vert.glsl.ts +42 -0
  149. package/src/types/graphDataTypes.ts +143 -0
  150. package/src/types/sigmaCustomRenderersTypes.ts +13 -0
  151. package/src/utils/__tests__/graph.spec.ts +152 -0
  152. package/src/utils/__tests__/graphLayout.spec.ts +125 -0
  153. package/src/utils/__tests__/hopsData.spec.ts +456 -0
  154. package/src/utils/__tests__/processResponceToGraph.spec.ts +165 -0
  155. package/src/utils/graph.ts +83 -0
  156. package/src/utils/graphLayout.ts +70 -0
  157. package/src/utils/hopsData.ts +198 -0
  158. package/src/utils/processResponseToGraph.ts +32 -0
  159. package/tsconfig.json +4 -0
  160. package/webpack.config.js +10 -0
  161. package/bundle.js +0 -2
  162. package/bundle.js.LICENSE.txt +0 -54
  163. /package/{5271a9e7b6651c852e93.png → public/5271a9e7b6651c852e93.png} +0 -0
  164. /package/{main.css → public/main.css} +0 -0
@@ -0,0 +1,289 @@
1
+ import {renderHook, act} from '@testing-library/react-hooks';
2
+ import {useSigma, useRegisterEvents} from '@react-sigma/core';
3
+ import {MultiGraph} from 'graphology';
4
+ import {HopsResponse, RelationTypeDirection} from '@reltio/mdm-sdk';
5
+ import {drawTooltip} from '../../rendering/canvas/tooltip';
6
+ import {useSigmaCustomRenderers} from '../../contexts/SigmaCustomRenderersContext';
7
+ import {fillGraphWithHopsResponse} from '../../utils/graph';
8
+ import {useTooltipRenderer} from '../useTooltipRenderer';
9
+ import {getMousePos} from '../../utils/graphLayout';
10
+
11
+ jest.mock('@react-sigma/core');
12
+ jest.mock('../../contexts/SigmaCustomRenderersContext');
13
+ jest.mock('../../rendering/canvas/tooltip');
14
+ jest.mock('../../utils/graphLayout');
15
+
16
+ const data: HopsResponse = {
17
+ entities: [
18
+ {uri: 'node1', type: 'hcp', traversedRelations: 2, untraversedRelations: 0},
19
+ {uri: 'node2', type: 'hcp', traversedRelations: 3, untraversedRelations: 2},
20
+ {uri: 'node3', type: 'hca', traversedRelations: 1, untraversedRelations: 0}
21
+ ],
22
+ relations: [
23
+ {
24
+ uri: 'r1',
25
+ type: 'relType1',
26
+ startObject: {
27
+ objectURI: 'node1'
28
+ },
29
+ endObject: {
30
+ objectURI: 'node2'
31
+ },
32
+ direction: RelationTypeDirection.directed
33
+ },
34
+ {
35
+ uri: 'r2',
36
+ type: 'relType2',
37
+ startObject: {
38
+ objectURI: 'node2'
39
+ },
40
+ endObject: {
41
+ objectURI: 'node2'
42
+ },
43
+ direction: RelationTypeDirection.directed
44
+ },
45
+ {
46
+ uri: 'r3',
47
+ type: 'relType3',
48
+ startObject: {
49
+ objectURI: 'node2'
50
+ },
51
+ endObject: {
52
+ objectURI: 'node3'
53
+ },
54
+ direction: RelationTypeDirection.directed
55
+ },
56
+ {
57
+ uri: 'r4',
58
+ type: 'relType4',
59
+ startObject: {
60
+ objectURI: 'node3'
61
+ },
62
+ endObject: {
63
+ objectURI: 'node3'
64
+ },
65
+ direction: RelationTypeDirection.directed
66
+ }
67
+ ]
68
+ };
69
+
70
+ const edges = {
71
+ r1: {
72
+ size: 1,
73
+ color: 'rgba(0, 0, 0, 0.2)',
74
+ forceLabel: false,
75
+ loop: false,
76
+ relationTypeUri: 'relType1',
77
+ direction: RelationTypeDirection.directed,
78
+ relationLabel: 'relation label 1'
79
+ },
80
+ r2: {
81
+ size: 1,
82
+ color: 'rgba(0, 0, 0, 0.2)',
83
+ forceLabel: false,
84
+ loop: true,
85
+ hovered: true,
86
+ relationTypeUri: 'relType2',
87
+ direction: RelationTypeDirection.directed,
88
+ relationLabel: 'relation label 2'
89
+ },
90
+ r3: {
91
+ size: 1,
92
+ color: 'rgba(0, 0, 0, 0.2)',
93
+ forceLabel: false,
94
+ loop: false,
95
+ relationTypeUri: 'relType3',
96
+ direction: RelationTypeDirection.directed,
97
+ relationLabel: 'relation label 3'
98
+ },
99
+ r4: {
100
+ size: 1,
101
+ color: 'rgba(0, 0, 0, 0.2)',
102
+ forceLabel: true,
103
+ loop: true,
104
+ hovered: true,
105
+ relationTypeUri: 'relType4',
106
+ direction: RelationTypeDirection.directed,
107
+ relationLabel: 'relation label 4'
108
+ }
109
+ };
110
+
111
+ const nodes = {
112
+ node1: {
113
+ size: 12,
114
+ label: 'node 1',
115
+ x: 0.5,
116
+ y: 0.4
117
+ },
118
+ node2: {
119
+ size: 12,
120
+ label: 'node 2',
121
+ x: 0.6,
122
+ y: 0.6
123
+ },
124
+ node3: {
125
+ size: 12,
126
+ label: 'node 3',
127
+ x: 0.8,
128
+ y: 0.7,
129
+ highlighted: true
130
+ }
131
+ };
132
+
133
+ const startHook = (graph, selfRelationLoopsCanvas?, selfRelationsPathsMap?) =>
134
+ renderHook(
135
+ ({graph, selfRelationLoopsCanvas, selfRelationsPathsMap}) =>
136
+ useTooltipRenderer(graph, selfRelationLoopsCanvas, selfRelationsPathsMap),
137
+ {initialProps: {graph, selfRelationLoopsCanvas, selfRelationsPathsMap}}
138
+ );
139
+
140
+ const createGraph = () => {
141
+ const graph = new MultiGraph();
142
+ fillGraphWithHopsResponse(graph, data);
143
+ return graph;
144
+ };
145
+
146
+ describe('useTooltipRenderer tests', () => {
147
+ const getEdgeDisplayData = jest.fn().mockImplementation((edgeId) => edges[edgeId]);
148
+ const getNodeDisplayData = jest.fn().mockImplementation((nodeId) => nodes[nodeId]);
149
+
150
+ const addRenderers = jest.fn();
151
+ const registerEvents = jest.fn();
152
+
153
+ beforeEach(() => {
154
+ (useSigmaCustomRenderers as jest.Mock).mockReturnValue({addRenderers});
155
+ (useSigma as jest.Mock).mockReturnValue({getEdgeDisplayData, getNodeDisplayData});
156
+ (useRegisterEvents as jest.Mock).mockReturnValue(registerEvents);
157
+ (getMousePos as jest.Mock).mockImplementation((_canvas, event) => ({x: event.clientX, y: event.clientY}));
158
+ });
159
+
160
+ afterEach(() => {
161
+ jest.clearAllMocks();
162
+ });
163
+
164
+ const getTooltipsRenderer = () => addRenderers.mock.calls[addRenderers.mock.calls.length - 1][0].tooltips;
165
+ const getEnterEdge = () => registerEvents.mock.calls[registerEvents.mock.calls.length - 1][0].enterEdge;
166
+ const getLeaveEdge = () => registerEvents.mock.calls[registerEvents.mock.calls.length - 1][0].leaveEdge;
167
+ const getMousemove = () => registerEvents.mock.calls[registerEvents.mock.calls.length - 1][0].mousemove;
168
+
169
+ it('enterEdge event should work correctly', () => {
170
+ const graph = createGraph();
171
+ const {result} = startHook(graph);
172
+ const enterEdge = getEnterEdge();
173
+
174
+ expect(result.current).toBe(null);
175
+
176
+ act(() => {
177
+ enterEdge({edge: 'r1'});
178
+ });
179
+
180
+ expect(result.current).toBe('r1');
181
+ });
182
+
183
+ it('leaveEdge event should work correctly', () => {
184
+ const graph = createGraph();
185
+ const {result} = startHook(graph);
186
+ const enterEdge = getEnterEdge();
187
+ const leaveEdge = getLeaveEdge();
188
+
189
+ act(() => {
190
+ enterEdge({edge: 'r1'});
191
+ });
192
+
193
+ expect(result.current).toBe('r1');
194
+
195
+ act(() => {
196
+ leaveEdge();
197
+ });
198
+
199
+ expect(result.current).toBe(null);
200
+ });
201
+
202
+ it('mousemove event should work correctly if cursor over self relation loop', () => {
203
+ const selfRelationsPathsMap = {
204
+ r1: 'path1',
205
+ r2: 'path2'
206
+ };
207
+
208
+ const selfRelationLoopsCanvas = {
209
+ canvasElement: 'canvas',
210
+ context: {
211
+ isPointInStroke: jest.fn((val) => (val === 'path2' ? true : false))
212
+ }
213
+ };
214
+
215
+ const graph = createGraph();
216
+ const {result} = startHook(graph, selfRelationLoopsCanvas, selfRelationsPathsMap);
217
+ const mousemove = getMousemove();
218
+
219
+ expect(result.current).toBe(null);
220
+
221
+ act(() => {
222
+ const event = {x: 100, y: 100, original: {clientX: 120, clientY: 120}};
223
+ mousemove(event);
224
+ });
225
+
226
+ expect(result.current).toBe('r2');
227
+ });
228
+
229
+ it('mousemove event should work correctly if cursor not over self relation loop', () => {
230
+ const selfRelationsPathsMap = {
231
+ r1: 'path1',
232
+ r2: 'path2'
233
+ };
234
+
235
+ const selfRelationLoopsCanvas = {
236
+ canvasElement: 'canvas',
237
+ context: {
238
+ isPointInStroke: jest.fn().mockReturnValue(false)
239
+ }
240
+ };
241
+
242
+ const graph = createGraph();
243
+ const {result} = startHook(graph, selfRelationLoopsCanvas, selfRelationsPathsMap);
244
+ const mousemove = getMousemove();
245
+
246
+ expect(result.current).toBe(null);
247
+
248
+ act(() => {
249
+ const event = {x: 100, y: 100, original: {clientX: 120, clientY: 120}};
250
+ mousemove(event);
251
+ });
252
+
253
+ expect(result.current).toBe(null);
254
+ });
255
+
256
+ it('mousemove event should work correctly if selfRelationLoopsCanvas does not exists', () => {
257
+ const graph = createGraph();
258
+ const {result} = startHook(graph);
259
+ const mousemove = getMousemove();
260
+
261
+ act(() => {
262
+ const event = {x: 100, y: 100, original: {clientX: 120, clientY: 120}};
263
+ mousemove(event);
264
+ });
265
+
266
+ expect(getMousePos).toHaveBeenCalledTimes(0);
267
+ expect(result.current).toBe(null);
268
+ });
269
+
270
+ it('should draw tooltip correctly', () => {
271
+ const graph = createGraph();
272
+
273
+ startHook(graph);
274
+
275
+ const {renderer} = getTooltipsRenderer();
276
+ const mousemove = getMousemove();
277
+
278
+ act(() => {
279
+ mousemove({x: 100, y: 100, original: {clientX: 120, clientY: 120}});
280
+ });
281
+
282
+ act(() => {
283
+ renderer('context');
284
+ });
285
+
286
+ expect(drawTooltip).toHaveBeenCalledTimes(1);
287
+ expect(drawTooltip).toHaveBeenCalledWith('context', 100, 85, 'relation label 2');
288
+ });
289
+ });
@@ -0,0 +1,308 @@
1
+ import {useEffect, useState} from 'react';
2
+ import {useSelector} from 'react-redux';
3
+ import {memoizeWith} from 'ramda';
4
+ import {useLoadGraph, useRegisterEvents, useSetSettings, useSigma} from '@react-sigma/core';
5
+ import Graph from 'graphology';
6
+ import {animateNodes} from 'sigma/utils/animate';
7
+ import {toUndirected} from 'graphology-operators';
8
+ import {
9
+ Entity,
10
+ HopsResponse,
11
+ RelationTypeDirection,
12
+ getRelationType,
13
+ getLabel,
14
+ getEntityImage,
15
+ getRelationTypePropWithInheritance
16
+ } from '@reltio/mdm-sdk';
17
+ import mdm from '@reltio/mdm-module';
18
+ import {
19
+ EdgeAttributes,
20
+ Filters,
21
+ GraphologyLayout,
22
+ GraphologyWorkerLayoutConstructor,
23
+ GraphPathType,
24
+ NodeAttributes
25
+ } from '../types/graphDataTypes';
26
+ import {directionTypeToEdgeType, getPathToNode, mouseIsOnNode} from '../utils/graphLayout';
27
+ import {useSelfRelationLoopsRenderer} from './useSelfRelationLoopsRenderer';
28
+ import {useTooltipRenderer} from './useTooltipRenderer';
29
+
30
+ const memoizedGetEntityImage = memoizeWith((metadata, storagePath, entity) => entity.type, getEntityImage);
31
+
32
+ const SELECTED_NODE: Partial<NodeAttributes> = {
33
+ size: 24,
34
+ buttonSize: 8,
35
+ buttonOffset: [19, 19]
36
+ };
37
+
38
+ const DEFAULT_NODE: Partial<NodeAttributes> = {
39
+ size: 12,
40
+ buttonSize: 6,
41
+ buttonOffset: [8, 13]
42
+ };
43
+
44
+ const WORKER_LAYOUT_ANIMATION_DURATION = 5000;
45
+ const SYNC_LAYOUT_ANIMATION_DURATION = 1000;
46
+
47
+ type Props = {
48
+ data: HopsResponse;
49
+ graph: Graph;
50
+ syncLayout?: GraphologyLayout<Record<string, unknown>>;
51
+ workerLayout?: GraphologyWorkerLayoutConstructor<Record<string, unknown>>;
52
+ layoutSettings?: Record<string, unknown>;
53
+ isDirected?: boolean;
54
+ selectedNode: string;
55
+ onNodeClick: (node: string) => void;
56
+ onNodeCollapse: (node: string, path: GraphPathType) => void;
57
+ onNodeExpand: (node: string) => void;
58
+ filters?: Filters;
59
+ };
60
+
61
+ export const useGraphLayout = ({
62
+ data,
63
+ graph,
64
+ syncLayout,
65
+ workerLayout,
66
+ layoutSettings,
67
+ selectedNode,
68
+ isDirected = false,
69
+ onNodeClick,
70
+ onNodeCollapse,
71
+ onNodeExpand,
72
+ filters
73
+ }: Props): void => {
74
+ const sigma = useSigma();
75
+ const loadGraph = useLoadGraph();
76
+ const registerEvents = useRegisterEvents();
77
+ const setSettings = useSetSettings();
78
+
79
+ const {selfRelationsPathsMap, selfRelationLoopsCanvas} = useSelfRelationLoopsRenderer(graph);
80
+ const hoveredEdge = useTooltipRenderer(graph, selfRelationLoopsCanvas, selfRelationsPathsMap);
81
+
82
+ const entity: Entity = useSelector(mdm.selectors.getEntity);
83
+ const metadata = useSelector(mdm.selectors.getMetadata);
84
+ const absoluteImagePath = useSelector(mdm.selectors.getAbsoluteImagePath) || '';
85
+
86
+ const [hoveredNode, setHoveredNode] = useState<string>(undefined);
87
+ const [undirectedGraph, setUndirectedGraph] = useState<Graph>(null);
88
+ const [selectedPath, setSelectedPath] = useState<GraphPathType>({});
89
+
90
+ const entityUri = entity?.uri;
91
+
92
+ useEffect(() => {
93
+ sigma.getCamera().setState({ratio: 1.2});
94
+ }, [graph, sigma]);
95
+
96
+ useEffect(() => {
97
+ setUndirectedGraph(toUndirected(graph));
98
+ loadGraph(graph);
99
+ if (syncLayout) {
100
+ const positions = syncLayout(sigma.getGraph(), layoutSettings);
101
+ animateNodes(sigma.getGraph(), positions, {duration: SYNC_LAYOUT_ANIMATION_DURATION});
102
+ }
103
+ if (workerLayout) {
104
+ const worker = new workerLayout(sigma.getGraph(), layoutSettings);
105
+ worker.start();
106
+ const timer = setTimeout(() => {
107
+ worker.stop();
108
+ }, WORKER_LAYOUT_ANIMATION_DURATION);
109
+ return () => {
110
+ clearTimeout(timer);
111
+ worker.kill();
112
+ };
113
+ }
114
+ }, [data, graph, syncLayout, workerLayout, sigma, layoutSettings, loadGraph, isDirected]);
115
+
116
+ useEffect(() => {
117
+ const pathToNode = getPathToNode({
118
+ graph: sigma.getGraph(),
119
+ undirectedGraph,
120
+ selectedNode,
121
+ rootNodeUri: entityUri
122
+ });
123
+ setSelectedPath(pathToNode);
124
+ }, [selectedNode, entityUri, sigma, undirectedGraph]);
125
+
126
+ useEffect(() => {
127
+ registerEvents({
128
+ enterNode: ({node}) => {
129
+ setHoveredNode(node);
130
+ },
131
+ leaveNode: () => {
132
+ setHoveredNode(undefined);
133
+ },
134
+ clickNode: ({node}) => {
135
+ onNodeClick(node);
136
+ },
137
+ click: (event) => {
138
+ const findClosestNode = ({x, y}) => {
139
+ const coordForGraph = sigma.viewportToGraph({x, y});
140
+ const graph = sigma.getGraph();
141
+ const closestNodes = graph
142
+ .nodes()
143
+ .map((nodeId) => {
144
+ const attrs = graph.getNodeAttributes(nodeId);
145
+ const distance =
146
+ Math.pow(coordForGraph.x - attrs.x, 2) + Math.pow(coordForGraph.y - attrs.y, 2);
147
+ return {nodeId, distance, hidden: attrs.hidden};
148
+ })
149
+ .sort((a, b) => a.distance - b.distance)
150
+ .filter(({hidden}) => !hidden);
151
+
152
+ return closestNodes.length > 0 ? closestNodes[0].nodeId : null;
153
+ };
154
+
155
+ const nodeId = findClosestNode(event);
156
+
157
+ if (!nodeId) return;
158
+
159
+ const closestNode = sigma.getNodeDisplayData(nodeId) as NodeAttributes;
160
+
161
+ const center = sigma.framedGraphToViewport({
162
+ x: closestNode.x,
163
+ y: closestNode.y
164
+ });
165
+
166
+ const dx = sigma.scaleSize(closestNode.buttonOffset[0]);
167
+ const dy = sigma.scaleSize(closestNode.buttonOffset[1]);
168
+ const buttonSize = sigma.scaleSize(closestNode.buttonSize);
169
+
170
+ const isExpandClicked = mouseIsOnNode(event.x, event.y, center.x + dx, center.y - dy, buttonSize);
171
+ const isCollapseClicked = mouseIsOnNode(event.x, event.y, center.x + dx, center.y + dy, buttonSize);
172
+
173
+ if (closestNode.showExpand && isExpandClicked) {
174
+ onNodeExpand(nodeId);
175
+ onNodeClick(nodeId);
176
+ }
177
+
178
+ if (closestNode.showCollapse && isCollapseClicked) {
179
+ const pathToNode = getPathToNode({
180
+ graph: sigma.getGraph(),
181
+ undirectedGraph,
182
+ selectedNode: nodeId,
183
+ rootNodeUri: entityUri
184
+ });
185
+ onNodeCollapse(nodeId, pathToNode);
186
+ onNodeClick(nodeId);
187
+ }
188
+ }
189
+ });
190
+ }, [sigma, registerEvents, onNodeClick, onNodeCollapse, onNodeExpand, entityUri, undirectedGraph]);
191
+
192
+ useEffect(() => {
193
+ const graph = sigma.getGraph();
194
+ let selectedNodeNeighbours: string[] | undefined;
195
+ try {
196
+ selectedNodeNeighbours = graph.neighbors(selectedNode);
197
+ } catch (e) {
198
+ //do nothing
199
+ }
200
+
201
+ setSettings({
202
+ nodeReducer: (node: string, data: NodeAttributes) => {
203
+ const {entityTypesUris = []} = filters || {};
204
+ const pathToIcon = memoizedGetEntityImage(metadata, absoluteImagePath, {type: data.entityTypeUri});
205
+
206
+ const neighbors = graph.neighbors(node);
207
+ const hiddenNeighborsCount = neighbors
208
+ .map((nodeId) => graph.getNodeAttribute(nodeId, 'hidden'))
209
+ .filter((x) => !!x).length;
210
+
211
+ const isNodeInPath = (selectedPath.nodes || []).includes(node);
212
+ const isHoveredNode = hoveredNode === node;
213
+ const isRootNode = node === entityUri;
214
+ const isSelectedNode = node === selectedNode;
215
+ const isInactiveNode =
216
+ selectedNodeNeighbours &&
217
+ !selectedNodeNeighbours.includes(node) &&
218
+ selectedNode !== node &&
219
+ selectedNode !== entityUri &&
220
+ !isNodeInPath;
221
+ const isHiddenNode =
222
+ ![selectedNode, entityUri].includes(node) &&
223
+ entityTypesUris.length > 0 &&
224
+ !entityTypesUris.includes(data.entityTypeUri);
225
+
226
+ const nodeProps = node === selectedNode ? SELECTED_NODE : DEFAULT_NODE;
227
+
228
+ const newData: NodeAttributes = {
229
+ ...data,
230
+ ...nodeProps,
231
+ color: '#0072CE',
232
+ image: pathToIcon,
233
+ showExpand: data.untraversedRelationsCount > 0 || hiddenNeighborsCount > 0,
234
+ showCollapse: data.traversedRelationsCount > 1 && neighbors.length - hiddenNeighborsCount > 1
235
+ };
236
+
237
+ if (isNodeInPath || isSelectedNode || isRootNode) {
238
+ newData.inPath = true;
239
+ newData.color = isRootNode ? '#00FFFF' : data.color;
240
+ } else if (isInactiveNode) {
241
+ newData.label = !isHoveredNode ? '' : data.label;
242
+ newData.inactive = true;
243
+ }
244
+
245
+ if (isHoveredNode) {
246
+ newData.highlighted = true;
247
+ }
248
+
249
+ if (isHiddenNode) {
250
+ newData.hidden = true;
251
+ }
252
+
253
+ return newData;
254
+ },
255
+ edgeReducer: (edge: string, data: EdgeAttributes) => {
256
+ const {relationTypesUris = []} = filters || {};
257
+ const newData: EdgeAttributes = {...data, hidden: false};
258
+ const isEdgeInPath = (selectedPath.edges || []).includes(edge);
259
+ const relationType = getRelationType(metadata, data.relationTypeUri);
260
+ const relationTypeColor = getRelationTypePropWithInheritance(metadata, relationType, 'typeColor');
261
+ const selectedNodeIsNotRoot = selectedNode && selectedNode !== entityUri;
262
+ const isHiddenEdge =
263
+ (selectedNodeIsNotRoot && !graph.hasExtremity(edge, selectedNode) && !isEdgeInPath) ||
264
+ (relationTypesUris.length > 0 && !relationTypesUris.includes(data.relationTypeUri));
265
+
266
+ if (relationTypeColor) {
267
+ newData.color = relationTypeColor;
268
+ }
269
+
270
+ if (hoveredEdge === edge) {
271
+ newData.hovered = true;
272
+ }
273
+
274
+ if (isHiddenEdge) {
275
+ newData.hidden = true;
276
+ }
277
+
278
+ if (isDirected) {
279
+ newData.arrowSizeRatio = 4;
280
+ }
281
+
282
+ if (isEdgeInPath) {
283
+ newData.color = '#0072CE';
284
+ newData.size = 4;
285
+ newData.arrowSizeRatio = isDirected ? 1.4 : data.arrowSizeRatio;
286
+ }
287
+
288
+ newData.type = directionTypeToEdgeType(isDirected ? data.direction : RelationTypeDirection.undirected);
289
+ newData.relationLabel = getLabel(relationType?.label);
290
+
291
+ return newData;
292
+ },
293
+ enableEdgeHoverEvents: true
294
+ });
295
+ }, [
296
+ sigma,
297
+ setSettings,
298
+ hoveredNode,
299
+ selectedNode,
300
+ selectedPath,
301
+ entityUri,
302
+ metadata,
303
+ absoluteImagePath,
304
+ isDirected,
305
+ filters,
306
+ hoveredEdge
307
+ ]);
308
+ };
@@ -0,0 +1,19 @@
1
+ import {useState, useEffect} from 'react';
2
+ import {GraphLayout} from '@reltio/mdm-sdk';
3
+ import {GraphState} from '../types/graphDataTypes';
4
+
5
+ export const useGraphLayoutSelector = (graphLayout?: GraphLayout): Pick<GraphState, 'layout' | 'setLayout'> => {
6
+ const [layout, setLayout] = useState<GraphLayout>(GraphLayout.SIMPLE_NETWORK);
7
+
8
+ useEffect(() => {
9
+ const isCorrectGraphLayout = Object.values(GraphLayout).some((layout) => graphLayout === layout);
10
+ if (isCorrectGraphLayout) {
11
+ setLayout(graphLayout);
12
+ }
13
+ }, [graphLayout]);
14
+
15
+ return {
16
+ layout,
17
+ setLayout
18
+ };
19
+ };