View difference between Paste ID: uik3NSDw and i1UmLXd5
SHOW: | | - or go back to the newest paste.
1
/*Copyright (c) 2013-2016, Rob Schmuecker
2
 All rights reserved.
3
 
4
 Redistribution and use in source and binary forms, with or without
5
 modification, are permitted provided that the following conditions are met:
6
 
7
 * Redistributions of source code must retain the above copyright notice, this
8
 list of conditions and the following disclaimer.
9
 
10
 * Redistributions in binary form must reproduce the above copyright notice,
11
 this list of conditions and the following disclaimer in the documentation
12
 and/or other materials provided with the distribution.
13
 
14
 * The name Rob Schmuecker may not be used to endorse or promote products
15
 derived from this software without specific prior written permission.
16
 
17
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
 DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
21
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22
 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24
 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/
27
28
29
// Get JSON data
30
treeJSON = d3.json("flare.json", function (error, treeData) {
31
32
    // Calculate total nodes, max label length
33
    var totalNodes = 0;
34
    var maxLabelLength = 0;
35
    // variables for drag/drop
36
    var selectedNode = null;
37
    var draggingNode = null;
38
    // panning variables
39
    var panSpeed = 200;
40
    var panBoundary = 20; // Within 20px from edges will pan when dragging.
41
    // Misc. variables
42
    var i = 0;
43
    var duration = 750;
44
    var root;
45
46
    // size of the diagram
47
    var viewerWidth = $(document).width();
48
    var viewerHeight = $(document).height();
49
50
    var tree = d3.layout.tree()
51
            .size([viewerHeight, viewerWidth]);
52
53
    // QUA SOTTO CAMBIA L'ORIENTAMENTO DEGLI ARCHETTI! 
54
    // define a d3 diagonal projection for use by the node paths later on.
55
    var diagonal = d3.svg.diagonal()
56
            .projection(function (d) {
57
                return [d.x, d.y];
58
            });
59
60
    // A recursive helper function for performing some setup by walking through all nodes
61
62
    function visit(parent, visitFn, childrenFn) {
63
        if (!parent)
64
            return;
65
66
        visitFn(parent);
67
68
        var children = childrenFn(parent);
69
        if (children) {
70
            var count = children.length;
71
            for (var i = 0; i < count; i++) {
72
                visit(children[i], visitFn, childrenFn);
73
            }
74
        }
75
    }
76
77
    // Call visit function to establish maxLabelLength
78
    visit(treeData, function (d) {
79
        totalNodes++;
80
        maxLabelLength = Math.max(d.name.length, maxLabelLength);
81
82
    }, function (d) {
83
        return d.children && d.children.length > 0 ? d.children : null;
84
    });
85
86
87
    // sort the tree according to the node names
88
89
    function sortTree() {
90
        tree.sort(function (a, b) {
91
            return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
92
        });
93
    }
94
    // Sort the tree initially incase the JSON isn't in a sorted order.
95
    sortTree();
96
97
    // TODO: Pan function, can be better implemented.
98
99
    function pan(domNode, direction) {
100
        var speed = panSpeed;
101
        if (panTimer) {
102
            clearTimeout(panTimer);
103
            translateCoords = d3.transform(svgGroup.attr("transform"));
104
            if (direction == 'left' || direction == 'right') {
105
                translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
106
                translateY = translateCoords.translate[1];
107
            } else if (direction == 'up' || direction == 'down') {
108
                translateX = translateCoords.translate[0];
109
                translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
110
            }
111
            scaleX = translateCoords.scale[0];
112
            scaleY = translateCoords.scale[1];
113
            scale = zoomListener.scale();
114
            svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
115
            d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
116
            zoomListener.scale(zoomListener.scale());
117
            zoomListener.translate([translateX, translateY]);
118
            panTimer = setTimeout(function () {
119
                pan(domNode, speed, direction);
120
            }, 50);
121
        }
122
    }
123
124
    // Define the zoom function for the zoomable tree
125
126
    function zoom() {
127
        svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
128
    }
129
130
131
    // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
132
    var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
133
134
    function initiateDrag(d, domNode) {
135
        draggingNode = d;
136
        d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none');
137
        d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show');
138
        d3.select(domNode).attr('class', 'node activeDrag');
139
140
        svgGroup.selectAll("g.node").sort(function (a, b) { // select the parent and sort the path's
141
            if (a.id != draggingNode.id)
142
                return 1; // a is not the hovered element, send "a" to the back
143
            else
144
                return -1; // a is the hovered element, bring "a" to the front
145
        });
146
        // if nodes has children, remove the links and nodes
147
        if (nodes.length > 1) {
148
            // remove link paths
149
            links = tree.links(nodes);
150
            nodePaths = svgGroup.selectAll("path.link")
151
                    .data(links, function (d) {
152
                        return d.target.id;
153
                    }).remove();
154
            // remove child nodes
155
            nodesExit = svgGroup.selectAll("g.node")
156
                    .data(nodes, function (d) {
157
                        return d.id;
158
                    }).filter(function (d, i) {
159
                if (d.id == draggingNode.id) {
160
                    return false;
161
                }
162
                return true;
163
            }).remove();
164
        }
165
166
        // remove parent link
167
        parentLink = tree.links(tree.nodes(draggingNode.parent));
168
        svgGroup.selectAll('path.link').filter(function (d, i) {
169
            if (d.target.id == draggingNode.id) {
170
                return true;
171
            }
172
            return false;
173
        }).remove();
174
175
        dragStarted = null;
176
    }
177
178
    // define the baseSvg, attaching a class for styling and the zoomListener
179
    var baseSvg = d3.select("#tree-container").append("svg")
180
            .attr("width", viewerWidth)
181
            .attr("height", viewerHeight)
182
            .attr("class", "overlay")
183
            .call(zoomListener);
184
185
186
    // Define the drag listeners for drag/drop behaviour of nodes.
187
    dragListener = d3.behavior.drag()
188
            .on("dragstart", function (d) {
189
                if (d == root) {
190
                    return;
191
                }
192
                dragStarted = true;
193
                nodes = tree.nodes(d);
194
                d3.event.sourceEvent.stopPropagation();
195
                // it's important that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the mouseover event and the underlying node will not detect it d3.select(this).attr('pointer-events', 'none');
196
            })
197
            .on("drag", function (d) {
198
                if (d == root) {
199
                    return;
200
                }
201
                if (dragStarted) {
202
                    domNode = this;
203
                    initiateDrag(d, domNode);
204
                }
205
206
                // get coords of mouseEvent relative to svg container to allow for panning
207
                relCoords = d3.mouse($('svg').get(0));
208
                if (relCoords[0] < panBoundary) {
209
                    panTimer = true;
210
                    pan(this, 'left');
211
                } else if (relCoords[0] > ($('svg').width() - panBoundary)) {
212
213
                    panTimer = true;
214
                    pan(this, 'right');
215
                } else if (relCoords[1] < panBoundary) {
216
                    panTimer = true;
217
                    pan(this, 'up');
218
                } else if (relCoords[1] > ($('svg').height() - panBoundary)) {
219
                    panTimer = true;
220
                    pan(this, 'down');
221
                } else {
222
                    try {
223
                        clearTimeout(panTimer);
224
                    } catch (e) {
225
226
                    }
227
                }
228
229
                d.x0 += d3.event.dy;
230
                d.y0 += d3.event.dx;
231
                var node = d3.select(this);
232
                node.attr("transform", "translate(" + d.x0 + "," + d.y0 + ")");
233
                updateTempConnector();
234
            }).on("dragend", function (d) {
235
        if (d == root) {
236
            return;
237
        }
238
        domNode = this;
239
        if (selectedNode) {
240
            // now remove the element from the parent, and insert it into the new elements children
241
            var index = draggingNode.parent.children.indexOf(draggingNode);
242
            if (index > -1) {
243
                draggingNode.parent.children.splice(index, 1);
244
            }
245
            if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') {
246
                if (typeof selectedNode.children !== 'undefined') {
247
                    selectedNode.children.push(draggingNode);
248
                } else {
249
                    selectedNode._children.push(draggingNode);
250
                }
251
            } else {
252
                selectedNode.children = [];
253
                selectedNode.children.push(draggingNode);
254
            }
255
            // Make sure that the node being added to is expanded so user can see added node is correctly moved
256
            expand(selectedNode);
257
            sortTree();
258
            endDrag();
259
        } else {
260
            endDrag();
261
        }
262
    });
263
264
    function endDrag() {
265
        selectedNode = null;
266
        d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
267
        d3.select(domNode).attr('class', 'node');
268
        // now restore the mouseover event or we won't be able to drag a 2nd time
269
        d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
270
        updateTempConnector();
271
        if (draggingNode !== null) {
272
            update(root);
273
            centerNode(draggingNode);
274
            draggingNode = null;
275
        }
276
    }
277
278
    // Helper functions for collapsing and expanding nodes.
279
280
    function collapse(d) {
281
        if (d.children) {
282
            d._children = d.children;
283
            d._children.forEach(collapse);
284
            d.children = null;
285
        }
286
    }
287
288
    function expand(d) {
289
        if (d._children) {
290
            d.children = d._children;
291
            d.children.forEach(expand);
292
            d._children = null;
293
        }
294
    }
295
296
    var overCircle = function (d) {
297
        selectedNode = d;
298
        updateTempConnector();
299
    };
300
    var outCircle = function (d) {
301
        selectedNode = null;
302
        updateTempConnector();
303
    };
304
305
    // Function to update the temporary connector indicating dragging affiliation
306
    var updateTempConnector = function () {
307
        var data = [];
308
        if (draggingNode !== null && selectedNode !== null) {
309
            // have to flip the source coordinates since we did this for the existing connectors on the original tree
310
            data = [{
311
                    source: {
312
                        x: selectedNode.y0,
313
                        y: selectedNode.x0
314
                    },
315
                    target: {
316
                        x: draggingNode.y0,
317
                        y: draggingNode.x0
318
                    }
319
                }];
320
        }
321
        var link = svgGroup.selectAll(".templink").data(data);
322
323
        link.enter().append("path")
324
                .attr("class", "templink")
325
                .attr("d", d3.svg.diagonal())
326
                .attr('pointer-events', 'none');
327
328
        link.attr("d", d3.svg.diagonal());
329
330
        link.exit().remove();
331
    };
332
333
    // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
334
335
    function centerNode(source) {
336
        scale = zoomListener.scale();
337
        x = -source.y0;
338
        y = -source.x0;
339
        x = x * scale + viewerWidth / 2;
340
        y = y * scale + viewerHeight / 2;
341
        d3.select('g').transition()
342
                .duration(duration)
343
                .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
344
        zoomListener.scale(scale);
345
        zoomListener.translate([x, y]);
346
    }
347
348
    // Toggle children function
349
350
    function toggleChildren(d) {
351
        if (d.children) {
352
            d._children = d.children;
353
            d.children = null;
354
        } else if (d._children) {
355
            d.children = d._children;
356
            d._children = null;
357
        }
358
        return d;
359
    }
360
361
    // Toggle children on click.
362
363
    function click(d) {
364
        if (d3.event.defaultPrevented)
365
            return; // click suppressed
366
        
367
        console.log(d3.select(this).select('text').html());
368
        
369
        d3.select(this).append('span').attr('class', 'ok3');
370
        
371
        document.getElementById('ok1').innerHTML = d3.select(this).select('text').html();
372
        document.getElementById('ok1').style.visibility = 'visible';
373
        d = toggleChildren(d);
374
        update(d);
375
        centerNode(d);
376
    }
377
378
    function update(source) {
379
        // Compute the new height, function counts total children of root node and sets tree height accordingly.
380
        // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
381
        // This makes the layout more consistent.
382
        var levelWidth = [1];
383
        var childCount = function (level, n) {
384
385
            if (n.children && n.children.length > 0) {
386
                if (levelWidth.length <= level + 1)
387
                    levelWidth.push(0);
388
389
                levelWidth[level + 1] += n.children.length;
390
                n.children.forEach(function (d) {
391
                    childCount(level + 1, d);
392
                });
393
            }
394
        };
395
        childCount(0, root);
396
        var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line  
397
        tree = tree.size([newHeight, viewerWidth]);
398
399
        // Compute the new tree layout.
400
        var nodes = tree.nodes(root).reverse(),
401
                links = tree.links(nodes);
402
403
        // Set widths between levels based on maxLabelLength.
404
        nodes.forEach(function (d) {
405
            d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
406
            // alternatively to keep a fixed scale one can set a fixed depth per level
407
            // Normalize for fixed-depth by commenting out below line
408
            // d.y = (d.depth * 500); //500px per level.
409
        });
410
411
        // Update the nodes…
412
        node = svgGroup.selectAll("g.node")
413
                .data(nodes, function (d) {
414
                    return d.id || (d.id = ++i);
415
                });
416
417
        // Enter any new nodes at the parent's previous position.
418
        var nodeEnter = node.enter().append("g")
419
                .call(dragListener)
420
                .attr("class", "node")
421
                .attr("transform", function (d) {
422
                    return "translate(" + source.x0 + "," + source.y0 + ")";
423
                })
424
                .on('click', click);
425
426
        nodeEnter.append("circle")
427
                .attr('class', 'nodeCircle')
428
                .attr("r", 0)
429
                .style("fill", function (d) {
430
                    return d._children ? "lightsteelblue" : "#fff";
431
                });
432
433
        nodeEnter.append("text")
434
                .attr("x", function (d) {
435
                    return d.children || d._children ? -10 : 10;
436
                })
437
                .attr("dy", ".35em")
438
                .attr('class', 'nodeText')
439
                .attr("text-anchor", function (d) {
440
                    return d.children || d._children ? "end" : "start";
441
                })
442
                .text(function (d) {
443
                    return d.name;
444
                })
445
                .style("fill-opacity", 0);
446
447
        // phantom node to give us mouseover in a radius around it
448
        nodeEnter.append("circle")
449
                .attr('class', 'ghostCircle')
450
                .attr("r", 30)
451
                .attr("opacity", 0.2) // change this to zero to hide the target area
452
                .style("fill", "red")
453
                .attr('pointer-events', 'mouseover')
454
                .on("mouseover", function (node) {
455
                    overCircle(node);
456
                })
457
                .on("mouseout", function (node) {
458
                    outCircle(node);
459
                });
460
461
        // Update the text to reflect whether node has children or not.
462
        node.select('text')
463
                .attr("x", function (d) {
464
                    return d.children || d._children ? -10 : 10;
465
                })
466
                .attr("text-anchor", function (d) {
467
                    return d.children || d._children ? "end" : "start";
468
                })
469
                .text(function (d) {
470
                    return d.name;
471
                });
472
473
        // Change the circle fill depending on whether it has children and is collapsed
474
        node.select("circle.nodeCircle")
475
                .attr("r", 4.5)
476
                .style("fill", function (d) {
477
                    return d._children ? "lightsteelblue" : "#fff";
478
                });
479
480
        // Transition nodes to their new position.
481
        var nodeUpdate = node.transition()
482
                .duration(duration)
483
                .attr("transform", function (d) {
484
                    return "translate(" + d.x + "," + d.y + ")";
485
                });
486
487
        // Fade the text in
488
        nodeUpdate.select("text")
489
                .style("fill-opacity", 1);
490
491
        // Transition exiting nodes to the parent's new position.
492
        var nodeExit = node.exit().transition()
493
                .duration(duration)
494
                .attr("transform", function (d) {
495
                    return "translate(" + source.x + "," + source.y + ")";
496
                })
497
                .remove();
498
499
        nodeExit.select("circle")
500
                .attr("r", 0);
501
502
        nodeExit.select("text")
503
                .style("fill-opacity", 0);
504
505
        // Update the links…
506
        var link = svgGroup.selectAll("path.link")
507
                .data(links, function (d) {
508
                    return d.target.id;
509
                });
510
511
        // Enter any new links at the parent's previous position.
512
        link.enter().insert("path", "g")
513
                .attr("class", "link")
514
                .attr("d", function (d) {
515
                    var o = {
516
                        x: source.x0,
517
                        y: source.y0
518
                    };
519
                    return diagonal({
520
                        source: o,
521
                        target: o
522
                    });
523
                });
524
525
        // Transition links to their new position.
526
        link.transition()
527
                .duration(duration)
528
                .attr("d", diagonal);
529
530
        // Transition exiting nodes to the parent's new position.
531
        link.exit().transition()
532
                .duration(duration)
533
                .attr("d", function (d) {
534
                    var o = {
535
                        x: source.x,
536
                        y: source.y
537
                    };
538
                    return diagonal({
539
                        source: o,
540
                        target: o
541
                    });
542
                })
543
                .remove();
544
545
        // Stash the old positions for transition.
546
        nodes.forEach(function (d) {
547
            d.x0 = d.x;
548
            d.y0 = d.y;
549
        });
550
    }
551
552
    // Append a group which holds all nodes and which the zoom Listener can act upon.
553
    var svgGroup = baseSvg.append("g");
554
555
    // Define the root
556
    root = treeData;
557
    root.x0 = viewerHeight / 2;
558
    root.y0 = 0;
559
560
    // Layout the tree initially and center on the root node.
561
    update(root);
562
    centerNode(root);
563
});