@things-factory/integration-ui 6.1.103 → 6.1.105

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.
@@ -0,0 +1,1812 @@
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
+ iconMap: this.fontAwesomeIcons(),
29
+ icons: undefined,
30
+ imageMap: {},
31
+ images: undefined,
32
+ infoPanel: true,
33
+ minCollision: undefined,
34
+ graphData: undefined,
35
+ dataUrl: undefined,
36
+ nodeOutlineFillColor: undefined,
37
+ nodeRadius: 25,
38
+ relationshipColor: '#a5abb6',
39
+ zoomFit: false
40
+ } as any
41
+
42
+ constructor(_selector: any, _options: any) {
43
+ this.init(_selector, _options)
44
+ }
45
+
46
+ appendGraph(container) {
47
+ this.svg = container
48
+ .append('svg')
49
+ .attr('width', '100%')
50
+ .attr('height', '100%')
51
+ .attr('class', 'graph-viewer')
52
+ .call(
53
+ d3.zoom().on('zoom', (event, d) => {
54
+ var scale = event.transform.k,
55
+ translate = [event.transform.x, event.transform.y]
56
+
57
+ if (this.svgTranslate) {
58
+ translate[0] += this.svgTranslate[0]
59
+ translate[1] += this.svgTranslate[1]
60
+ }
61
+
62
+ if (this.svgScale) {
63
+ scale *= this.svgScale
64
+ }
65
+
66
+ this.svg.attr('transform', 'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')')
67
+ })
68
+ )
69
+ .on('dblclick.zoom', null)
70
+ .append('g')
71
+ .attr('width', '100%')
72
+ .attr('height', '100%')
73
+
74
+ this.svgRelationships = this.svg.append('g').attr('class', 'relationships')
75
+
76
+ this.svgNodes = this.svg.append('g').attr('class', 'nodes')
77
+ }
78
+
79
+ appendImageToNode(node) {
80
+ return node
81
+ .append('image')
82
+ .attr('height', d => {
83
+ return this.icon(d) ? '24px' : '30px'
84
+ })
85
+ .attr('x', d => {
86
+ return this.icon(d) ? '5px' : '-15px'
87
+ })
88
+ .attr('xlink:href', d => {
89
+ return this.image(d)
90
+ })
91
+ .attr('y', d => {
92
+ return this.icon(d) ? '5px' : '-16px'
93
+ })
94
+ .attr('width', d => {
95
+ return this.icon(d) ? '24px' : '30px'
96
+ })
97
+ }
98
+
99
+ appendInfoPanel(container) {
100
+ return container.append('div').attr('class', 'graph-info')
101
+ }
102
+
103
+ appendInfoElement(cls, isNode, property, value?: any) {
104
+ var elem = this.info.append('a')
105
+
106
+ elem
107
+ .attr('href', '#')
108
+ .attr('class', cls)
109
+ .html('<strong>' + property + '</strong>' + (value ? ': ' + value : ''))
110
+
111
+ if (!value) {
112
+ elem
113
+ .style('background-color', d => {
114
+ return this.options.nodeOutlineFillColor
115
+ ? this.options.nodeOutlineFillColor
116
+ : isNode
117
+ ? this.class2color(property)
118
+ : this.defaultColor()
119
+ })
120
+ .style('border-color', d => {
121
+ return this.options.nodeOutlineFillColor
122
+ ? this.class2darkenColor(this.options.nodeOutlineFillColor)
123
+ : isNode
124
+ ? this.class2darkenColor(property)
125
+ : this.defaultDarkenColor()
126
+ })
127
+ .style('color', d => {
128
+ return this.options.nodeOutlineFillColor ? this.class2darkenColor(this.options.nodeOutlineFillColor) : '#fff'
129
+ })
130
+ }
131
+ }
132
+
133
+ appendInfoElementClass(cls, node) {
134
+ this.appendInfoElement(cls, true, node)
135
+ }
136
+
137
+ appendInfoElementProperty(cls, property, value) {
138
+ this.appendInfoElement(cls, false, property, value)
139
+ }
140
+
141
+ appendInfoElementRelationship(cls, relationship) {
142
+ this.appendInfoElement(cls, false, relationship)
143
+ }
144
+
145
+ appendNode() {
146
+ return this.node
147
+ .enter()
148
+ .append('g')
149
+ .attr('class', d => {
150
+ var highlight,
151
+ i,
152
+ classes = 'node',
153
+ label = d.labels[0]
154
+
155
+ if (this.icon(d)) {
156
+ classes += ' node-icon'
157
+ }
158
+
159
+ if (this.image(d)) {
160
+ classes += ' node-image'
161
+ }
162
+
163
+ if (this.options.highlight) {
164
+ for (i = 0; i < this.options.highlight.length; i++) {
165
+ highlight = this.options.highlight[i]
166
+
167
+ if (d.labels[0] === highlight.class && d.properties[highlight.property] === highlight.value) {
168
+ classes += ' node-highlighted'
169
+ break
170
+ }
171
+ }
172
+ }
173
+
174
+ return classes
175
+ })
176
+ .on('click', (event, d) => {
177
+ d.fx = d.fy = null
178
+
179
+ if (typeof this.options.onNodeClick === 'function') {
180
+ this.options.onNodeClick(d)
181
+ }
182
+ })
183
+ .on('dblclick', (event, d) => {
184
+ this.stickNode(d, event)
185
+
186
+ if (typeof this.options.onNodeDoubleClick === 'function') {
187
+ this.options.onNodeDoubleClick(d)
188
+ }
189
+ })
190
+ .on('mouseenter', (event, d) => {
191
+ if (this.info) {
192
+ this.updateInfo(d)
193
+ }
194
+
195
+ if (typeof this.options.onNodeMouseEnter === 'function') {
196
+ this.options.onNodeMouseEnter(d)
197
+ }
198
+ })
199
+ .on('mouseleave', (event, d) => {
200
+ if (this.info) {
201
+ this.clearInfo()
202
+ }
203
+
204
+ if (typeof this.options.onNodeMouseLeave === 'function') {
205
+ this.options.onNodeMouseLeave(d)
206
+ }
207
+ })
208
+ .call(
209
+ d3
210
+ .drag()
211
+ .on('start', this.dragStarted.bind(this))
212
+ .on('drag', this.dragged.bind(this))
213
+ .on('end', this.dragEnded.bind(this))
214
+ )
215
+ }
216
+
217
+ appendNodeToGraph() {
218
+ var n = this.appendNode()
219
+
220
+ this.appendRingToNode(n)
221
+ // this.appendBoxToNode(n)
222
+ this.appendOutlineToNode(n)
223
+
224
+ if (this.options.icons) {
225
+ this.appendTextToNode(n)
226
+ }
227
+
228
+ if (this.options.images) {
229
+ this.appendImageToNode(n)
230
+ }
231
+
232
+ return n
233
+ }
234
+
235
+ appendOutlineToNode(node) {
236
+ return node
237
+ .append('circle')
238
+ .attr('class', 'outline')
239
+ .attr('r', this.options.nodeRadius)
240
+ .style('fill', d => {
241
+ return this.options.nodeOutlineFillColor ? this.options.nodeOutlineFillColor : this.class2color(d.labels[0])
242
+ })
243
+ .style('stroke', d => {
244
+ return this.options.nodeOutlineFillColor
245
+ ? this.class2darkenColor(this.options.nodeOutlineFillColor)
246
+ : this.class2darkenColor(d.labels[0])
247
+ })
248
+ .append('title')
249
+ .text(d => {
250
+ return this.toString(d)
251
+ })
252
+ }
253
+
254
+ appendBoxToNode(node) {
255
+ const rect = node
256
+ .append('rect')
257
+ .attr('class', 'node-rect')
258
+ .attr('width', 160) // 사각형의 너비
259
+ .attr('height', 30) // 사각형의 높이
260
+ .attr('x', -80) // 사각형의 중심을 기준으로 x 위치 조정
261
+ .attr('y', -15) // 사각형의 중심을 기준으로 y 위치 조정
262
+ .style('fill', d => {
263
+ return this.options.nodeOutlineFillColor ? this.options.nodeOutlineFillColor : this.class2color(d.labels[0])
264
+ })
265
+ .style('stroke', d => {
266
+ return this.options.nodeOutlineFillColor
267
+ ? this.class2darkenColor(this.options.nodeOutlineFillColor)
268
+ : this.class2darkenColor(d.labels[0])
269
+ })
270
+ .append('title')
271
+ .text(d => {
272
+ return this.toString(d)
273
+ })
274
+
275
+ node
276
+ .append('text')
277
+ .attr('class', 'node-text')
278
+ .attr('x', 0)
279
+ .attr('y', 4)
280
+ .attr('text-anchor', 'middle') // 텍스트 중앙 정렬
281
+ .attr('fill', 'black') // 텍스트 색상을 검은색으로 변경
282
+ .text(d => d.text)
283
+
284
+ return rect
285
+ }
286
+
287
+ appendRingToNode(node) {
288
+ return node
289
+ .append('circle')
290
+ .attr('class', 'ring')
291
+ .attr('r', this.options.nodeRadius * 1.16)
292
+ .append('title')
293
+ .text(d => {
294
+ return this.toString(d)
295
+ })
296
+ }
297
+
298
+ appendTextToNode(node) {
299
+ return node
300
+ .append('text')
301
+ .attr('class', d => {
302
+ return 'text' + (this.icon(d) ? ' icon' : '')
303
+ })
304
+ .attr('fill', '#ffffff')
305
+ .attr('font-size', d => {
306
+ return this.icon(d) ? this.options.nodeRadius + 'px' : '10px'
307
+ })
308
+ .attr('pointer-events', 'none')
309
+ .attr('text-anchor', 'middle')
310
+ .attr('y', d => {
311
+ return this.icon(d) ? Math.round(this.options.nodeRadius * 0.32) + 'px' : '4px'
312
+ })
313
+ .html(d => {
314
+ var _icon = this.icon(d)
315
+ return _icon ? '&#x' + _icon : d.text
316
+ })
317
+ }
318
+
319
+ appendRandomDataToNode(d, maxNodesToGenerate) {
320
+ var data = this.randomD3Data(d, maxNodesToGenerate)
321
+ this.updateWithGraphData(data)
322
+ }
323
+
324
+ appendRelationship() {
325
+ return this.relationship
326
+ .enter()
327
+ .append('g')
328
+ .attr('class', 'relationship')
329
+ .on('dblclick', (event, d) => {
330
+ if (typeof this.options.onRelationshipDoubleClick === 'function') {
331
+ this.options.onRelationshipDoubleClick(d)
332
+ }
333
+ })
334
+ .on('mouseenter', (event, d) => {
335
+ if (this.info) {
336
+ this.updateInfo(d)
337
+ }
338
+ })
339
+ }
340
+
341
+ appendOutlineToRelationship(r) {
342
+ return r.append('path').attr('class', 'outline').attr('fill', '#a5abb6').attr('stroke', 'none')
343
+ }
344
+
345
+ appendOverlayToRelationship(r) {
346
+ return r.append('path').attr('class', 'overlay')
347
+ }
348
+
349
+ appendTextToRelationship(r) {
350
+ return r
351
+ .append('text')
352
+ .attr('class', 'text')
353
+ .attr('fill', '#000000')
354
+ .attr('font-size', '8px')
355
+ .attr('pointer-events', 'none')
356
+ .attr('text-anchor', 'middle')
357
+ .text(d => {
358
+ return d.type
359
+ })
360
+ }
361
+
362
+ appendRelationshipToGraph() {
363
+ var relationship = this.appendRelationship(),
364
+ text = this.appendTextToRelationship(relationship),
365
+ outline = this.appendOutlineToRelationship(relationship),
366
+ overlay = this.appendOverlayToRelationship(relationship)
367
+
368
+ return {
369
+ outline: outline,
370
+ overlay: overlay,
371
+ relationship: relationship,
372
+ text: text
373
+ }
374
+ }
375
+
376
+ class2color(cls) {
377
+ var color = this.classes2colors[cls]
378
+
379
+ if (!color) {
380
+ // color = this.options.colors[Math.min(numClasses, this.options.colors.length - 1)];
381
+ color = this.options.colors[this.numClasses % this.options.colors.length]
382
+ this.classes2colors[cls] = color
383
+ this.numClasses++
384
+ }
385
+
386
+ return color
387
+ }
388
+
389
+ class2darkenColor(cls) {
390
+ return d3.rgb(this.class2color(cls)).darker(1)
391
+ }
392
+
393
+ clearInfo() {
394
+ this.info.html('')
395
+ }
396
+
397
+ color() {
398
+ return this.options.colors[(this.options.colors.length * Math.random()) << 0]
399
+ }
400
+
401
+ colors() {
402
+ // d3.schemeCategory10,
403
+ // d3.schemeCategory20,
404
+ return [
405
+ '#68bdf6', // light blue
406
+ '#6dce9e', // green #1
407
+ '#faafc2', // light pink
408
+ '#f2baf6', // purple
409
+ '#ff928c', // light red
410
+ '#fcea7e', // light yellow
411
+ '#ffc766', // light orange
412
+ '#405f9e', // navy blue
413
+ '#a5abb6', // dark gray
414
+ '#78cecb', // green #2,
415
+ '#b88cbb', // dark purple
416
+ '#ced2d9', // light gray
417
+ '#e84646', // dark red
418
+ '#fa5f86', // dark pink
419
+ '#ffab1a', // dark orange
420
+ '#fcda19', // dark yellow
421
+ '#797b80', // black
422
+ '#c9d96f', // pistacchio
423
+ '#47991f', // green #3
424
+ '#70edee', // turquoise
425
+ '#ff75ea' // pink
426
+ ]
427
+ }
428
+
429
+ contains(array, id) {
430
+ var filter = array.filter(function (elem) {
431
+ return elem.id === id
432
+ })
433
+
434
+ return filter.length > 0
435
+ }
436
+
437
+ defaultColor() {
438
+ return this.options.relationshipColor
439
+ }
440
+
441
+ defaultDarkenColor() {
442
+ return d3.rgb(this.options.colors[this.options.colors.length - 1]).darker(1)
443
+ }
444
+
445
+ dragEnded(d, event) {
446
+ if (!event.active) {
447
+ this.simulation.alphaTarget(0)
448
+ }
449
+
450
+ if (typeof this.options.onNodeDragEnd === 'function') {
451
+ this.options.onNodeDragEnd(d)
452
+ }
453
+ }
454
+
455
+ dragged(d, event) {
456
+ this.stickNode(d, event)
457
+ }
458
+
459
+ dragStarted(d, event) {
460
+ if (!event.active) {
461
+ this.simulation.alphaTarget(0.3).restart()
462
+ }
463
+
464
+ d.fx = d.x
465
+ d.fy = d.y
466
+
467
+ if (typeof this.options.onNodeDragStart === 'function') {
468
+ this.options.onNodeDragStart(d)
469
+ }
470
+ }
471
+
472
+ extend(obj1, obj2) {
473
+ var obj = {}
474
+
475
+ this.merge(obj, obj1)
476
+ this.merge(obj, obj2)
477
+
478
+ return obj
479
+ }
480
+
481
+ fontAwesomeIcons() {
482
+ return {
483
+ glass: 'f000',
484
+ music: 'f001',
485
+ search: 'f002',
486
+ 'envelope-o': 'f003',
487
+ heart: 'f004',
488
+ star: 'f005',
489
+ 'star-o': 'f006',
490
+ user: 'f007',
491
+ film: 'f008',
492
+ 'th-large': 'f009',
493
+ th: 'f00a',
494
+ 'th-list': 'f00b',
495
+ check: 'f00c',
496
+ 'remove,close,times': 'f00d',
497
+ 'search-plus': 'f00e',
498
+ 'search-minus': 'f010',
499
+ 'power-off': 'f011',
500
+ signal: 'f012',
501
+ 'gear,cog': 'f013',
502
+ 'trash-o': 'f014',
503
+ home: 'f015',
504
+ 'file-o': 'f016',
505
+ 'clock-o': 'f017',
506
+ road: 'f018',
507
+ download: 'f019',
508
+ 'arrow-circle-o-down': 'f01a',
509
+ 'arrow-circle-o-up': 'f01b',
510
+ inbox: 'f01c',
511
+ 'play-circle-o': 'f01d',
512
+ 'rotate-right,repeat': 'f01e',
513
+ refresh: 'f021',
514
+ 'list-alt': 'f022',
515
+ lock: 'f023',
516
+ flag: 'f024',
517
+ headphones: 'f025',
518
+ 'volume-off': 'f026',
519
+ 'volume-down': 'f027',
520
+ 'volume-up': 'f028',
521
+ qrcode: 'f029',
522
+ barcode: 'f02a',
523
+ tag: 'f02b',
524
+ tags: 'f02c',
525
+ book: 'f02d',
526
+ bookmark: 'f02e',
527
+ print: 'f02f',
528
+ camera: 'f030',
529
+ font: 'f031',
530
+ bold: 'f032',
531
+ italic: 'f033',
532
+ 'text-height': 'f034',
533
+ 'text-width': 'f035',
534
+ 'align-left': 'f036',
535
+ 'align-center': 'f037',
536
+ 'align-right': 'f038',
537
+ 'align-justify': 'f039',
538
+ list: 'f03a',
539
+ 'dedent,outdent': 'f03b',
540
+ indent: 'f03c',
541
+ 'video-camera': 'f03d',
542
+ 'photo,image,picture-o': 'f03e',
543
+ pencil: 'f040',
544
+ 'map-marker': 'f041',
545
+ adjust: 'f042',
546
+ tint: 'f043',
547
+ 'edit,pencil-square-o': 'f044',
548
+ 'share-square-o': 'f045',
549
+ 'check-square-o': 'f046',
550
+ arrows: 'f047',
551
+ 'step-backward': 'f048',
552
+ 'fast-backward': 'f049',
553
+ backward: 'f04a',
554
+ play: 'f04b',
555
+ pause: 'f04c',
556
+ stop: 'f04d',
557
+ forward: 'f04e',
558
+ 'fast-forward': 'f050',
559
+ 'step-forward': 'f051',
560
+ eject: 'f052',
561
+ 'chevron-left': 'f053',
562
+ 'chevron-right': 'f054',
563
+ 'plus-circle': 'f055',
564
+ 'minus-circle': 'f056',
565
+ 'times-circle': 'f057',
566
+ 'check-circle': 'f058',
567
+ 'question-circle': 'f059',
568
+ 'info-circle': 'f05a',
569
+ crosshairs: 'f05b',
570
+ 'times-circle-o': 'f05c',
571
+ 'check-circle-o': 'f05d',
572
+ ban: 'f05e',
573
+ 'arrow-left': 'f060',
574
+ 'arrow-right': 'f061',
575
+ 'arrow-up': 'f062',
576
+ 'arrow-down': 'f063',
577
+ 'mail-forward,share': 'f064',
578
+ expand: 'f065',
579
+ compress: 'f066',
580
+ plus: 'f067',
581
+ minus: 'f068',
582
+ asterisk: 'f069',
583
+ 'exclamation-circle': 'f06a',
584
+ gift: 'f06b',
585
+ leaf: 'f06c',
586
+ fire: 'f06d',
587
+ eye: 'f06e',
588
+ 'eye-slash': 'f070',
589
+ 'warning,exclamation-triangle': 'f071',
590
+ plane: 'f072',
591
+ calendar: 'f073',
592
+ random: 'f074',
593
+ comment: 'f075',
594
+ magnet: 'f076',
595
+ 'chevron-up': 'f077',
596
+ 'chevron-down': 'f078',
597
+ retweet: 'f079',
598
+ 'shopping-cart': 'f07a',
599
+ folder: 'f07b',
600
+ 'folder-open': 'f07c',
601
+ 'arrows-v': 'f07d',
602
+ 'arrows-h': 'f07e',
603
+ 'bar-chart-o,bar-chart': 'f080',
604
+ 'twitter-square': 'f081',
605
+ 'facebook-square': 'f082',
606
+ 'camera-retro': 'f083',
607
+ key: 'f084',
608
+ 'gears,cogs': 'f085',
609
+ comments: 'f086',
610
+ 'thumbs-o-up': 'f087',
611
+ 'thumbs-o-down': 'f088',
612
+ 'star-half': 'f089',
613
+ 'heart-o': 'f08a',
614
+ 'sign-out': 'f08b',
615
+ 'linkedin-square': 'f08c',
616
+ 'thumb-tack': 'f08d',
617
+ 'external-link': 'f08e',
618
+ 'sign-in': 'f090',
619
+ trophy: 'f091',
620
+ 'github-square': 'f092',
621
+ upload: 'f093',
622
+ 'lemon-o': 'f094',
623
+ phone: 'f095',
624
+ 'square-o': 'f096',
625
+ 'bookmark-o': 'f097',
626
+ 'phone-square': 'f098',
627
+ twitter: 'f099',
628
+ 'facebook-f,facebook': 'f09a',
629
+ github: 'f09b',
630
+ unlock: 'f09c',
631
+ 'credit-card': 'f09d',
632
+ 'feed,rss': 'f09e',
633
+ 'hdd-o': 'f0a0',
634
+ bullhorn: 'f0a1',
635
+ bell: 'f0f3',
636
+ certificate: 'f0a3',
637
+ 'hand-o-right': 'f0a4',
638
+ 'hand-o-left': 'f0a5',
639
+ 'hand-o-up': 'f0a6',
640
+ 'hand-o-down': 'f0a7',
641
+ 'arrow-circle-left': 'f0a8',
642
+ 'arrow-circle-right': 'f0a9',
643
+ 'arrow-circle-up': 'f0aa',
644
+ 'arrow-circle-down': 'f0ab',
645
+ globe: 'f0ac',
646
+ wrench: 'f0ad',
647
+ tasks: 'f0ae',
648
+ filter: 'f0b0',
649
+ briefcase: 'f0b1',
650
+ 'arrows-alt': 'f0b2',
651
+ 'group,users': 'f0c0',
652
+ 'chain,link': 'f0c1',
653
+ cloud: 'f0c2',
654
+ flask: 'f0c3',
655
+ 'cut,scissors': 'f0c4',
656
+ 'copy,files-o': 'f0c5',
657
+ paperclip: 'f0c6',
658
+ 'save,floppy-o': 'f0c7',
659
+ square: 'f0c8',
660
+ 'navicon,reorder,bars': 'f0c9',
661
+ 'list-ul': 'f0ca',
662
+ 'list-ol': 'f0cb',
663
+ strikethrough: 'f0cc',
664
+ underline: 'f0cd',
665
+ table: 'f0ce',
666
+ magic: 'f0d0',
667
+ truck: 'f0d1',
668
+ pinterest: 'f0d2',
669
+ 'pinterest-square': 'f0d3',
670
+ 'google-plus-square': 'f0d4',
671
+ 'google-plus': 'f0d5',
672
+ money: 'f0d6',
673
+ 'caret-down': 'f0d7',
674
+ 'caret-up': 'f0d8',
675
+ 'caret-left': 'f0d9',
676
+ 'caret-right': 'f0da',
677
+ columns: 'f0db',
678
+ 'unsorted,sort': 'f0dc',
679
+ 'sort-down,sort-desc': 'f0dd',
680
+ 'sort-up,sort-asc': 'f0de',
681
+ envelope: 'f0e0',
682
+ linkedin: 'f0e1',
683
+ 'rotate-left,undo': 'f0e2',
684
+ 'legal,gavel': 'f0e3',
685
+ 'dashboard,tachometer': 'f0e4',
686
+ 'comment-o': 'f0e5',
687
+ 'comments-o': 'f0e6',
688
+ 'flash,bolt': 'f0e7',
689
+ sitemap: 'f0e8',
690
+ umbrella: 'f0e9',
691
+ 'paste,clipboard': 'f0ea',
692
+ 'lightbulb-o': 'f0eb',
693
+ exchange: 'f0ec',
694
+ 'cloud-download': 'f0ed',
695
+ 'cloud-upload': 'f0ee',
696
+ 'user-md': 'f0f0',
697
+ stethoscope: 'f0f1',
698
+ suitcase: 'f0f2',
699
+ 'bell-o': 'f0a2',
700
+ coffee: 'f0f4',
701
+ cutlery: 'f0f5',
702
+ 'file-text-o': 'f0f6',
703
+ 'building-o': 'f0f7',
704
+ 'hospital-o': 'f0f8',
705
+ ambulance: 'f0f9',
706
+ medkit: 'f0fa',
707
+ 'fighter-jet': 'f0fb',
708
+ beer: 'f0fc',
709
+ 'h-square': 'f0fd',
710
+ 'plus-square': 'f0fe',
711
+ 'angle-double-left': 'f100',
712
+ 'angle-double-right': 'f101',
713
+ 'angle-double-up': 'f102',
714
+ 'angle-double-down': 'f103',
715
+ 'angle-left': 'f104',
716
+ 'angle-right': 'f105',
717
+ 'angle-up': 'f106',
718
+ 'angle-down': 'f107',
719
+ desktop: 'f108',
720
+ laptop: 'f109',
721
+ tablet: 'f10a',
722
+ 'mobile-phone,mobile': 'f10b',
723
+ 'circle-o': 'f10c',
724
+ 'quote-left': 'f10d',
725
+ 'quote-right': 'f10e',
726
+ spinner: 'f110',
727
+ circle: 'f111',
728
+ 'mail-reply,reply': 'f112',
729
+ 'github-alt': 'f113',
730
+ 'folder-o': 'f114',
731
+ 'folder-open-o': 'f115',
732
+ 'smile-o': 'f118',
733
+ 'frown-o': 'f119',
734
+ 'meh-o': 'f11a',
735
+ gamepad: 'f11b',
736
+ 'keyboard-o': 'f11c',
737
+ 'flag-o': 'f11d',
738
+ 'flag-checkered': 'f11e',
739
+ terminal: 'f120',
740
+ code: 'f121',
741
+ 'mail-reply-all,reply-all': 'f122',
742
+ 'star-half-empty,star-half-full,star-half-o': 'f123',
743
+ 'location-arrow': 'f124',
744
+ crop: 'f125',
745
+ 'code-fork': 'f126',
746
+ 'unlink,chain-broken': 'f127',
747
+ question: 'f128',
748
+ info: 'f129',
749
+ exclamation: 'f12a',
750
+ superscript: 'f12b',
751
+ subscript: 'f12c',
752
+ eraser: 'f12d',
753
+ 'puzzle-piece': 'f12e',
754
+ microphone: 'f130',
755
+ 'microphone-slash': 'f131',
756
+ shield: 'f132',
757
+ 'calendar-o': 'f133',
758
+ 'fire-extinguisher': 'f134',
759
+ rocket: 'f135',
760
+ maxcdn: 'f136',
761
+ 'chevron-circle-left': 'f137',
762
+ 'chevron-circle-right': 'f138',
763
+ 'chevron-circle-up': 'f139',
764
+ 'chevron-circle-down': 'f13a',
765
+ html5: 'f13b',
766
+ css3: 'f13c',
767
+ anchor: 'f13d',
768
+ 'unlock-alt': 'f13e',
769
+ bullseye: 'f140',
770
+ 'ellipsis-h': 'f141',
771
+ 'ellipsis-v': 'f142',
772
+ 'rss-square': 'f143',
773
+ 'play-circle': 'f144',
774
+ ticket: 'f145',
775
+ 'minus-square': 'f146',
776
+ 'minus-square-o': 'f147',
777
+ 'level-up': 'f148',
778
+ 'level-down': 'f149',
779
+ 'check-square': 'f14a',
780
+ 'pencil-square': 'f14b',
781
+ 'external-link-square': 'f14c',
782
+ 'share-square': 'f14d',
783
+ compass: 'f14e',
784
+ 'toggle-down,caret-square-o-down': 'f150',
785
+ 'toggle-up,caret-square-o-up': 'f151',
786
+ 'toggle-right,caret-square-o-right': 'f152',
787
+ 'euro,eur': 'f153',
788
+ gbp: 'f154',
789
+ 'dollar,usd': 'f155',
790
+ 'rupee,inr': 'f156',
791
+ 'cny,rmb,yen,jpy': 'f157',
792
+ 'ruble,rouble,rub': 'f158',
793
+ 'won,krw': 'f159',
794
+ 'bitcoin,btc': 'f15a',
795
+ file: 'f15b',
796
+ 'file-text': 'f15c',
797
+ 'sort-alpha-asc': 'f15d',
798
+ 'sort-alpha-desc': 'f15e',
799
+ 'sort-amount-asc': 'f160',
800
+ 'sort-amount-desc': 'f161',
801
+ 'sort-numeric-asc': 'f162',
802
+ 'sort-numeric-desc': 'f163',
803
+ 'thumbs-up': 'f164',
804
+ 'thumbs-down': 'f165',
805
+ 'youtube-square': 'f166',
806
+ youtube: 'f167',
807
+ xing: 'f168',
808
+ 'xing-square': 'f169',
809
+ 'youtube-play': 'f16a',
810
+ dropbox: 'f16b',
811
+ 'stack-overflow': 'f16c',
812
+ instagram: 'f16d',
813
+ flickr: 'f16e',
814
+ adn: 'f170',
815
+ bitbucket: 'f171',
816
+ 'bitbucket-square': 'f172',
817
+ tumblr: 'f173',
818
+ 'tumblr-square': 'f174',
819
+ 'long-arrow-down': 'f175',
820
+ 'long-arrow-up': 'f176',
821
+ 'long-arrow-left': 'f177',
822
+ 'long-arrow-right': 'f178',
823
+ apple: 'f179',
824
+ windows: 'f17a',
825
+ android: 'f17b',
826
+ linux: 'f17c',
827
+ dribbble: 'f17d',
828
+ skype: 'f17e',
829
+ foursquare: 'f180',
830
+ trello: 'f181',
831
+ female: 'f182',
832
+ male: 'f183',
833
+ 'gittip,gratipay': 'f184',
834
+ 'sun-o': 'f185',
835
+ 'moon-o': 'f186',
836
+ archive: 'f187',
837
+ bug: 'f188',
838
+ vk: 'f189',
839
+ weibo: 'f18a',
840
+ renren: 'f18b',
841
+ pagelines: 'f18c',
842
+ 'stack-exchange': 'f18d',
843
+ 'arrow-circle-o-right': 'f18e',
844
+ 'arrow-circle-o-left': 'f190',
845
+ 'toggle-left,caret-square-o-left': 'f191',
846
+ 'dot-circle-o': 'f192',
847
+ wheelchair: 'f193',
848
+ 'vimeo-square': 'f194',
849
+ 'turkish-lira,try': 'f195',
850
+ 'plus-square-o': 'f196',
851
+ 'space-shuttle': 'f197',
852
+ slack: 'f198',
853
+ 'envelope-square': 'f199',
854
+ wordpress: 'f19a',
855
+ openid: 'f19b',
856
+ 'institution,bank,university': 'f19c',
857
+ 'mortar-board,graduation-cap': 'f19d',
858
+ yahoo: 'f19e',
859
+ google: 'f1a0',
860
+ reddit: 'f1a1',
861
+ 'reddit-square': 'f1a2',
862
+ 'stumbleupon-circle': 'f1a3',
863
+ stumbleupon: 'f1a4',
864
+ delicious: 'f1a5',
865
+ digg: 'f1a6',
866
+ 'pied-piper-pp': 'f1a7',
867
+ 'pied-piper-alt': 'f1a8',
868
+ drupal: 'f1a9',
869
+ joomla: 'f1aa',
870
+ language: 'f1ab',
871
+ fax: 'f1ac',
872
+ building: 'f1ad',
873
+ child: 'f1ae',
874
+ paw: 'f1b0',
875
+ spoon: 'f1b1',
876
+ cube: 'f1b2',
877
+ cubes: 'f1b3',
878
+ behance: 'f1b4',
879
+ 'behance-square': 'f1b5',
880
+ steam: 'f1b6',
881
+ 'steam-square': 'f1b7',
882
+ recycle: 'f1b8',
883
+ 'automobile,car': 'f1b9',
884
+ 'cab,taxi': 'f1ba',
885
+ tree: 'f1bb',
886
+ spotify: 'f1bc',
887
+ deviantart: 'f1bd',
888
+ soundcloud: 'f1be',
889
+ database: 'f1c0',
890
+ 'file-pdf-o': 'f1c1',
891
+ 'file-word-o': 'f1c2',
892
+ 'file-excel-o': 'f1c3',
893
+ 'file-powerpoint-o': 'f1c4',
894
+ 'file-photo-o,file-picture-o,file-image-o': 'f1c5',
895
+ 'file-zip-o,file-archive-o': 'f1c6',
896
+ 'file-sound-o,file-audio-o': 'f1c7',
897
+ 'file-movie-o,file-video-o': 'f1c8',
898
+ 'file-code-o': 'f1c9',
899
+ vine: 'f1ca',
900
+ codepen: 'f1cb',
901
+ jsfiddle: 'f1cc',
902
+ 'life-bouy,life-buoy,life-saver,support,life-ring': 'f1cd',
903
+ 'circle-o-notch': 'f1ce',
904
+ 'ra,resistance,rebel': 'f1d0',
905
+ 'ge,empire': 'f1d1',
906
+ 'git-square': 'f1d2',
907
+ git: 'f1d3',
908
+ 'y-combinator-square,yc-square,hacker-news': 'f1d4',
909
+ 'tencent-weibo': 'f1d5',
910
+ qq: 'f1d6',
911
+ 'wechat,weixin': 'f1d7',
912
+ 'send,paper-plane': 'f1d8',
913
+ 'send-o,paper-plane-o': 'f1d9',
914
+ history: 'f1da',
915
+ 'circle-thin': 'f1db',
916
+ header: 'f1dc',
917
+ paragraph: 'f1dd',
918
+ sliders: 'f1de',
919
+ 'share-alt': 'f1e0',
920
+ 'share-alt-square': 'f1e1',
921
+ bomb: 'f1e2',
922
+ 'soccer-ball-o,futbol-o': 'f1e3',
923
+ tty: 'f1e4',
924
+ binoculars: 'f1e5',
925
+ plug: 'f1e6',
926
+ slideshare: 'f1e7',
927
+ twitch: 'f1e8',
928
+ yelp: 'f1e9',
929
+ 'newspaper-o': 'f1ea',
930
+ wifi: 'f1eb',
931
+ calculator: 'f1ec',
932
+ paypal: 'f1ed',
933
+ 'google-wallet': 'f1ee',
934
+ 'cc-visa': 'f1f0',
935
+ 'cc-mastercard': 'f1f1',
936
+ 'cc-discover': 'f1f2',
937
+ 'cc-amex': 'f1f3',
938
+ 'cc-paypal': 'f1f4',
939
+ 'cc-stripe': 'f1f5',
940
+ 'bell-slash': 'f1f6',
941
+ 'bell-slash-o': 'f1f7',
942
+ trash: 'f1f8',
943
+ copyright: 'f1f9',
944
+ at: 'f1fa',
945
+ eyedropper: 'f1fb',
946
+ 'paint-brush': 'f1fc',
947
+ 'birthday-cake': 'f1fd',
948
+ 'area-chart': 'f1fe',
949
+ 'pie-chart': 'f200',
950
+ 'line-chart': 'f201',
951
+ lastfm: 'f202',
952
+ 'lastfm-square': 'f203',
953
+ 'toggle-off': 'f204',
954
+ 'toggle-on': 'f205',
955
+ bicycle: 'f206',
956
+ bus: 'f207',
957
+ ioxhost: 'f208',
958
+ angellist: 'f209',
959
+ cc: 'f20a',
960
+ 'shekel,sheqel,ils': 'f20b',
961
+ meanpath: 'f20c',
962
+ buysellads: 'f20d',
963
+ connectdevelop: 'f20e',
964
+ dashcube: 'f210',
965
+ forumbee: 'f211',
966
+ leanpub: 'f212',
967
+ sellsy: 'f213',
968
+ shirtsinbulk: 'f214',
969
+ simplybuilt: 'f215',
970
+ skyatlas: 'f216',
971
+ 'cart-plus': 'f217',
972
+ 'cart-arrow-down': 'f218',
973
+ diamond: 'f219',
974
+ ship: 'f21a',
975
+ 'user-secret': 'f21b',
976
+ motorcycle: 'f21c',
977
+ 'street-view': 'f21d',
978
+ heartbeat: 'f21e',
979
+ venus: 'f221',
980
+ mars: 'f222',
981
+ mercury: 'f223',
982
+ 'intersex,transgender': 'f224',
983
+ 'transgender-alt': 'f225',
984
+ 'venus-double': 'f226',
985
+ 'mars-double': 'f227',
986
+ 'venus-mars': 'f228',
987
+ 'mars-stroke': 'f229',
988
+ 'mars-stroke-v': 'f22a',
989
+ 'mars-stroke-h': 'f22b',
990
+ neuter: 'f22c',
991
+ genderless: 'f22d',
992
+ 'facebook-official': 'f230',
993
+ 'pinterest-p': 'f231',
994
+ whatsapp: 'f232',
995
+ server: 'f233',
996
+ 'user-plus': 'f234',
997
+ 'user-times': 'f235',
998
+ 'hotel,bed': 'f236',
999
+ viacoin: 'f237',
1000
+ train: 'f238',
1001
+ subway: 'f239',
1002
+ medium: 'f23a',
1003
+ 'yc,y-combinator': 'f23b',
1004
+ 'optin-monster': 'f23c',
1005
+ opencart: 'f23d',
1006
+ expeditedssl: 'f23e',
1007
+ 'battery-4,battery-full': 'f240',
1008
+ 'battery-3,battery-three-quarters': 'f241',
1009
+ 'battery-2,battery-half': 'f242',
1010
+ 'battery-1,battery-quarter': 'f243',
1011
+ 'battery-0,battery-empty': 'f244',
1012
+ 'mouse-pointer': 'f245',
1013
+ 'i-cursor': 'f246',
1014
+ 'object-group': 'f247',
1015
+ 'object-ungroup': 'f248',
1016
+ 'sticky-note': 'f249',
1017
+ 'sticky-note-o': 'f24a',
1018
+ 'cc-jcb': 'f24b',
1019
+ 'cc-diners-club': 'f24c',
1020
+ clone: 'f24d',
1021
+ 'balance-scale': 'f24e',
1022
+ 'hourglass-o': 'f250',
1023
+ 'hourglass-1,hourglass-start': 'f251',
1024
+ 'hourglass-2,hourglass-half': 'f252',
1025
+ 'hourglass-3,hourglass-end': 'f253',
1026
+ hourglass: 'f254',
1027
+ 'hand-grab-o,hand-rock-o': 'f255',
1028
+ 'hand-stop-o,hand-paper-o': 'f256',
1029
+ 'hand-scissors-o': 'f257',
1030
+ 'hand-lizard-o': 'f258',
1031
+ 'hand-spock-o': 'f259',
1032
+ 'hand-pointer-o': 'f25a',
1033
+ 'hand-peace-o': 'f25b',
1034
+ trademark: 'f25c',
1035
+ registered: 'f25d',
1036
+ 'creative-commons': 'f25e',
1037
+ gg: 'f260',
1038
+ 'gg-circle': 'f261',
1039
+ tripadvisor: 'f262',
1040
+ odnoklassniki: 'f263',
1041
+ 'odnoklassniki-square': 'f264',
1042
+ 'get-pocket': 'f265',
1043
+ 'wikipedia-w': 'f266',
1044
+ safari: 'f267',
1045
+ chrome: 'f268',
1046
+ firefox: 'f269',
1047
+ opera: 'f26a',
1048
+ 'internet-explorer': 'f26b',
1049
+ 'tv,television': 'f26c',
1050
+ contao: 'f26d',
1051
+ '500px': 'f26e',
1052
+ amazon: 'f270',
1053
+ 'calendar-plus-o': 'f271',
1054
+ 'calendar-minus-o': 'f272',
1055
+ 'calendar-times-o': 'f273',
1056
+ 'calendar-check-o': 'f274',
1057
+ industry: 'f275',
1058
+ 'map-pin': 'f276',
1059
+ 'map-signs': 'f277',
1060
+ 'map-o': 'f278',
1061
+ map: 'f279',
1062
+ commenting: 'f27a',
1063
+ 'commenting-o': 'f27b',
1064
+ houzz: 'f27c',
1065
+ vimeo: 'f27d',
1066
+ 'black-tie': 'f27e',
1067
+ fonticons: 'f280',
1068
+ 'reddit-alien': 'f281',
1069
+ edge: 'f282',
1070
+ 'credit-card-alt': 'f283',
1071
+ codiepie: 'f284',
1072
+ modx: 'f285',
1073
+ 'fort-awesome': 'f286',
1074
+ usb: 'f287',
1075
+ 'product-hunt': 'f288',
1076
+ mixcloud: 'f289',
1077
+ scribd: 'f28a',
1078
+ 'pause-circle': 'f28b',
1079
+ 'pause-circle-o': 'f28c',
1080
+ 'stop-circle': 'f28d',
1081
+ 'stop-circle-o': 'f28e',
1082
+ 'shopping-bag': 'f290',
1083
+ 'shopping-basket': 'f291',
1084
+ hashtag: 'f292',
1085
+ bluetooth: 'f293',
1086
+ 'bluetooth-b': 'f294',
1087
+ percent: 'f295',
1088
+ gitlab: 'f296',
1089
+ wpbeginner: 'f297',
1090
+ wpforms: 'f298',
1091
+ envira: 'f299',
1092
+ 'universal-access': 'f29a',
1093
+ 'wheelchair-alt': 'f29b',
1094
+ 'question-circle-o': 'f29c',
1095
+ blind: 'f29d',
1096
+ 'audio-description': 'f29e',
1097
+ 'volume-control-phone': 'f2a0',
1098
+ braille: 'f2a1',
1099
+ 'assistive-listening-systems': 'f2a2',
1100
+ 'asl-interpreting,american-sign-language-interpreting': 'f2a3',
1101
+ 'deafness,hard-of-hearing,deaf': 'f2a4',
1102
+ glide: 'f2a5',
1103
+ 'glide-g': 'f2a6',
1104
+ 'signing,sign-language': 'f2a7',
1105
+ 'low-vision': 'f2a8',
1106
+ viadeo: 'f2a9',
1107
+ 'viadeo-square': 'f2aa',
1108
+ snapchat: 'f2ab',
1109
+ 'snapchat-ghost': 'f2ac',
1110
+ 'snapchat-square': 'f2ad',
1111
+ 'pied-piper': 'f2ae',
1112
+ 'first-order': 'f2b0',
1113
+ yoast: 'f2b1',
1114
+ themeisle: 'f2b2',
1115
+ 'google-plus-circle,google-plus-official': 'f2b3',
1116
+ 'fa,font-awesome': 'f2b4'
1117
+ }
1118
+ }
1119
+
1120
+ icon(d) {
1121
+ var code
1122
+
1123
+ if (this.options.iconMap && this.options.showIcons && this.options.icons) {
1124
+ if (this.options.icons[d.labels[0]] && this.options.iconMap[this.options.icons[d.labels[0]]]) {
1125
+ code = this.options.iconMap[this.options.icons[d.labels[0]]]
1126
+ } else if (this.options.iconMap[d.labels[0]]) {
1127
+ code = this.options.iconMap[d.labels[0]]
1128
+ } else if (this.options.icons[d.labels[0]]) {
1129
+ code = this.options.icons[d.labels[0]]
1130
+ }
1131
+ }
1132
+
1133
+ return code
1134
+ }
1135
+
1136
+ image(d) {
1137
+ var i, imagesForLabel, img, imgLevel, label, labelPropertyValue, property, value
1138
+
1139
+ if (this.options.images) {
1140
+ imagesForLabel = this.options.imageMap[d.labels[0]]
1141
+
1142
+ if (imagesForLabel) {
1143
+ imgLevel = 0
1144
+
1145
+ for (i = 0; i < imagesForLabel.length; i++) {
1146
+ labelPropertyValue = imagesForLabel[i].split('|')
1147
+
1148
+ switch (labelPropertyValue.length) {
1149
+ case 3:
1150
+ value = labelPropertyValue[2]
1151
+ /* falls through */
1152
+ case 2:
1153
+ property = labelPropertyValue[1]
1154
+ /* falls through */
1155
+ case 1:
1156
+ label = labelPropertyValue[0]
1157
+ }
1158
+
1159
+ if (
1160
+ d.labels[0] === label &&
1161
+ (!property || d.properties[property] !== undefined) &&
1162
+ (!value || d.properties[property] === value)
1163
+ ) {
1164
+ if (labelPropertyValue.length > imgLevel) {
1165
+ img = this.options.images[imagesForLabel[i]]
1166
+ imgLevel = labelPropertyValue.length
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ return img
1174
+ }
1175
+
1176
+ init(_selector, _options) {
1177
+ this.initIconMap()
1178
+
1179
+ this.merge(this.options, _options)
1180
+
1181
+ if (this.options.icons) {
1182
+ this.options.showIcons = true
1183
+ }
1184
+
1185
+ if (!this.options.minCollision) {
1186
+ this.options.minCollision = this.options.nodeRadius * 2
1187
+ }
1188
+
1189
+ this.initImageMap()
1190
+
1191
+ this.selector = _selector
1192
+
1193
+ this.container = d3.select(this.selector)
1194
+
1195
+ // this.container.attr('class', 'graph-container').html('')
1196
+
1197
+ if (this.options.infoPanel) {
1198
+ this.info = this.appendInfoPanel(this.container)
1199
+ }
1200
+
1201
+ this.appendGraph(this.container)
1202
+
1203
+ this.simulation = this.initSimulation()
1204
+
1205
+ if (this.options.graphData) {
1206
+ this.loadGraphData()
1207
+ } else if (this.options.dataUrl) {
1208
+ this.loadGraphDataFromUrl(this.options.dataUrl)
1209
+ } else {
1210
+ console.error('Error: both graphData and dataUrl are empty!')
1211
+ }
1212
+ }
1213
+
1214
+ initIconMap() {
1215
+ Object.keys(this.options.iconMap).forEach((key, index) => {
1216
+ var keys = key.split(','),
1217
+ value = this.options.iconMap[key]
1218
+
1219
+ keys.forEach(key => {
1220
+ this.options.iconMap[key] = value
1221
+ })
1222
+ })
1223
+ }
1224
+
1225
+ initImageMap() {
1226
+ var key, keys, selector
1227
+ const images = this.options.images as any
1228
+
1229
+ for (key in images) {
1230
+ if (images.hasOwnProperty(key)) {
1231
+ keys = key.split('|')
1232
+
1233
+ if (!this.options.imageMap[keys[0]]) {
1234
+ this.options.imageMap[keys[0]] = [key]
1235
+ } else {
1236
+ this.options.imageMap[keys[0]].push(key)
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+
1242
+ initSimulation() {
1243
+ const x = this.svg.node().parentElement.parentElement.clientWidth / 2
1244
+ const y = this.svg.node().parentElement.parentElement.clientHeight / 2
1245
+
1246
+ var simulation = d3
1247
+ .forceSimulation()
1248
+ // .velocityDecay(0.8)
1249
+ // .force('x', d3.force().strength(0.002))
1250
+ // .force('y', d3.force().strength(0.002))
1251
+ .force(
1252
+ 'collide',
1253
+ d3
1254
+ .forceCollide()
1255
+ .radius(d => {
1256
+ return this.options.minCollision
1257
+ })
1258
+ .iterations(2)
1259
+ )
1260
+ .force('charge', d3.forceManyBody())
1261
+ .force(
1262
+ 'link',
1263
+ d3.forceLink().id(d => {
1264
+ return d.id
1265
+ })
1266
+ )
1267
+ .force('center', d3.forceCenter(x, y))
1268
+ .on('tick', () => {
1269
+ this.tick()
1270
+ })
1271
+ .on('end', () => {
1272
+ if (this.options.zoomFit && !this.justLoaded) {
1273
+ this.justLoaded = true
1274
+ this.zoomFit(2)
1275
+ }
1276
+ })
1277
+
1278
+ return simulation
1279
+ }
1280
+
1281
+ loadGraphData() {
1282
+ this.nodes = []
1283
+ this.relationships = []
1284
+
1285
+ this.updateWithGraphData(this.options.graphData)
1286
+ }
1287
+
1288
+ loadGraphDataFromUrl(dataUrl) {
1289
+ this.nodes = []
1290
+ this.relationships = []
1291
+
1292
+ d3.json(dataUrl, (error, data) => {
1293
+ if (error) {
1294
+ throw error
1295
+ }
1296
+
1297
+ this.updateWithGraphData(data)
1298
+ })
1299
+ }
1300
+
1301
+ merge(target, source) {
1302
+ Object.keys(source).forEach(property => {
1303
+ target[property] = source[property]
1304
+ })
1305
+ }
1306
+
1307
+ graphDataToD3Data(data) {
1308
+ var graph = {
1309
+ nodes: [] as any[],
1310
+ relationships: [] as any[]
1311
+ }
1312
+
1313
+ data.results.forEach(result => {
1314
+ result.data.forEach(data => {
1315
+ data.graph.nodes.forEach(node => {
1316
+ if (!this.contains(graph.nodes, node.id)) {
1317
+ graph.nodes.push(node)
1318
+ }
1319
+ })
1320
+
1321
+ data.graph.relationships.forEach(function (relationship) {
1322
+ relationship.source = relationship.startNode
1323
+ relationship.target = relationship.endNode
1324
+ graph.relationships.push(relationship)
1325
+ })
1326
+
1327
+ data.graph.relationships.sort(function (a, b) {
1328
+ if (a.source > b.source) {
1329
+ return 1
1330
+ } else if (a.source < b.source) {
1331
+ return -1
1332
+ } else {
1333
+ if (a.target > b.target) {
1334
+ return 1
1335
+ }
1336
+
1337
+ if (a.target < b.target) {
1338
+ return -1
1339
+ } else {
1340
+ return 0
1341
+ }
1342
+ }
1343
+ })
1344
+
1345
+ for (var i = 0; i < data.graph.relationships.length; i++) {
1346
+ if (
1347
+ i !== 0 &&
1348
+ data.graph.relationships[i].source === data.graph.relationships[i - 1].source &&
1349
+ data.graph.relationships[i].target === data.graph.relationships[i - 1].target
1350
+ ) {
1351
+ data.graph.relationships[i].linknum = data.graph.relationships[i - 1].linknum + 1
1352
+ } else {
1353
+ data.graph.relationships[i].linknum = 1
1354
+ }
1355
+ }
1356
+ })
1357
+ })
1358
+
1359
+ return graph
1360
+ }
1361
+
1362
+ randomD3Data(d, maxNodesToGenerate) {
1363
+ var data = {
1364
+ nodes: [] as any[],
1365
+ relationships: [] as any[]
1366
+ },
1367
+ i,
1368
+ label,
1369
+ node,
1370
+ numNodes = ((maxNodesToGenerate * Math.random()) << 0) + 1,
1371
+ relationship,
1372
+ s = this.size()
1373
+
1374
+ for (i = 0; i < numNodes; i++) {
1375
+ label = this.randomLabel()
1376
+
1377
+ node = {
1378
+ id: s.nodes + 1 + i,
1379
+ labels: [label],
1380
+ properties: {
1381
+ random: label
1382
+ },
1383
+ x: d.x,
1384
+ y: d.y
1385
+ }
1386
+
1387
+ data.nodes[data.nodes.length] = node
1388
+
1389
+ relationship = {
1390
+ id: s.relationships + 1 + i,
1391
+ type: label.toUpperCase(),
1392
+ startNode: d.id,
1393
+ endNode: s.nodes + 1 + i,
1394
+ properties: {
1395
+ from: Date.now()
1396
+ },
1397
+ source: d.id,
1398
+ target: s.nodes + 1 + i,
1399
+ linknum: s.relationships + 1 + i
1400
+ }
1401
+
1402
+ data.relationships[data.relationships.length] = relationship
1403
+ }
1404
+
1405
+ return data
1406
+ }
1407
+
1408
+ randomLabel() {
1409
+ var icons = Object.keys(this.options.iconMap)
1410
+ return icons[(icons.length * Math.random()) << 0]
1411
+ }
1412
+
1413
+ rotate(cx, cy, x, y, angle) {
1414
+ var radians = (Math.PI / 180) * angle,
1415
+ cos = Math.cos(radians),
1416
+ sin = Math.sin(radians),
1417
+ nx = cos * (x - cx) + sin * (y - cy) + cx,
1418
+ ny = cos * (y - cy) - sin * (x - cx) + cy
1419
+
1420
+ return { x: nx, y: ny }
1421
+ }
1422
+
1423
+ rotatePoint(c, p, angle) {
1424
+ return this.rotate(c.x, c.y, p.x, p.y, angle)
1425
+ }
1426
+
1427
+ rotation(source, target) {
1428
+ return (Math.atan2(target.y - source.y, target.x - source.x) * 180) / Math.PI
1429
+ }
1430
+
1431
+ size() {
1432
+ return {
1433
+ nodes: this.nodes.length,
1434
+ relationships: this.relationships.length
1435
+ }
1436
+ }
1437
+ /*
1438
+ function smoothTransform(elem, translate, scale) {
1439
+ var animationMilliseconds = 5000,
1440
+ timeoutMilliseconds = 50,
1441
+ steps = parseInt(animationMilliseconds / timeoutMilliseconds);
1442
+
1443
+ setTimeout(function() {
1444
+ smoothTransformStep(elem, translate, scale, timeoutMilliseconds, 1, steps);
1445
+ }, timeoutMilliseconds);
1446
+ }
1447
+
1448
+ function smoothTransformStep(elem, translate, scale, timeoutMilliseconds, step, steps) {
1449
+ var progress = step / steps;
1450
+
1451
+ elem.attr('transform', 'translate(' + (translate[0] * progress) + ', ' + (translate[1] * progress) + ') scale(' + (scale * progress) + ')');
1452
+
1453
+ if (step < steps) {
1454
+ setTimeout(function() {
1455
+ smoothTransformStep(elem, translate, scale, timeoutMilliseconds, step + 1, steps);
1456
+ }, timeoutMilliseconds);
1457
+ }
1458
+ }
1459
+ */
1460
+
1461
+ stickNode(d, event) {
1462
+ d.fx = event.x
1463
+ d.fy = event.y
1464
+ }
1465
+
1466
+ tick() {
1467
+ this.tickNodes()
1468
+ this.tickRelationships()
1469
+ }
1470
+
1471
+ tickNodes() {
1472
+ if (this.node) {
1473
+ this.node.attr('transform', d => {
1474
+ return 'translate(' + d.x + ', ' + d.y + ')'
1475
+ })
1476
+ }
1477
+ }
1478
+
1479
+ tickRelationships() {
1480
+ if (this.relationship) {
1481
+ this.relationship.attr('transform', d => {
1482
+ var angle = this.rotation(d.source, d.target)
1483
+ return 'translate(' + d.source.x + ', ' + d.source.y + ') rotate(' + angle + ')'
1484
+ })
1485
+
1486
+ this.tickRelationshipsTexts()
1487
+ this.tickRelationshipsOutlines()
1488
+ this.tickRelationshipsOverlays()
1489
+ }
1490
+ }
1491
+
1492
+ tickRelationshipsOutlines() {
1493
+ const self = this
1494
+ this.relationship.each(function (this, relationship) {
1495
+ var rel = d3.select(this)
1496
+ var outline = rel.select('.outline'),
1497
+ text = rel.select('.text'),
1498
+ bbox = text.node().getBBox(),
1499
+ padding = 3
1500
+
1501
+ outline.attr('d', d => {
1502
+ var center = { x: 0, y: 0 },
1503
+ angle = self.rotation(d.source, d.target),
1504
+ textBoundingBox = text.node().getBBox(),
1505
+ textPadding = 5,
1506
+ u = self.unitaryVector(d.source, d.target),
1507
+ textMargin = {
1508
+ x: (d.target.x - d.source.x - (textBoundingBox.width + textPadding) * u.x) * 0.5,
1509
+ y: (d.target.y - d.source.y - (textBoundingBox.width + textPadding) * u.y) * 0.5
1510
+ },
1511
+ n = self.unitaryNormalVector(d.source, d.target),
1512
+ rotatedPointA1 = self.rotatePoint(
1513
+ center,
1514
+ { x: 0 + (self.options.nodeRadius + 1) * u.x - n.x, y: 0 + (self.options.nodeRadius + 1) * u.y - n.y },
1515
+ angle
1516
+ ),
1517
+ rotatedPointB1 = self.rotatePoint(center, { x: textMargin.x - n.x, y: textMargin.y - n.y }, angle),
1518
+ rotatedPointC1 = self.rotatePoint(center, { x: textMargin.x, y: textMargin.y }, angle),
1519
+ rotatedPointD1 = self.rotatePoint(
1520
+ center,
1521
+ { x: 0 + (self.options.nodeRadius + 1) * u.x, y: 0 + (self.options.nodeRadius + 1) * u.y },
1522
+ angle
1523
+ ),
1524
+ rotatedPointA2 = self.rotatePoint(
1525
+ center,
1526
+ { x: d.target.x - d.source.x - textMargin.x - n.x, y: d.target.y - d.source.y - textMargin.y - n.y },
1527
+ angle
1528
+ ),
1529
+ rotatedPointB2 = self.rotatePoint(
1530
+ center,
1531
+ {
1532
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x - n.x - u.x * self.options.arrowSize,
1533
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y - n.y - u.y * self.options.arrowSize
1534
+ },
1535
+ angle
1536
+ ),
1537
+ rotatedPointC2 = self.rotatePoint(
1538
+ center,
1539
+ {
1540
+ x:
1541
+ d.target.x -
1542
+ d.source.x -
1543
+ (self.options.nodeRadius + 1) * u.x -
1544
+ n.x +
1545
+ (n.x - u.x) * self.options.arrowSize,
1546
+ y:
1547
+ d.target.y -
1548
+ d.source.y -
1549
+ (self.options.nodeRadius + 1) * u.y -
1550
+ n.y +
1551
+ (n.y - u.y) * self.options.arrowSize
1552
+ },
1553
+ angle
1554
+ ),
1555
+ rotatedPointD2 = self.rotatePoint(
1556
+ center,
1557
+ {
1558
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x,
1559
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y
1560
+ },
1561
+ angle
1562
+ ),
1563
+ rotatedPointE2 = self.rotatePoint(
1564
+ center,
1565
+ {
1566
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x + (-n.x - u.x) * self.options.arrowSize,
1567
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y + (-n.y - u.y) * self.options.arrowSize
1568
+ },
1569
+ angle
1570
+ ),
1571
+ rotatedPointF2 = self.rotatePoint(
1572
+ center,
1573
+ {
1574
+ x: d.target.x - d.source.x - (self.options.nodeRadius + 1) * u.x - u.x * self.options.arrowSize,
1575
+ y: d.target.y - d.source.y - (self.options.nodeRadius + 1) * u.y - u.y * self.options.arrowSize
1576
+ },
1577
+ angle
1578
+ ),
1579
+ rotatedPointG2 = self.rotatePoint(
1580
+ center,
1581
+ { x: d.target.x - d.source.x - textMargin.x, y: d.target.y - d.source.y - textMargin.y },
1582
+ angle
1583
+ )
1584
+
1585
+ return (
1586
+ 'M ' +
1587
+ rotatedPointA1.x +
1588
+ ' ' +
1589
+ rotatedPointA1.y +
1590
+ ' L ' +
1591
+ rotatedPointB1.x +
1592
+ ' ' +
1593
+ rotatedPointB1.y +
1594
+ ' L ' +
1595
+ rotatedPointC1.x +
1596
+ ' ' +
1597
+ rotatedPointC1.y +
1598
+ ' L ' +
1599
+ rotatedPointD1.x +
1600
+ ' ' +
1601
+ rotatedPointD1.y +
1602
+ ' Z M ' +
1603
+ rotatedPointA2.x +
1604
+ ' ' +
1605
+ rotatedPointA2.y +
1606
+ ' L ' +
1607
+ rotatedPointB2.x +
1608
+ ' ' +
1609
+ rotatedPointB2.y +
1610
+ ' L ' +
1611
+ rotatedPointC2.x +
1612
+ ' ' +
1613
+ rotatedPointC2.y +
1614
+ ' L ' +
1615
+ rotatedPointD2.x +
1616
+ ' ' +
1617
+ rotatedPointD2.y +
1618
+ ' L ' +
1619
+ rotatedPointE2.x +
1620
+ ' ' +
1621
+ rotatedPointE2.y +
1622
+ ' L ' +
1623
+ rotatedPointF2.x +
1624
+ ' ' +
1625
+ rotatedPointF2.y +
1626
+ ' L ' +
1627
+ rotatedPointG2.x +
1628
+ ' ' +
1629
+ rotatedPointG2.y +
1630
+ ' Z'
1631
+ )
1632
+ })
1633
+ })
1634
+ }
1635
+
1636
+ tickRelationshipsOverlays() {
1637
+ this.relationshipOverlay.attr('d', d => {
1638
+ var center = { x: 0, y: 0 },
1639
+ angle = this.rotation(d.source, d.target),
1640
+ n1 = this.unitaryNormalVector(d.source, d.target),
1641
+ n = this.unitaryNormalVector(d.source, d.target, 50),
1642
+ rotatedPointA = this.rotatePoint(center, { x: 0 - n.x, y: 0 - n.y }, angle),
1643
+ rotatedPointB = this.rotatePoint(
1644
+ center,
1645
+ { x: d.target.x - d.source.x - n.x, y: d.target.y - d.source.y - n.y },
1646
+ angle
1647
+ ),
1648
+ rotatedPointC = this.rotatePoint(
1649
+ center,
1650
+ { x: d.target.x - d.source.x + n.x - n1.x, y: d.target.y - d.source.y + n.y - n1.y },
1651
+ angle
1652
+ ),
1653
+ rotatedPointD = this.rotatePoint(center, { x: 0 + n.x - n1.x, y: 0 + n.y - n1.y }, angle)
1654
+
1655
+ return (
1656
+ 'M ' +
1657
+ rotatedPointA.x +
1658
+ ' ' +
1659
+ rotatedPointA.y +
1660
+ ' L ' +
1661
+ rotatedPointB.x +
1662
+ ' ' +
1663
+ rotatedPointB.y +
1664
+ ' L ' +
1665
+ rotatedPointC.x +
1666
+ ' ' +
1667
+ rotatedPointC.y +
1668
+ ' L ' +
1669
+ rotatedPointD.x +
1670
+ ' ' +
1671
+ rotatedPointD.y +
1672
+ ' Z'
1673
+ )
1674
+ })
1675
+ }
1676
+
1677
+ tickRelationshipsTexts() {
1678
+ this.relationshipText.attr('transform', d => {
1679
+ var angle = (this.rotation(d.source, d.target) + 360) % 360,
1680
+ mirror = angle > 90 && angle < 270,
1681
+ center = { x: 0, y: 0 },
1682
+ n = this.unitaryNormalVector(d.source, d.target),
1683
+ nWeight = mirror ? 2 : -3,
1684
+ point = {
1685
+ x: (d.target.x - d.source.x) * 0.5 + n.x * nWeight,
1686
+ y: (d.target.y - d.source.y) * 0.5 + n.y * nWeight
1687
+ },
1688
+ rotatedPoint = this.rotatePoint(center, point, angle)
1689
+
1690
+ return 'translate(' + rotatedPoint.x + ', ' + rotatedPoint.y + ') rotate(' + (mirror ? 180 : 0) + ')'
1691
+ })
1692
+ }
1693
+
1694
+ toString(d) {
1695
+ var s = d.labels ? d.labels[0] : d.type
1696
+
1697
+ s += ' (<id>: ' + d.id
1698
+
1699
+ Object.keys(d.properties).forEach(function (property) {
1700
+ s += ', ' + property + ': ' + JSON.stringify(d.properties[property])
1701
+ })
1702
+
1703
+ s += ')'
1704
+
1705
+ return s
1706
+ }
1707
+
1708
+ unitaryNormalVector(source, target, newLength?: any) {
1709
+ var center = { x: 0, y: 0 },
1710
+ vector = this.unitaryVector(source, target, newLength)
1711
+
1712
+ return this.rotatePoint(center, vector, 90)
1713
+ }
1714
+
1715
+ unitaryVector(source, target, newLength?: any) {
1716
+ var length =
1717
+ Math.sqrt(Math.pow(target.x - source.x, 2) + Math.pow(target.y - source.y, 2)) / Math.sqrt(newLength || 1)
1718
+
1719
+ return {
1720
+ x: (target.x - source.x) / length,
1721
+ y: (target.y - source.y) / length
1722
+ }
1723
+ }
1724
+
1725
+ updateWithD3Data(d3Data) {
1726
+ this.updateNodesAndRelationships(d3Data.nodes, d3Data.relationships)
1727
+ }
1728
+
1729
+ updateWithGraphData(graphData) {
1730
+ var d3Data = this.graphDataToD3Data(graphData)
1731
+ this.updateWithD3Data(d3Data)
1732
+ }
1733
+
1734
+ updateInfo(d) {
1735
+ this.clearInfo()
1736
+
1737
+ if (d.labels) {
1738
+ this.appendInfoElementClass('class', d.labels[0])
1739
+ } else {
1740
+ this.appendInfoElementRelationship('class', d.type)
1741
+ }
1742
+
1743
+ this.appendInfoElementProperty('property', '&lt;id&gt;', d.id)
1744
+
1745
+ Object.keys(d.properties).forEach(property => {
1746
+ this.appendInfoElementProperty('property', property, JSON.stringify(d.properties[property]))
1747
+ })
1748
+ }
1749
+
1750
+ updateNodes(n) {
1751
+ Array.prototype.push.apply(this.nodes, n)
1752
+
1753
+ this.node = this.svgNodes.selectAll('.node').data(this.nodes, d => {
1754
+ return d.id
1755
+ })
1756
+ var nodeEnter = this.appendNodeToGraph()
1757
+ this.node = nodeEnter.merge(this.node)
1758
+ }
1759
+
1760
+ updateNodesAndRelationships(n, r) {
1761
+ this.updateRelationships(r)
1762
+ this.updateNodes(n)
1763
+
1764
+ this.simulation.nodes(this.nodes)
1765
+ this.simulation.force('link').links(this.relationships)
1766
+ }
1767
+
1768
+ updateRelationships(r) {
1769
+ Array.prototype.push.apply(this.relationships, r)
1770
+
1771
+ this.relationship = this.svgRelationships.selectAll('.relationship').data(this.relationships, d => {
1772
+ return d.id
1773
+ })
1774
+
1775
+ var relationshipEnter = this.appendRelationshipToGraph()
1776
+
1777
+ this.relationship = relationshipEnter.relationship.merge(this.relationship)
1778
+
1779
+ this.relationshipOutline = this.svg.selectAll('.relationship .outline')
1780
+ this.relationshipOutline = relationshipEnter.outline.merge(this.relationshipOutline)
1781
+
1782
+ this.relationshipOverlay = this.svg.selectAll('.relationship .overlay')
1783
+ this.relationshipOverlay = relationshipEnter.overlay.merge(this.relationshipOverlay)
1784
+
1785
+ this.relationshipText = this.svg.selectAll('.relationship .text')
1786
+ this.relationshipText = relationshipEnter.text.merge(this.relationshipText)
1787
+ }
1788
+
1789
+ zoomFit(transitionDuration) {
1790
+ var bounds = this.svg.node().getBBox(),
1791
+ parent = this.svg.node().parentElement.parentElement,
1792
+ fullWidth = parent.clientWidth,
1793
+ fullHeight = parent.clientHeight,
1794
+ width = bounds.width,
1795
+ height = bounds.height,
1796
+ midX = bounds.x + width / 2,
1797
+ midY = bounds.y + height / 2
1798
+
1799
+ if (width === 0 || height === 0) {
1800
+ return // nothing to fit
1801
+ }
1802
+
1803
+ this.svgScale = 0.85 / Math.max(width / fullWidth, height / fullHeight)
1804
+ this.svgTranslate = [fullWidth / 2 - this.svgScale * midX, fullHeight / 2 - this.svgScale * midY]
1805
+
1806
+ this.svg.attr(
1807
+ 'transform',
1808
+ 'translate(' + this.svgTranslate[0] + ', ' + this.svgTranslate[1] + ') scale(' + this.svgScale + ')'
1809
+ )
1810
+ // smoothTransform(this.svgTranslate, this.svgScale);
1811
+ }
1812
+ }