I'm trying to create a labelled double tree using d3js tree layout. Functionality is working fine so far , but the problem I face is , i'm unable to place the label in the middle of the link.
Right now it appears like this ( once you have added enough nodes )
This is the part of the code where I add the edge and label inside a g element. I'm trying add the label in the midpoint of the link , but it's not working. ( the code shown is for the right side of the double tree )
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 25);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
Here is the working code so far -
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
svg.append("g").attr("id", "right-edges")
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.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")
.attr("class", function(d) {
console.log(d);
if (d.parent == "null") {
if (d.children)
{
return "node rightparent collapsed" //since its root its parent is null
} else {
return "node rightparent"
}
} else {
if (d.children) {
return "node rightchild collapsed" //all nodes with parent will have this class
} else {
return "node rightchild" //all nodes with parent will have this class
}
}
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
addRightChild.append("rect")
.attr("x", "90")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addRightChild.append("line")
.attr("x1", 95)
.attr("y1", 1)
.attr("x2", 105)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addRightChild.append("line")
.attr("x1", 100)
.attr("y1", -4)
.attr("x2", 100)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
// adding the right chevron
var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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("text")
.style("fill-opacity", 1e-6);
// Update the links…
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 25);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// d3.selectAll(".right-tree-container .remove-child").remove();
// var removeButton = edge.selectAll(".remove-child")
// .data(links, function(d) {
// return d.target.id + d.source.id;
// });
// removeButton.enter().insert("rect", "edge-container")
// .attr("class", "remove-child")
// .attr("x", function(d) {
// var x = (d.source.y + d.target.y) / 2
// return parseInt(x + 45);
// })
// .attr("y", function(d) {
// var y = (d.source.x + d.target.x) / 2
// return y;
// })
// .attr("width","20")
// .attr("height","20")
// .attr("rx","10")
// .attr("ry","10")
// .style("fill","white")
// .style("stroke","#444")
// .style("stroke-width","2");
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to the child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addRightChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
svg.append("g").attr("id", "left-edges")
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (d.depth * 180);
});
// Update the nodes…
var node = svg.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")
.attr("class", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
var edge = d3.select("#left-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().append("text")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 45);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
var link = edge.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Update the links…
// Enter any new links at the parent's previous position.
link.enter().insert("path", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to teh child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
z-index: 100;
}
.asset-title {
padding: 15px;
float: left;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
.left-chevron,
.right-chevron {
display: none;
}
.collapsed .left-chevron,
.collapsed .right-chevron {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
I've added to your code. Below is the result. The main part was this :
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2;
d.thisText = "test-label";
return x - d.thisText.length/2 + 5; //added 5 to make up for node width. This needs to be worked out properly
})
It's not exactly center as I haven't implemented the different between the center of the node and the label. But changing the above piece of code will get the result you want :) Hope this helps.
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
svg.append("g").attr("id", "right-edges")
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.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")
.attr("class", function(d) {
console.log(d);
if (d.parent == "null") {
if (d.children)
{
return "node rightparent collapsed" //since its root its parent is null
} else {
return "node rightparent"
}
} else {
if (d.children) {
return "node rightchild collapsed" //all nodes with parent will have this class
} else {
return "node rightchild" //all nodes with parent will have this class
}
}
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? 0 : 0;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
addRightChild.append("rect")
.attr("x", "90")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addRightChild.append("line")
.attr("x1", 95)
.attr("y1", 1)
.attr("x2", 105)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addRightChild.append("line")
.attr("x1", 100)
.attr("y1", -4)
.attr("x2", 100)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
// adding the right chevron
var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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("text")
.style("fill-opacity", 1e-6);
// Update the links…
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.target.y + d.source.y) / 2
return x - 50;
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// d3.selectAll(".right-tree-container .remove-child").remove();
// var removeButton = edge.selectAll(".remove-child")
// .data(links, function(d) {
// return d.target.id + d.source.id;
// });
// removeButton.enter().insert("rect", "edge-container")
// .attr("class", "remove-child")
// .attr("x", function(d) {
// var x = (d.source.y + d.target.y) / 2
// return parseInt(x + 45);
// })
// .attr("y", function(d) {
// var y = (d.source.x + d.target.x) / 2
// return y;
// })
// .attr("width","20")
// .attr("height","20")
// .attr("rx","10")
// .attr("ry","10")
// .style("fill","white")
// .style("stroke","#444")
// .style("stroke-width","2");
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to the child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addRightChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
svg.append("g").attr("id", "left-edges")
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (d.depth * 180);
});
// Update the nodes…
var node = svg.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")
.attr("class", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.attr("y", "-10")
.attr("height", 20)
.attr("width", 20)
.attr("rx", 10)
.attr("ry", 10)
.style("stroke", "#444")
.style("stroke-width", "2")
.style("fill", "#ccc");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
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", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
var edge = d3.select("#left-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().append("text")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2;
d.thisText = "test-label";
return x - d.thisText.length/2 + 5;
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text(function(d){return d.thisText})
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
var link = edge.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Update the links…
// Enter any new links at the parent's previous position.
link.enter().insert("path", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//this is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach(function(child) {
if (child.id != target.id) {
//add to teh child list if target id is not same
//so that the node target is removed.
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
z-index: 100;
}
.asset-title {
padding: 15px;
float: left;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
.left-chevron,
.right-chevron {
display: none;
}
.collapsed .left-chevron,
.collapsed .right-chevron {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
You would need to do the same with the Y. But instead of taking away the textLength/2, just take away the textHeight/2 if you want it exactly central
Having an atlas force graph with setup as follow, I would like to zoom in and out on mouse wheel events from anywhere in the drawing area but nodes (circles) in order to allow dragging individual nodes.
var svg = graph.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("circle")
.attr("class", "circle");
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
The issue I have with this code is that clicking on a node and dragging it drags the whole graph, whereas when removing the call(... redraw) part it would let me drag individual nodes.
Is there a way to mix both behaviors and either prevent zooming when pointer is inside a node, or have node event prevalent on global (svg) events?
<!DOCTYPE html>
<html>
<head>
<title>Fidlde</title>
<script type="text/javascript" src="d3-master/d3.v3.min.js"></script>
<style>
.circle {
fill: #F5F5F5;
stroke: #999999;
stroke-width: 3;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.link {
stroke: #999999;
stroke-opacity: .6;
stroke-width: 3;
}
</style>
</head>
<body>
<div id="graph">Hello!</div>
<script>
// graph size
var width = 400;
var height = 400;
var nodes = [{name: 'A'}, {name: 'B'}, {name: 'C'}, {name: 'D'}];
var edges = [{source: 'A', target: 'B'}, {source: 'B', target: 'C'}, {source: 'C', target: 'A'}, {source: 'C', target: 'D'}];
var nodeMap = {};
nodes.forEach(function(x) { nodeMap[x.name] = x; });
var links = edges.map(function(x) {
return { source: nodeMap[x.source], target: nodeMap[x.target], value: 1 };
});
var graph = d3.select("#graph");
var svg = graph.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
var force = d3.layout.force()
.gravity(.25)
.distance(140)
.charge(-3500)
.size([width, height]);
/* Issue was here, the following code addresses it.
Thanks to Lars and Cool Blue - see comments
var drag = force.drag()
.on("dragstart", dragstart);
*/
var stdDragStart = force.drag().on("dragstart.force");
force.drag()
.on("dragstart", function(d){
//prevent dragging on the nodes from dragging the canvas
d3.event.sourceEvent.stopPropagation();
stdDragStart.call(this, d);
});
force
.nodes(nodes)
.links(links)
.friction(0.8)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("circle")
.attr("class", "circle")
.attr("r", 10);
node.append("text")
.attr("dx", -4)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
// redraw after zooming
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
function dblclick(d) {
d3.select(this).classed("fixed", d.fixed = false);
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
</script>
</body>
</html>
In oder to address the remaining dragging node issue, I made the following changes in the code:
node.enter()
.append("svg:g")
.attr("pointer-events", "all")
.attr("id", function(d) { return '_'+d.name })
.attr("class", "node")
.on("click", nodeClick)
.on("dblclick", nodeDoubleClick)
.on("mouseover", nodeMouseOver)
.on("mouseout", nodeMouseOut)
.call(force.drag);
function nodeClick(d) {
// fix the current node to its position
d.fixed = true;
}
function nodeDoubleClick(d) {
// release the current node
d.fixed = false;
}
function nodeMouseOver(d) {
// move the current node to front - some nodes are overlapping each others
var sel = d3.select(this);
sel.moveToFront();
// stop the whole graph
force.stop();
}
function nodeMouseOut(d) {
// resume node motion
force.start();
}
I also removed the following dragstart function which remained from previous code and was probably called while zooming.
/* function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
*/
Everything is now properly working. Thank you all for your contributions.
try this snippet of code :) also works
var width=600;
var height=600;
var nodes=[{
"name":"n1"
},{
"name":"n2"
},{
"name":"n3"
},{
"name":"n4"
},{
"name":"n5"
}];
var links=[{"source":0,"target":1},
{"source":0,"target":2},
{"source":0,"target":3},
{"source":1,"target":4},
{"source":2,"target":4},
{"source":3,"target":2}];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("transform","translate(200,200)");
svg.append("rect")
.attr("width",width)
.attr("height",height)
.attr("fill","none")
.attr("pointer-events","all")
.call(d3.behavior.zoom().on("zoom", redraw));;
var force=d3.layout.force().charge(-400).linkDistance(200);
force.nodes(nodes).links(links).start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
var node = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class","circle")
.call(force.drag);
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});