I need to draw a tree with d3.js that has two "roots" and that is "force-directed" The json of this graph (very simple, it's just a trial) is:
{
"nodes":
[
{"name":"node1",
"children":[
{"name":"node2", "children":[{"name":"node3"}, {"name":"node4"}]},
{"name":"node5", "children":[{"name":"node6", "children":[{"name":"node8"}]}, {"name":"node7"}]}
]},
{"name":"node9",
"children":[
{"name":"node2", "children":[]}
]}
]
}
I tried to use the force layout, but it doesn't seem to work. So I thought that maybe I'm using the wrong layout for my goal, but I can't figure out which layout I should use.
My code is:
var width = 1200,
height = 800,
graph;
var force = d3.layout.force()
.linkDistance(100)
.charge(-120)
.gravity(.05)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("svg:defs").append("svg:marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 30)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("dirGraph.json", function(error, json) {
graph = json;
for(var i=0; i<graph.nodes.length; i++)
update(graph.nodes[i]);
})
function update(root) {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
force
.nodes(nodes)
.links(links)
.start();
node = node.data(nodes, function(d){return d.id;});
node.exit().remove();
var nodeEnter = node.enter().append("svg:g")
.attr("id", function(d){return d.id;})
.attr("class", "node")
.on("click", click)
.call(force.drag);
nodeEnter.append("circle").attr("r", 20);
nodeEnter.append("text").attr("dy", ".35em").text(function(d){return d.name;});
node.select("circle").style("fill", color);
link = link.data(links, function(d){return d.target.id;});
link.exit().remove();
link.enter().insert("line", ".node").attr("class", "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;})
.attr("marker-end", "url(#arrowhead)");
}
function tick() {
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
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 + ")";
});
}
function color(d) {
return d._children ? "#3182bd" // collapsed package
: "#c6dbef"; // expanded package
}
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if(node.children) node.children.forEach(recurse);
if(!node.id) node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
function click(d) {
if (d3.event.defaultPrevented) return; // ignore drag
if(d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
My question is: should I use a different layout or should I use the force layout in a different way?
Related
I have this code, and I have an algorithm to put the line between two nodes. I want this line to join the #nodo4 with the #nodo6, the rectangles are the nodes and each one has the same name as its id.
The code is a bit long but the important part to achieve this is here:
setTimeout(() => {
let source = d3.select("#node4");
let target = d3.select("#node6");
source.datum(source.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
target.datum(target.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
d3.select("#g_main").append("line")
.style("stroke", "black") // colour the line
.attr("x1", source.attr('nodeX')) // x position of the first end of the line
.attr("y1", source.attr('nodeY')) // y position of the first end of the line
.attr("x2", target.attr('nodeX')) // x position of the second end of the line
.attr("y2", target.attr('nodeY')); // y position of the second end of the line
}, 5000)
but I have problems getting the line to appear in the place where it should be and considering that I can constantly zoom and pan.
I want a dynamic solution because in the future I want to put a line that connects to other nodes and I would like to do this dynamic calculation.
what am I doing wrong?
var width = 960,
height = 800;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30;
var tree = d3.layout.tree().nodeSize([220, 40]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000).style("border", "1px solid red")
.call(zm = d3.behavior.zoom().scaleExtent([0.3, 3]).on("zoom", redraw)).append("g").attr("id", "g_main")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
var root = {
"name": "node6",
"children": [{
"name": "node5",
"respuesta": "SI",
"children": [{
"name": "node4",
"children": [{
"name": "node3"
}]
}]
}, {
"name": "node2",
"respuesta": "NO"
},
{
"name": "node1",
"respuesta": "SI"
}
]
}
root.x0 = 0;
root.y0 = height / 2;
root.children.forEach(collapse);
update(root);
root.x0 = 0;
root.y0 = height / 2;
setTimeout(() => {
let source = d3.select("#node4");
let target = d3.select("#node6");
source.datum(source.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
target.datum(target.node().getBoundingClientRect())
.attr('nodeX', d => d.x + d.width / 2)
.attr('nodeY', d => d.y + d.height / 2)
d3.select("#g_main").append("line")
.style("stroke", "black") // colour the line
.attr("x1", source.attr('nodeX')) // x position of the first end of the line
.attr("y1", source.attr('nodeY')) // y position of the first end of the line
.attr("x2", target.attr('nodeX')) // x position of the second end of the line
.attr("y2", target.attr('nodeY')); // y position of the second end of the line
}, 5000)
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
// d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
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 = 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", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
nodeEnter.append("rect")
.attr("id", function(d) {
return "node" + d.id
})
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "white")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("image").attr("href", "plus-flat.png").attr("width", (d) => {
let length = 0;
if (d.children) {
length = d.children.length;
} else {
length = 0;
}
if (d.name == "INICIO" && length == 0) {
return 0;
} else if (d.name == "INICIO" && length != 0) {
return 0;
} else {
return 25;
}
}).style("transform", "translate(65px, -10px)")
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
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.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.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")
.style("stroke", (d) => {
let respuesta = d.target.respuesta;
if (respuesta == "SI") {
return "#2a8841";
} else if (respuesta == "NO") {
return "#d44646";
} else {
return "#b7b7b7";
}
})
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.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();
link.append("text").text("otros")
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
<script src="https://d3js.org/d3.v3.js"></script>
<div id="body"></div>
result expected,something like this:
Don't override the datum of source and target. That means you will not be able to redraw the tree, because you override all the values you need for it.
Also, don't use getBoundingClientRect(). What if you zoom, or pan? It distorts and the line will never be at the correct place.
Instead, rely on the data you already gave the nodes, you have it already, and you can access it by calling .datum() with no arguments! Then, it's no problem to calculate the positions the nodes will have, and also no problem to add a line exactly where you want it, regardless of zooming or scrolling.
var width = 960,
height = 800;
var i = 0,
duration = 750,
rectW = 100,
rectH = 30;
var tree = d3.layout.tree().nodeSize([220, 40]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000).style("border", "1px solid red")
.call(zm = d3.behavior.zoom().scaleExtent([0.3, 3]).on("zoom", redraw)).append("g").attr("id", "g_main")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
var root = {
"name": "node6",
"children": [{
"name": "node5",
"respuesta": "SI",
"children": [{
"name": "node4",
"children": [{
"name": "node3"
}]
}]
}, {
"name": "node2",
"respuesta": "NO"
},
{
"name": "node1",
"respuesta": "SI"
}
]
}
root.x0 = 0;
root.y0 = height / 2;
root.children.forEach(collapse);
update(root);
root.x0 = 0;
root.y0 = height / 2;
setTimeout(() => {
let source = d3.select("#node4").datum();
let target = d3.select("#node6").datum();
d3.select("#g_main").append("line")
.style("stroke", "black") // colour the line
.attr("x1", source.x0 + rectW / 2) // x position of the first end of the line
.attr("y1", source.y0) // y position of the first end of the line
.attr("x2", target.x0 + rectW / 2) // x position of the second end of the line
.attr("y2", target.y0 + rectH); // y position of the second end of the line
}, 1000)
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
// d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
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 = 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", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
nodeEnter.append("rect")
.attr("id", function(d) {
return "node" + d.id
})
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "white")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("image").attr("href", "plus-flat.png").attr("width", (d) => {
let length = 0;
if (d.children) {
length = d.children.length;
} else {
length = 0;
}
if (d.name == "INICIO" && length == 0) {
return 0;
} else if (d.name == "INICIO" && length != 0) {
return 0;
} else {
return 25;
}
}).style("transform", "translate(65px, -10px)")
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
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.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.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")
.style("stroke", (d) => {
let respuesta = d.target.respuesta;
if (respuesta == "SI") {
return "#2a8841";
} else if (respuesta == "NO") {
return "#d44646";
} else {
return "#b7b7b7";
}
})
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.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();
link.append("text").text("otros")
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
<script src="https://d3js.org/d3.v3.js"></script>
<div id="body"></div>
I managed to get this code to work with some things I needed. It works on version 3 of d3.js.
I would like to migrate it to version 5.
I could do it in parts, such as
d3.layout.tree() => d3.tree()
but I don't know how to solve the rest of the problems, I look in the documentation and I don't understand how to make the changes.
This is my live code:
https://jsfiddle.net/z4fah7gm/
This is my code:
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var root = {
"name": "flare",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "HierarchicalCluster",
"size": 6714 }]
}
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
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 = 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", "node")
.attr("transform", function (d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
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.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.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("x", rectW / 2)
.attr("y", rectH / 2)
.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;
});
}
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
It is tedious to convert v3 to v4/5. Since you are stuck with the tree.nodes (root) problems, I tried to adjust the code to get it passed.
First, get the hierarchy structure from the root data.
var root = {
"name": "flare",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "HierarchicalCluster",
"size": 6714 }]
}
let rootInTree = d3.hierarchy(root, function(d){return d.children;})
rootInTree.x0 = 0;
rootInTree.y0 = height / 2;
Then in the update function, try to access nodes and links by using
function update(source) {
// Compute the new tree layout.
var treeData = tree(rootInTree);
var nodes = treeData.descendants(),// tree.nodes(root).reverse(),
links = treeData.descendants().slice(1);// tree.links(nodes);
// ... other things
}
...then will pass the tree.nodes(root) problem, and face the next one, probably with zoom.
See more examples about tree after v3.
I want to change the tree within the link bellow to v5 of d3js. Note that the current tree is in v3. I'm not familiar with d3js v5 :( I know that there are a lot of experts here.
I have changed the script version in this link codepen.io/augbog/pen/LEXZKK from v3 to v5. Namely, to be like this ,but it doesn't read the property 'tree' in this command "d3.layout.tree().nodeSize([70, 40]);"
This is the code that I'm using
https://codepen.io/augbog/pen/LEXZKK
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
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 = 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", "node")
.attr("transform", function (d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
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.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.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("x", rectW / 2)
.attr("y", rectH / 2)
.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;
});
}
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
There are several changes in d3v5 and your current version (d3v3). A lot of them are not backward compatible.
d3.layout.tree() is invalid since d3v4. Entire d3.layout.layoutName has been done away with. In d3v5, the correct way is to use d3.tree().nodeSize([w, h]).
For more information, you should see the official changelog for both v4 and v5. Also check and compare the API references for tree in d3v5 and d3v3.
I created tree layout as in JSFiddle example http://jsfiddle.net/oo66o0q0/15/.
Requirement is path should highlighted in red and with extra width when user click on node's right click menu "Highlight Route" option.
This is working in chrome correctly but in IE highlighted route color becomes black.
If I remove markers then it works in IE as well.
How to resolve this issue in IE but not removing markers?
function treeInitialize(graphData){
diagramLayout = d3.select("#diagramLayout")
.attr("id", "diagramLayout")//set id
.attr("width", width)//set width
.attr("height", height)//set height
.append("g")
.attr("transform", "translate(" + 20 + "," + 20 + ")")
markerRefx = 40;
var data2 = graphData.links.filter(function(l){
if(l.target == undefined && l.source == undefined){
return false;
}else{
return true;
}
});
data2.push(JSON.parse('{"target":"glossforArrow","source":""}'))
var treeData = d3.stratify().id(function(d){ return d.target; }).parentId(function(d) {
return d.source;
})(data2)
nodes = d3.hierarchy(treeData, function(d) {return d.children;});
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, nodes);
newHeight = d3.max(levelWidth) * 100;
var tree = d3.tree().size([height, width])
tree.size([newHeight,height/2]);
tree.separation(function (a, b) {
return a.parent == b.parent ? 50 : 100;
});
nodes = tree(nodes);
treeLayout(nodes);
function treeLayout(nodes){
var node = diagramLayout.selectAll(".node");
node = node.data(nodes.descendants());
var link = diagramLayout.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", "1px")
.attr("stroke-opacity", "0.3")
.attr("d",connector)
nodes.descendants().slice(1).forEach(function(d) {
var mark = diagramLayout.append("svg:defs").selectAll("marker")//
.data(["start"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", -markerRefx)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto")
.attr("stroke", "#000")
.attr("fill", "#000")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("stroke-width", "0.3px")
.attr("transform","rotate(180,5, 0)");
});
link.attr("marker-start", "url(#start)")
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("height", nodeHeight)
.attr("width", nodeWidth)
nodeEnter.attr("transform", function(d) {
return "translate(" + project(d.x, d.y) + ")";
}).on('contextmenu', menuCall);
var nodeIcon = nodeEnter.append("rect")
.attr("class", "rect")
.attr("x", -20)
.attr("y", -20)
.attr("rx", 10)
.attr("width", 40)
.attr("height", 40)
.attr("stroke-width", function(d) { return Math.sqrt(2); })
.attr("stroke-opacity", "0.3")
.attr("stroke", "#000" )
.attr("fill", "blue" )
//wrap(nodeText, 8)
}
}
function connector(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
}
function project(x, y) {
return [x,y];
}
function menuCall(di,i) {
var nodeClicked = d3.select(this);
var menuitems = ["Highlight route"];
d3.selectAll('.context-menu').data([1])
.enter()
.append('div')
.attr('class', 'context-menu');
// close menu
d3.select('body').on('click.context-menu', function() {
d3.select('.context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
d3.selectAll('.context-menu')
.html('')
.append('ul')
.selectAll('li')
.data(menuitems).enter()
.append('li')
.on('click' , function(d) {
// d3.select('.context-menu').style('display', 'none');
if(d=="Highlight route"){
var id = nodeClicked.datum().data.id;
var node = diagramLayout.selectAll(".node");
var link = diagramLayout.selectAll(".link");
link.style("stroke","black").style("stroke-width", "1.5px")
var linkSelected = link.filter(function (d, i) {
console.log(d.data.id)
console.log(id)
return d.data.id === id;});
linkSelected.style("stroke", "red").style("stroke-width", "5px");
}
d3.select('.context-menu').style('display', 'none');
}).text(function(di) { return di; });
d3.select('.context-menu').style('display', 'none');
// show the context menu
d3.select('.context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
}
Looks like a bug in IE. If you inspect the DOM after you apply the highlight, IE reports that the inline style has overridden the sheet style (as it should), but the path does not update:
The only fix I can think us is to remove the sheet stroke style on class link
.link {
stroke-opacity: .6;
}
And just apply all the styles in-line.
Updated fiddle.
Here i'm working with sample D3 Tree chart in this jsfiddle, & in this tree chart, i need to include Html table inside all nodes including parent and child. So it may look like following:
Refer Sample Image
Same in the image above, i need table inside every node.
D3 JS code:
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("#body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1,3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
d3.select("#body").style("height", "800px");
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 = 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", "node")
.attr("transform", function (d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
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.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.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("x", rectW / 2)
.attr("y", rectH / 2)
.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;
});
}
// 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);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}