I use d3.tree().nodeSize() to Calculate the coordinates,but I see the cousin's distance is twice that of the brothers.see the Image
I want the distance to be consistent.I did not see how to solve in the api,How to deal with it?
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(40,200)");
var tree = d3.tree()
.nodeSize([50,50]);//make node space
var data={
id:1,
children:[
{
id:2,
children:[
{
id:3
},
{
id:4
}
]
},
{
id:5,
children:[
{id:6},
{id:7}
]
}
]
};
var root=d3.hierarchy(data)
tree(root)
//console.log(root)
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal).style("fill","none").style("stroke-width",1).style("stroke","#000000");
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });// add node
node.append("circle")
.attr("r", 2.5);
node.append("text")
.attr("dy", 3)
.attr("x", function(d) { return d.children ? -8 : 8; })
.style("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.id; });
function diagonal(d) {
return "M" + d.y + "," + d.x
+ "L" + d.parent.y + "," + d.parent.x;
}
<svg width="960" height="2400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
recently I see tree.separation to deal this problem
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(40,200)");
var tree = d3.tree()
.nodeSize([50,50])//make node space
.separation(function(a,b){
return a.parrent==b.parrent?1:1
});// make separation accessor 1
var data={
id:1,
children:[
{
id:2,
children:[
{
id:3
},
{
id:4
}
]
},
{
id:5,
children:[
{id:6},
{id:7}
]
}
]
};
var root=d3.hierarchy(data)
tree(root)
//console.log(root)
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal).style("fill","none").style("stroke-width",1).style("stroke","#000000");
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });// add node
node.append("circle")
.attr("r", 2.5);
node.append("text")
.attr("dy", 3)
.attr("x", function(d) { return d.children ? -8 : 8; })
.style("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.id; });
function diagonal(d) {
return "M" + d.y + "," + d.x
+ "L" + d.parent.y + "," + d.parent.x;
}
<svg width="960" height="2400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
return a.parrent==b.parrent?1:1
doesn't do anything.
You should use return a.parrent==b.parrent?1:2 or just return 1
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 have prototype code I am working with here:
jsfiddle
The example shows how to add a segment when new data is added but not how to remove it again when the data changes [back]. I am fairly new to d3 and don't quite get the exit() function yet...
if you reverse the initial and second dataset you will see that the grapes segment is never removed. Thanks in advance!
any help would be great!
The update code: (my final chart needs to update on a timer when data changes)
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { return this.current = d; });
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) { return "translate(" + (arc.centroid(d)) +
")"; })
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) { return 1e-6; }
else { return 1; }
})
.text(function(d) { return d.data.label; });
};
To remove elements without data, you have to use exit(), which...
Returns the exit selection: existing DOM elements in the selection for which no new datum was found.
So, inside your updateChart function, you need an exit selection for both paths and texts:
var newArcs = arcs.data(donut(dataset));
newArcs.exit().remove();
var newSlices = sliceLabel.data(donut(dataset));
newSlices.exit().remove();
Here is your updated code:
// Setup all the constants
var duration = 500;
var width = 500
var height = 300
var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9);
var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"];
// Test Data
var d2 = [{
label: 'apples',
value: 20
}, {
label: 'oranges',
value: 50
}, {
label: 'pears',
value: 100
}];
var d1 = [{
label: 'apples',
value: 100
}, {
label: 'oranges',
value: 20
}, {
label: 'pears',
value: 20
}, {
label: 'grapes',
value: 20
}];
// Set the initial data
var data = d1
var updateChart = function(dataset) {
var newArcs = arcs.data(donut(dataset));
newArcs.exit().remove();
newArcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
newArcs.transition()
.duration(duration)
.attrTween("d", arcTween);
var newSlices = sliceLabel.data(donut(dataset));
newSlices.exit().remove();
newSlices.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
});
sliceLabel.data(donut(dataset)).enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
};
var color = d3.scale.category20();
var donut = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.each(function(d) {
return this.current = d;
});
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
// Update the data
setInterval(function(model) {
return updateChart(d2);
}, 2000);
// Tween Function
var arcTween = function(a) {
var i = d3.interpolate(this.current, a);
this.current = i(0);
return function(t) {
return arc(i(t));
};
};
.arcLabel {
font: 10px sans-serif;
fill: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I am trying to use General Pattern to update my Pack Layout but have not been successful. Below is my fiddle. So when Randomize Data is clicked the data should be updated.
Here is my full fiddle.
function update() {
root = d3.hierarchy(data)
.sum(function(d) {
return d.size;
})
.sort(function(a, b) {
return b.value - a.value;
});
var node = g.selectAll(".node")
.data(pack(root).descendants())
.enter().append("g")
.attr("class", function(d) {
return d.children ? "node" : "leaf node";
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.data.name + "\n" + format(d.value);
});
node.append("circle")
.attr("r", function(d) {
return d.r;
});
node.filter(function(d) {
return !d.children;
}).append("text")
.attr("dy", "0.3em")
.text(function(d) {
return d.data.name.substring(0, d.r / 3);
});
}
Your code only have the "enter" selection. You're missing an "update" selection (and eventually an "exit" selection).
The "update" selection can be something like this:
var nodeUpdate = g.selectAll(".node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});;
var circleUpdate = nodeUpdate.select("circle")
.attr("r", function(d) {
return d.r;
});
Check the fiddle: https://jsfiddle.net/or7pLnjp/
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?