This repository has been archived by the owner on Jun 25, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgang.html
1057 lines (836 loc) · 27 KB
/
gang.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Gang Connections</title>
<style>
body,
h1,
h2 {
color: #444;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-weight: 300;
}
#graph {
float: left;
position: relative;
}
#notes {
float: left;
margin-left: 20px;
}
h1,
h2 {
margin: 0;
}
h1 {
font-size: 1.4em;
margin-bottom: 0.2em;
}
h2 {
font-size: 1.1em;
margin-bottom: 1em;
}
.artwork img {
border: 1px solid #fff;
-webkit-box-shadow: 0 3px 5px rgba(0, 0, 0, .3);
-moz-box-shadow: rgba(0, 0, 0, .3) 0 3px 5px;
border-color: #a2a2a2 9;
width: 170px;
height: 170px;
}
ul {
list-style: none;
padding-left: 0;
}
li {
padding-top: 0.2em;
}
.node circle,
circle.node {
cursor: pointer;
fill: #ccc;
stroke: #fff;
stroke-width: 1px;
}
.edge line,
line.edge {
cursor: pointer;
stroke: #aaa;
stroke-width: 2px;
}
.labelNode text,
text.labelNode {
cursor: pointer;
fill: #444;
font-size: 11px;
font-weight: normal;
}
ul.connection {
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
cursor: pointer;
font-size: 11px;
font-weight: normal;
padding: 10px;
position: absolute;
}
ul.connection:before,
ul.connection:after {
border: 10px solid transparent;
content: '';
position: absolute;
}
ul.connection:before {
border-bottom-color: #f0f0f0;
top: -19px;
left: 20px;
z-index: 2;
}
ul.connection:after {
border-bottom-color: rgba(0, 0, 0, 0.2);
top: -21px;
left: 20px;
z-index: 1;
}
/*
ul.connection li {
background-color: #eee;
border-left: 1px #444 solid;
border-right: 1px #444 solid;
font-size: 11px;
font-weight: normal;
margin: 0 50% 0 -50%;
padding: 2px 4px;
}
ul.connection li:first-child {
border-top: 1px #444 solid;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
ul.connection li:last-child {
border-bottom: 1px #444 solid;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
*/
ul.connection.hidden {
display: none;
}
</style>
</head>
<body>
<div id='container'>
<div id='graph'></div>
<div id='notes'></div>
</div>
<script src='http://d3js.org/d3.v3.min.js'></script>
<script>
// Define the dimensions of the visualization. We're using
// a size that's convenient for displaying the graphic on
// http://jsDataV.is
var width = 528,
height = 576;
// Visual properties of the graph are next. We need to make
// those that are going to be animated accessible to the
// JavaScript.
var labelFill = '#444';
var adjLabelFill = '#aaa';
var edgeStroke = '#aaa';
var nodeFill = '#ccc';
var nodeRadius = 10;
var selectedNodeRadius = 30;
var linkDistance = Math.min(width, height) / 4;
// Find the main graph container.
var graph = d3.select('#graph');
// Create the SVG container for the visualization and
// define its dimensions.
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
// Select the container for the notes and dimension it.
var notes = d3.select('#notes')
.style({
'width': 620 - width + 'px',
'height': height + 'px'
});
// Utility function to update the position properties
// of an arbtrary edge that's part of a D3 selection.
// The optional parameter is the array of nodes for
// the edges. If present, the source and target properties
// are assumed to be indices in this array rather than
// direct references.
var positionEdge = function(edge, nodes) {
edge.attr('x1', function(d) {
return nodes ? nodes[d.source].x : d.source.x;
}).attr('y1', function(d) {
return nodes ? nodes[d.source].y : d.source.y;
}).attr('x2', function(d) {
return nodes ? nodes[d.target].x : d.target.x;
}).attr('y2', function(d) {
return nodes ? nodes[d.target].y : d.target.y;
});
};
// Utility function to update the position properties
// of an arbitrary node that's part of a D3 selection.
var positionNode = function(node) {
node.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
};
// Utility function to position text associated with
// a label pseudo-node. The optional third parameter
// requests transition to the specified fill color.
var positionLabelText = function(text, pseudonode, fillColor) {
// What's the width of the text element?
var textWidth = text.getBBox().width;
// How far is the pseudo-node from the real one?
var diffX = pseudonode.x - pseudonode.node.x;
var diffY = pseudonode.y - pseudonode.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
// Shift in the x-direction a fraction of the text width
var shiftX = textWidth * (diffX - dist) / (dist * 2);
shiftX = Math.max(-textWidth, Math.min(0, shiftX));
var shiftY = pseudonode.node.selected ? selectedNodeRadius : nodeRadius;
shiftY = 0.5 * shiftY * diffY / Math.abs(diffY);
var select = d3.select(text);
if (fillColor) {
select = select.transition().style('fill', fillColor);
}
select.attr('transform', 'translate(' + shiftX + ',' + shiftY + ')');
};
// Define the data.
data = [
{
"gang": "Los Zetas",
"title": "Los Zetas",
"itunes": "https://itunes.apple.com/us/album/quintet-jazz-at-massey-hall/id152035858",
"cover": "http://tinyurl.com/nmq6exr",
"color": "#B03239",
"text": "#191A18",
"gang_members": [
"Pedro Garcia",
"Juan Rodriguez",
"Miguel Martinez",
"Jose Lopez",
"Francisco Gonzalez",
"Antonio Perez",
"Manuel Sanchez",
"John Ramirez",
"Roger Santana",
"Daniel Torres",
"Juan Garcia"
]
},{
"gang": "Barrio Azteca",
"title": "Barrio Azteca",
"itunes": "https://itunes.apple.com/us/album/best-hot-5-hot-7-recordings/id201274363",
"cover": "https://orwells1984oregon.files.wordpress.com/2010/05/zuniz1.jpg",
"color": "#B08865",
"text": "#483830",
"gang_members": [
"Antonio Flores",
"Roger Santana",
"Manuel Rivera",
"John Gomez",
"Daniel Diaz"
]
},{
"gang": "The Bloods",
"title": "The Bloods",
"itunes": "https://itunes.apple.com/us/album/blue-train-bonus-track-version/id724748588",
"cover": "https://orwells1984oregon.files.wordpress.com/2010/03/b-11.jpg",
"color": "#0C7F90",
"text": "#041E1F",
"gang_members": [
"Jorge Reyes",
"John Morales",
"David Cruz",
"Germoso Cruz",
"Juan Ortiz",
"Martin Chavez"
]
},{
"gang": "Los Yolos",
"title": "Los Yolos",
"itunes": "https://itunes.apple.com/us/album/getz-gilberto/id485540980",
"cover": "http://orwells1984oregon.files.wordpress.com/2010/11/mendozadelacruz-luis-alberto.jpg",
"color": "#ED8C42",
"text": "#24231B",
"gang_members": [
"Pablo Ramos",
"Juan Ruiz",
"Astrud Avila",
"João Mendoza",
"Antonio Carlos Castillo",
"Sebastião Romero"
]
},{
"gang": "Los Tacos",
"title": "Los Tacos",
"itunes": "https://itunes.apple.com/us/album/getz-gilberto/id485540980",
"cover": "http://orwells1984oregon.files.wordpress.com/2010/11/mendozadelacruz-luis-alberto.jpg",
"color": "#ED8C42",
"text": "#24231B",
"gang_members": [
"Pablo Ramos",
"Juan Ruiz",
"Astrud Avila",
"João Mendoza",
"Antonio Carlos Castillo",
"Sebastião Romero",
"Juan Garcia"
]
},{
"gang": "Los Burritos",
"title": "Los Burritos",
"itunes": "https://itunes.apple.com/us/album/getz-gilberto/id485540980",
"cover": "http://orwells1984oregon.files.wordpress.com/2010/11/mendozadelacruz-luis-alberto.jpg",
"color": "#ED8C42",
"text": "#24231B",
"gang_members": [
"Pablo Ramos",
"Juan Ruiz",
"Astrud Avila",
"João Mendoza",
"Antonio Carlos Castillo",
"Sebastião Romero"
]
},{
"gang": "Los Cheesos",
"title": "Los Cheesos",
"itunes": "https://itunes.apple.com/us/album/getz-gilberto/id485540980",
"cover": "http://orwells1984oregon.files.wordpress.com/2010/11/mendozadelacruz-luis-alberto.jpg",
"color": "#ED8C42",
"text": "#24231B",
"gang_members": [
"Pablo Ramos",
"Juan Ruiz",
"Astrud Avila",
"João Mendoza",
"Antonio Carlos Castillo",
"Sebastião Romero"
]
},{
"gang": "Los Lolos",
"title": "Los Lolos",
"itunes": "https://itunes.apple.com/us/album/getz-gilberto/id485540980",
"cover": "http://orwells1984oregon.files.wordpress.com/2010/11/mendozadelacruz-luis-alberto.jpg",
"color": "#ED8C42",
"text": "#24231B",
"gang_members": [
"Pablo Ramos",
"Juan Ruiz",
"Astrud Avila",
"João Mendoza",
"Antonio Carlos Castillo",
"Sebastião Romero"
]
},{
"gang": "Los Folos",
"title": "Los Folos",
"itunes": "https://itunes.apple.com/us/album/getz-gilberto/id485540980",
"cover": "http://orwells1984oregon.files.wordpress.com/2010/11/mendozadelacruz-luis-alberto.jpg",
"color": "#ED8C42",
"text": "#24231B",
"gang_members": [
"Pablo Ramos",
"Juan Ruiz",
"Astrud Avila",
"João Mendoza",
"Antonio Carlos Castillo",
"Sebastião Romero"
]
}
];
// Find the graph nodes from the data set. Each
// album is a separate node.
var nodes = data.map(function(entry, idx, list) {
// This iteration returns a new object for
// each node.
var node = {};
// We retain some of the album's properties.
node.title = entry.title;
node.subtitle = entry.suspect;
node.image = entry.cover;
node.url = entry.itunes;
node.color = entry.color;
node.text = entry.text;
// We'll also copy the gang_members, again using
// a more neutral property. At the risk of
// some confusion, we're going to use the term
// "link" to refer to an individual connection
// between nodes, and we'll use the more
// mathematically correct term "edge" to refer
// to a line drawn between nodes on the graph.
// (This may be confusing because D3 refers to
// the latter as "links."
node.links = entry.gang_members.slice(0);
// As long as we're iterating through the nodes
// array, take the opportunity to create an
// initial position for the nodes. Somewhat
// arbitrarily, we start the nodes off in a
// circle in the center of the container.
var radius = 0.4 * Math.min(height, width);
var theta = idx * 2 * Math.PI / list.length;
node.x = (width / 2) + radius * Math.sin(theta);
node.y = (height / 2) + radius * Math.cos(theta);
// Return the newly created object so it can be
// added to the nodes array.
return node;
});
// Identify all the indivual links between nodes on
// the graph. As noted above, we're using the term
// "link" to refer to a single connection. As we'll
// see below, we'll call lines drawn on the graph
// (which may represent a combination of multiple
// links) "edges" in a nod to the more mathematically
// minded.
var links = [];
// Start by iterating through the albums.
data.forEach(function(srcNode, srcIdx, srcList) {
// For each album, iterate through the gang_members.
srcNode.gang_members.forEach(function(srcLink) {
// For each musican in the "src" album, iterate
// through the remaining albums in the list.
for (var tgtIdx = srcIdx + 1; tgtIdx < srcList.length; tgtIdx++) {
// Use a variable to refer to the "tgt"
// album for convenience.
var tgtNode = srcList[tgtIdx];
// Is there any musician in the "tgt"
// album that matches the musican we're
// currently considering from the "src"
// album?
if (tgtNode.gang_members.some(function(tgtLink) {
return tgtLink === srcLink;
})) {
// When we do find a match, add a new
// link to the links array.
links.push({
source: srcIdx,
target: tgtIdx,
link: srcLink
});
}
}
});
});
// Now create the edges for our graph. We do that by
// eliminating duplicates from the links array.
var edges = [];
// Iterate through the links array.
links.forEach(function(link) {
// Assume for now that the current link is
// unique.
var existingEdge = false;
// Look through the edges we've collected so
// far to see if the current link is already
// present.
for (var idx = 0; idx < edges.length; idx++) {
// A duplicate link has the same source
// and target values.
if ((link.source === edges[idx].source) &&
(link.target === edges[idx].target)) {
// When we find an existing link, remember
// it.
existingEdge = edges[idx];
// And stop looking.
break;
}
}
// If we found an existing edge, all we need
// to do is add the current link to it.
if (existingEdge) {
existingEdge.links.push(link.link);
} else {
// If there was no existing edge, we can
// create one now.
edges.push({
source: link.source,
target: link.target,
links: [link.link]
});
}
});
// Start the creation of the graph by adding the edges.
// We add these first so they'll appear "underneath"
// the nodes.
var edgeSelection = svg.selectAll('.edge')
.data(edges)
.enter()
.append('line')
.classed('edge', true)
.style('stroke', edgeStroke)
.call(positionEdge, nodes);
// Next up are the nodes.
var nodeSelection = svg.selectAll('.node')
.data(nodes)
.enter()
.append('g')
.classed('node', true)
.call(positionNode);
nodeSelection.append('circle')
.attr('r', nodeRadius)
.attr('data-node-index', function(d, i) {
return i;
})
.style('fill', nodeFill)
// Now that we have our main selections (edges and
// nodes), we can create some subsets of those
// selections that will be helpful. Those subsets
// will be tied to individual nodes, so we'll
// start by iterating through them. We do that
// in two separate passes.
nodeSelection.each(function(node) {
// First let's identify all edges that are
// incident to the node. We collect those as
// a D3 selection so we can manipulate the
// set easily with D3 utilities.
node.incidentEdgeSelection = edgeSelection
.filter(function(edge) {
return nodes[edge.source] === node ||
nodes[edge.target] === node;
});
});
// Now make a second pass through the nodes.
nodeSelection.each(function(node) {
// For this pass we want to find all adjacencies.
// An adjacent node shares an edge with the
// current node.
node.adjacentNodeSelection = nodeSelection
.filter(function(otherNode) {
// Presume that the nodes are not adjacent.
var isAdjacent = false;
// We can't be adjacent to ourselves.
if (otherNode !== node) {
// Look the incident edges of both nodes to
// see if there are any in common.
node.incidentEdgeSelection.each(function(edge) {
otherNode.incidentEdgeSelection.each(function(otherEdge) {
if (edge === otherEdge) {
isAdjacent = true;
}
});
});
}
return isAdjacent;
});
});
// Next we create a array for the node labels.
// We're going to use a "hidden" force layout to
// position the labels so they don't overlap
// each other. ("Hidden" because the links won't
// be visible.)
var labels = [];
var labelLinks = [];
nodes.forEach(function(node, idx) {
// For each node on the graph we create
// two pseudo-nodes for its label. Once
// pseudo-node will be anchored to the
// center of the real node, while the
// second will be linked to that node.
// Add the pseudo-nodes to their array.
labels.push({
node: node
});
labels.push({
node: node
});
// And create a link between them.
labelLinks.push({
source: idx * 2,
target: idx * 2 + 1
});
});
// Construct the selections for the label layout.
// There's no need to add any markup for the
// pseudo-links between the label nodes, but
// we do need a selection so we can run the
// force layout.
var labelLinkSelection = svg.selectAll('line.labelLink')
.data(labelLinks);
// The label pseud-nodes themselves are just
// `<g>` containers.
var labelSelection = svg.selectAll('g.labelNode')
.data(labels)
.enter()
.append('g')
.classed('labelNode', true);
// Now add the text itself. Of the paired
// pseudo-nodes, only odd ones get the text
// elements.
labelSelection.append('text')
.text(function(d, i) {
return i % 2 == 0 ? '' : d.node.title;
})
.attr('data-node-index', function(d, i) {
return i % 2 == 0 ? 'none' : Math.floor(i / 2);
});
// The last bit of markup are the lists of
// connections for each link.
var connectionSelection = graph.selectAll('ul.connection')
.data(edges)
.enter()
.append('ul')
.classed('connection hidden', true)
.attr('data-edge-index', function(d, i) {
return i;
});
connectionSelection.each(function(connection) {
var selection = d3.select(this);
connection.links.forEach(function(link) {
selection.append('li')
.text(link);
})
})
// Create the main force layout.
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(edges)
.linkDistance(linkDistance)
.charge(-500);
// Create the force layout for the labels.
var labelForce = d3.layout.force()
.size([width, height])
.nodes(labels)
.links(labelLinks)
.gravity(0)
.linkDistance(0)
.linkStrength(0.8)
.charge(-100);
// Let users drag the nodes.
nodeSelection.call(force.drag);
// Function to handle clicks on node elements
var nodeClicked = function(node) {
// Ignore events based on dragging.
if (d3.event.defaultPrevented) return;
// Remember whether or not the clicked
// node is currently selected.
var selected = node.selected;
// Keep track of the desired text color.
var fillColor;
// In all cases we start by resetting
// all the nodes and edges to their
// de-selected state. We may override
// this transition for some nodes and
// edges later.
nodeSelection
.each(function(node) {
node.selected = false;
})
.selectAll('circle')
.transition()
.attr('r', nodeRadius)
.style('fill', nodeFill);
edgeSelection
.transition()
.style('stroke', edgeStroke);
labelSelection
.transition()
.style('opacity', 0);
// Now see if the node wasn't previously selected.
if (!selected) {
// This node wasn't selected before, so
// we want to select it now. That means
// changing the styles of some of the
// elements in the graph.
// First we transition the incident edges.
node.incidentEdgeSelection
.transition()
.style('stroke', node.color);
// Now we transition the adjacent nodes.
node.adjacentNodeSelection.selectAll('circle')
.transition()
.attr('r', nodeRadius)
.style('fill', node.color);
labelSelection
.filter(function(label) {
var adjacent = false;
node.adjacentNodeSelection.each(function(d) {
if (label.node === d) {
adjacent = true;
}
})
return adjacent;
})
.transition()
.style('opacity', 1)
.selectAll('text')
.style('fill', adjLabelFill);
// And finally, transition the node itself.
d3.selectAll('circle[data-node-index="' + node.index + '"]')
.transition()
.attr('r', selectedNodeRadius)
.style('fill', node.color);
// Make sure the node's label is visible
labelSelection
.filter(function(label) {
return label.node === node;
})
.transition()
.style('opacity', 1);
// And note the desired color for bundling with
// the transition of the label position.
fillColor = node.text;
// Delete the current notes section to prepare
// for new information.
notes.selectAll('*').remove();
// Fill in the notes section with informationm
// from the node. Because we want to transition
// this to match the transitions on the graph,
// we first set it's opacity to 0.
notes.style({
'opacity': 0
});
// Now add the notes content.
notes.append('h1').text(node.title);
notes.append('h2').text(node.subtitle);
if (node.url && node.image) {
notes.append('div')
.classed('artwork', true)
.append('a')
.attr('href', node.url)
.append('img')
.attr('src', node.image);
}
var list = notes.append('ul');
node.links.forEach(function(link) {
list.append('li')
.text(link);
})
// With the content in place, transition
// the opacity to make it visible.
notes.transition().style({
'opacity': 1
});
} else {
// Since we're de-selecting the current
// node, transition the notes section
// and then remove it.
notes.transition()
.style({
'opacity': 0
})
.each('end', function() {
notes.selectAll('*').remove();
});
// Transition all the labels to their
// default styles.
labelSelection
.transition()
.style('opacity', 1)
.selectAll('text')
.style('fill', labelFill);
// The fill color for the current node's
// label must also be bundled with its
// position transition.
fillColor = labelFill;
}
// Toggle the selection state for the node.
node.selected = !selected;
// Update the position of the label text.
var text = d3.select('text[data-node-index="' + node.index + '"]').node();
var label = null;
labelSelection.each(function(d) {
if (d.node === node) {
label = d;
}
})
if (text && label) {
positionLabelText(text, label, fillColor);
}
};
// Function to handle click on edges.
var edgeClicked = function(edge, idx) {
// Remember the current selection state of the edge.
var selected = edge.selected;
// Transition all connections to hidden. If the
// current edge needs to be displayed, it's transition
// will be overridden shortly.
connectionSelection
.each(function(edge) {
edge.selected = false;
})
.transition()
.style('opacity', 0)
.each('end', function() {
d3.select(this).classed('hidden', true);
});
// If the current edge wasn't selected before, we
// want to transition it to the selected state now.
if (!selected) {
d3.select('ul.connection[data-edge-index="' + idx + '"]')
.classed('hidden', false)
.style('opacity', 0)
.transition()
.style('opacity', 1);
}
// Toggle the resulting selection state for the edge.
edge.selected = !selected;
};
// Handle clicks on the nodes.
nodeSelection.on('click', nodeClicked);
labelSelection.on('click', function(pseudonode) {
nodeClicked(pseudonode.node);
});
// Handle clicks on the edges.
edgeSelection.on('click', edgeClicked);
connectionSelection.on('click', edgeClicked);
// Animate the force layout.
force.on('tick', function() {
// Constrain all the nodes to remain in the
// graph container.
nodeSelection.each(function(node) {
node.x = Math.max(node.x, 2 * selectedNodeRadius);
node.y = Math.max(node.y, 2 * selectedNodeRadius);
node.x = Math.min(node.x, width - 2 * selectedNodeRadius);
node.y = Math.min(node.y, height - 2 * selectedNodeRadius);
});
// Kick the label layout to make sure it doesn't
// finish while the main layout is still running.
labelForce.start();
// Calculate the positions of the label nodes.