As shown in the diagram, I am trying to increase the gap between last nodes on either side of the tree layout as they are overlapping
Is there any way to do it in D3?
{
"name": "",
"type": "network",
"children": [{
"name": "",
"type": "lb",
"children": [{
"name": "",
"type": "mm",
"id": "app",
"connServer": "s",
"size": 3938
}]
},
{
"name": "",
"type": "vm",
"children": [{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "appServer",
"id": "app1",
"connServer": "db1",
"size": 3938
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "webServer",
"id": "web1",
"connServer": "app1",
"size": 3534
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "dataServer",
"id": "db1",
"connServer": "app1",
"size": 7074
}]
}]
},
{
"name": "",
"type": "vm",
"children": [{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "webServer",
"id": "web2",
"connServer": "app1",
"size": 721
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "dataServer",
"id": "db2",
"connServer": "db1",
"size": 721
}]
}]
},
{
"name": "",
"type": "vm",
"children": [{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "appServer",
"id": "app2",
"connServer": "db3",
"size": 721
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "webServer",
"id": "web3",
"connServer": "app2",
"size": 721
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "dataServer",
"id": "db3",
"connServer": "app2",
"size": 721
}]
}]
},
{
"name": "",
"type": "vm",
"children": [{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "appServer",
"id": "app3",
"connServer": "db4",
"size": 721
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "webServer",
"id": "web4",
"connServer": "app3",
"size": 721
}]
},
{
"name": "",
"type": "container",
"children": [{
"name": "",
"type": "dataServer",
"id": "db4",
"connServer": "app3",
"size": 721
}]
}]
},{
"name": "",
"type": "sto",
"children": [{
"name": "",
"type": "mm",
"id": "app",
"connServer": "s",
"size": 3938
}]
}]
}
Here is the code ,i am using for building the tree layout,when i use separate() the child nodes of tree layout are not aligned together in space,they stick to their places,without separate method,collpased child node will be wrapped together/aligned in space /** * */ var tooltipMap = d3.map();
// Get JSON data
treeJSON = d3.json("network.json", function(error, treeData) {
// Calculate total nodes, max label length
var totalNodes = 0;
var maxLabelLength = 15;
// 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 docWidth = $(document).width();
var viewerWidth = docWidth / (1.361);
var docHeight = $(document).height();
var halfHeight = docHeight / 2;
var quarterHeight = docHeight / 4;
viewerHeight = halfHeight + quarterHeight;
var tree = d3.layout.tree().size([ viewerHeight, viewerWidth ]);
/*
* var tree = d3.layout.tree().separation(function(a, b) { return ((a.parent ==
* root) && (b.parent == root)) ? 3 : 1; }).size([ viewerHeight, viewerWidth -
* 160 ]);
*/
// define a d3 diagonal projection for use by the node paths
// later on.
var diagonal = d3.svg.diagonal().projection(function(d) {
return [ d.x, -d.y ];
});
// 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 : d.children;
});
function getConServers(element, event, status) {
var conServerNode;
var targetElement = event.target;
var targetId = targetElement.id;
if (targetId != null) {
var server = tooltipMap.get(targetElement.id);
if (server != null) {
var connectedServer = server.get("connId");
var outerTarget = d3.select("#" + "outer" + targetId);
var outerCon = d3.select("#" + "outer" + connectedServer);
if (status == "enter") {
outerTarget.style("stroke", "#48C127");
outerTarget.style("stroke-width", 3);
outerCon.style("stroke", "#F07A0B");
outerCon.style("stroke-width", 3);
} else if (status == "exit") {
outerTarget.style("stroke", "#fff");
outerCon.style("stroke", "#fff");
outerTarget.style("stroke-width", 1);
outerCon.style("stroke-width", 1);
}
}
}
}
// 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").attr("id", "treesvg").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;
var mySvg = d3.select("#tree-container");
mySvg.select('g').transition().duration(duration).attr("transform",
"translate(" + (x - 230) + "," + (y + 411) + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([ x, y ]);
var shootX, shootY;
// shootX=x+270;
// shootY=y+350;
shootX = "850.242468772961", shootY = "450.75";
getShootImage(shootX, shootY);
}
/**
* This function is used to add shooting image and text into the frame.
*
* @param shootImgX
* @param shootTextX
* @return
*/
function getShootImage(shootImgX, shootImgY) {
d3.select("#shootCircle").remove();
var imgUrl = "image/target-icon.png";
baseSvg.append("image").attr("id", "shootCircle").attr("xlink:href", imgUrl).attr("x", shootImgX).attr("y",
shootImgY).attr("width", 50).attr("height", 50).style("fill", "red").classed("shoot", true);
baseSvg.append("text").text("Shooting Gun").attr("x", shootImgX).attr("y", shootImgY + 50).attr("width", 50)
.attr("height", 50).style("font-weight", "bold");
var loadButton = document.createElement("input");
loadButton.setAttribute("type", "button");
loadButton.setAttribute("value", "Add Load");
loadButton.setAttribute("class", "btn btn-cust-info");
loadButton.setAttribute("id", "addload");
loadButton.setAttribute("x", shootImgX);
loadButton.setAttribute("y", shootImgY);
loadButton.setAttribute("width", 500);
loadButton.setAttribute("height", 500);
loadButton.style.align = "left";
}
// 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(10);
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 * 7)); // 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.
/** My code -starts here * */
if (nodes[i] != null && nodes[i] != 'undefined') {
if (nodes[i].type == "appServer") {
var toolMap = d3.map();
toolMap.set("connId", d.connServer);
toolMap.set("id", d.id);
toolMap.set("type", "Web server");
tooltipMap.set(d.id, toolMap);
} else if (nodes[i].type == "webServer") {
var toolMap = d3.map();
toolMap.set("connId", d.connServer);
toolMap.set("id", d.id);
toolMap.set("type", "Web server");
tooltipMap.set(d.id, toolMap);
} else if (nodes[i].type == "dataServer") {
var toolMap = d3.map();
toolMap.set("connId", d.connServer);
toolMap.set("id", d.id);
toolMap.set("type", "Web server");
tooltipMap.set(d.id, toolMap);
}
}
/** My code ends here */
});
// 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);
// Adding the outer circle to highlight the connected servers
nodeEnter.append("circle").attr({
r : 15
}).attr("id", function(d, i) {
if (nodes[i].type == "appServer") {
return "outer" + nodes[i].id;
} else if (nodes[i].type == "webServer") {
return "outer" + nodes[i].id;
} else if (nodes[i].type == "dataServer") {
return "outer" + nodes[i].id;
}
}).style("fill", "transparent").style("stroke-width", function(d, i) {
if (nodes[i].type == "appServer") {
return "1";
} else if (nodes[i].type == "webServer") {
return "1";
} else if (nodes[i].type == "dataServer") {
return "1";
} else {
return "0";
}
}).style("stroke", "#fff");
nodeEnter.append("circle").attr("r", 5).attr("id", function(d, i) {
if (nodes[i].type == "appServer") {
return nodes[i].id;
} else if (nodes[i].type == "webServer") {
return nodes[i].id;
} else if (nodes[i].type == "dataServer") {
return nodes[i].id;
}
}).style("filter", function(d, i) {
if (nodes[i].type == "vm") {
return "url(#virtualMac)";
} else if (nodes[i].type == "container") {
return "url(#container)";
} else if (nodes[i].type == "appServer") {
return "url(#appserver)";
} else if (nodes[i].type == "webServer") {
return "url(#webserver)";
} else if (nodes[i].type == "sto") {
return "url(#storage)";
} else if (nodes[i].type == "dataServer") {
return "url(#dbserver)";
} else if (nodes[i].type == "network") {
return "url(#network)";
} else if (nodes[i].type == "lb") {
return "url(#loadbalancer)";
}
}).style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
}).on("mouseover", function(d) {
getConServers(this, d3.event, "enter");
}).on("mouseout", function(d) {
getConServers(this, d3.event, "exit");
});
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.x - 8) + "," + -d.y + ")";
});
// 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("stroke-dasharray", function(d) {
return (d.source.parent) ? "6,6" : "1,0";
}).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);
addLoadSLAButtons();
});
function addLoadSLAButtons() {
var slaDiv = document.createElement("div");
slaDiv.setAttribute("id", "slaDiv");
slaDiv.style.position = "relative";
slaDiv.style.left = "35px";
slaDiv.style.top = "-250px";
slaDiv.style.width = "100px";
var slaButton = document.createElement("input");
slaButton.setAttribute("type", "button");
slaButton.setAttribute("value", "View/Modify ServiceContract");
slaButton.setAttribute("class", "btn btn-info");
slaButton.setAttribute("id", "addSla");
slaButton.style.position = "absolute";
slaButton.setAttribute("onclick", "");// this will be modified once we
// receive clarification on the
// functionality.
document.getElementById("tree-container").appendChild(slaDiv);
document.getElementById("slaDiv").appendChild(slaButton);
var relDiv = document.createElement("div");
relDiv.setAttribute("id", "relDiv");
relDiv.style.position = "relative";
relDiv.style.left = "35px";
relDiv.style.top = "-190px";
relDiv.style.width = "100px";
var loadButton = document.createElement("input");
loadButton.setAttribute("type", "button");
loadButton.setAttribute("value", "Add Load");
loadButton.setAttribute("class", "btn btn-cust-info");
loadButton.setAttribute("id", "addload");
loadButton.style.position = "absolute";
loadButton.style.align = "left";
loadButton.setAttribute("onclick", ""); // reLoadPage()
document.getElementById("tree-container").appendChild(relDiv);
document.getElementById("relDiv").appendChild(loadButton);
}
If you add a child to the nodes that you need to move, and than make that child and its correspondent link invisible, that you will have the gap you need. However, I admit this solution is a little quick and dirty.
The problem here that position of the nodes are computed by so-called Reingold-Tilford algorithm automatically within d3.js, and it requires a lot of knowledge to modify the algorithm internally, or use some other algorithm. But some solutions with twaeaking like I described are possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With