@things-factory/integration-ui 7.0.33 → 7.0.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. package/client/analysis/graph-data.ts +34 -0
  2. package/client/analysis/graph-viewer-old.ts +1097 -0
  3. package/client/analysis/graph-viewer-style.ts +5 -1
  4. package/client/analysis/graph-viewer.ts +245 -942
  5. package/client/analysis/node.ts +73 -0
  6. package/client/analysis/relationship.ts +19 -0
  7. package/client/analysis/utils.ts +41 -0
  8. package/client/pages/integration-analysis.ts +160 -71
  9. package/dist-client/analysis/graph-data.d.ts +36 -0
  10. package/dist-client/analysis/graph-data.js +2 -0
  11. package/dist-client/analysis/graph-data.js.map +1 -0
  12. package/dist-client/analysis/graph-viewer-old.d.ts +110 -0
  13. package/dist-client/analysis/graph-viewer-old.js +808 -0
  14. package/dist-client/analysis/graph-viewer-old.js.map +1 -0
  15. package/dist-client/analysis/graph-viewer-style.js +5 -1
  16. package/dist-client/analysis/graph-viewer-style.js.map +1 -1
  17. package/dist-client/analysis/graph-viewer.d.ts +25 -99
  18. package/dist-client/analysis/graph-viewer.js +189 -703
  19. package/dist-client/analysis/graph-viewer.js.map +1 -1
  20. package/dist-client/analysis/node.d.ts +4 -0
  21. package/dist-client/analysis/node.js +59 -0
  22. package/dist-client/analysis/node.js.map +1 -0
  23. package/dist-client/analysis/relationship.d.ts +4 -0
  24. package/dist-client/analysis/relationship.js +13 -0
  25. package/dist-client/analysis/relationship.js.map +1 -0
  26. package/dist-client/analysis/utils.d.ts +20 -0
  27. package/dist-client/analysis/utils.js +31 -0
  28. package/dist-client/analysis/utils.js.map +1 -0
  29. package/dist-client/pages/integration-analysis.d.ts +8 -3
  30. package/dist-client/pages/integration-analysis.js +153 -70
  31. package/dist-client/pages/integration-analysis.js.map +1 -1
  32. package/dist-client/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +2 -2
@@ -1,26 +1,15 @@
1
1
  import * as d3 from 'd3';
2
2
  export class GraphViewer {
3
3
  constructor(_selector, _options) {
4
- this.classes2colors = {};
5
- this.justLoaded = false;
6
- this.numClasses = 0;
7
- this.options = {
8
- arrowSize: 4,
9
- colors: this.colors(),
10
- highlight: undefined,
11
- infoPanel: true,
12
- minCollision: undefined,
13
- graphData: undefined,
14
- dataUrl: undefined,
15
- nodeOutlineFillColor: undefined,
16
- nodeRadius: 25,
17
- relationshipColor: '#a5abb6',
18
- zoomFit: false
19
- };
20
- this.init(_selector, _options);
4
+ this.nodes = [];
5
+ this.relationships = [];
6
+ this.needZoomFit = true;
7
+ this.options = Object.assign({ arrowSize: 4, colors: this.colors(), highlight: undefined, infoPanel: true, minCollision: undefined, graphData: undefined, dataUrl: undefined, nodeOutlineFillColor: undefined, nodeRadius: 25, relationshipColor: '#a5abb6', zoomFit: false, classes2colors: {}, numClasses: 0 }, _options);
8
+ this.init(_selector);
21
9
  }
22
- appendGraph(container) {
23
- this.svg = container
10
+ init(selector) {
11
+ this.svg = d3
12
+ .select(selector)
24
13
  .append('svg')
25
14
  .attr('width', '100%')
26
15
  .attr('height', '100%')
@@ -37,91 +26,145 @@ export class GraphViewer {
37
26
  this.svg.attr('transform', 'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')');
38
27
  }))
39
28
  .on('dblclick.zoom', null)
40
- .append('g')
29
+ .append('g') // 그룹 요소 추가
41
30
  .attr('width', '100%')
42
31
  .attr('height', '100%');
43
- this.svgRelationships = this.svg.append('g').attr('class', 'relationships');
32
+ // Define arrow markers for graph links
33
+ this.svg
34
+ .append('defs')
35
+ .append('marker')
36
+ .attr('id', 'arrow')
37
+ .attr('viewBox', '0 -5 10 10')
38
+ .attr('refX', 10)
39
+ .attr('refY', 0)
40
+ .attr('markerWidth', 6)
41
+ .attr('markerHeight', 6)
42
+ .attr('orient', 'auto')
43
+ .append('path')
44
+ .attr('d', 'M0,-5L10,0L0,5')
45
+ .attr('fill', this.options.relationshipColor);
44
46
  this.svgNodes = this.svg.append('g').attr('class', 'nodes');
47
+ this.svgRelationships = this.svg.append('g').attr('class', 'relationships');
48
+ this.simulation = this.initSimulation();
49
+ if (this.options.graphData) {
50
+ this.updateWithGraphData(this.options.graphData);
51
+ }
45
52
  }
