@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
@@ -0,0 +1,1097 @@
1
+ import * as d3 from 'd3'
2
+
3
+ export class GraphViewer {
4
+ public simulation
5
+
6
+ private container
7
+ private info
8
+ private node
9
+ private nodes
10
+ private relationship
11
+ private relationshipOutline
12
+ private relationshipOverlay
13
+ private relationshipText
14
+ private relationships
15
+ private selector
16
+ private svg
17
+ private svgNodes
18
+ private svgRelationships
19
+ private svgScale
20
+ private svgTranslate: any
21
+ private classes2colors = {}
22
+ private justLoaded = false
23
+ private numClasses = 0
24
+ private options = {
25
+ arrowSize: 4,
26
+ colors: this.colors(),
27
+ highlight: undefined,
28
+ infoPanel: true,
29
+ minCollision: undefined,
30
+ graphData: undefined,
31
+ dataUrl: undefined,
32
+ nodeOutlineFillColor: undefined,
33
+ nodeRadius: 25,
34
+ relationshipColor: '#a5abb6',
35
+ zoomFit: false
36
+ } as any
37
+
38
+ constructor(_selector: any, _options: any) {
39
+ this.init(_selector, _options)
40
+ }
41
+
42
+ appendGraph(container) {
43
+ this.svg = container
44
+ .append('svg')
45
+ .attr('width', '100%')
46
+ .attr('height', '100%')
47
+ .attr('class', 'graph-viewer')
48
+ .call(
49
+ d3.zoom().on('zoom', (event, d) => {
50
+ var scale = event.transform.k,
51
+ translate = [event.transform.x, event.transform.y]
52
+
53
+ if (this.svgTranslate) {
54
+ translate[0] += this.svgTranslate[0]
55
+ translate[1] += this.svgTranslate[1]
56
+ }
57
+
58
+ if (this.svgScale) {
59
+ scale *= this.svgScale
60
+ }
61
+
62
+ this.svg.attr('transform', 'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')')
63
+ })
64
+ )
65
+ .on('dblclick.zoom', null)
66
+ .append('g')
67
+ .attr('width', '100%')
68
+ .attr('height', '100%')
69
+
70
+ this.svgRelationships = this.svg.append('g').attr('class', 'relationships')
71
+
72
+ this.svgNodes = this.svg.append('g').attr('class', 'nodes')
73
+ }
74
+
75
+ appendImageToNode(node) {
76
+ return node
77
+ .append('image')
78
+ .attr('height', d => {
79
+ return this.icon(d) ? '24px' : '30px'
80
+ })
81
+ .attr('x', d => {
82
+ return this.icon(d) ? '5px' : '-15px'
83
+ })
84
+ .attr('xlink:href', d => {
85
+ return this.image(d)
86
+ })
87
+ .attr('y', d => {
88
+ return this.icon(d) ? '5px' : '-16px'
89
+ })
90
+ .attr('width', d => {
91
+ return this.icon(d) ? '24px' : '30px'
92
+ })
93
+ }
94
+
95
+ appendInfoPanel(container) {
96
+ return container.append('div').attr('class', 'graph-info')
97
+ }
98
+
99
+ appendInfoElement(cls, isNode, property, value?: any) {
100
+ var elem = this.info.append('a')
101
+
102
+ elem
103
+ .attr('href', '#')
104
+ .attr('class', cls)
105
+ .html('<strong>' + property + '</strong>' + (value ? ': ' + value : ''))
106
+
107
+ if (!value) {
108
+ elem
109
+ .style('background-color', d => {
110
+ return this.options.nodeOutlineFillColor
111
+ ? this.options.nodeOutlineFillColor
112
+ : isNode
113
+ ? this.class2color(property)
114
+ : this.defaultColor()
115
+ })
116
+ .style('border-color', d => {
117
+ return this.options.nodeOutlineFillColor
118
+ ? this.class2darkenColor(this.options.nodeOutlineFillColor)
119
+ : isNode
120
+ ? this.class2darkenColor(property)
121
+ : this.defaultDarkenColor()
122
+ })
123
+ .style('color', d => {
124
+ return this.options.nodeOutlineFillColor ? this.class2darkenColor(this.options.nodeOutlineFillColor) : '#fff'
125
+ })
126
+ }
127
+ }
128
+
129
+ appendInfoElementClass(cls, node) {
130
+ this.appendInfoElement(cls, true, node)
131
+ }
132
+
133
+ appendInfoElementProperty(cls, property, value) {
134
+ this.appendInfoElement(cls, false, property, value)
135
+ }
136
+
137
+ appendInfoElementRelationship(cls, relationship) {
138
+ this.appendInfoElement(cls, false, relationship)
139
+ }
140
+
141
+ appendNode() {
142
+ return this.node
143
+ .enter()
144
+ .append('g')
145
+ .attr('class', d => {
146
+ var highlight,
147
+ i,
148
+ classes = 'node',
149
+ label = d.labels[0]
150
+
151
+ if (this.icon(d)) {
152
+ classes += ' node-icon'
153
+ }
154
+
155
+ if (this.image(d)) {
156
+ classes += ' node-image'
157
+ }
158
+
159
+ if (this.options.highlight) {
160
+ for (i = 0; i < this.options.highlight.length; i++) {
161
+ highlight = this.options.highlight[i]
162
+
163
+ if (d.labels[0] === highlight.class && d.properties[highlight.property] === highlight.value) {
164
+ classes += ' node-highlighted'
165
+ break
166
+ }
167
+ }
168
+ }
169
+
170
+ return classes
171
+ })
172
+ .on('click', (event, d) => {
173
+ d.fx = d.fy = null
174
+
175
+ if (typeof this.options.onNodeClick === 'function') {
176
+ this.options.onNodeClick(d)
177
+ }
178
+ })
179
+ .on('dblclick', (event, d) => {
180
+ this.stickNode(event, d)
181
+
182
+ if (typeof this.options.onNodeDoubleClick === 'function') {
183
+ this.options.onNodeDoubleClick(d)
184
+ }
185
+ })
186
+ .on('mouseenter', (event, d) => {
187
+ if (this.info) {
188
+ this.updateInfo(d)
189
+ }
190
+
191
+ if (typeof this.options.onNodeMouseEnter === 'function') {
192
+ this.options.onNodeMouseEnter(d)
193
+ }
194
+ })
195
+ .on('mouseleave', (event, d) => {
196
+ if (this.info) {
197
+ this.clearInfo()
198
+ }
199
+
200
+ if (typeof this.options.onNodeMouseLeave === 'function') {
201
+ this.options.onNodeMouseLeave(d)
202
+ }
203
+ })
204
+ .call(
205
+ d3
206
+ .drag()
207
+ .on('start', this.dragStarted.bind(this))
208
+ .on('drag', this.dragged.bind(this))
209
+ .on('end', this.dragEnded.bind(this))
210
+ )
211
+ }
212
+
213
+ appendNodeToGraph() {
214
+ var n = this.appendNode()
215
+
216
+ this.appendRingToNode(n)
217
+ // this.appendBoxToNode(n)
218
+ this.appendOutlineToNode(n)
219
+
220
+ if (this.options.icons) {
221
+ this.appendTextToNode(n)
222
+ }
223
+
224
+ if (this.options.images) {
225
+ this.appendImageToNode(n)
226
+ }
227
+
228
+ return n
229
+ }
230
+
231
+ appendOutlineToNode(node) {
232
+ const outline = node
233
+ .append('circle')
234
+ .attr('class', 'outline')
235
+ .attr('r', this.options.nodeRadius)
236
+ .style('fill', d => {
237
+ return this.options.nodeOutlineFillColor ? this.options.nodeOutlineFillColor : this.class2color(d.labels[0])
238
+ })
239
+ .style('stroke', d => {
240
+ return this.options.nodeOutlineFillColor
241
+ ? this.class2darkenColor(this.options.nodeOutlineFillColor)
242
+ : this.class2darkenColor(d.labels[0])
243
+ })
244
+ .append('title')
245
+ .text(d => {
246
+ return this.toString(d)
247
+ })
248
+
249
+ node
250
+ .append('text')
251
+ .attr('class', 'node-text')
252
+ .attr('x', 0)
253
+ .attr('y', 52)
254
+ .attr('text-anchor', 'middle') // 텍스트 중앙 정렬
255
+ .attr('fill', 'black') // 텍스트 색상을 검은색으로 변경
256
+ .text(d => d.text)
257
+
258
+ return outline
259
+ }
260
+
261
+ appendBoxToNode(node) {
262
+ const rect = node
263
+ .append('rect')
264
+ .attr('class', 'node-rect')
265
+ .attr('width', 160) // 사각형의 너비
266
+ .attr('height', 30) // 사각형의 높이
267
+ .attr('x', -80) // 사각형의 중심을 기준으로 x 위치 조정
268
+ .attr('y', -15) // 사각형의 중심을 기준으로 y 위치 조정
269
+ .style('fill', d => {
270
+ return this.options.nodeOutlineFillColor ? this.options.nodeOutlineFillColor : this.class2color(d.labels[0])
271
+ })
272
+ .style('stroke', d => {
273
+ return this.options.nodeOutlineFillColor
274
+ ? this.class2darkenColor(this.options.nodeOutlineFillColor)
275
+ : this.class2darkenColor(d.labels[0])
276
+ })
277
+ .append('title')
278
+ .text(d => {
279
+ return this.toString(d)
280
+ })
281
+
282
+ node
283
+ .append('text')
284
+ .attr('class', 'node-text')
285
+ .attr('x', 0)
286
+ .attr('y', 4)
287
+ .attr('text-anchor', 'middle') // 텍스트 중앙 정렬
288
+ .attr('fill', 'black') // 텍스트 색상을 검은색으로 변경
289
+ .text(d => d.text)
290
+
291
+ return rect
292
+ }
293
+
294
+ appendRingToNode(node) {
295
+ return node
296
+ .append('circle')
297
+ .attr('class', 'ring')
298
+ .attr('r', this.options.nodeRadius * 1.16)
299
+ .append('title')
300
+ .text(d => {
301
+ return this.toString(d)
302
+ })
303
+ }
304
+
305
+ appendTextToNode(node) {
306
+ return node
307
+ .append('text')
308
+ .attr('fill', '#ffffff')
309
+ .attr('pointer-events', 'none')
310
+ .attr('text-anchor', 'middle')
311
+ .attr('y', '24px')
312
+ .attr('font-family', 'Material Symbols Outlined')
313
+ .attr('font-size', '48px')
314
+ .attr('text-anchor', 'middle')
315
+ .attr('alignment-baseline', 'top')
316
+ .text(d => this.icon(d))
317
+ }
318
+
319
+ appendRelationship() {
320
+ return this.relationship
321
+ .enter()
322
+ .append('g')
323
+ .attr('class', 'relationship')
324
+ .on('dblclick', (event, d) => {
325
+ if (typeof this.options.onRelationshipDoubleClick === 'function') {
326
+ this.options.onRelationshipDoubleClick(d)
327
+ }
328
+ })
329
+ .on('mouseenter', (event, d) => {
330
+ if (this.info) {
331
+ this.updateInfo(d)
332
+ }
333
+ })
334
+ }
335
+
336
+ appendOutlineToRelationship(r) {
337
+ return r.append('path').attr('class', 'outline').attr('fill', '#a5abb6').attr('stroke', 'none')
338
+ }
339
+
340
+ appendOverlayToRelationship(r) {
341
+ return r.append('path').attr('class', 'overlay')
342
+ }
343
+
344
+ appendTextToRelationship(r) {
345
+ return r
346
+ .append('text')
347
+ .attr('class', 'text')
348
+ .attr('fill', '#000000')
349
+ .attr('font-size', '8px')
350
+ .attr('pointer-events', 'none')
351
+ .attr('text-anchor', 'middle')
352
+ .text(d => {
353
+ return d.type
354
+ })
355
+ }
356
+
357
+ appendRelationshipToGraph() {
358
+ var relationship = this.appendRelationship(),
359
+ text = this.appendTextToRelationship(relationship),
360
+ outline = this.appendOutlineToRelationship(relationship),
361
+ overlay = this.appendOverlayToRelationship(relationship)
362
+
363
+ return {
364
+ outline: outline,
365
+ overlay: overlay,
366
+ relationship: relationship,
367
+ text: text
368
+ }
369
+ }
370
+
371
+ class2color(cls) {
372
+ var color = this.classes2colors[cls]
373
+
374
+ if (!color) {
375
+ // color = this.options.colors[Math.min(numClasses, this.options.colors.length - 1)];
376
+ color = this.options.colors[this.numClasses % this.options.colors.length]
377
+ this.classes2colors[cls] = color
378
+ this.numClasses++
379
+ }
380
+
381
+ return color
382
+ }
383
+
384
+ class2darkenColor(cls) {
385
+ return d3.rgb(this.class2color(cls)).darker(1)
386
+ }
387
+
388
+ clearInfo() {
389
+ this.info.html('')
390
+ }
391
+
392
+ color() {
393
+ return this.options.colors[(this.options.colors.length * Math.random()) << 0]
394
+ }
395
+
396
+ colors() {
397
+ // d3.schemeCategory10,
398
+ // d3.schemeCategory20,
399
+ return [
400
+ '#68bdf6', // light blue
401
+ '#6dce9e', // green #1
402
+ '#faafc2', // light pink
403
+ '#f2baf6', // purple
404
+ '#ff928c', // light red
405
+ '#fcea7e', // light yellow
406
+ '#ffc766', // light orange
407
+ '#405f9e', // navy blue
408
+ '#a5abb6', // dark gray
409
+ '#78cecb', // green #2,
410
+ '#b88cbb', // dark purple
411
+ '#ced2d9', // light gray
412
+ '#e84646', // dark red
413
+ '#fa5f86', // dark pink
414
+ '#ffab1a', // dark orange
415
+ '#fcda19', // dark yellow
416
+ '#797b80', // black
417
+ '#c9d96f', // pistacchio
418
+ '#47991f', // green #3
419
+ '#70edee', // turquoise
420
+ '#ff75ea' // pink
421
+ ]
422
+ }
423
+
424
+ contains(array, id) {
425
+ var filter = array.filter(function (elem) {
426
+ return elem.id === id
427
+ })
428
+
429
+ return filter.length > 0
430
+ }
431
+
432
+ defaultColor() {
433
+ return this.options.relationshipColor
434
+ }
435
+
436
+ defaultDarkenColor() {
437
+ return d3.rgb(this.options.colors[this.options.colors.length - 1]).darker(1)
438
+ }
439
+
440
+ dragEnded(event, d) {
441
+ if (!event.active) {
442
+ this.simulation.alphaTarget(0)
443
+ }
444
+
445
+ if (typeof this.options.onNodeDragEnd === 'function') {
446
+ this.options.onNodeDragEnd(d)
447
+ }
448
+ }
449
+
450
+ dragged(event, d) {
451
+ this.stickNode(event, d)
452
+ }
453
+
454
+ dragStarted(event, d) {
455
+ if (!event.active) {
456
+ this.simulation.alphaTarget(0.3).restart()
457
+ }
458
+
459
+ d.fx = d.x
460
+ d.fy = d.y
461
+
462
+ if (typeof this.options.onNodeDragStart === 'function') {
463
+ this.options.onNodeDragStart(d)
464
+ }
465
+ }
466
+
467
+ extend(obj1, obj2) {
468
+ var obj = {}
469
+
470
+ this.merge(obj, obj1)
471
+ this.merge(obj, obj2)
472
+
473
+ return obj
474
+ }
475
+
476
+ icon(d) {
477
+ return d.icon
478
+ }
479
+
480
+ image(d) {
481
+ var i, imagesForLabel, img, imgLevel, label, labelPropertyValue, property, value
482
+
483
+ if (this.options.images) {
484
+ imagesForLabel = this.options.imageMap[d.labels[0]]
485
+
486
+ if (imagesForLabel) {
487
+ imgLevel = 0
488
+
489
+ for (i = 0; i < imagesForLabel.length; i++) {
490
+ labelPropertyValue = imagesForLabel[i].split('|')
491
+
492
+ switch (labelPropertyValue.length) {
493
+ case 3:
494
+ value = labelPropertyValue[2]
495
+ /* falls through */
496
+ case 2:
497
+ property = labelPropertyValue[1]
498
+ /* falls through */
499
+ case 1:
500
+ label = labelPropertyValue[0]
501
+ }
502
+
503
+ if (
504
+ d.labels[0] === label &&
505
+ (!property || d.properties[property] !== undefined) &&
506
+ (!value || d.properties[property] === value)
507
+ ) {
508
+ if (labelPropertyValue.length > imgLevel) {
509
+ img = this.options.images[imagesForLabel[i]]
510
+ imgLevel = labelPropertyValue.length
511
+ }
512
+ }
513
+ }
514
+ }
515
+ }
516
+
517
+ return img
518
+ }
519
+
520
+ init(_selector, _options) {
521
+ this.merge(this.options, _options)
522
+
523
+ if (this.options.icons) {
524
+ this.options.showIcons = true
525
+ }
526
+
527
+ if (!this.options.minCollision) {
528
+ this.options.minCollision = this.options.nodeRadius * 2
529
+ }
530
+
531
+ this.selector = _selector
532
+
533
+ this.container = d3.select(this.selector)
534
+
535
+ // this.container.attr('class', 'graph-container').html('')
536
+
537
+ if (this.options.infoPanel) {
538
+ this.info = this.appendInfoPanel(this.container)
539
+ }
540
+
541
+ this.appendGraph(this.container)
542
+
543
+ this.simulation = this.initSimulation()
544
+
545
+ if (this.options.graphData) {
546
+ this.loadGraphData()
547
+ } else if (this.options.dataUrl) {
548
+ this.loadGraphDataFromUrl(this.options.dataUrl)
549
+ } else {
550
+ console.error('Error: both graphData and dataUrl are empty!')
551
+ }
552
+ }
553
+
554
+ initSimulation() {
555
+ const x = this.svg.node().parentElement.parentElement.clientWidth / 2
556
+ const y = this.svg.node().parentElement.parentElement.clientHeight / 2
557
+
558
+ var simulation = d3
559
+ .forceSimulation()
560
+ .force(
561
+ 'collide',
562
+ d3
563
+ .forceCollide()
564
+ .radius(d => {
565
+ return this.options.minCollision
566
+ })
567
+ .iterations(2)
568
+ )
569
+ .force('charge', d3.forceManyBody())
570
+ .force(
571
+ 'link',
572
+ d3.forceLink().id(d => {
573
+ return d.id
574
+ })
575
+ )
576
+ .force('center', d3.forceCenter(x, y))
577
+ .on('tick', () => {
578
+ this.tick()
579
+ })
580
+ .on('end', () => {
581
+ if (this.options.zoomFit && !this.justLoaded) {
582
+ this.justLoaded = true
583
+ this.zoomFit(2)
584
+ }
585
+ })
586
+
587
+ return simulation
588
+ }
589
+
590
+ loadGraphData() {
591
+ this.nodes = []
592
+ this.relationships = []
593
+
594
+ this.updateWithGraphData(this.options.graphData)
595
+ }
596
+
597
+ loadGraphDataFromUrl(dataUrl) {
598
+ this.nodes = []
599
+ this.relationships = []
600
+
601
+ d3.json(dataUrl, (error, data) => {
602
+ if (error) {
603
+ throw error
604
+ }
605
+
606
+ this.updateWithGraphData(data)
607
+ })
608
+ }
609
+
610
+ merge(target, source) {
611
+ Object.keys(source).forEach(property => {
612
+ target[property] = source[property]
613
+ })
614
+ }
615
+
616
+ graphDataToD3Data(data) {
617
+ var graph = {
618
+ nodes: [] as any[],
619
+ relationships: [] as any[]
620
+ }
621
+
622
+ data.results.forEach(result => {
623
+ result.data.forEach(data => {
624
+ data.graph.nodes.forEach(node => {
625
+ if (!this.contains(graph.nodes, node.id)) {
626
+ graph.nodes.push(node)
627
+ }
628
+ })
629
+
630
+ data.graph.relationships.forEach(function (relationship) {
631
+ relationship.source = relationship.startNode
632
+ relationship.target = relationship.endNode
633
+ graph.relationships.push(relationship)
634
+ })
635
+
636
+ data.graph.relationships.sort(function (a, b) {
637
+ if (a.source > b.source) {
638
+ return 1
639
+ } else if (a.source < b.source) {
640
+ return -1
641
+ } else {
642
+ if (a.target > b.target) {
643
+ return 1
644
+ }
645
+
646
+ if (a.target < b.target) {
647
+ return -1
648
+ } else {
649
+ return 0
650
+ }
651
+ }
652
+ })
653
+
654
+ for (var i = 0; i < data.graph.relationships.length; i++) {
655
+ if (
656
+ i !== 0 &&
657
+ data.graph.relationships[i].source === data.graph.relationships[i - 1].source &&
658
+ data.graph.relationships[i].target === data.graph.relationships[i - 1].target
659
+ ) {
660
+ data.graph.relationships[i].linknum = data.graph.relationships[i - 1].linknum + 1
661
+ } else {
662
+ data.graph.relationships[i].linknum = 1
663
+ }
664
+ }
665
+ })
666
+ })
667
+
668
+ return graph
669
+ }
670
+
671
+ randomD3Data(d, maxNodesToGenerate) {
672
+ var data = {
673
+ nodes: [] as any[],
674
+ relationships: [] as any[]
675
+ },
676
+ i,
677
+ label,
678
+ node,
679
+ numNodes = ((maxNodesToGenerate * Math.random()) << 0) + 1,
680
+ relationship,
681
+ s = this.size()
682
+
683
+ for (i = 0; i < numNodes; i++) {
684
+ label = this.randomLabel()
685
+
686
+ node = {
687
+ id: s.nodes + 1 + i,
688
+ labels: [label],
689
+ properties: {
690
+ random: label
691
+ },
692
+ x: d.x,
693
+ y: d.y
694
+ }
695
+
696
+ data.nodes[data.nodes.length] = node
697
+
698
+ relationship = {
699
+ id: s.relationships + 1 + i,
700
+ type: label.toUpperCase(),
701
+ startNode: d.id,
702
+ endNode: s.nodes + 1 + i,
703
+ properties: {
704
+ from: Date.now()
705
+ },
706
+ source: d.id,
707
+ target: s.nodes + 1 + i,
708
+ linknum: s.relationships + 1 + i
709
+ }
710
+
711
+ data.relationships[data.relationships.length] = relationship
712
+ }
713
+
714
+ return data
715
+ }
716
+
717
+ randomLabel() {
718
+ var icons = Object.keys(this.options.iconMap)
719
+ return icons[(icons.length * Math.random()) << 0]
720
+ }
721
+
722
+ rotate(cx, cy, x, y, angle) {
723
+ var radians = (Math.PI / 180) * angle,
724
+ cos = Math.cos(radians),
725
+ sin = Math.sin(radians),
726
+ nx = cos * (x - cx) + sin * (y - cy) + cx,
727
+ ny = cos * (y - cy) - sin * (x - cx) + cy
728
+
729
+ return { x: nx, y: ny }
730
+ }
731
+
732
+ rotatePoint(c, p, angle) {
733
+ return this.rotate(c.x, c.y, p.x, p.y, angle)
734
+ }
735
+
736
+ rotation(source, target) {
737
+ return (Math.atan2(target.y - source.y, target.x - source.x) * 180) / Math.PI
738
+ }
739
+
740
+ size() {
741
+ return {
742
+ nodes: this.nodes.length,
743
+ relationships: this.relationships.length
744
+ }
745
+ }
746
+
747
+ stickNode(event, d) {
748
+ d.fx = event.x
749
+ d.fy = event.y
750
+ }
751
+
752
+ tick() {
753
+ this.tickNodes()
754
+ this.tickRelationships()
755
+ }
756
+
757
+ tickNodes() {
758
+ if (this.node) {
759
+ this.node.attr('transform', d => {
760
+ return 'translate(' + d.x + ', ' + d.y + ')'
761
+ })
762
+ }
763
+ }
764
+
765
+ tickRelationships() {
766
+ if (this.relationship) {
767
+ this.relationship.attr('transform', d => {
768
+ var angle = this.rotation(d.source, d.target)
769
+ return 'translate(' + d.source.x + ', ' + d.source.y + ') rotate(' + angle + ')'
770
+ })
771
+
772
+ this.tickRelationshipsTexts()
773
+ this.tickRelationshipsOutlines()
774
+ this.tickRelationshipsOverlays()
775
+ }
776
+ }
777
+
778
+ tickRelationshipsOutlines() {
779
+ const self = this
780
+ this.relationship.each(function (this, relationship) {
781
+ var rel = d3.select(this)
782
+ var outline = rel.select('.outline'),
783
+ text = rel.select('.text'),
784
+ bbox = text.node().getBBox(),
785
+ padding = 3
786
+
787
+ outline.attr('d', d => {
788
+ var center = { x: 0, y: 0 },
789
+ angle = self.rotation(d.source, d.target),
790
+ textBoundingBox = text.node().getBBox(),
791
+ textPadding = 5,
792
+ u = self.unitaryVector(d.source, d.target),
793
+ textMargin = {
794
+ x: (d.target.x - d.source.x - (textBoundingBox.width + textPadding) * u.x) * 0.5,
795
+ y: (d.target.y - d.source.y - (textBoundingBox.width + textPadding) * u.y) * 0.5
796
+ },
797
+ n = self.unitaryNormalVector(d.source, d.target),
798
+ rotatedPointA1 = self.rotatePoint(
799
+ center,
800
+ { x: 0 + (self.options.nodeRadius + 1) * u.x - n.x, y: 0 + (self.options.nodeRadius + 1) * u.y - n.y },
801
+ angle
802
+ ),
803
+ rotatedPointB1 = self.rotatePoint(center, { x: textMargin.x - n.x, y: textMargin.y - n.y }, angle),
804
+ rotatedPointC1 = self.rotatePoint(center, { x: textMargin.x, y: textMargin.y }, angle),
805
+ rotatedPointD1 = self.rotatePoint(
806
+ center,
807
+ { x: 0 + (self.options.nodeRadius + 1) * u.x, y: 0 + (self.options.nodeRadius + 1) * u.y },
808
+ angle
809
+ ),
810
+ rotatedPointA2 = self.rotatePoint(
811
+ center,
812
+ { x: d.target.x - d.source.x - textMargin.x - n.x, y: d.target.y - d.source.y - textMargin.y - n.y },
813
+ angle
814
+ ),
815
+ rotatedPointB2 = self.rotatePoint(
816
+ center,
817
+ {
818
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x - n.x - u.x * self.options.arrowSize,
819
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y - n.y - u.y * self.options.arrowSize
820
+ },
821
+ angle
822
+ ),
823
+ rotatedPointC2 = self.rotatePoint(
824
+ center,
825
+ {
826
+ x:
827
+ d.target.x -
828
+ d.source.x -
829
+ (self.options.nodeRadius + 1) * u.x -
830
+ n.x +
831
+ (n.x - u.x) * self.options.arrowSize,
832
+ y:
833
+ d.target.y -
834
+ d.source.y -
835
+ (self.options.nodeRadius + 1) * u.y -
836
+ n.y +
837
+ (n.y - u.y) * self.options.arrowSize
838
+ },
839
+ angle
840
+ ),
841
+ rotatedPointD2 = self.rotatePoint(
842
+ center,
843
+ {
844
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x,
845
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y
846
+ },
847
+ angle
848
+ ),
849
+ rotatedPointE2 = self.rotatePoint(
850
+ center,
851
+ {
852
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x + (-n.x - u.x) * self.options.arrowSize,
853
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y + (-n.y - u.y) * self.options.arrowSize
854
+ },
855
+ angle
856
+ ),
857
+ rotatedPointF2 = self.rotatePoint(
858
+ center,
859
+ {
860
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x - u.x * self.options.arrowSize,
861
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y - u.y * self.options.arrowSize
862
+ },
863
+ angle
864
+ ),
865
+ rotatedPointG2 = self.rotatePoint(
866
+ center,
867
+ { x: d.target.x - d.source.x - textMargin.x, y: d.target.y - d.source.y - textMargin.y },
868
+ angle
869
+ )
870
+
871
+ return (
872
+ 'M ' +
873
+ rotatedPointA1.x +
874
+ ' ' +
875
+ rotatedPointA1.y +
876
+ ' L ' +
877
+ rotatedPointB1.x +
878
+ ' ' +
879
+ rotatedPointB1.y +
880
+ ' L ' +
881
+ rotatedPointC1.x +
882
+ ' ' +
883
+ rotatedPointC1.y +
884
+ ' L ' +
885
+ rotatedPointD1.x +
886
+ ' ' +
887
+ rotatedPointD1.y +
888
+ ' Z M ' +
889
+ rotatedPointA2.x +
890
+ ' ' +
891
+ rotatedPointA2.y +
892
+ ' L ' +
893
+ rotatedPointB2.x +
894
+ ' ' +
895
+ rotatedPointB2.y +
896
+ ' L ' +
897
+ rotatedPointC2.x +
898
+ ' ' +
899
+ rotatedPointC2.y +
900
+ ' L ' +
901
+ rotatedPointD2.x +
902
+ ' ' +
903
+ rotatedPointD2.y +
904
+ ' L ' +
905
+ rotatedPointE2.x +
906
+ ' ' +
907
+ rotatedPointE2.y +
908
+ ' L ' +
909
+ rotatedPointF2.x +
910
+ ' ' +
911
+ rotatedPointF2.y +
912
+ ' L ' +
913
+ rotatedPointG2.x +
914
+ ' ' +
915
+ rotatedPointG2.y +
916
+ ' Z'
917
+ )
918
+ })
919
+ })
920
+ }
921
+
922
+ tickRelationshipsOverlays() {
923
+ this.relationshipOverlay.attr('d', d => {
924
+ var center = { x: 0, y: 0 },
925
+ angle = this.rotation(d.source, d.target),
926
+ n1 = this.unitaryNormalVector(d.source, d.target),
927
+ n = this.unitaryNormalVector(d.source, d.target, 50),
928
+ rotatedPointA = this.rotatePoint(center, { x: 0 - n.x, y: 0 - n.y }, angle),
929
+ rotatedPointB = this.rotatePoint(
930
+ center,
931
+ { x: d.target.x - d.source.x - n.x, y: d.target.y - d.source.y - n.y },
932
+ angle
933
+ ),
934
+ rotatedPointC = this.rotatePoint(
935
+ center,
936
+ { x: d.target.x - d.source.x + n.x - n1.x, y: d.target.y - d.source.y + n.y - n1.y },
937
+ angle
938
+ ),
939
+ rotatedPointD = this.rotatePoint(center, { x: 0 + n.x - n1.x, y: 0 + n.y - n1.y }, angle)
940
+
941
+ return (
942
+ 'M ' +
943
+ rotatedPointA.x +
944
+ ' ' +
945
+ rotatedPointA.y +
946
+ ' L ' +
947
+ rotatedPointB.x +
948
+ ' ' +
949
+ rotatedPointB.y +
950
+ ' L ' +
951
+ rotatedPointC.x +
952
+ ' ' +
953
+ rotatedPointC.y +
954
+ ' L ' +
955
+ rotatedPointD.x +
956
+ ' ' +
957
+ rotatedPointD.y +
958
+ ' Z'
959
+ )
960
+ })
961
+ }
962
+
963
+ tickRelationshipsTexts() {
964
+ this.relationshipText.attr('transform', d => {
965
+ var angle = (this.rotation(d.source, d.target) + 360) % 360,
966
+ mirror = angle > 90 && angle < 270,
967
+ center = { x: 0, y: 0 },
968
+ n = this.unitaryNormalVector(d.source, d.target),
969
+ nWeight = mirror ? 2 : -3,
970
+ point = {
971
+ x: (d.target.x - d.source.x) * 0.5 + n.x * nWeight,
972
+ y: (d.target.y - d.source.y) * 0.5 + n.y * nWeight
973
+ },
974
+ rotatedPoint = this.rotatePoint(center, point, angle)
975
+
976
+ return 'translate(' + rotatedPoint.x + ', ' + rotatedPoint.y + ') rotate(' + (mirror ? 180 : 0) + ')'
977
+ })
978
+ }
979
+
980
+ toString(d) {
981
+ var s = d.labels ? d.labels[0] : d.type
982
+
983
+ s += ' (<id>: ' + d.id
984
+
985
+ Object.keys(d.properties).forEach(function (property) {
986
+ s += ', ' + property + ': ' + JSON.stringify(d.properties[property])
987
+ })
988
+
989
+ s += ')'
990
+
991
+ return s
992
+ }
993
+
994
+ unitaryNormalVector(source, target, newLength?: any) {
995
+ var center = { x: 0, y: 0 },
996
+ vector = this.unitaryVector(source, target, newLength)
997
+
998
+ return this.rotatePoint(center, vector, 90)
999
+ }
1000
+
1001
+ unitaryVector(source, target, newLength?: any) {
1002
+ var length =
1003
+ Math.sqrt(Math.pow(target.x - source.x, 2) + Math.pow(target.y - source.y, 2)) / Math.sqrt(newLength || 1)
1004
+
1005
+ return {
1006
+ x: (target.x - source.x) / length,
1007
+ y: (target.y - source.y) / length
1008
+ }
1009
+ }
1010
+
1011
+ updateWithD3Data(d3Data) {
1012
+ this.updateNodesAndRelationships(d3Data.nodes, d3Data.relationships)
1013
+ }
1014
+
1015
+ updateWithGraphData(graphData) {
1016
+ var d3Data = this.graphDataToD3Data(graphData)
1017
+ this.updateWithD3Data(d3Data)
1018
+ }
1019
+
1020
+ updateInfo(d) {
1021
+ this.clearInfo()
1022
+
1023
+ if (d.labels) {
1024
+ this.appendInfoElementClass('class', d.labels[0])
1025
+ } else {
1026
+ this.appendInfoElementRelationship('class', d.type)
1027
+ }
1028
+
1029
+ this.appendInfoElementProperty('property', '&lt;id&gt;', d.id)
1030
+
1031
+ Object.keys(d.properties).forEach(property => {
1032
+ this.appendInfoElementProperty('property', property, JSON.stringify(d.properties[property]))
1033
+ })
1034
+ }
1035
+
1036
+ updateNodes(n) {
1037
+ Array.prototype.push.apply(this.nodes, n)
1038
+
1039
+ this.node = this.svgNodes.selectAll('.node').data(this.nodes, d => {
1040
+ return d.id
1041
+ })
1042
+ var nodeEnter = this.appendNodeToGraph()
1043
+ this.node = nodeEnter.merge(this.node)
1044
+ }
1045
+
1046
+ updateNodesAndRelationships(n, r) {
1047
+ this.updateRelationships(r)
1048
+ this.updateNodes(n)
1049
+
1050
+ this.simulation.nodes(this.nodes)
1051
+ this.simulation.force('link').links(this.relationships)
1052
+ }
1053
+
1054
+ updateRelationships(r) {
1055
+ Array.prototype.push.apply(this.relationships, r)
1056
+
1057
+ this.relationship = this.svgRelationships.selectAll('.relationship').data(this.relationships, d => {
1058
+ return d.id
1059
+ })
1060
+
1061
+ var relationshipEnter = this.appendRelationshipToGraph()
1062
+
1063
+ this.relationship = relationshipEnter.relationship.merge(this.relationship)
1064
+
1065
+ this.relationshipOutline = this.svg.selectAll('.relationship .outline')
1066
+ this.relationshipOutline = relationshipEnter.outline.merge(this.relationshipOutline)
1067
+
1068
+ this.relationshipOverlay = this.svg.selectAll('.relationship .overlay')
1069
+ this.relationshipOverlay = relationshipEnter.overlay.merge(this.relationshipOverlay)
1070
+
1071
+ this.relationshipText = this.svg.selectAll('.relationship .text')
1072
+ this.relationshipText = relationshipEnter.text.merge(this.relationshipText)
1073
+ }
1074
+
1075
+ zoomFit(transitionDuration) {
1076
+ var bounds = this.svg.node().getBBox(),
1077
+ parent = this.svg.node().parentElement.parentElement,
1078
+ fullWidth = parent.clientWidth,
1079
+ fullHeight = parent.clientHeight,
1080
+ width = bounds.width,
1081
+ height = bounds.height,
1082
+ midX = bounds.x + width / 2,
1083
+ midY = bounds.y + height / 2
1084
+
1085
+ if (width === 0 || height === 0) {
1086
+ return // nothing to fit
1087
+ }
1088
+
1089
+ this.svgScale = 0.85 / Math.max(width / fullWidth, height / fullHeight)
1090
+ this.svgTranslate = [fullWidth / 2 - this.svgScale * midX, fullHeight / 2 - this.svgScale * midY]
1091
+
1092
+ this.svg.attr(
1093
+ 'transform',
1094
+ 'translate(' + this.svgTranslate[0] + ', ' + this.svgTranslate[1] + ') scale(' + this.svgScale + ')'
1095
+ )
1096
+ }
1097
+ }