D3 js Drag and Drop Zoomable Tree. Zoomable, Panning, Collapsible Tree with Auto-sizing.
Below you can see an example of a D3.js Drag and Drop Zoomable Tree which is collapsible, panning and auto-sizing.
I recently had a need for this functionality for a project and was unable to find any previous examples of such on the internet. Using the awesome D3.js library by Mike Bostock, I was able to get exactly what I needed with a bit of trial and error. Here I open up my efforts for others to leverage. It is certainly a visually pleasing and nifty tool. You can theme it with CSS as you would anything else.
This example pulls together various examples of work with trees in D3.js.
The panning functionality can certainly be improved in my opinion and I would be thrilled to see better solutions contributed.
One can do all manner of housekeeping or server related calls on the drop event to manage a remote tree dataset for example. However here I simply append any dropped node as a child to the node being dropped upon.
Dragging can be performed on any node other than root (flare). Dropping can be done on any node.
Panning can either be done by dragging an empty part of the SVG around or dragging a node towards an edge.
Zooming is performed by either double clicking on an empty part of the SVG or by scrolling the mouse-wheel. To Zoom out hold shift when double-clicking.
Expanding and collapsing of nodes is achieved by clicking on the desired node.
The tree auto-calculates its sizes both horizontally and vertically so it can adapt between many nodes being present in the view to very few whilst making the view manageable and pleasing on the eye.
The source code can be found here: https://gist.github.com/robschmuecker/7880033
And an example of it working with code in the same view is here:- http://bl.ocks.org/robschmuecker/7880033
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 |
/* Copyright (c) 2013-2016, Rob Schmuecker All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name Rob Schmuecker may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Get JSON data treeJSON = d3.json("flare.json", function(error, treeData) { // Calculate total nodes, max label length var totalNodes = 0; var maxLabelLength = 0; // variables for drag/drop var selectedNode = null; var draggingNode = null; // panning variables var panSpeed = 200; var panBoundary = 20; // Within 20px from edges will pan when dragging. // Misc. variables var i = 0; var duration = 750; var root; // size of the diagram var viewerWidth = $(document).width(); var viewerHeight = $(document).height(); var tree = d3.layout.tree() .size([viewerHeight, viewerWidth]); // define a d3 diagonal projection for use by the node paths later on. var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; }); // A recursive helper function for performing some setup by walking through all nodes function visit(parent, visitFn, childrenFn) { if (!parent) return; visitFn(parent); var children = childrenFn(parent); if (children) { var count = children.length; for (var i = 0; i < count; i++) { visit(children[i], visitFn, childrenFn); } } } // Call visit function to establish maxLabelLength visit(treeData, function(d) { totalNodes++; maxLabelLength = Math.max(d.name.length, maxLabelLength); }, function(d) { return d.children && d.children.length > 0 ? d.children : null; }); // sort the tree according to the node names function sortTree() { tree.sort(function(a, b) { return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1; }); } // Sort the tree initially incase the JSON isn't in a sorted order. sortTree(); // TODO: Pan function, can be better implemented. function pan(domNode, direction) { var speed = panSpeed; if (panTimer) { clearTimeout(panTimer); translateCoords = d3.transform(svgGroup.attr("transform")); if (direction == 'left' || direction == 'right') { translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed; translateY = translateCoords.translate[1]; } else if (direction == 'up' || direction == 'down') { translateX = translateCoords.translate[0]; translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed; } scaleX = translateCoords.scale[0]; scaleY = translateCoords.scale[1]; scale = zoomListener.scale(); svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")"); d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")"); zoomListener.scale(zoomListener.scale()); zoomListener.translate([translateX, translateY]); panTimer = setTimeout(function() { pan(domNode, speed, direction); }, 50); } } // Define the zoom function for the zoomable tree function zoom() { svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom); function initiateDrag(d, domNode) { draggingNode = d; d3.select(domNode).select('.ghostCircle').attr('pointer-events', 'none'); d3.selectAll('.ghostCircle').attr('class', 'ghostCircle show'); d3.select(domNode).attr('class', 'node activeDrag'); svgGroup.selectAll("g.node").sort(function(a, b) { // select the parent and sort the path's if (a.id != draggingNode.id) return 1; // a is not the hovered element, send "a" to the back else return -1; // a is the hovered element, bring "a" to the front }); // if nodes has children, remove the links and nodes if (nodes.length > 1) { // remove link paths links = tree.links(nodes); nodePaths = svgGroup.selectAll("path.link") .data(links, function(d) { return d.target.id; }).remove(); // remove child nodes nodesExit = svgGroup.selectAll("g.node") .data(nodes, function(d) { return d.id; }).filter(function(d, i) { if (d.id == draggingNode.id) { return false; } return true; }).remove(); } // remove parent link parentLink = tree.links(tree.nodes(draggingNode.parent)); svgGroup.selectAll('path.link').filter(function(d, i) { if (d.target.id == draggingNode.id) { return true; } return false; }).remove(); dragStarted = null; } // define the baseSvg, attaching a class for styling and the zoomListener var baseSvg = d3.select("#tree-container").append("svg") .attr("width", viewerWidth) .attr("height", viewerHeight) .attr("class", "overlay") .call(zoomListener); // Define the drag listeners for drag/drop behaviour of nodes. dragListener = d3.behavior.drag() .on("dragstart", function(d) { if (d == root) { return; } dragStarted = true; nodes = tree.nodes(d); d3.event.sourceEvent.stopPropagation(); // 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'); }) .on("drag", function(d) { if (d == root) { return; } if (dragStarted) { domNode = this; initiateDrag(d, domNode); } // get coords of mouseEvent relative to svg container to allow for panning relCoords = d3.mouse($('svg').get(0)); if (relCoords[0] < panBoundary) { panTimer = true; pan(this, 'left'); } else if (relCoords[0] > ($('svg').width() - panBoundary)) { panTimer = true; pan(this, 'right'); } else if (relCoords[1] < panBoundary) { panTimer = true; pan(this, 'up'); } else if (relCoords[1] > ($('svg').height() - panBoundary)) { panTimer = true; pan(this, 'down'); } else { try { clearTimeout(panTimer); } catch (e) { } } d.x0 += d3.event.dy; d.y0 += d3.event.dx; var node = d3.select(this); node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")"); updateTempConnector(); }).on("dragend", function(d) { if (d == root) { return; } domNode = this; if (selectedNode) { // now remove the element from the parent, and insert it into the new elements children var index = draggingNode.parent.children.indexOf(draggingNode); if (index > -1) { draggingNode.parent.children.splice(index, 1); } if (typeof selectedNode.children !== 'undefined' || typeof selectedNode._children !== 'undefined') { if (typeof selectedNode.children !== 'undefined') { selectedNode.children.push(draggingNode); } else { selectedNode._children.push(draggingNode); } } else { selectedNode.children = []; selectedNode.children.push(draggingNode); } // Make sure that the node being added to is expanded so user can see added node is correctly moved expand(selectedNode); sortTree(); endDrag(); } else { endDrag(); } }); function endDrag() { selectedNode = null; d3.selectAll('.ghostCircle').attr('class', 'ghostCircle'); d3.select(domNode).attr('class', 'node'); // now restore the mouseover event or we won't be able to drag a 2nd time d3.select(domNode).select('.ghostCircle').attr('pointer-events', ''); updateTempConnector(); if (draggingNode !== null) { update(root); centerNode(draggingNode); draggingNode = null; } } // Helper functions for collapsing and expanding nodes. function collapse(d) { if (d.children) { d._children = d.children; d._children.forEach(collapse); d.children = null; } } function expand(d) { if (d._children) { d.children = d._children; d.children.forEach(expand); d._children = null; } } var overCircle = function(d) { selectedNode = d; updateTempConnector(); }; var outCircle = function(d) { selectedNode = null; updateTempConnector(); }; // Function to update the temporary connector indicating dragging affiliation var updateTempConnector = function() { var data = []; if (draggingNode !== null && selectedNode !== null) { // have to flip the source coordinates since we did this for the existing connectors on the original tree data = [{ source: { x: selectedNode.y0, y: selectedNode.x0 }, target: { x: draggingNode.y0, y: draggingNode.x0 } }]; } var link = svgGroup.selectAll(".templink").data(data); link.enter().append("path") .attr("class", "templink") .attr("d", d3.svg.diagonal()) .attr('pointer-events', 'none'); link.attr("d", d3.svg.diagonal()); link.exit().remove(); }; // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children. function centerNode(source) { scale = zoomListener.scale(); x = -source.y0; y = -source.x0; x = x * scale + viewerWidth / 2; y = y * scale + viewerHeight / 2; d3.select('g').transition() .duration(duration) .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")"); zoomListener.scale(scale); zoomListener.translate([x, y]); } // Toggle children function function toggleChildren(d) { if (d.children) { d._children = d.children; d.children = null; } else if (d._children) { d.children = d._children; d._children = null; } return d; } // Toggle children on click. function click(d) { if (d3.event.defaultPrevented) return; // click suppressed d = toggleChildren(d); update(d); centerNode(d); } function update(source) { // Compute the new height, function counts total children of root node and sets tree height accordingly. // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed // This makes the layout more consistent. var levelWidth = [1]; var childCount = function(level, n) { if (n.children && n.children.length > 0) { if (levelWidth.length <= level + 1) levelWidth.push(0); levelWidth[level + 1] += n.children.length; n.children.forEach(function(d) { childCount(level + 1, d); }); } }; childCount(0, root); var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line tree = tree.size([newHeight, viewerWidth]); // Compute the new tree layout. var nodes = tree.nodes(root).reverse(), links = tree.links(nodes); // Set widths between levels based on maxLabelLength. nodes.forEach(function(d) { d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px // alternatively to keep a fixed scale one can set a fixed depth per level // Normalize for fixed-depth by commenting out below line // d.y = (d.depth * 500); //500px per level. }); // Update the nodes… node = svgGroup.selectAll("g.node") .data(nodes, function(d) { return d.id || (d.id = ++i); }); // Enter any new nodes at the parent's previous position. var nodeEnter = node.enter().append("g") .call(dragListener) .attr("class", "node") .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) .on('click', click); nodeEnter.append("circle") .attr('class', 'nodeCircle') .attr("r", 0) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); nodeEnter.append("text") .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) .attr("dy", ".35em") .attr('class', 'nodeText') .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) .text(function(d) { return d.name; }) .style("fill-opacity", 0); // phantom node to give us mouseover in a radius around it nodeEnter.append("circle") .attr('class', 'ghostCircle') .attr("r", 30) .attr("opacity", 0.2) // change this to zero to hide the target area .style("fill", "red") .attr('pointer-events', 'mouseover') .on("mouseover", function(node) { overCircle(node); }) .on("mouseout", function(node) { outCircle(node); }); // Update the text to reflect whether node has children or not. node.select('text') .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) .text(function(d) { return d.name; }); // Change the circle fill depending on whether it has children and is collapsed node.select("circle.nodeCircle") .attr("r", 4.5) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); // Transition nodes to their new position. var nodeUpdate = node.transition() .duration(duration) .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); // Fade the text in nodeUpdate.select("text") .style("fill-opacity", 1); // Transition exiting nodes to the parent's new position. var nodeExit = node.exit().transition() .duration(duration) .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) .remove(); nodeExit.select("circle") .attr("r", 0); nodeExit.select("text") .style("fill-opacity", 0); // Update the links… var link = svgGroup.selectAll("path.link") .data(links, function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. link.enter().insert("path", "g") .attr("class", "link") .attr("d", function(d) { var o = { x: source.x0, y: source.y0 }; return diagonal({ source: o, target: o }); }); // Transition links to their new position. link.transition() .duration(duration) .attr("d", diagonal); // Transition exiting nodes to the parent's new position. link.exit().transition() .duration(duration) .attr("d", function(d) { var o = { x: source.x, y: source.y }; return diagonal({ source: o, target: o }); }) .remove(); // Stash the old positions for transition. nodes.forEach(function(d) { d.x0 = d.x; d.y0 = d.y; }); } // Append a group which holds all nodes and which the zoom Listener can act upon. var svgGroup = baseSvg.append("g"); // Define the root root = treeData; root.x0 = viewerHeight / 2; root.y0 = 0; // Layout the tree initially and center on the root node. update(root); centerNode(root); }); |
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 |
.node { cursor: pointer; } .overlay{ background-color:#EEE; } .node circle { fill: #fff; stroke: steelblue; stroke-width: 1.5px; } .node text { font-size:10px; font-family:sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 1.5px; } .templink { fill: none; stroke: red; stroke-width: 3px; } .ghostCircle.show{ display:block; } .ghostCircle, .activeDrag .ghostCircle{ display: none; } |
Edit 14/10/2015
such a great work @@
i was looking for the same and i redirect here. but i have to implement collapsible feature inside bounded force layout graph. i’ve tried so much but couldn’t integrate collapsible feature inside force directed graph. please help me or guide me how to do that.. also i posted this issue on stackoverflow .. have a look on this
http://stackoverflow.com/questions/20857298/how-to-merge-collapsible-feature-in-bounded-force-directed-graph
Thanks 🙂
Hi Jay,
I’ve had a look and with a few small tweaks of your JSON and the Javascript code from the example at http://bl.ocks.org/mbostock/1062288 it works Here is my answer on Stack Overflow I also have also posted a fiddle at http://jsfiddle.net/robschmuecker/AA7LZ/ for you to see it in action. Thanks for stopping by 😉
Hey Rob,
Amazing example you posted there. Your demo with 50,000 nodes is particular impressive.
Is there a way to get the flare.json file back with the modified data from the drag and drop?
Looking forward to your response. Thanks mate.
Paul
Hi Paul,
I’m glad you found the example useful! Thanks for looking too! In terms of getting the flare_json back out once it’s been changed it would be a case of walking the “treeData” object and just sanitizing it to remove any of the coordinate values which get appended by D3.js and then downloading that (although not sure how easy that is without posting it back to the server to force a download). To see what I mean you can add a
console.log(treeData)
call as the first line of theupdate
function and inspect the tree whenever you make any changes. It is a reflection of the tree as it currently stands. What I personally do on each “action” is send an ajax request to the server which then updates it’s copy and if there’s an error I revert back to how the tree was before the “action”. That way, with all being well the tree on in the browser and the tree on the server are the same and you can initiate a download from the server. Alternatively, if you didn’t want to save any of the data on the server but would rather the user be able to keep a “copy” of their changes you would post back the “sanitized” version of thetreeData
object to your server and serve it as a JSON file for the user to download. With such a method you could also allow users to “upload/load” a hierarchical structure which the server would then send to the browser and the user can edit at will before downloading a ‘modified’ copy.Thanks for all the hints on how to do this.
For anyone who is still struggling, I uploaded a post of stack overflow, which explains how to do it step by step – http://stackoverflow.com/questions/20534883/d3-update-data-behind-the-visualization/22369507#22369507.
I’m a newbie here but thanks for a truly fantastic demo especially the feature where you can modify the tree with drag’n’drop of nodes. I’m in a (non-profit) DNA-project where this could be very useful to visualize haplogroups in a tree, see example: http://www.viitasaari-dna.org/haplotree/isogg.html. The ultimate thing at this stage would be to get a modified tree back to the console as “sanitized ” JSON (as you describe above). I looked into and tried the other example referred to by Paul Knittel but it didn’t work for me with your original drag’n’drop tree. You don’t happen to have a working example of that?
Hello Jari,
I don’t have an example of that . . . or at least I didn’t!
You simply need to make a recursive function which strips out (or only keeps) certain properties of each node.
e.g.
function returnSanitized(node){
var sanNode = {};
sanNode.name = node.name;
sanNode.children = [];
if(node.children){
node.children.forEach(function(child, index, array){
sanNode.children.push(returnSanitized(child));
});
}
return sanNode;
}
Then you can call this at any point passing in the root node. Then you could stringify it to JSON to get your JSON string if needed. e.g.:
sanitizedNodes = returnSanitized(root);
console.log(sanitizedNodes);
console.log(JSON.stringify(sanitizedNodes));
A demo can be found here: http://jsfiddle.net/robschmuecker/6xrhewq1/1/
This is crazy, thanks a bunch! You have free accommodation, food and drinks whenever you come to Halmstad, Sweden! 🙂
Hi Jari,
My pleasure, glad it worked. Halmstad here I come! 🙂
OH, I missed your first reply…….
I get the wanted output in the console by adding the call: console.log(JSON.stringify(root)) under Update; but it only works when the page is loaded. When I drag’n’drop the nodes I get the error: “Uncaught TypeError: Converting circular structure to JSON “. Any idea or simple solution?
Hi Jari,
Not sure which browser you are using but it works fine for me on Chrome when dragging to rearrange the structure. As mentioned I did that fix/solution for you earlier on-the-fly so haven’t tested it across browsers. Possibly something simple to do with the way some browsers are less tolerant?! Have you googled the error message?
R.
Hi Rob,
Thanks a lot for this demo, We are trying to update the graph very often, on click of a button, we are creating a JSON and displaying its required graph. But the code breaks on IE9. What happens is, when we click a button, we can see the graph being created initially, but if we click the button again to create another tree, we do not see any change reflected and the page appears blank. Essentially If I clear the cache and reload the page, the updated diagram appears, while this is not the case with Firefox and Chrome. Is it the D3.js Incompatibility or something in general with regards to JSON Parsing and Caching with regards to IE9.
Hi Bhavin,
I will take a look at the issue and get back to you soon.
Thanks,
R.
Hi Bhavin,
I have had a look at this and seem to be getting inconsistent viewing/errors between viewing it on this site and on the http://bl.ocks.org/robschmuecker/7880033 version. Also I’m currently running IE11 with a “compat mode” of IE9 which is not 100% fool-proof! Perhaps you can make a bare-bones http://jsfiddle.net/ which we could try and debug to isolate your IE9 issues?
HI,
Do you know how add label to the edges of this tree ?
I searched in Google and found some results from Stackoverflow, without any success?
Hi,
I’m not quite sure I understand what you mean. However to add labels outside of the tree you can get the calculations from within the code of the coordinates of the tree and it’s nodes and place text labels wherever you like. You can see what I mean in the following lines lifted from the node-specific label generation part of the code.
nodeEnter.append("text")
.attr("x", function (d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
})
However you would append the text to the SVG at the appropriate coords.
Hi Rob
The example you posted here is awesome.
I want to read data from xml file or otherwise use a local javascript object to read the data.
Can you suggest some simple way to achieve this?
Hi Sum,
Have a look at this, I think this answers your question hopefully.
http://stackoverflow.com/questions/10682856/how-to-import-xml-data-using-d3-js
Hi Rob
I really love this project.
I would like to ask you for advice of how to best add new nodes to the tree.
Ideally I’d like a plus button somewhere which creates a free-standing node, then be able to drag this new node to someplace on the tree.
Then on the drag end “action” I will send an ajax request to the server which makes it’s own updates to the underlying data model as you suggested in the comment response above.
I am open to a different implementation, the essential point being that I need to be able to add new nodes to the tree which are unknown at the time of loading.
Here is a simplified version of your example:
http://codepen.io/anon/pen/Eclkm/
Also, it would be great if we could set all of the nodes to be collapsed by default.
Thanks in advance.
Hi Tom,
Apologies for the delay! What you are trying to do is all possible, without doing all the code myself and thinking aloud you’re going to want to :
I use the ajax update on node drop already in several places successfully.
As for giving you a bit of a hint with adding, check out this example. http://bl.ocks.org/mbostock/929623
If you need more help just give a shout!
Regarding starting all the nodes off collapsed you can simply add this code before the last two lines of the inital JS
// Collapse all children of roots children before rendering.
root.children.forEach(function(child){
collapse(child);
});
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
Hope all that makes sense. Good luck 😉
R.
Thanks Rob, this is very helpful.
I appreciate you taking the time.
I’ll give it a go!
Hi,
Awesome work ! Both of your exemples are amazing.
I just had 2 questions :
– Tell me if I’m wrong, but you had to code the hierarchy of your code, is there a way to obtain it from a database ? And is it possible to communicate with the database quickly enough to see the results when someone dropped a node ?
– What are the node sizes ?
Thanks. I’m really new to JS and even more to d3 but I’ll be really glad to understand it better.
Hey buddy,
You can either add the node hierarchy manually from the client-side such as an upload. Alternatively you may want to start with a fixed number of nodes and allow the “client” to reposition them. But yes in previous projects I build the hierarchy directly from DB and echo it out as JSON for D3.js to use. How you end up doing that depends on your database structure. You may well need to loop/recurse through all levels to get all the children of parents etc, starting with a “root” level or you can use a “nested set” database model which may speed up the processing. I have used this successfully on trees with about 5K nodes but as it is on data which is not nested set, the recursion does take several seconds to build the JSON hierarchy before the page loads.
To answer the second part of your first question, my experience on this is based on an “Admin user” being logged in and being the only person manipulating the backend data. If you have a multi-user environment what I am about to say below will not apply as you will need to have a polling/push setup to keep both the “client” and “backend” in sync.
I successfully update the underlying database structure via ajax calls in a very speedy manner. Time, however isn’t such a big factor since as we’re letting the ajax calls happen in the background, the user can carry on. As long as the ajax call(s) returns successfully, the “client-side” representation is already in sync (as it was actually “ahead”) with the DB, however if there has been an error for whatever reason I notify the user and the safest thing is to reload the page/JSON data to get a true reflection of the state of the DB hierarchy again.
Hope that helps, any issue, give us a shout.
😉
R.
Hi Rob,
I am working on D3 graph http://jsbin.com/adUnIwuH/8/edit …
I just had one question
how can i change the color of each circle?
Can you suggest a way to achieve this?
Hi Henna,
You are already filling the circle green with these lines of your code:
// Add circle
node.append("circle")
.attr('r', 5)
.attr("fill", 'green');
So you can specify which colour you like to “Fill” in either hexadecimal or string format. Alternatively since you are giving your nodes a class of “node” you can specify this via CSS like below:
.node circle {
fill: red;
}
This is demonstrated in the link here http://jsbin.com/adUnIwuH/23/edit
Hope that helps,
😉
Hi Rob,
What I want to achieve is to draw a collapsable tree with data from mysql database .I have seen example
where json file is use to get data , Please help me how to draw collapsable tree & we take data from database table
Hi Suraj,
This depends on your backend which is interfacing with your database. You can make your JSON structure at the backend and then render the JSON in the page before instantiating the tree which references the JSON.
R.
Thks for your reply Rob , but I’m not able to get it working .As you have mention in Reply to ReddPool on 17 april 2014 that in your previous projects you build the hierarchy directly from DB and echo it out as JSON for D3.js to use.I am trying to achieve the same from last 2 days but not able to do it.
Can to plz help me with how to build the hierarchy directly from DB for collapsible tree and echo it out as JSON for D3.js and how to use it in D3.js .I have not done this before that why asking you all this things in details .Hope you understand.
Thanks in advance .
Hi Suraj, You will have to give more information as to the structure of your database, the type of database, the backend language you are using etc. As otherwise I cannot help.
Thanks,
R.
Hi Rob,
I am using PHP and mysql .Database structure anything which is suitable for d3.js colapsible tree.Example Database Table. id(auto increment), name, parent . (or even u can change structure if u want to )
I have to create var treeData array as in Fiddle ( http://jsfiddle.net/draut/nCttr/3/ ).
I have to create this structure(dynamically) in php by calling values of Database.
You can also provide help on stackoverflow
http://stackoverflow.com/questions/23514763/convert-flat-json-to-tree-view-like-json/23515077? noredirect=1#comment36069211_23515077
Any help is appreciable .Thanks…
Hi Suraj,
I’ve previously also had to do this for a flat data-structure. I used a recursive PHP function. Here is what I had to do in PHP: (note code not tested)
// MySQL connection defined elsewhere as $con and assuming your table is called Categories
function getCategories($name = null) {
if(is_null($name)){
//get all top-level parents
$sqlString = "SELECT * FROM Categories WHERE parent IS NULL";
} else {
$sqlString = "SELECT * FROM Categories WHERE parent = '$name'";
}
// Initialise empty categories array;
$allCats = array();
$result = mysqli_query($con, $sqlString);
while ($row = mysqli_fetch_array($result)) {
$cat['name'] = $row['name'];
$cat['parent'] = $row['parent'];
// Fetch any children it may have
$childCats = getCategories($row['name']);
if (count($childCats) > 0) {
$childCat['children'] = $childCats;
}
$cat['children'][] = $childCat;
$allCats['children'][] = $cat;
}
return $allCats;
}
$allCats['name'] = "All Categories";
$allCats['parent'] = null;
$allCats['children'] = getCategories();
echo json_encode($allCats);
Thanks Rob for your time.I am trying to implement with your code.Definately let u know after successfully execution.
Really cool what you have done here!!! 🙂
I am trying implement it locally without a server where the data is hard coded in the code itself. I am not able to achieve it, I am guessing, due to the usage of node.js . Is there a way to achieve the rendering of the tree without a server? Can the node.js references be substituted with equivalent ones?
Thanking you in advance.
Arun
Hi Arun,
That’s no problem whatsoever. Just make sure to do all your processing on document ready. Here below I leverage the jQuery ready function on the document.
$(document).ready(function(){
yourJSON = {
"name": "flare",
"children": [{
"name": "analytics",
"children": [{
"name": "cluster",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "CommunityStructure",
"size": 3812
}, {
"name": "HierarchicalCluster",
"size": 6714
}, {
"name": "MergeEdge",
"size": 743
}]
}, {
"name": "graph",
"children": [{
"name": "BetweennessCentrality",
"size": 3534
}, {
"name": "LinkDistance",
"size": 5731
}, {
"name": "MaxFlowMinCut",
"size": 7840
}, {
"name": "ShortestPaths",
"size": 5914
}, {
"name": "SpanningTree",
"size": 3416
}]
}]
}]
};
// Now instead of
// treeJSON = d3.json("flare.json", function(error, treeData) {...
// You can do this
treeData = yourJSON; // and the rest will work as normal.
...
)}; // this is the ending brackets of the jQuery ready function but you can use the ones from the d3.json method.
That works great!!! Thanks a lot.
I tried it but I still get a blank page.. Here is my code:
$(document).ready(function(){
json = { here is my complete json.flare content
};
treeData = json;
“your complete javascript code” without treeJSON = d3.json(“flare.json”, function(error, treeData) {…
I got no error on the console.
Here is my index.jade file:
extends ../layouts/home
head
meta(http-equiv=’encoding’, content=’text/html’, charset=’UTF-8′)
block content
script(type=’text/javascript’ src=’js/d3.min.js’)
script(type=’text/javascript’ src=’http://code.jquery.com/jquery-1.10.2.min.js’)
script(type=’text/javascript’ src=’js/dndTree.js’)
link(rel=’stylesheet’, href=’stylesheets/tree.css’)
could you please give us a hint on how one could show in abeautiful and clear way the size of each node in the diagram? Seems all this info now is not shown, right? For example we could increase the circle’s node size? or use a color climax? (cold colors small values. warm colors higher values?)
thank you
Hi Perok,
Many thanks for stopping by. Adding the additional information is all very possible in a myriad of ways. The easiest (from a coding perspective) would be to add the additional node data to the “text” element added to each node. However you can certainly get more creative with the display of the data. Have a look at these two examples: http://www.brightpointinc.com/interactive/budget/index.html?source=d3js and http://bl.ocks.org/csarsurvey/8999077 (which is a direct fork of the code illustrated here).
Hi, Rob:
First of all, I would like to thank you for your example.
Next, a question: what is your code licence?
Regards,
Roberto.
Hi Roberto,
Whilst I haven’t specifically stated any particular license. All code here is to be considered the same as the D3.js library itself, BSD. Basically, you can do what you want with my code, but I would appreciate attribution 😉
Many thanks,
R.
Hi Rob,
This work is really awesome. As I’m trying to implement it I’d like to know how to disable the zoom on mouse wheel. How can I do it?
Regards,
Gehiks
Hi Gehiks, Essentially you would be disabling the zooming feature apart from double-click to zoom in and shit+double-click to zoom out. Is this what you want? If so you can look at adding
if(d3.event.sourceEvent.type == 'wheel'){
like thisd3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
return;
}
// Define the zoom function for the zoomable tree
to the zoom function however it seems to have a memory of sorts and when you click it will zoom to where the mouse “had” scrolled.function zoom() {
if(d3.event.sourceEvent.type == 'wheel'){
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
return;
}
svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
I will look into this further and report back.
Thanks,
R.
Hi Rob,
Your work is really good !
It is perfect to represent the organization of the society in which I work.
I just have a little question: is it possible to obtain a vertical tree ?
It would be really great!
Thank you in advance.
Regards,
Guillaume.
Bonjour Guillame!
Yes it is possible to create a vertical tree of course. In it’s easiest fashion, you can just reverse the x and y in ‘var = diagonal…’ so instead of:-
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
You would change it to:-
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x, d.y];
});
This same needs to be done in the ‘nodeEnter, nodeUpdate, nodeExit, rag, updateTempConnector’ functions for example instead of:-
var nodeEnter = node.enter().append("g")
.call(dragListener)
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")"; // <-- You will change this <-- }) .on('click', click);
You would put
var nodeEnter = node.enter().append("g")
.call(dragListener)
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")"; // <-- To this <-- }) .on('click', click);
This will be sufficient but then you will need to play with the other variables in the script to change the spacing for the nodes which is currently found here
var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
which you can change to perhaps be a function of maxLabelLength.I will see if I can find the time to post an example of a vertical tree.
Bonne journée
I just want to remove “size” attribute from JSON file, what changes i need to do in code?
Hi Swapnil,
The size has effect in this example, I just used the flare.json which comes as part of download for D3.js as an example. The code exhibited here can consume any JSON that has the structure with ‘name’ and ‘children’.
Thanks,
R.
Thanks Rob.
Hi,
I want to do something similar to the tree diagram above. However my problem is that I have a child node which takes relationship from two parental nodes.
For example: A-> B, C-> D and B&D -> E (Both B and D have a common child D) How can I fix this.
Thank you very much for helping me out.
Br,
Raja
Hi Raja,
Apologies, this is not something that I have had the need to do and I don’t think will work with this code without some significant modification as what would a node do when it is dragged/dropped? Also the JSON structure does not naturally allow for a multiple parent scenario out-of-the-box. Perhaps we can splice out the drag/drop functionality totally and just keep the zoomable/pannable functionality. I would suggest that you make a standard tree and then load a separate JSON which defines the additional multiple parents for a particular node. Then iterate through this and add new “links”.
Hope that helps,
R.
Ok thank you Rob,
Br,
Raja
Hi
Can you point any sources, how to have multiple json files for one visualization in d3?
Thank you,
Br,
Raja
Hi Raja,
Here is a brief example without an additional JSON file but that is easily added via either jQuery or d3.json methods.
https://gist.github.com/robschmuecker/6afc2ecb05b191359862
You can see that I have only coupled 1 set of nodes but you can couple as many as you like! You will have to handle the enter/exit of nodes somehow too.
Thanks,
R.
Hi Rob,
Thanx for the quick reply.. can you give a working example for the same
As per the previous comments i understood that
First i should draw the tree without cross connections
then i should have seperate json file which will have cross connections
am i correct in understanding it … if so how should be my second json file look like… i saw your couplings example in github but can i have a working example for the same
if my understanding is wrong can you explain in detail. Thank you very much
Here is the example working http://bl.ocks.org/robschmuecker/6afc2ecb05b191359862
You can see the format of the second “couplings.json” file in the source code there. You will have to work-out a few simple technicalities to get it working properly. If you’re struggling you can always get in touch and I can look at giving you more specific help for a fee.
Thanks,
R.
Hi rob,
I saw this on D3s main website in example section and found this very useful but I have an issue.
Every time the tree is loaded , it shows the entire tree which if is huge covers the entire page and it becomes difficult to than close each node one by one, I want to manually open nodes and edges which I want.
Can you suggest what changes I have to make in the code??
Hi Tapan,
Please have a look at another example I made which incorporates 50,000 nodes.
The gist is here https://gist.github.com/robschmuecker/7926762 with a working demo to be found here http://bl.ocks.org/robschmuecker/7926762.
Here all nodes are collapsed apart from the immediate children of the root node and I believe the only modification I made to the code was calling the below method before displaying the tree by calling the initial
update(root)
function at the bottom of the javascript.// Collapse all children of roots children before rendering.
root.children.forEach(function(child){
collapse(child);
});
Hope that helps,
Thanks,
R.
Hi, thanks for this great example!
I’m using your code as a reference for something I’m building. I was wondering though — when you drag, you remove the children nodes and links. However if that node wasn’t originally collapsed, it would expand again when it’s connected to another node. Where in your code does that happen? My code so far would remove the children nodes and links but then after I drop the node, it stays collapsed.
Also, would it possible to drag a node somewhere and then having the node stay at the dropped spot and the links extended to the node?
Thanks again.
Hi Jessie,
If I get you correct, you want any node to become and stay collapsed when you drop on another node, even if it wasn’t collapsed when you started dragging it?
Then you would modify the
endDrag
function accordingly:function endDrag() {
selectedNode = null;
d3.selectAll('.ghostCircle').attr('class', 'ghostCircle');
d3.select(domNode).attr('class', 'node');
// now restore the mouseover event or we won't be able to drag a 2nd time
d3.select(domNode).select('.ghostCircle').attr('pointer-events', '');
updateTempConnector();
if (draggingNode !== null) {
collapse(draggingNode); //<--- collapse the node in question at all times when dropped. update(root); centerNode(draggingNode); draggingNode = null; } }
As for your next question, it is possible to drag and then leave the node where you dropped it. This would be a bit more customisation than I can warrant doing right now. Please feel free to get in touch via my Contact page and we could discuss what your exact requirements are and whether I can help you on a consultancy basis.
Thanks,
R.
Rob – this is stunningly good. I just wanted to say I appreciate you posting the demo and code and explanation tremendously – I learn best from good working examples, and that is this. The people like yourself who take the time and effort to share are my personal heroes. Just wanted to say thank you – so, Thank you!
Wow thanks Tom!
Really appreciate your kind words! Makes my day 🙂
Thanks for stopping by.
R.
Hi Rob,
Like everybody else, I’m super thankful that you posted this online. I couldn’t have found something more fitting for what I want to do than this.
I was wondering if it would be possible to replace the diagonal with a polyline, or a line with multiple points. Basically, I am going for less of a curvy link between nodes, and more of an angled off, straight-edged kind of link. Does that make sense? If you have an idea of how you’d approach this, I’d love to hear it, thanks so much again!
Hi Virginia,
You need to substitute the call to diagonal with another function. In this example http://bl.ocks.org/mbostock/2966094 the function called is “elbow”.
You could use the below code and substitute that for each call to diagonal. This is always when adding the ‘d’ attribute e.g.
.attr("d", straightLines);
function straightLines(d, i){
return "M" + d.source.y + "," + d.source.x
+ "L" + d.target.y + "," + d.target.x;
}
Have a look at an incomplete demo here http://jsfiddle.net/robschmuecker/3HL4a/287/
Hope that helps,
Thanks,
R.
Hi Rob,
Thanks for this example, the code, the tuto. That’s great especially for a beginner! I like the tree representation and I used it for our current project. The idea is to map legal and institutional frameworks and it works well. This is my first D3 visualization and I like it. I have two questions though: how can I change the size of the link between nodes? And the second one is: would it be possible to add a search function?
Thanks.
Mélanie
Hi Mélanie,
To change the link size you can either make them vertically wider by changing the value here:
var newHeight = d3.max(levelWidth) * 50; // 50 pixels per line
and you can make them horizontally wider/longer by changing the values below:
// Set widths between levels based on maxLabelLength.
nodes.forEach(function (d) {
d.y = (d.depth * (maxLabelLength * 40)); //maxLabelLength * 40px
// alternatively to keep a fixed scale one can set a fixed depth per level
// Normalize for fixed-depth by commenting out below line
// d.y = (d.depth * 500); //500px per level.
});
Both of these changes have been implemented as an example here http://jsfiddle.net/5nHGz/2/
As for the search function I have indeed done this for a client previously and could certainly help you with such. Feel free to get in touch if you want to discuss the options.
Thanks,
R.
Hi Rob,
Thanks for your quick answer and sorry in the late reply!
Very nice work!
Is it possible to load the json at on click event based on node ? (aka lazy loading?)
in other words, intercept the click event, display some data, update the child nodes (server side json with only 1 level children) and then collapse the branch ?
Vlad
Hi Vlad, yes I’m sure this is possible too. Whilst I haven’t actually done this before I see no issues in doing so if you can handle the loading time in a smart manner so that the user can understand that they are waiting for results.
Let us know if you get it working as I’m sure the community would appreciate it.
Thanks,
R.
Hi Rob,
What I want to achieve is to diff the ghostCircle where mouseover event happened. for example,make the ghostCircle have a green color which indicates that it might be a proper “drop” action.
Is it possible ? thanks.
Hi, I’m not sure I completely understand. Do you want to change the colour of all
ghostCircle
‘s (you can do this in the CSS) or just the one being hovered on which thetempConnector
line is connected to?just the one being hovered on which the tempConnector line is connected to. ———–yes ,this’s what i want.
—————————–
and i have another question: is there a good way to avoid overlapping of nodes ? when i change the size of each circle (no more the default 4.5 , but between 5 to 15 randomly), nodes overlapped. So i change the code in function “update” as following :
var newHeight = d3.max(levelWidth) * 35 ; // default 25 pixels per line
is there a better advise ? 🙂
OK – to change the colour of the one being hovered upon, we’d edit the node creation a little. Note that the below code can be improved by changing the z-index properties of the “hovered” node but I’ll leave that as a little exercise for you 🙂
/////
// phantom node to give us mouseover in a radius around it
nodeEnter.append("circle")
.attr('class', 'ghostCircle')
.attr("r", 30)
.attr("opacity", 0.2) // change this to zero to hide the target area
.style("fill", "red")
.attr('pointer-events', 'mouseover')
.on("mouseover", function (node) {
overCircle(node, this);; // <-- added 'this' as a reference }) .on("mouseout", function (node) { outCircle(node, this); // <-- added 'this' as a reference }); /// // Then amend the functions below to do as you please with the "ghostCircle" var overCircle = function (d, that) { selectedNode = d; d3.select(that).style("fill", "green"); updateTempConnector(); }; var outCircle = function (d, that) { d3.select(that).style("fill", "red"); selectedNode = null; updateTempConnector(); };
As for increasing the
lineHeight
, yes that is your best bet. For example here http://jsfiddle.net/robschmuecker/3Uw2L/1/much appreciated !!! 🙂
If you are going for most excellent contents like myself, only visit this
site all the time since it gives quality
contents, thanks
My site google
You are amazing..! You stop by everyone’s question and answered everyone..! Hats off bro.! I appreciate it..! Thanks for the help you did to all..! 🙂
Thanks Pradeep! It’s my pleasure.
Hi Rob,
First of all, congrats for the superb work and support.
I was wondering about mobile browsers behaviour of the drag&drop functionality. I have tested it on chrome browser in both samsung tablet, mobiles and other android devices and it seems to be a very elusive thing to do for the user and sometimes it simply does not work.
Is this something to do with D3js and mobile browsers (I am new to D3js) or is there any workaround / fix to achieve drag&drop in mobile devices?
Thanks in advance,
quique
Hi Queque,
There are a couple of reasons for this one is the events of
mousover
andmouseout
aren’t implemented withtouch
in the way that one might assume. http://www.html5rocks.com/en/mobile/touchandmouse/ The demo I have posted is not touch friendly and I will look into making a touch-friendly one some time. Also you must check that yourviewport
doesn’t zoom http://stackoverflow.com/questions/4389932/how-do-you-disable-viewport-zooming-on-mobile-safari http://stackoverflow.com/questions/11345896/full-webpage-and-disabled-zoom-viewport-meta-tag-for-all-mobile-browsersHope that clears a few things up.
R.
Dear Rob Schmuecker!
Thanks for your posts. I have a question: in case of thousand of nodes, query and build json data are not good for perfomance. How to lazy load the children when the parent node clicked?
This would need a form of loading indicator to notify the user that some action is taking place (such as an AJAX call to fetch the additional nodes) and then when you have got the data you need to add them into the “tree” and re-parse everything to make sure that the “links” get associated by re-calling the “links” method e.g. “links = tree.links(nodes);” etc and the rest should be pretty much the same. Hope that helps, it’s quite a brief overview but the principle’s are there!
Thanks,
R.
Hi Rob!
I’m modifying your code, i need to custom the tree, tree with text, image inside the rectangular nodes and direction is top-bottom.
I have tried to modify some codes, but running not fine. Could you help me out?
Thanks a lot!
Hi beckham, I sent you an email about this earlier, I can certainly help you but this is a quite customised request and will take some time. Please see your email. Thanks, R.
dear Rob,
my question also similar, how can make top bottom nodes and mouseover for image and css contents.
Hi,
A very good piece of work.Is there anyway through which I can get a list of all nodes in your tree? tree.nodes(root) function returns only those nodes which are not the descendants of a collapsed node, however what I require is a list of all nodes irrespective of whether any of there ancestor is collapsed or not…
Well you have
treeData
which is the variable exposed by the initial loading offlare.json
in the calltreeJSON = d3.json("flare.json", function(error, treeData) {...
which is an object containing all your initial nodes. That should be what you are after. Thanks. R.Hi Rob,
I want to change the color and the width of the link dynamic based on the no of visits to the path and the useful status. For example if my paths a->b->c->d has 3000 visits and useful 1, whereas a->b->e-f has 200 visits and useful 0, where should i make the change in the code to reflect the effect whenever a node is selected
Also,
In one of your previous comments you mentioned about
http://www.brightpointinc.com/interactive/budget/index.html?source=d3js
Can you also share the code for the same.
Look at the page source!
If you are not able to understand the code to such an extent then it’s probably not helping you or me if I tell/show you how to do it?!
I am not here to do your work for you!1
Hi Rob,
Can we change the orientation of the flow from left to right to right to left. Root opens the nodes on the left then on right
Yes, just flip the x and y throughout the code.
Hi
thanks for your post, I’d like to say that I am going to save the changes when I drag and drop some tree items and I’d like to edit the layout by using drag and drop option and then save new layout, can you please tell me how I can do that ?
thanks.
Hi Mojtaba, there are various comments which I have made regarding this. Please take a look through the comments. Many thanks,
R.
Thank you so much for the response.
I have another question. I’d like all the nodes to be closed when I click on the root node.
for example I have a root node with 10 children and each child has 5 children. assume that all children are open and when I click on the root and then reopen it, I want all 5 children of to be closed and only the first level 10 children be shown. How can I do that ?
thanks in advance.
Hi Mojtaba,
Have a look at the “expand” and “collapse” functions and how you would copy and amend them to suit your needs e.g. compare the node to root and only then collapse the children of the children e.g. making a function called collapseRootChildren. This can then be called by “collapse” when you collapse the root node only.
Hope that helps,
R.
Hello Rob,
Awesome, fantastic … great stuff. Thanks for sharing. You answered so many comments… great attitude. Not sure if you are still following/answering the comments, if so would mind to have a look into few things I would like to tweak. (Sorry I am pretty much beginner in d3)
1. To put hyper-links ( <a href..) on each node text, which part of your code needs to be updated?
2. For long text (to set as text or hyper-link text) how to wrap or make fit in multiple lines?
Finally, not really d3 stuff, more of creating the hierarchical data structure. I am using a function that returns the following when I pass id 1234:
{ id: "1234", value : 34, children : [ { id: "3456", value: 56} , { id: "9870", value: 43} ] }
now I need some kind of recursion so that when I pass the "id" of "children" it returns "children" and then the json gets updated. As an example if I pass id "3456":
First it returns : { id: "3456", value: 56, children : [ { id : "8899", value : 45} ] }
And then the root document gets updated like:
{ id: "1234", value : 34, children : [ { id: "3456", value: 56, children : [ { id : "8899", value : 45} ] } , { id: "9870", value: 43} ] } ..note how the root json gets updated with children of "3456". So this is basically recursively getting "children" and updating the original json document, am trying to solve it in ruby.
Any help would be greatly appreciated! By the way, just curious to know, have you any interest in freelancing work?
Thanks,
Rashed
Hi Rashed,
Thanks for commenting. In regards to your recursion function, it’s a little bit ambiguous as to where you want this done. I’m assuming you want to do this serverside and hence mention using Ruby. I unfortunately am not too familiar with Ruby but I would just write a normal recursive function similar to this:-
function getChildren(parent){
children = null;
retChildren = new Array(); // the array returned containing children if any.
// do something like query your database to get children here
children = //something returning either children or 0/null here e.g.
SELECT * FROM nodes WHERE parent_id = parent['id']
or similarforeach(children as child){
tempChild = new Array();
tempChild['value'] = child['value'];
tempChild['id'] = child['id'];
tempChild['children'] = getChildren(child); // here is where we recurse down into the children's children and so-on.
retChildren[] = tempChild;
}
return retChildren;
}
Then you would call that passing your root node as the
parent
.Yes I am always interested in Freelance work!
Hope it helps,
Thanks,
R.
Hello Rob,
Thanks a lot for your reply. Yeah it’s something to be done in server side. I really appreciate your idea, will try and get back to you.
Am trying to tweak your d3 code to :
1. Include hyper-links on node text
2. Wrap long text fit in either multiple lines
Any guide line how to achieve the above mentioned features? Do I need to use something called “foreign object”? Grateful if you could comment.
Good to know your interest. Will get back to you soon!
Cheers,
-Rashed
Hi Rashed,
Foreign object does work however is not supported by most if not all versions of Internet Explorer 🙁
As for hyperlinks you can just “emulate” them by styling them correctly with font-color, text-decoration:underline; and pointer styling to make them look like links and then handle the opening of the link on the ‘click’ listener such as this which needs a “link” property in the JSON object for the given node…
function click(d) {
if (d3.event.defaultPrevented) return; // click suppressed
if(!d.children && !d._children){
window.open(d.link,'_blank');
return;
}
d = toggleChildren(d);
update(d);
centerNode(d);
}
Hi Rob,
Amazing work. Thank you very much.
I havent started to change your code to my needs just yet, beacuse of business rules I need to imnplement.
I need a tree that spreads left and right of the root node. Something like your example with a mirror in the middle :). Can it be done? If it can than this is a solution that I can start to implement.
I have seen the radial solution: http://jsfiddle.net/Nivaldo/CbGh2/, but it is not good for me because the nodes run wild when expanded. I want them to behave like they do in your example. I have a hunch that if I want to implement this i’ll need to change the D3.js file?
Once again, thank you for all the hard work you have done.
Hi,
Found the solution on : http://bl.ocks.org/jdarling/2073ba4c2d640236e2e2.
Now all I need to do is apply it on your tree view becuase of look and feel.
I made some changes to the layout of the nodes, because I want particular nodes left and right.
I added level attribute to JSON top nodes and depending on that showing them left or right instead of a mod keyword.
for (; i < l; i++) {
if (json.children[i]["level"] == "0") {
json.left.push(json.children[i]);
json.children[i].position = 'left';
} else {
json.right.push(json.children[i]);
json.children[i].position = 'right';
}
}
D.G.
Hi again! I use “centerNode((root),zoomListener.scale(0.25));” to set the zoom level depending of the extent of branches in different haplotrees we have, refer to: http://www.viitasaari-dna.org/haplotree/
But how to zoom into an arbitrary node? I tried to to use the id or the name instead of the ‘root’ above but I can’t get it to work!
I haven’t seen you around in Halmstad yet!! The food and drinks are still here! 😉
Bes regards
Jari
Hi Rob, Great example. I was going through your snippet. http://jsfiddle.net/robschmuecker/GFe96/3/ from http://stackoverflow.com/questions/24857126/how-to-make-drag-and-drop-of-nodes-work-inside-a-radial-reingold-tilford-tree-in
I noticed that when we zoom into the tree, the tree diagram is shifted to 0,0 to the top left. Could you please explain how do we circumvent the problem?
Hi Vams,
This is not a straight-forward problem I think. You will need to debug this by checking the coordinates sent to the zoom event (also in relation to the root node coords) and then modifying accordingly. I am guessing that due to the way that the radial tree works the coordinates are offset somewhat than in a linear tree.
Thanks,
R.
Hi Rob,
That’s an awesome visualization of d3 you have made. I want to take this one step ahead i.e. I want to display a tool-tip containing the data of the nodes on mouseover.
Please help me with that?
Thanks in advance.
Hello Amit,
I would suggest that you listen to “mouse-over” and then show a tooltip accordingly at the x,y coordinates by either implementing something like the jQueryUI library or Bootstrap. The requirement is quite broad and as such I cannot give more comment unfortunately!
Thanks,
R.
Amit, here’s an easy example for you to display node name (d.name) and id (d.id) in a tooltip: http://jsfiddle.net/reblace/6FkBd/2/
You can also check out: http://haplotree.info/playground/n/
Best regards
Jari
PS. Thanks to Rob for for valuable information and support!
Hi Jari,
Many thanks for opening and sharing your example with Amit and the rest of us!
Thanks,
R.
Hello Rob,
thank you for sharing your implementation of tree view. It already helped me a lot.
Also thank you Jari for opening and sharing your example of tooltip. I also need to have the tooltip implemented in the tree view.
I copied the functions mouseover(d) and mouseout(d) from your example to Rob tree view code. But it is still not showing the tooltip when mouseover the node. Do you have any tips where should I add them and if there is anything else I should do?
Thanks for any help.
Flavia
I just found my mistake. Thank you both for sharing your solutions.
BR
Flavia
hi…
i would like to thank u very mush
but i have two question
1-how to change circle size??
2-how change the point(x,y)to root node??
thank u very very very very much…..
Hello Rasha,
To answer point 1. You would amend the “r” attribute of the circle
e.g.
nodeEnter.append("circle")
.attr('class', 'ghostCircle')
.attr("r", 30)
I unfortunately don’t understand what you mean by point 2. 🙁
Thanks,
R.
hi how are u
i would like to ask question
can we do son nodes appear in circle ship around father node like sun brights with all the features of this model or it is impossible???
please help me ….. i am waiting u
This is possible to an extent, you will need to use a radial projection such as this http://bl.ocks.org/mbostock/4063550
also have a look here for many awesome examples https://github.com/mbostock/d3/wiki/Gallery
Hi, I sent you an email about a question I have. Also,following is my second question:
in “flare.json” what does:
“”size”: 3938″ means or does.
I changed its value but nothing seems to happen visually.
Thanks
Hi John,
That size property is just in the sample flare.json used as an example JSON in many d3.js examples and has no bearing in the example I have posted.
Thanks,
R.
dear mr.rob,
your tree is fantastic!! and thanks for your good work efforts!!
can you please make a hoover / mouseover for the nodes detail…
Hi,
I have a structure where a node is child to more than one parent. Example: A->B; A->C->B. But this is not working in this case. Can it be handled?
Hello Garima, Please have a look at this solution posted here http://bl.ocks.org/robschmuecker/6afc2ecb05b191359862 in response to a previous comment.
Hey there Rob! I’ve been working on an implementation of this treeview using json that is sent by a server and then consumed by us. It’s pretty much up and running at this point but I’m still having trouble when I click to hide a node’s child nodes. All of my nodes and links get bundled up in that one node I have clicked. Any idea what might be happening in this case? Really great stuff by the way and thank you beforehand with any help you can provide me with!
Hi Randy,
Not sure what could be the issue, could you perhaps post some code or a demo on jsfiddle?
Thanks,
R.
Hi Rob!
Thanks very much, this is a great post. How can i print the total tree into A0 paper? Thank you!
Hi Tony,
Glad you enjoy the post, as for printing onto A0, this is not too easily feasible straight-off. You can always contact me directly and we can discuss some of the possibilities. Thanks,
R.
This is tremendous work Rob I have to admit! There’s one lingering question I have after going through your code and the different comments on this thread: do you think there would be a way to reverse the tree to JSON after you’ve modified it? For my purposes, I see value in being able to output the newly-modified tree to JSON and comparing it to the original input. Do you have any insight on that?
Hello Laurent,
If you look into the comments I did make a function that walks back through the tree to output new JSON. Have a look here http://www.robschmuecker.com/d3-js-drag-and-drop-zoomable-tree/comment-page-1/#comment-30799
Thanks,
R.
Thanks very much Rob – that worked brilliantly!
Hi Rob!
The nodes order in my json data is A-B-C-D-E, but when show on the tree, the order did not like that, it becomes E-C-D-A-B, how can i fix it? Thank you!
Hi Mike,
In the example, if you were to look through the code you’ll find a function called “sortTree” which gets called on the tree. You can amend or omit that to your desire.
Thanks,
R.
It works fine, thanks Rob! 🙂
Rob, this is awesome. Great job!
Can’t wait to play around with this.
Astounding code and example! Congratulations!
One question: How can I structure the JSON code to differentiate between nodes that are categories and nodes that point to a webpage (the end of the tree would be links to web pages), so I can style them properly… I’m trying with no success…
thanks in advance!
🙂
Hi Flavio,
This should be simple to check if the node has “children” and if it doesn’t it is in hour case a weblink and the user must navigate to the intended link. SO you wouldn’t need to change the JSON per se apart from add a weblink for the end nodes and then listen to the “click” on the nodes/labels and differentiate between the two types of click. Email me if you want more assistance.
Hi Rob, Thanks for the information… reading other posts in this page I found a way to do that… done!
Thanks again!