Advertisement
Guest User

d3 demo

a guest
Oct 5th, 2017
62
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement