@reltio/sources 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.
- package/index.ts +1 -0
- package/package.json +38 -21
- package/public/bundle.js +205 -0
- package/public/bundle.js.LICENSE.txt +79 -0
- package/public/package.json +22 -0
- package/scripts/build/index.js +20 -0
- package/src/components/CrosswalkTreeView/CrosswalkTreeView.tsx +47 -0
- package/src/components/CrosswalkTreeView/__tests__/CrosswalkTreeView.spec.tsx +72 -0
- package/src/components/CrosswalkTreeView/__tests__/helpers.spec.ts +961 -0
- package/src/components/CrosswalkTreeView/helpers.ts +91 -0
- package/src/components/CrosswalkTreeView/styles.ts +10 -0
- package/src/components/EmptyState/EmptyState.tsx +20 -0
- package/src/components/EmptyState/__tests__/EmptyState.test.tsx +13 -0
- package/src/components/EmptyState/icons/EmptySources.svg +43 -0
- package/src/components/EmptyState/styles.ts +19 -0
- package/src/components/LegendTable/LegendTable.tsx +124 -0
- package/src/components/LegendTable/LegendTableRow/ContributorRow/ContributorRow.tsx +46 -0
- package/src/components/LegendTable/LegendTableRow/ContributorRow/__tests__/ContributorRow.test.tsx +103 -0
- package/src/components/LegendTable/LegendTableRow/ContributorRow/styles.ts +31 -0
- package/src/components/LegendTable/LegendTableRow/LegendTableRow.tsx +179 -0
- package/src/components/LegendTable/LegendTableRow/__tests__/LegendTableRow.test.tsx +371 -0
- package/src/components/LegendTable/LegendTableRow/styles.ts +31 -0
- package/src/components/LegendTable/__tests__/LegendTable.test.tsx +273 -0
- package/src/components/LegendTable/__tests__/helpers.test.ts +541 -0
- package/src/components/LegendTable/helpers.ts +64 -0
- package/src/components/LegendTable/styles.ts +17 -0
- package/src/components/LegendView/LegendView.tsx +201 -0
- package/src/components/LegendView/__tests__/LegendView.test.tsx +307 -0
- package/src/components/LegendView/styles.ts +51 -0
- package/src/components/RightSidePanel/RightSidePanel.tsx +25 -0
- package/src/components/RightSidePanel/__tests__/RightSidePanel.test.tsx +28 -0
- package/src/components/RightSidePanel/styles.ts +14 -0
- package/src/components/RulesestsSelector/RulesestsSelector.tsx +31 -0
- package/src/components/RulesestsSelector/RulesetOption/RulesetOption.tsx +25 -0
- package/src/components/RulesestsSelector/RulesetOption/__tests__/RulesetOption.test.tsx +25 -0
- package/src/components/RulesestsSelector/RulesetOption/styles.ts +27 -0
- package/src/components/RulesestsSelector/RulesetsMenuList/RulesetsMenuList.tsx +14 -0
- package/src/components/RulesestsSelector/__tests__/RulesetsSelector.test.tsx +91 -0
- package/src/components/RulesestsSelector/styles.ts +7 -0
- package/src/components/SankeyChart/Link.tsx +31 -0
- package/src/components/SankeyChart/MergeTooltip.tsx +37 -0
- package/src/components/SankeyChart/Node.tsx +137 -0
- package/src/components/SankeyChart/SankeyChart.tsx +103 -0
- package/src/components/SankeyChart/__tests__/Link.spec.tsx +127 -0
- package/src/components/SankeyChart/__tests__/MergeTooltip.spec.tsx +22 -0
- package/src/components/SankeyChart/__tests__/Node.spec.tsx +248 -0
- package/src/components/SankeyChart/__tests__/SankeyChart.spec.tsx +161 -0
- package/src/components/SankeyChart/__tests__/__snapshots__/SankeyChart.spec.tsx.snap +183 -0
- package/src/components/SankeyChart/sankey.ts +322 -0
- package/src/components/SankeyChart/styles.ts +41 -0
- package/src/components/SankeyChart/useSankey.tsx +44 -0
- package/src/components/SelectableTree/SelectableTree.tsx +46 -0
- package/src/components/SelectableTree/__tests__/SelectableTree.test.tsx +28 -0
- package/src/components/SelectableTree/__tests__/__snapshots__/SelectableTree.test.tsx.snap +59 -0
- package/src/components/SelectableTree/styles.ts +42 -0
- package/src/components/SourcesView/SourcesView.tsx +318 -0
- package/src/components/SourcesView/__tests__/IntegrationSourcesView.spec.tsx +199 -0
- package/src/components/SourcesView/__tests__/SourcesView.spec.tsx +903 -0
- package/src/components/SourcesView/__tests__/data/metadata.data.ts +27 -0
- package/src/components/SourcesView/__tests__/data/store.data.ts +65 -0
- package/src/components/SourcesView/icons/LegendIcon.svg +8 -0
- package/src/components/SourcesView/index.tsx +57 -0
- package/src/components/SourcesView/styles.ts +44 -0
- package/src/components/TreeActionSnackbar/TreeActionSnackbar.tsx +38 -0
- package/src/components/TreeActionSnackbar/__tests__/TreeActionSnackbar.test.tsx +38 -0
- package/src/components/TreeActionSnackbar/styles.ts +11 -0
- package/src/components/UnmergeDialog/UnmergeDialog.tsx +78 -0
- package/src/components/UnmergeDialog/__tests__/UnmergeDialog.test.tsx +124 -0
- package/src/components/UnmergeDialog/__tests__/testData.data.ts +271 -0
- package/src/components/UnmergeDialog/styles.ts +34 -0
- package/src/components/UnsubscribeDialog/UnsubscribeDialog.tsx +64 -0
- package/src/components/UnsubscribeDialog/__tests__/UnsubscribeDialog.test.tsx +59 -0
- package/src/components/UnsubscribeDialog/styles.ts +31 -0
- package/src/hooks/__tests__/useCrosswalkTree.specs.tsx +68 -0
- package/src/hooks/__tests__/useCrosswalkTreeActions.specs.ts +110 -0
- package/src/hooks/__tests__/useCrosswalksDisplay.specs.tsx +213 -0
- package/src/hooks/__tests__/useEntityWithSurvivorshipGroups.specs.ts +160 -0
- package/src/hooks/useCrosswalkTree.ts +38 -0
- package/src/hooks/useCrosswalkTreeActions.ts +83 -0
- package/src/hooks/useCrosswalksDisplay.ts +110 -0
- package/src/hooks/useEntityWithSurvivorshipGroups.tsx +51 -0
- package/src/icons/notMatch.svg +14 -0
- package/src/index.ts +1 -0
- package/src/services/errors.ts +11 -0
- package/src/types/LegendMode.ts +5 -0
- package/src/types/SankeyChartData.ts +31 -0
- package/src/types/SourcesSavedState.ts +3 -0
- package/src/types/UnmergeContributorEvent.ts +7 -0
- package/src/types/UnsubscribeContributorEvent.ts +6 -0
- package/src/types/index.ts +4 -0
- package/tsconfig.json +4 -0
- package/webpack.config.js +10 -0
- package/bundle.js +0 -2
- package/bundle.js.LICENSE.txt +0 -42
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {sum, map, pipe, groupBy, toPairs, sort, nth, prop, reduce, last} from 'ramda';
|
|
2
|
+
import {SankeyLink, SankeyData} from '../../types/SankeyChartData';
|
|
3
|
+
|
|
4
|
+
const sumValues = (links: SankeyLink[]): number => pipe(map(prop('value')), sum)(links);
|
|
5
|
+
const interpolateNumber = (a: number, b: number) => ((a = +a), (b = +b), (t) => a * (1 - t) + b * t);
|
|
6
|
+
|
|
7
|
+
export default function Sankey() {
|
|
8
|
+
const lastLinksSize = 22;
|
|
9
|
+
|
|
10
|
+
let nodeWidth = 24,
|
|
11
|
+
smallestNodeMaxHeight = 15,
|
|
12
|
+
smallestNodeMinHeight = 10,
|
|
13
|
+
nodePadding = 8,
|
|
14
|
+
size = [1, 1] as [number, number];
|
|
15
|
+
|
|
16
|
+
function sankey({nodes, links}: SankeyData) {
|
|
17
|
+
const graph = {nodes, links};
|
|
18
|
+
computeNodeLinks(graph);
|
|
19
|
+
computeNodeValues(graph);
|
|
20
|
+
computeNodeBreadths(graph);
|
|
21
|
+
computeNodeDepths(graph);
|
|
22
|
+
computeLinkDepths(graph);
|
|
23
|
+
correctNodesDepth(graph);
|
|
24
|
+
return graph;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
sankey.nodeWidth = () => nodeWidth;
|
|
28
|
+
sankey.setNodeWidth = (width: number) => ((nodeWidth = +width), sankey);
|
|
29
|
+
|
|
30
|
+
sankey.nodePadding = () => nodePadding;
|
|
31
|
+
sankey.setNodePadding = (padding: number) => ((nodePadding = +padding), sankey);
|
|
32
|
+
|
|
33
|
+
sankey.smallestNodeMaxHeight = () => smallestNodeMaxHeight;
|
|
34
|
+
sankey.setSmallestNodeMaxHeight = (height: number) => ((smallestNodeMaxHeight = +height), sankey);
|
|
35
|
+
|
|
36
|
+
sankey.smallestNodeMinHeight = () => smallestNodeMinHeight;
|
|
37
|
+
sankey.setSmallestNodeMinHeight = (height: number) => ((smallestNodeMinHeight = +height), sankey);
|
|
38
|
+
|
|
39
|
+
sankey.size = () => size;
|
|
40
|
+
sankey.setSize = (newSize: [number, number]) => ((size = newSize), sankey);
|
|
41
|
+
|
|
42
|
+
sankey.update = (graph) => {
|
|
43
|
+
computeLinkDepths(graph);
|
|
44
|
+
return graph;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
sankey.link = () => {
|
|
48
|
+
let curvature = 0.5;
|
|
49
|
+
|
|
50
|
+
function link(d) {
|
|
51
|
+
const lastLine = !d.target.sourceLinks || d.target.sourceLinks.length == 0;
|
|
52
|
+
const addPixels = 0.13;
|
|
53
|
+
const x0 = d.source.x + d.source.dx,
|
|
54
|
+
x1 = d.target.x,
|
|
55
|
+
xi = interpolateNumber(x0, x1),
|
|
56
|
+
x2 = xi(curvature),
|
|
57
|
+
x3 = xi(1 - curvature),
|
|
58
|
+
y0 = d.source.y + d.sy - addPixels,
|
|
59
|
+
y1 = d.target.y + d.ty - addPixels;
|
|
60
|
+
const appendix = !lastLine
|
|
61
|
+
? `L${x1 + nodeWidth},${y1} L${x1 + nodeWidth},${y1 + d.dy + 2 * addPixels}`
|
|
62
|
+
: '';
|
|
63
|
+
|
|
64
|
+
return y0 === y1
|
|
65
|
+
? `M${x0},${y0}
|
|
66
|
+
L${x1},${y1}${appendix}
|
|
67
|
+
L${x1},${y1 + d.dy + 2 * addPixels}
|
|
68
|
+
L${x0},${y0 + d.dy + 2 * addPixels} Z`
|
|
69
|
+
: `M${x0},${y0}
|
|
70
|
+
C${x2},${y0} ${x3},${y1} ${x1},${y1}${appendix}
|
|
71
|
+
L${x1},${y1 + d.dy + 2 * addPixels}
|
|
72
|
+
C${x3},${y1 + d.dy + 2 * addPixels} ${x2},${y0 + d.dy + 2 * addPixels}
|
|
73
|
+
${x0},${y0 + d.dy + 2 * addPixels} Z`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
link.curvature = function (_) {
|
|
77
|
+
if (!arguments.length) return curvature;
|
|
78
|
+
curvature = +_;
|
|
79
|
+
return link;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return link;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Populate the sourceLinks and targetLinks for each node.
|
|
86
|
+
function computeNodeLinks({nodes, links}: SankeyData) {
|
|
87
|
+
nodes.forEach((node) => {
|
|
88
|
+
node.sourceLinks = [];
|
|
89
|
+
node.targetLinks = [];
|
|
90
|
+
});
|
|
91
|
+
links.forEach((link) => {
|
|
92
|
+
link.source.sourceLinks.push(link);
|
|
93
|
+
link.target.targetLinks.push(link);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Compute the value (size) of each node by summing the associated links.
|
|
98
|
+
function computeNodeValues({nodes}: SankeyData) {
|
|
99
|
+
nodes.forEach((node) => {
|
|
100
|
+
node.value = Math.max(sumValues(node.sourceLinks), sumValues(node.targetLinks));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Iteratively assign the breadth (x-position) for each node.
|
|
105
|
+
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
|
|
106
|
+
// nodes with no incoming links are assigned breadth zero, while
|
|
107
|
+
// nodes with no outgoing links are assigned the maximum breadth.
|
|
108
|
+
function computeNodeBreadths({nodes}: SankeyData) {
|
|
109
|
+
if (!nodes.length) return;
|
|
110
|
+
let remainingNodes = [nodes[0]],
|
|
111
|
+
nextNodes,
|
|
112
|
+
x = 0;
|
|
113
|
+
|
|
114
|
+
while (remainingNodes.length) {
|
|
115
|
+
nextNodes = [];
|
|
116
|
+
remainingNodes.forEach((node) => {
|
|
117
|
+
node.x = x;
|
|
118
|
+
node.dx = nodeWidth;
|
|
119
|
+
node.targetLinks.forEach((link) => {
|
|
120
|
+
if (nextNodes.indexOf(link.source) < 0) {
|
|
121
|
+
nextNodes.push(link.source);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
remainingNodes = nextNodes;
|
|
126
|
+
++x;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
nodes.forEach((node) => {
|
|
130
|
+
node.x = x - 1 - node.x;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (x > 2) {
|
|
134
|
+
const w = (size[0] - 2 * nodeWidth - lastLinksSize) / (x - 2);
|
|
135
|
+
nodes.forEach((node) => {
|
|
136
|
+
if (!node.sourceLinks || node.sourceLinks.length == 0) node.x = size[0] - nodeWidth;
|
|
137
|
+
else node.x *= w;
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
nodes[0].x = size[0] - nodeWidth;
|
|
141
|
+
nodes[1].x = nodes[0].x - lastLinksSize - nodeWidth;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function computeNodeDepths({nodes, links}: SankeyData) {
|
|
146
|
+
const nodesByBreadth = pipe(
|
|
147
|
+
groupBy(prop('x')),
|
|
148
|
+
toPairs,
|
|
149
|
+
sort((a, b) => a[0] - b[0]),
|
|
150
|
+
map(nth(1))
|
|
151
|
+
)(nodes);
|
|
152
|
+
|
|
153
|
+
//
|
|
154
|
+
initializeNodeDepth();
|
|
155
|
+
resolveCollisions();
|
|
156
|
+
|
|
157
|
+
const max = pipe(
|
|
158
|
+
map((nodes) => (nodes.length - 1) * nodePadding + sum(map((node) => node.dy, nodes))),
|
|
159
|
+
reduce(Math.max, 0)
|
|
160
|
+
)(nodesByBreadth);
|
|
161
|
+
|
|
162
|
+
const y1 = (size[1] - max) / 2;
|
|
163
|
+
|
|
164
|
+
nodesByBreadth.forEach((nodes) => {
|
|
165
|
+
const {y, dy} = last(nodes);
|
|
166
|
+
const y0 = size[1] - (y + dy);
|
|
167
|
+
nodes.forEach((node) => {
|
|
168
|
+
node.y += y0;
|
|
169
|
+
if (y1 > 0) node.y -= y1;
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
function initializeNodeDepth() {
|
|
174
|
+
let ky = pipe(
|
|
175
|
+
map((nodes) => (size[1] - (nodes.length - 1) * nodePadding) / sumValues(nodes)),
|
|
176
|
+
reduce(Math.min, Infinity)
|
|
177
|
+
)(nodesByBreadth);
|
|
178
|
+
|
|
179
|
+
const min = pipe(
|
|
180
|
+
map((node) => node.value / (node.targetLinks?.length || 1)),
|
|
181
|
+
reduce(Math.min, Infinity)
|
|
182
|
+
)(nodes);
|
|
183
|
+
|
|
184
|
+
ky = Math.max(Math.min(ky, smallestNodeMaxHeight / min), smallestNodeMinHeight / min);
|
|
185
|
+
|
|
186
|
+
nodesByBreadth.forEach((nodes) => {
|
|
187
|
+
nodes.forEach((node, i) => {
|
|
188
|
+
node.y = i;
|
|
189
|
+
node.dy = node.value * ky;
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
links.forEach((link) => {
|
|
194
|
+
link.dy = link.value * ky;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function resolveCollisions() {
|
|
199
|
+
nodesByBreadth.forEach((nodes) => {
|
|
200
|
+
let node,
|
|
201
|
+
dy,
|
|
202
|
+
y0 = 0,
|
|
203
|
+
i;
|
|
204
|
+
const n = nodes.length;
|
|
205
|
+
|
|
206
|
+
// Push any overlapping nodes down.
|
|
207
|
+
nodes.sort((a, b) => a.y - b.y);
|
|
208
|
+
for (i = 0; i < n; ++i) {
|
|
209
|
+
node = nodes[i];
|
|
210
|
+
dy = y0 - node.y;
|
|
211
|
+
if (dy > 0) node.y += dy;
|
|
212
|
+
y0 = node.y + node.dy + nodePadding;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// If the bottommost node goes outside the bounds, push it back up.
|
|
216
|
+
dy = y0 - nodePadding - size[1];
|
|
217
|
+
if (dy > 0) {
|
|
218
|
+
y0 = node.y -= dy;
|
|
219
|
+
|
|
220
|
+
// Push any overlapping nodes back up.
|
|
221
|
+
for (i = n - 2; i >= 0; --i) {
|
|
222
|
+
node = nodes[i];
|
|
223
|
+
dy = node.y + node.dy + nodePadding - y0;
|
|
224
|
+
if (dy > 0) node.y -= dy;
|
|
225
|
+
y0 = node.y;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function computeLinkDepths({nodes}: SankeyData) {
|
|
233
|
+
const nodesByBreadth = pipe(
|
|
234
|
+
groupBy(prop('x')),
|
|
235
|
+
toPairs,
|
|
236
|
+
sort((a: number, b: number) => a[0] - b[0]),
|
|
237
|
+
map(nth(1))
|
|
238
|
+
)(nodes);
|
|
239
|
+
|
|
240
|
+
nodesByBreadth.forEach((nodes) => {
|
|
241
|
+
nodes.forEach((node) => {
|
|
242
|
+
if (node.targetLinks?.length) {
|
|
243
|
+
const result = [];
|
|
244
|
+
const linksByUuid = node.targetLinks.map((l) => l.crosswalk.uuid);
|
|
245
|
+
const sourceNodes = [];
|
|
246
|
+
node.targetLinks.forEach(function (l) {
|
|
247
|
+
if (this.indexOf(l.source) == -1) this.push(l.source);
|
|
248
|
+
}, sourceNodes);
|
|
249
|
+
for (let i = 0; i < sourceNodes.length; i++)
|
|
250
|
+
for (let j = 0; j < sourceNodes[i].sourceLinks.length; j++) {
|
|
251
|
+
const index = linksByUuid.indexOf(sourceNodes[i].sourceLinks[j].crosswalk.uuid);
|
|
252
|
+
if (index != -1) result.push(node.targetLinks[index]);
|
|
253
|
+
}
|
|
254
|
+
node.targetLinks = result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (node.sourceLinks?.length) {
|
|
258
|
+
const result = [];
|
|
259
|
+
if (node.targetLinks?.length) {
|
|
260
|
+
const linksByUuid = node.sourceLinks.map((l) => l.crosswalk.uuid);
|
|
261
|
+
for (let i = 0; i < node.targetLinks.length; i++) {
|
|
262
|
+
const index = linksByUuid.indexOf(node.targetLinks[i].crosswalk.uuid);
|
|
263
|
+
if (index != -1) result.push(node.sourceLinks[index]);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const resultByUuid = result.map((l) => l.crosswalk.uuid);
|
|
268
|
+
const newSources = node.sourceLinks.filter(function (l) {
|
|
269
|
+
return this.indexOf(l.crosswalk.uuid) == -1;
|
|
270
|
+
}, resultByUuid);
|
|
271
|
+
|
|
272
|
+
node.sourceLinks = newSources.concat(result);
|
|
273
|
+
if (result.length > 0) {
|
|
274
|
+
let sizeForNewSources = 0;
|
|
275
|
+
for (let i = 0; i < newSources.length; i++) sizeForNewSources += newSources[i].dy;
|
|
276
|
+
node.sizeForNewSources = sizeForNewSources;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
nodes.forEach((node) => {
|
|
283
|
+
let sy = 0,
|
|
284
|
+
ty = node.sizeForNewSources || 0;
|
|
285
|
+
delete node.sizeForNewSources;
|
|
286
|
+
node.sourceLinks.forEach((link) => {
|
|
287
|
+
link.sy = sy;
|
|
288
|
+
sy += link.dy;
|
|
289
|
+
});
|
|
290
|
+
node.targetLinks.forEach((link) => {
|
|
291
|
+
link.ty = ty;
|
|
292
|
+
ty += link.dy;
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function correctNodesDepth({nodes}: SankeyData) {
|
|
298
|
+
const nodesByBreadth = pipe(
|
|
299
|
+
groupBy(prop('x')),
|
|
300
|
+
toPairs,
|
|
301
|
+
sort((a: number, b: number) => b[0] - a[0]),
|
|
302
|
+
map(nth(1))
|
|
303
|
+
)(nodes);
|
|
304
|
+
|
|
305
|
+
nodesByBreadth.forEach((nodes) => {
|
|
306
|
+
nodes = nodes.concat().sort((a, b) => a.y - b.y);
|
|
307
|
+
nodes.forEach((node, i) => {
|
|
308
|
+
if (node.sourceLinks?.length) {
|
|
309
|
+
const {target, ty, dy} = last(node.sourceLinks);
|
|
310
|
+
let y0 = target.y + ty + dy - node.dy;
|
|
311
|
+
if (nodes[i - 1]?.x == node.x && y0 < nodes[i - 1]?.y + nodes[i - 1]?.dy + nodePadding)
|
|
312
|
+
y0 = nodes[i - 1].y + nodes[i - 1].dy + nodePadding;
|
|
313
|
+
if (node.y > y0) {
|
|
314
|
+
node.y = y0;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return sankey;
|
|
322
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {makeStyles} from '@mui/styles';
|
|
2
|
+
|
|
3
|
+
export const useStyles = makeStyles({
|
|
4
|
+
tooltipTitle: {
|
|
5
|
+
fontWeight: 500,
|
|
6
|
+
fontSize: '16px',
|
|
7
|
+
lineHeight: '24px',
|
|
8
|
+
letterSpacing: '0.15px',
|
|
9
|
+
marginBottom: '8px',
|
|
10
|
+
userSelect: 'none'
|
|
11
|
+
},
|
|
12
|
+
tooltipRow: {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
fontSize: '13px',
|
|
16
|
+
lineHeight: '15px',
|
|
17
|
+
fontWeight: 'normal',
|
|
18
|
+
'&:not(:last-child)': {
|
|
19
|
+
marginBottom: '4px'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
tooltipIcon: {
|
|
23
|
+
fontSize: '18px',
|
|
24
|
+
color: 'rgba(0, 0, 0, 0.54)',
|
|
25
|
+
marginRight: '8px'
|
|
26
|
+
},
|
|
27
|
+
node: {
|
|
28
|
+
stroke: '#000000',
|
|
29
|
+
strokeWidth: 1,
|
|
30
|
+
strokeOpacity: 0.38
|
|
31
|
+
},
|
|
32
|
+
rootNode: {
|
|
33
|
+
transform: 'rotate(90deg)',
|
|
34
|
+
userSelect: 'none',
|
|
35
|
+
fontSize: '13px'
|
|
36
|
+
},
|
|
37
|
+
popupContainer: {
|
|
38
|
+
padding: 5,
|
|
39
|
+
boxSizing: 'border-box'
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {useMemo} from 'react';
|
|
2
|
+
import {SankeyData, SankeyNode} from '../../types/SankeyChartData';
|
|
3
|
+
import sankey from './sankey';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
data: SankeyData;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const useSankey = ({data, width, height}: Props) =>
|
|
12
|
+
useMemo(() => {
|
|
13
|
+
if (!width || !height) return {nodes: [], links: []};
|
|
14
|
+
|
|
15
|
+
const shallowCopy = (data: SankeyData): SankeyData => {
|
|
16
|
+
const safeNodes = data.nodes.map((node) => ({...node}));
|
|
17
|
+
const getSafeNode = (originalNode: SankeyNode) => {
|
|
18
|
+
const index = data.nodes.indexOf(originalNode);
|
|
19
|
+
return safeNodes[index];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const safeLinks = data.links.map((link) => ({
|
|
23
|
+
...link,
|
|
24
|
+
source: getSafeNode(link.source),
|
|
25
|
+
target: getSafeNode(link.target)
|
|
26
|
+
}));
|
|
27
|
+
return {nodes: safeNodes, links: safeLinks};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const sankeyLayout = sankey()
|
|
31
|
+
.setNodeWidth(14)
|
|
32
|
+
.setNodePadding(20)
|
|
33
|
+
.setSize([height - 2, width - 2]);
|
|
34
|
+
|
|
35
|
+
const nodeWidth = sankeyLayout.nodeWidth();
|
|
36
|
+
const path = sankeyLayout.link();
|
|
37
|
+
|
|
38
|
+
const safeData = shallowCopy(data);
|
|
39
|
+
const {nodes, links}: SankeyData = sankeyLayout(safeData);
|
|
40
|
+
|
|
41
|
+
return {nodes, links, nodeWidth, path};
|
|
42
|
+
}, [data, height, width]);
|
|
43
|
+
|
|
44
|
+
export default useSankey;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, {useCallback} from 'react';
|
|
2
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
3
|
+
import InfoIcon from '@mui/icons-material/Info';
|
|
4
|
+
import {CrosswalksDisplayProvider, noop} from '@reltio/components';
|
|
5
|
+
import {
|
|
6
|
+
CrosswalkBase,
|
|
7
|
+
CrosswalkTree,
|
|
8
|
+
getCrosswalkColor as getUniqueCrosswalkColor,
|
|
9
|
+
getCrosswalkId
|
|
10
|
+
} from '@reltio/mdm-sdk';
|
|
11
|
+
import classnames from 'classnames';
|
|
12
|
+
import CrosswalkTreeView from '../CrosswalkTreeView/CrosswalkTreeView';
|
|
13
|
+
import {useStyles} from './styles';
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
coloredCrosswalks: string[];
|
|
17
|
+
crosswalkTree: CrosswalkTree;
|
|
18
|
+
selected: boolean;
|
|
19
|
+
title: string;
|
|
20
|
+
onSelect?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const SelectableTree = ({coloredCrosswalks, crosswalkTree, selected, title, onSelect = noop}: Props) => {
|
|
24
|
+
const styles = useStyles();
|
|
25
|
+
const getCrosswalkColor = useCallback(
|
|
26
|
+
(crosswalk: CrosswalkBase) => {
|
|
27
|
+
return coloredCrosswalks.includes(crosswalk.uri)
|
|
28
|
+
? getUniqueCrosswalkColor(getCrosswalkId(crosswalk))
|
|
29
|
+
: '#CACCD1';
|
|
30
|
+
},
|
|
31
|
+
[coloredCrosswalks]
|
|
32
|
+
);
|
|
33
|
+
return (
|
|
34
|
+
<CrosswalksDisplayProvider getCrosswalkColor={getCrosswalkColor}>
|
|
35
|
+
<div className={styles.container} onClick={onSelect}>
|
|
36
|
+
<span className={styles.title}>
|
|
37
|
+
{title} <InfoIcon className={styles.hintIcon} />
|
|
38
|
+
</span>
|
|
39
|
+
<div className={classnames(styles.graph, {[`${styles.selectedTree}`]: selected})}>
|
|
40
|
+
{selected && <CheckCircleIcon className={styles.checkedIcon} />}
|
|
41
|
+
<CrosswalkTreeView crosswalkTree={crosswalkTree} disableUnmerge={true} />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</CrosswalksDisplayProvider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {render} from '@testing-library/react';
|
|
3
|
+
import {SelectableTree} from '../SelectableTree';
|
|
4
|
+
import {crosswalkTree} from '../../UnmergeDialog/__tests__/testData.data';
|
|
5
|
+
|
|
6
|
+
describe('SelectableTree', () => {
|
|
7
|
+
const props = {
|
|
8
|
+
onSelect: jest.fn(),
|
|
9
|
+
title: 'the name',
|
|
10
|
+
coloredCrosswalks: [],
|
|
11
|
+
crosswalkTree
|
|
12
|
+
};
|
|
13
|
+
it('should render component correctly', () => {
|
|
14
|
+
const {asFragment} = render(<SelectableTree {...props} selected={true} />);
|
|
15
|
+
expect(asFragment()).toMatchSnapshot();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should render CheckCircleIcon if selected === true', () => {
|
|
19
|
+
const {container} = render(<SelectableTree {...props} selected={true} />);
|
|
20
|
+
expect(container.querySelector('.checkedIcon')).toBeInTheDocument();
|
|
21
|
+
expect(container.querySelector('.title').textContent).toBe('the name ');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should not render CheckCircleIcon if selected === false', () => {
|
|
25
|
+
const {container} = render(<SelectableTree {...props} selected={false} />);
|
|
26
|
+
expect(container.querySelector('.checkedIcon')).not.toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`SelectableTree should render component correctly 1`] = `
|
|
4
|
+
<DocumentFragment>
|
|
5
|
+
<div
|
|
6
|
+
class="container"
|
|
7
|
+
>
|
|
8
|
+
<span
|
|
9
|
+
class="title"
|
|
10
|
+
>
|
|
11
|
+
the name
|
|
12
|
+
<svg
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium hintIcon css-i4bv87-MuiSvgIcon-root"
|
|
15
|
+
data-testid="InfoIcon"
|
|
16
|
+
focusable="false"
|
|
17
|
+
viewBox="0 0 24 24"
|
|
18
|
+
>
|
|
19
|
+
<path
|
|
20
|
+
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
</span>
|
|
24
|
+
<div
|
|
25
|
+
class="graph selectedTree"
|
|
26
|
+
>
|
|
27
|
+
<svg
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium checkedIcon css-i4bv87-MuiSvgIcon-root"
|
|
30
|
+
data-testid="CheckCircleIcon"
|
|
31
|
+
focusable="false"
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
>
|
|
34
|
+
<path
|
|
35
|
+
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
|
36
|
+
/>
|
|
37
|
+
</svg>
|
|
38
|
+
<div
|
|
39
|
+
class="graph"
|
|
40
|
+
data-reltio-id="reltio-crosswalk-tree"
|
|
41
|
+
>
|
|
42
|
+
<svg
|
|
43
|
+
cursor="auto"
|
|
44
|
+
height="200"
|
|
45
|
+
width="0"
|
|
46
|
+
>
|
|
47
|
+
<g
|
|
48
|
+
pointer-events="auto"
|
|
49
|
+
transform="translate(0,199) rotate(-90)"
|
|
50
|
+
>
|
|
51
|
+
<g />
|
|
52
|
+
<g />
|
|
53
|
+
</g>
|
|
54
|
+
</svg>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</DocumentFragment>
|
|
59
|
+
`;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {makeStyles} from '@mui/styles';
|
|
2
|
+
|
|
3
|
+
export const useStyles = makeStyles((theme) => ({
|
|
4
|
+
container: {
|
|
5
|
+
display: 'flex',
|
|
6
|
+
flexDirection: 'column',
|
|
7
|
+
'& + &': {
|
|
8
|
+
marginLeft: '16px'
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
title: {
|
|
12
|
+
fontSize: '13px',
|
|
13
|
+
lineHeight: '15px',
|
|
14
|
+
color: '#666666',
|
|
15
|
+
marginBottom: '7px'
|
|
16
|
+
},
|
|
17
|
+
hintIcon: {
|
|
18
|
+
width: '16px',
|
|
19
|
+
height: '16px',
|
|
20
|
+
verticalAlign: 'middle'
|
|
21
|
+
},
|
|
22
|
+
graph: {
|
|
23
|
+
width: '400px',
|
|
24
|
+
height: '300px',
|
|
25
|
+
borderRadius: '8px',
|
|
26
|
+
background: 'rgba(0, 0, 0, 0.03)',
|
|
27
|
+
border: '1px solid rgba(0, 0, 0, 0.03)',
|
|
28
|
+
position: 'relative'
|
|
29
|
+
},
|
|
30
|
+
checkedIcon: {
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
height: '20px',
|
|
33
|
+
width: '20px',
|
|
34
|
+
right: '10px',
|
|
35
|
+
top: '10px',
|
|
36
|
+
color: theme.palette.primary.main
|
|
37
|
+
},
|
|
38
|
+
selectedTree: {
|
|
39
|
+
border: `1px solid ${theme.palette.primary.main}`,
|
|
40
|
+
background: 'rgba(0, 114, 206, 0.05)'
|
|
41
|
+
}
|
|
42
|
+
}));
|