46
- appendImageToNode(node) {
47
- return node
48
- .append('image')
49
- .attr('height', d => {
50
- return this.icon(d) ? '24px' : '30px';
51
- })
52
- .attr('x', d => {
53
- return this.icon(d) ? '5px' : '-15px';
54
- })
55
- .attr('xlink:href', d => {
56
- return this.image(d);
53
+ initSimulation() {
54
+ const x = this.svg.node().parentElement.parentElement.clientWidth / 2;
55
+ const y = this.svg.node().parentElement.parentElement.clientHeight / 2;
56
+ var simulation = d3
57
+ .forceSimulation()
58
+ .force('collide', d3
59
+ .forceCollide()
60
+ .radius(d => {
61
+ return this.options.minCollision;
57
62
  })
58
- .attr('y', d => {
59
- return this.icon(d) ? '5px' : '-16px';
63
+ .iterations(2))
64
+ .force('charge', d3.forceManyBody())
65
+ .force('link', d3.forceLink().id(d => {
66
+ return d.id;
67
+ }))
68
+ .force('center', d3.forceCenter(x, y))
69
+ .on('tick', () => {
70
+ this.tick();
60
71
  })
61
- .attr('width', d => {
62
- return this.icon(d) ? '24px' : '30px';
72
+ .on('end', () => {
73
+ if (this.options.zoomFit && this.needZoomFit) {
74
+ this.needZoomFit = false;
75
+ this.zoomFit();
76
+ }
63
77
  });
78
+ return simulation;
64
79
  }
65
- appendInfoPanel(container) {
66
- return container.append('div').attr('class', 'graph-info');
67
- }
68
- appendInfoElement(cls, isNode, property, value) {
69
- var elem = this.info.append('a');
70
- elem
71
- .attr('href', '#')
72
- .attr('class', cls)
73
- .html('<strong>' + property + '</strong>' + (value ? ': ' + value : ''));
74
- if (!value) {
75
- elem
76
- .style('background-color', d => {
77
- return this.options.nodeOutlineFillColor
78
- ? this.options.nodeOutlineFillColor
79
- : isNode
80
- ? this.class2color(property)
81
- : this.defaultColor();
82
- })
83
- .style('border-color', d => {
84
- return this.options.nodeOutlineFillColor
85
- ? this.class2darkenColor(this.options.nodeOutlineFillColor)
86
- : isNode
87
- ? this.class2darkenColor(property)
88
- : this.defaultDarkenColor();
89
- })
90
- .style('color', d => {
91
- return this.options.nodeOutlineFillColor ? this.class2darkenColor(this.options.nodeOutlineFillColor) : '#fff';
92
- });
93
- }
80
+ tick() {
81
+ this.svgNodes.selectAll('.node').attr('transform', (d) => `translate(${d.x}, ${d.y})`);
82
+ this.svgRelationships
83
+ .selectAll('.relationship')
84
+ .attr('x1', (d) => this.calculateIntersection(d.source, d.target).x1)
85
+ .attr('y1', (d) => this.calculateIntersection(d.source, d.target).y1)
86
+ .attr('x2', (d) => this.calculateIntersection(d.source, d.target).x2)
87
+ .attr('y2', (d) => this.calculateIntersection(d.source, d.target).y2);
88
+ this.svgRelationships.selectAll('.relationship-text').attr('transform', (d) => {
89
+ const midX = (this.calculateIntersection(d.source, d.target).x1 + this.calculateIntersection(d.source, d.target).x2) / 2;
90
+ const midY = (this.calculateIntersection(d.source, d.target).y1 + this.calculateIntersection(d.source, d.target).y2) / 2;
91
+ return `translate(${midX}, ${midY})`;
92
+ });
94
93
  }
95
- appendInfoElementClass(cls, node) {
96
- this.appendInfoElement(cls, true, node);
94
+ calculateIntersection(source, target) {
95
+ const dx = target.x - source.x;
96
+ const dy = target.y - source.y;
97
+ const distance = Math.sqrt(dx * dx + dy * dy);
98
+ const ratio = (distance - this.options.nodeRadius) / distance;
99
+ const x1 = source.x + dx * (this.options.nodeRadius / distance);
100
+ const y1 = source.y + dy * (this.options.nodeRadius / distance);
101
+ const x2 = target.x - dx * (this.options.nodeRadius / distance);
102
+ const y2 = target.y - dy * (this.options.nodeRadius / distance);
103
+ return { x1, y1, x2, y2 };
97
104
  }
98
- appendInfoElementProperty(cls, property, value) {
99
- this.appendInfoElement(cls, false, property, value);
105
+ colors() {
106
+ return [
107
+ '#68bdf6', // light blue
108
+ '#6dce9e', // green #1
109
+ '#faafc2', // light pink
110
+ '#f2baf6', // purple
111
+ '#ff928c', // light red
112
+ '#fcea7e', // light yellow
113
+ '#ffc766', // light orange
114
+ '#405f9e', // navy blue
115
+ '#a5abb6', // dark gray
116
+ '#78cecb', // green #2,
117
+ '#b88cbb', // dark purple
118
+ '#ced2d9', // light gray
119
+ '#e84646', // dark red
120
+ '#fa5f86', // dark pink
121
+ '#ffab1a', // dark orange
122
+ '#fcda19', // dark yellow
123
+ '#797b80', // black
124
+ '#c9d96f', // pistachio
125
+ '#47991f', // green #3
126
+ '#70edee', // turquoise
127
+ '#ff75ea' // pink
128
+ ];
100
129
  }
101
- appendInfoElementRelationship(cls, relationship) {
102
- this.appendInfoElement(cls, false, relationship);
130
+ updateWithGraphData(graphData) {
131
+ this.nodes = graphData.results[0].data[0].graph.nodes;
132
+ this.relationships = graphData.results[0].data[0].graph.relationships;
133
+ this.relationships.forEach(rel => {
134
+ const sourceNode = this.nodes.find(node => node.id === rel.startNode);
135
+ const targetNode = this.nodes.find(node => node.id === rel.endNode);
136
+ if (!sourceNode || !targetNode) {
137
+ console.warn(`Node not found for relationship: ${rel.id}`);
138
+ return;
139
+ }
140
+ rel.source = sourceNode;
141
+ rel.target = targetNode;
142
+ });
143
+ this.updateNodesAndRelationships();
144
+ }
145
+ updateNodesAndRelationships() {
146
+ this.needZoomFit = true;
147
+ this.svgNodes.selectAll('.node').remove();
148
+ this.svgRelationships.selectAll('.relationship').remove();
149
+ this.svgRelationships.selectAll('.relationship-text').remove();
150
+ this.appendNodesToGraph();
151
+ this.appendRelationshipsToGraph();
152
+ this.simulation.nodes(this.nodes);
153
+ this.simulation.force('link').links(this.relationships);
154
+ // 시뮬레이션을 강제로 재시작하여 레이아웃이 적절히 재정렬되도록 함
155
+ this.simulation.alpha(1).restart();
103
156
  }
104
- appendNode() {
105
- return this.node
157
+ appendNodesToGraph() {
158
+ const nodeEnter = this.svgNodes
159
+ .selectAll('.node')
160
+ .data(this.nodes, (d) => d.id)
106
161
  .enter()
107
162
  .append('g')
108
163
  .attr('class', d => {
109
164
  var highlight, i, classes = 'node', label = d.labels[0];
110
- if (this.icon(d)) {
165
+ if (d.icon) {
111
166
  classes += ' node-icon';
112
167
  }
113
- if (this.image(d)) {
114
- classes += ' node-image';
115
- }
116
- if (this.options.highlight) {
117
- for (i = 0; i < this.options.highlight.length; i++) {
118
- highlight = this.options.highlight[i];
119
- if (d.labels[0] === highlight.class && d.properties[highlight.property] === highlight.value) {
120
- classes += ' node-highlighted';
121
- break;
122
- }
123
- }
124
- }
125
168
  return classes;
126
169
  })
127
170
  .on('click', (event, d) => {
@@ -137,665 +180,102 @@ export class GraphViewer {
137
180
  }
138
181
  })
139
182
  .on('mouseenter', (event, d) => {
140
- if (this.info) {
141
- this.updateInfo(d);
142
- }
143
- if (typeof this.options.onNodeMouseEnter === 'function') {
144
- this.options.onNodeMouseEnter(d);
145
- }
183
+ event.target.dispatchEvent(new CustomEvent('node-mouseenter', {
184
+ detail: d,
185
+ bubbles: true
186
+ }));
146
187
  })
147
188
  .on('mouseleave', (event, d) => {
148
- if (this.info) {
149
- this.clearInfo();
150
- }
151
- if (typeof this.options.onNodeMouseLeave === 'function') {
152
- this.options.onNodeMouseLeave(d);
153
- }
189
+ event.target.dispatchEvent(new CustomEvent('node-mouseleave', {
190
+ detail: d,
191
+ bubbles: true
192
+ }));
154
193
  })
155
194
  .call(d3
156
195
  .drag()
157
196
  .on('start', this.dragStarted.bind(this))
158
197
  .on('drag', this.dragged.bind(this))
159
198
  .on('end', this.dragEnded.bind(this)));
160
- }
161
- appendNodeToGraph() {
162
- var n = this.appendNode();
163
- this.appendRingToNode(n);
164
- // this.appendBoxToNode(n)
165
- this.appendOutlineToNode(n);
166
- if (this.options.icons) {
167
- this.appendTextToNode(n);
168
- }
169
- if (this.options.images) {
170
- this.appendImageToNode(n);
171
- }
172
- return n;
173
- }
174
- appendOutlineToNode(node) {
175
- const outline = node
199
+ nodeEnter
176
200
  .append('circle')
177
201
  .attr('class', 'outline')
178
202
  .attr('r', this.options.nodeRadius)
179
- .style('fill', d => {
180
- return this.options.nodeOutlineFillColor ? this.options.nodeOutlineFillColor : this.class2color(d.labels[0]);
181
- })
182
- .style('stroke', d => {
183
- return this.options.nodeOutlineFillColor
184
- ? this.class2darkenColor(this.options.nodeOutlineFillColor)
185
- : this.class2darkenColor(d.labels[0]);
186
- })
187
- .append('title')
188
- .text(d => {
189
- return this.toString(d);
190
- });
191
- node
203
+ .style('fill', d => this.class2color(d.labels[0]))
204
+ .style('stroke', d => this.class2darkenColor(d.labels[0]))
205
+ .style('stroke-width', 2);
206
+ nodeEnter
192
207
  .append('text')
193
- .attr('class', 'node-text')
208
+ .attr('class', 'text icon')
194
209
  .attr('x', 0)
195
- .attr('y', 52)
196
- .attr('text-anchor', 'middle') // 텍스트 중앙 정렬
197
- .attr('fill', 'black') // 텍스트 색상을 검은색으로 변경
198
- .text(d => d.text);
199
- return outline;
200
- }
201
- appendBoxToNode(node) {
202
- const rect = node
203
- .append('rect')
204
- .attr('class', 'node-rect')
205
- .attr('width', 160) // 사각형의 너비
206
- .attr('height', 30) // 사각형의 높이
207
- .attr('x', -80) // 사각형의 중심을 기준으로 x 위치 조정
208
- .attr('y', -15) // 사각형의 중심을 기준으로 y 위치 조정
209
- .style('fill', d => {
210
- return this.options.nodeOutlineFillColor ? this.options.nodeOutlineFillColor : this.class2color(d.labels[0]);
211
- })
212
- .style('stroke', d => {
213
- return this.options.nodeOutlineFillColor
214
- ? this.class2darkenColor(this.options.nodeOutlineFillColor)
215
- : this.class2darkenColor(d.labels[0]);
216
- })
217
- .append('title')
218
- .text(d => {
219
- return this.toString(d);
220
- });
221
- node
222
- .append('text')
223
- .attr('class', 'node-text')
224
- .attr('x', 0)
225
- .attr('y', 4)
226
- .attr('text-anchor', 'middle') // 텍스트 중앙 정렬
227
- .attr('fill', 'black') // 텍스트 색상을 검은색으로 변경
228
- .text(d => d.text);
229
- return rect;
230
- }
231
- appendRingToNode(node) {
232
- return node
233
- .append('circle')
234
- .attr('class', 'ring')
235
- .attr('r', this.options.nodeRadius * 1.16)
236
- .append('title')
237
- .text(d => {
238
- return this.toString(d);
239
- });
240
- }
241
- appendTextToNode(node) {
242
- return node
243
- .append('text')
244
- .attr('fill', '#ffffff')
245
- .attr('pointer-events', 'none')
210
+ .attr('y', 0)
246
211
  .attr('text-anchor', 'middle')
247
- .attr('y', '24px')
212
+ .attr('dominant-baseline', 'central')
248
213
  .attr('font-family', 'Material Symbols Outlined')
249
- .attr('font-size', '48px')
214
+ .attr('font-size', '24px')
215
+ .attr('fill', '#000')
216
+ .text(d => this.getNodeIcon(d));
217
+ nodeEnter
218
+ .append('text')
219
+ .attr('dy', 40)
250
220
  .attr('text-anchor', 'middle')
251
- .attr('alignment-baseline', 'top')
252
- .text(d => this.icon(d));
221
+ .text(d => d.properties.name || d.id);
253
222
  }
254
- appendRelationship() {
255
- return this.relationship
223
+ stickNode(event, d) {
224
+ d.fx = event.x;
225
+ d.fy = event.y;
226
+ }
227
+ appendRelationshipsToGraph() {
228
+ const relationshipEnter = this.svgRelationships
229
+ .selectAll('.relationship')
230
+ .data(this.relationships.filter(rel => rel.source && rel.target), d => d.id)
256
231
  .enter()
257
232
  .append('g')
233
+ .attr('class', 'relationship-group');
234
+ relationshipEnter
235
+ .append('line')
258
236
  .attr('class', 'relationship')
259
- .on('dblclick', (event, d) => {
260
- if (typeof this.options.onRelationshipDoubleClick === 'function') {
261
- this.options.onRelationshipDoubleClick(d);
262
- }
263
- })
264
- .on('mouseenter', (event, d) => {
265
- if (this.info) {
266
- this.updateInfo(d);
267
- }
268
- });
269
- }
270
- appendOutlineToRelationship(r) {
271
- return r.append('path').attr('class', 'outline').attr('fill', '#a5abb6').attr('stroke', 'none');
272
- }
273
- appendOverlayToRelationship(r) {
274
- return r.append('path').attr('class', 'overlay');
275
- }
276
- appendTextToRelationship(r) {
277
- return r
237
+ .style('stroke', this.options.relationshipColor)
238
+ .style('stroke-width', 2)
239
+ .attr('marker-end', 'url(#arrow)');
240
+ relationshipEnter
278
241
  .append('text')
279
- .attr('class', 'text')
242
+ .attr('class', 'relationship-text')
280
243
  .attr('fill', '#000000')
281
244
  .attr('font-size', '8px')
282
245
  .attr('pointer-events', 'none')
283
246
  .attr('text-anchor', 'middle')
284
- .text(d => {
285
- return d.type;
286
- });
247
+ .text(d => d.type);
287
248
  }
288
- appendRelationshipToGraph() {
289
- var relationship = this.appendRelationship(), text = this.appendTextToRelationship(relationship), outline = this.appendOutlineToRelationship(relationship), overlay = this.appendOverlayToRelationship(relationship);
290
- return {
291
- outline: outline,
292
- overlay: overlay,
293
- relationship: relationship,
294
- text: text
295
- };
249
+ getNodeIcon(d) {
250
+ return d.icon || '';
296
251
  }
297
252
  class2color(cls) {
298
- var color = this.classes2colors[cls];
299
- if (!color) {
300
- // color = this.options.colors[Math.min(numClasses, this.options.colors.length - 1)];
301
- color = this.options.colors[this.numClasses % this.options.colors.length];
302
- this.classes2colors[cls] = color;
303
- this.numClasses++;
253
+ if (!this.options.classes2colors[cls]) {
254
+ this.options.classes2colors[cls] = this.options.colors[this.options.numClasses % this.options.colors.length];
255
+ this.options.numClasses++;
304
256
  }
305
- return color;
257
+ return this.options.classes2colors[cls];
306
258
  }
307
259
  class2darkenColor(cls) {
308
260
  return d3.rgb(this.class2color(cls)).darker(1);
309
261
  }
310
- clearInfo() {
311
- this.info.html('');
312
- }
313
- color() {
314
- return this.options.colors[(this.options.colors.length * Math.random()) << 0];
315
- }
316
- colors() {
317
- // d3.schemeCategory10,
318
- // d3.schemeCategory20,
319
- return [
320
- '#68bdf6', // light blue
321
- '#6dce9e', // green #1
322
- '#faafc2', // light pink
323
- '#f2baf6', // purple
324
- '#ff928c', // light red
325
- '#fcea7e', // light yellow
326
- '#ffc766', // light orange
327
- '#405f9e', // navy blue
328
- '#a5abb6', // dark gray
329
- '#78cecb', // green #2,
330
- '#b88cbb', // dark purple
331
- '#ced2d9', // light gray
332
- '#e84646', // dark red
333
- '#fa5f86', // dark pink
334
- '#ffab1a', // dark orange
335
- '#fcda19', // dark yellow
336
- '#797b80', // black
337
- '#c9d96f', // pistacchio
338
- '#47991f', // green #3
339
- '#70edee', // turquoise
340
- '#ff75ea' // pink
341
- ];
342
- }
343
- contains(array, id) {
344
- var filter = array.filter(function (elem) {
345
- return elem.id === id;
346
- });
347
- return filter.length > 0;
348
- }
349
- defaultColor() {
350
- return this.options.relationshipColor;
351
- }
352
- defaultDarkenColor() {
353
- return d3.rgb(this.options.colors[this.options.colors.length - 1]).darker(1);
354
- }
355
- dragEnded(event, d) {
356
- if (!event.active) {
357
- this.simulation.alphaTarget(0);
358
- }
359
- if (typeof this.options.onNodeDragEnd === 'function') {
360
- this.options.onNodeDragEnd(d);
361
- }
362
- }
363
- dragged(event, d) {
364
- this.stickNode(event, d);
365
- }
366
262
  dragStarted(event, d) {
367
- if (!event.active) {
263
+ if (!event.active)
368
264
  this.simulation.alphaTarget(0.3).restart();
369
- }
370
265
  d.fx = d.x;
371
266
  d.fy = d.y;
372
- if (typeof this.options.onNodeDragStart === 'function') {
373
- this.options.onNodeDragStart(d);
374
- }
375
- }
376
- extend(obj1, obj2) {
377
- var obj = {};
378
- this.merge(obj, obj1);
379
- this.merge(obj, obj2);
380
- return obj;
381
- }
382
- icon(d) {
383
- return d.icon;
384
- }
385
- image(d) {
386
- var i, imagesForLabel, img, imgLevel, label, labelPropertyValue, property, value;
387
- if (this.options.images) {
388
- imagesForLabel = this.options.imageMap[d.labels[0]];
389
- if (imagesForLabel) {
390
- imgLevel = 0;
391
- for (i = 0; i < imagesForLabel.length; i++) {
392
- labelPropertyValue = imagesForLabel[i].split('|');
393
- switch (labelPropertyValue.length) {
394
- case 3:
395
- value = labelPropertyValue[2];
396
- /* falls through */
397
- case 2:
398
- property = labelPropertyValue[1];
399
- /* falls through */
400
- case 1:
401
- label = labelPropertyValue[0];
402
- }
403
- if (d.labels[0] === label &&
404
- (!property || d.properties[property] !== undefined) &&
405
- (!value || d.properties[property] === value)) {
406
- if (labelPropertyValue.length > imgLevel) {
407
- img = this.options.images[imagesForLabel[i]];
408
- imgLevel = labelPropertyValue.length;
409
- }
410
- }
411
- }
412
- }
413
- }
414
- return img;
415
267
  }
416
- init(_selector, _options) {
417
- this.merge(this.options, _options);
418
- if (this.options.icons) {
419
- this.options.showIcons = true;
420
- }
421
- if (!this.options.minCollision) {
422
- this.options.minCollision = this.options.nodeRadius * 2;
423
- }
424
- this.selector = _selector;
425
- this.container = d3.select(this.selector);
426
- // this.container.attr('class', 'graph-container').html('')
427
- if (this.options.infoPanel) {
428
- this.info = this.appendInfoPanel(this.container);
429
- }
430
- this.appendGraph(this.container);
431
- this.simulation = this.initSimulation();
432
- if (this.options.graphData) {
433
- this.loadGraphData();
434
- }
435
- else if (this.options.dataUrl) {
436
- this.loadGraphDataFromUrl(this.options.dataUrl);
437
- }
438
- else {
439
- console.error('Error: both graphData and dataUrl are empty!');
440
- }
441
- }
442
- initSimulation() {
443
- const x = this.svg.node().parentElement.parentElement.clientWidth / 2;
444
- const y = this.svg.node().parentElement.parentElement.clientHeight / 2;
445
- var simulation = d3
446
- .forceSimulation()
447
- .force('collide', d3
448
- .forceCollide()
449
- .radius(d => {
450
- return this.options.minCollision;
451
- })
452
- .iterations(2))
453
- .force('charge', d3.forceManyBody())
454
- .force('link', d3.forceLink().id(d => {
455
- return d.id;
456
- }))
457
- .force('center', d3.forceCenter(x, y))
458
- .on('tick', () => {
459
- this.tick();
460
- })
461
- .on('end', () => {
462
- if (this.options.zoomFit && !this.justLoaded) {
463
- this.justLoaded = true;
464
- this.zoomFit(2);
465
- }
466
- });
467
- return simulation;
468
- }
469
- loadGraphData() {
470
- this.nodes = [];
471
- this.relationships = [];
472
- this.updateWithGraphData(this.options.graphData);
473
- }
474
- loadGraphDataFromUrl(dataUrl) {
475
- this.nodes = [];
476
- this.relationships = [];
477
- d3.json(dataUrl, (error, data) => {
478
- if (error) {
479
- throw error;
480
- }
481
- this.updateWithGraphData(data);
482
- });
483
- }
484
- merge(target, source) {
485
- Object.keys(source).forEach(property => {
486
- target[property] = source[property];
487
- });
488
- }
489
- graphDataToD3Data(data) {
490
- var graph = {
491
- nodes: [],
492
- relationships: []
493
- };
494
- data.results.forEach(result => {
495
- result.data.forEach(data => {
496
- data.graph.nodes.forEach(node => {
497
- if (!this.contains(graph.nodes, node.id)) {
498
- graph.nodes.push(node);
499
- }
500
- });
501
- data.graph.relationships.forEach(function (relationship) {
502
- relationship.source = relationship.startNode;
503
- relationship.target = relationship.endNode;
504
- graph.relationships.push(relationship);
505
- });
506
- data.graph.relationships.sort(function (a, b) {
507
- if (a.source > b.source) {
508
- return 1;
509
- }
510
- else if (a.source < b.source) {
511
- return -1;
512
- }
513
- else {
514
- if (a.target > b.target) {
515
- return 1;
516
- }
517
- if (a.target < b.target) {
518
- return -1;
519
- }
520
- else {
521
- return 0;
522
- }
523
- }
524
- });
525
- for (var i = 0; i < data.graph.relationships.length; i++) {
526
- if (i !== 0 &&
527
- data.graph.relationships[i].source === data.graph.relationships[i - 1].source &&
528
- data.graph.relationships[i].target === data.graph.relationships[i - 1].target) {
529
- data.graph.relationships[i].linknum = data.graph.relationships[i - 1].linknum + 1;
530
- }
531
- else {
532
- data.graph.relationships[i].linknum = 1;
533
- }
534
- }
535
- });
536
- });
537
- return graph;
538
- }
539
- randomD3Data(d, maxNodesToGenerate) {
540
- var data = {
541
- nodes: [],
542
- relationships: []
543
- }, i, label, node, numNodes = ((maxNodesToGenerate * Math.random()) << 0) + 1, relationship, s = this.size();
544
- for (i = 0; i < numNodes; i++) {
545
- label = this.randomLabel();
546
- node = {
547
- id: s.nodes + 1 + i,
548
- labels: [label],
549
- properties: {
550
- random: label
551
- },
552
- x: d.x,
553
- y: d.y
554
- };
555
- data.nodes[data.nodes.length] = node;
556
- relationship = {
557
- id: s.relationships + 1 + i,
558
- type: label.toUpperCase(),
559
- startNode: d.id,
560
- endNode: s.nodes + 1 + i,
561
- properties: {
562
- from: Date.now()
563
- },
564
- source: d.id,
565
- target: s.nodes + 1 + i,
566
- linknum: s.relationships + 1 + i
567
- };
568
- data.relationships[data.relationships.length] = relationship;
569
- }
570
- return data;
571
- }
572
- randomLabel() {
573
- var icons = Object.keys(this.options.iconMap);
574
- return icons[(icons.length * Math.random()) << 0];
575
- }
576
- rotate(cx, cy, x, y, angle) {
577
- var radians = (Math.PI / 180) * angle, cos = Math.cos(radians), sin = Math.sin(radians), nx = cos * (x - cx) + sin * (y - cy) + cx, ny = cos * (y - cy) - sin * (x - cx) + cy;
578
- return { x: nx, y: ny };
579
- }
580
- rotatePoint(c, p, angle) {
581
- return this.rotate(c.x, c.y, p.x, p.y, angle);
582
- }
583
- rotation(source, target) {
584
- return (Math.atan2(target.y - source.y, target.x - source.x) * 180) / Math.PI;
585
- }
586
- size() {
587
- return {
588
- nodes: this.nodes.length,
589
- relationships: this.relationships.length
590
- };
591
- }
592
- stickNode(event, d) {
268
+ dragged(event, d) {
593
269
  d.fx = event.x;
594
270
  d.fy = event.y;
595
271
  }
596
- tick() {
597
- this.tickNodes();
598
- this.tickRelationships();
599
- }
600
- tickNodes() {
601
- if (this.node) {
602
- this.node.attr('transform', d => {
603
- return 'translate(' + d.x + ', ' + d.y + ')';
604
- });
605
- }
606
- }
607
- tickRelationships() {
608
- if (this.relationship) {
609
- this.relationship.attr('transform', d => {
610
- var angle = this.rotation(d.source, d.target);
611
- return 'translate(' + d.source.x + ', ' + d.source.y + ') rotate(' + angle + ')';
612
- });
613
- this.tickRelationshipsTexts();
614
- this.tickRelationshipsOutlines();
615
- this.tickRelationshipsOverlays();
616
- }
617
- }
618
- tickRelationshipsOutlines() {
619
- const self = this;
620
- this.relationship.each(function (relationship) {
621
- var rel = d3.select(this);
622
- var outline = rel.select('.outline'), text = rel.select('.text'), bbox = text.node().getBBox(), padding = 3;
623
- outline.attr('d', d => {
624
- var center = { x: 0, y: 0 }, angle = self.rotation(d.source, d.target), textBoundingBox = text.node().getBBox(), textPadding = 5, u = self.unitaryVector(d.source, d.target), textMargin = {
625
- x: (d.target.x - d.source.x - (textBoundingBox.width + textPadding) * u.x) * 0.5,
626
- y: (d.target.y - d.source.y - (textBoundingBox.width + textPadding) * u.y) * 0.5
627
- }, n = self.unitaryNormalVector(d.source, d.target), rotatedPointA1 = self.rotatePoint(center, { x: 0 + (self.options.nodeRadius + 1) * u.x - n.x, y: 0 + (self.options.nodeRadius + 1) * u.y - n.y }, angle), rotatedPointB1 = self.rotatePoint(center, { x: textMargin.x - n.x, y: textMargin.y - n.y }, angle), rotatedPointC1 = self.rotatePoint(center, { x: textMargin.x, y: textMargin.y }, angle), rotatedPointD1 = self.rotatePoint(center, { x: 0 + (self.options.nodeRadius + 1) * u.x, y: 0 + (self.options.nodeRadius + 1) * u.y }, angle), rotatedPointA2 = self.rotatePoint(center, { x: d.target.x - d.source.x - textMargin.x - n.x, y: d.target.y - d.source.y - textMargin.y - n.y }, angle), rotatedPointB2 = self.rotatePoint(center, {
628
- x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x - n.x - u.x * self.options.arrowSize,
629
- y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y - n.y - u.y * self.options.arrowSize
630
- }, angle), rotatedPointC2 = self.rotatePoint(center, {
631
- x: d.target.x -
632
- d.source.x -
633
- (self.options.nodeRadius + 1) * u.x -
634
- n.x +
635
- (n.x - u.x) * self.options.arrowSize,
636
- y: d.target.y -
637
- d.source.y -
638
- (self.options.nodeRadius + 1) * u.y -
639
- n.y +
640
- (n.y - u.y) * self.options.arrowSize
641
- }, angle), rotatedPointD2 = self.rotatePoint(center, {
642
- x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x,
643
- y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y
644
- }, angle), rotatedPointE2 = self.rotatePoint(center, {
645
- x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x + (-n.x - u.x) * self.options.arrowSize,
646
- y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y + (-n.y - u.y) * self.options.arrowSize
647
- }, angle), rotatedPointF2 = self.rotatePoint(center, {
648
- x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x - u.x * self.options.arrowSize,
649
- y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y - u.y * self.options.arrowSize
650
- }, angle), rotatedPointG2 = self.rotatePoint(center, { x: d.target.x - d.source.x - textMargin.x, y: d.target.y - d.source.y - textMargin.y }, angle);
651
- return ('M ' +
652
- rotatedPointA1.x +
653
- ' ' +
654
- rotatedPointA1.y +
655
- ' L ' +
656
- rotatedPointB1.x +
657
- ' ' +
658
- rotatedPointB1.y +
659
- ' L ' +
660
- rotatedPointC1.x +
661
- ' ' +
662
- rotatedPointC1.y +
663
- ' L ' +
664
- rotatedPointD1.x +
665
- ' ' +
666
- rotatedPointD1.y +
667
- ' Z M ' +
668
- rotatedPointA2.x +
669
- ' ' +
670
- rotatedPointA2.y +
671
- ' L ' +
672
- rotatedPointB2.x +
673
- ' ' +
674
- rotatedPointB2.y +
675
- ' L ' +
676
- rotatedPointC2.x +
677
- ' ' +
678
- rotatedPointC2.y +
679
- ' L ' +
680
- rotatedPointD2.x +
681
- ' ' +
682
- rotatedPointD2.y +
683
- ' L ' +
684
- rotatedPointE2.x +
685
- ' ' +
686
- rotatedPointE2.y +
687
- ' L ' +
688
- rotatedPointF2.x +
689
- ' ' +
690
- rotatedPointF2.y +
691
- ' L ' +
692
- rotatedPointG2.x +
693
- ' ' +
694
- rotatedPointG2.y +
695
- ' Z');
696
- });
697
- });
698
- }
699
- tickRelationshipsOverlays() {
700
- this.relationshipOverlay.attr('d', d => {
701
- var center = { x: 0, y: 0 }, angle = this.rotation(d.source, d.target), n1 = this.unitaryNormalVector(d.source, d.target), n = this.unitaryNormalVector(d.source, d.target, 50), rotatedPointA = this.rotatePoint(center, { x: 0 - n.x, y: 0 - n.y }, angle), rotatedPointB = this.rotatePoint(center, { x: d.target.x - d.source.x - n.x, y: d.target.y - d.source.y - n.y }, angle), rotatedPointC = this.rotatePoint(center, { x: d.target.x - d.source.x + n.x - n1.x, y: d.target.y - d.source.y + n.y - n1.y }, angle), rotatedPointD = this.rotatePoint(center, { x: 0 + n.x - n1.x, y: 0 + n.y - n1.y }, angle);
702
- return ('M ' +
703
- rotatedPointA.x +
704
- ' ' +
705
- rotatedPointA.y +
706
- ' L ' +
707
- rotatedPointB.x +
708
- ' ' +
709
- rotatedPointB.y +
710
- ' L ' +
711
- rotatedPointC.x +
712
- ' ' +
713
- rotatedPointC.y +
714
- ' L ' +
715
- rotatedPointD.x +
716
- ' ' +
717
- rotatedPointD.y +
718
- ' Z');
719
- });
720
- }
721
- tickRelationshipsTexts() {
722
- this.relationshipText.attr('transform', d => {
723
- var angle = (this.rotation(d.source, d.target) + 360) % 360, mirror = angle > 90 && angle < 270, center = { x: 0, y: 0 }, n = this.unitaryNormalVector(d.source, d.target), nWeight = mirror ? 2 : -3, point = {
724
- x: (d.target.x - d.source.x) * 0.5 + n.x * nWeight,
725
- y: (d.target.y - d.source.y) * 0.5 + n.y * nWeight
726
- }, rotatedPoint = this.rotatePoint(center, point, angle);
727
- return 'translate(' + rotatedPoint.x + ', ' + rotatedPoint.y + ') rotate(' + (mirror ? 180 : 0) + ')';
728
- });
729
- }
730
- toString(d) {
731
- var s = d.labels ? d.labels[0] : d.type;
732
- s += ' (<id>: ' + d.id;
733
- Object.keys(d.properties).forEach(function (property) {
734
- s += ', ' + property + ': ' + JSON.stringify(d.properties[property]);
735
- });
736
- s += ')';
737
- return s;
738
- }
739
- unitaryNormalVector(source, target, newLength) {
740
- var center = { x: 0, y: 0 }, vector = this.unitaryVector(source, target, newLength);
741
- return this.rotatePoint(center, vector, 90);
742
- }
743
- unitaryVector(source, target, newLength) {
744
- var length = Math.sqrt(Math.pow(target.x - source.x, 2) + Math.pow(target.y - source.y, 2)) / Math.sqrt(newLength || 1);
745
- return {
746
- x: (target.x - source.x) / length,
747
- y: (target.y - source.y) / length
748
- };
749
- }
750
- updateWithD3Data(d3Data) {
751
- this.updateNodesAndRelationships(d3Data.nodes, d3Data.relationships);
752
- }
753
- updateWithGraphData(graphData) {
754
- var d3Data = this.graphDataToD3Data(graphData);
755
- this.updateWithD3Data(d3Data);
756
- }
757
- updateInfo(d) {
758
- this.clearInfo();
759
- if (d.labels) {
760
- this.appendInfoElementClass('class', d.labels[0]);
761
- }
762
- else {
763
- this.appendInfoElementRelationship('class', d.type);
764
- }
765
- this.appendInfoElementProperty('property', '&lt;id&gt;', d.id);
766
- Object.keys(d.properties).forEach(property => {
767
- this.appendInfoElementProperty('property', property, JSON.stringify(d.properties[property]));
768
- });
769
- }
770
- updateNodes(n) {
771
- Array.prototype.push.apply(this.nodes, n);
772
- this.node = this.svgNodes.selectAll('.node').data(this.nodes, d => {
773
- return d.id;
774
- });
775
- var nodeEnter = this.appendNodeToGraph();
776
- this.node = nodeEnter.merge(this.node);
777
- }
778
- updateNodesAndRelationships(n, r) {
779
- this.updateRelationships(r);
780
- this.updateNodes(n);
781
- this.simulation.nodes(this.nodes);
782
- this.simulation.force('link').links(this.relationships);
783
- }
784
- updateRelationships(r) {
785
- Array.prototype.push.apply(this.relationships, r);
786
- this.relationship = this.svgRelationships.selectAll('.relationship').data(this.relationships, d => {
787
- return d.id;
788
- });
789
- var relationshipEnter = this.appendRelationshipToGraph();
790
- this.relationship = relationshipEnter.relationship.merge(this.relationship);
791
- this.relationshipOutline = this.svg.selectAll('.relationship .outline');
792
- this.relationshipOutline = relationshipEnter.outline.merge(this.relationshipOutline);
793
- this.relationshipOverlay = this.svg.selectAll('.relationship .overlay');
794
- this.relationshipOverlay = relationshipEnter.overlay.merge(this.relationshipOverlay);
795
- this.relationshipText = this.svg.selectAll('.relationship .text');
796
- this.relationshipText = relationshipEnter.text.merge(this.relationshipText);
272
+ dragEnded(event, d) {
273
+ if (!event.active)
274
+ this.simulation.alphaTarget(0);
275
+ d.fx = null;
276
+ d.fy = null;
797
277
  }
798
- zoomFit(transitionDuration) {
278
+ zoomFit() {
799
279
  var bounds = this.svg.node().getBBox(), parent = this.svg.node().parentElement.parentElement, fullWidth = parent.clientWidth, fullHeight = parent.clientHeight, width = bounds.width, height = bounds.height, midX = bounds.x + width / 2, midY = bounds.y + height / 2;
800
280
  if (width === 0 || height === 0) {
801
281
  return; // nothing to fit
@@ -804,5 +284,11 @@ export class GraphViewer {
804
284
  this.svgTranslate = [fullWidth / 2 - this.svgScale * midX, fullHeight / 2 - this.svgScale * midY];
805
285
  this.svg.attr('transform', 'translate(' + this.svgTranslate[0] + ', ' + this.svgTranslate[1] + ') scale(' + this.svgScale + ')');
806
286
  }
287
+ size() {
288
+ return {
289
+ nodes: this.nodes.length,
290
+ relationships: this.relationships.length
291
+ };
292
+ }
807
293
  }
808
294
  //# sourceMappingURL=graph-viewer.js.map