I'm trying to build a tree using d3js. I'm having a 2 sided tree with a root in the center. I am showing parents towards the right of the root and showing children towards the left of the root. here is my code.
var data = {
"name": "Root",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
"children": [{
"name": "3",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "4",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}],
"parent": [{
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}]
};
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.children))
};
// Right data
var data2 = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.parent))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
// draw single tree
function drawTree(root, pos) {
var refType;
if (pos == 'left')
refType = 'left';
else
refType = 'right';
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree()
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link")
.data(links)
.enter()
link.append("path")
.attr("class", "link")
.attr("d", function (d) {
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;
});
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function (d) {
return "translate(" +
((d.source.y + d.target.y) / 2) + "," +
((d.source.x + d.target.x) / 2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g.selectAll(".node")
.data(nodes)
.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 + ")";
})
node.append('circle')
.attr('class', 'icon-wrap')
.attr('x', 0)
.attr('y', 0)
.attr('r', 25)
.style('fill', 'black');
node.append('image')
.attr('href', d => d.data.img)
.attr('x', '-25')
.attr('y', '-25')
.attr('height', '50')
.attr('width', '50');
node.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text(d => d.data.name);
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="800" height="550"></svg>
Here my issue is that I'm unable to get the logo seen in the root node.
Thanks
Despite the img property existing in the data object, it is missing in the two objects you're actually passing to d3.hierarchy(), which are data1 and data2. Therefore, it should be:
var data1 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.children))
};
The same for data2. Here is your code with those 2 changes:
var data = {
"name": "Root",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
"children": [{
"name": "3",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "4",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}],
"parent": [{
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}]
};
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.children))
};
// Right data
var data2 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.parent))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
// draw single tree
function drawTree(root, pos) {
var refType;
if (pos == 'left')
refType = 'left';
else
refType = 'right';
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree()
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link")
.data(links)
.enter()
link.append("path")
.attr("class", "link")
.attr("d", function(d) {
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;
});
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.y + d.target.y) / 2) + "," +
((d.source.x + d.target.x) / 2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g.selectAll(".node")
.data(nodes)
.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 + ")";
})
node.append('circle')
.attr('class', 'icon-wrap')
.attr('x', 0)
.attr('y', 0)
.attr('r', 25)
.style('fill', 'black');
node.append('image')
.attr('href', d => d.data.img)
.attr('x', '-25')
.attr('y', '-25')
.attr('height', '50')
.attr('width', '50');
node.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text(d => d.data.name);
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="800" height="550"></svg>
Related
I am trying to implement a tree structure with different svgs drawn at different nodes.
Here is the fiddle - https://jsfiddle.net/L3j7voar/
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var emptyDecisionBox = {
"name": "newDecision",
"id": "newId",
"value": "notSure",
"condition": "true",
};
var selectedNode;
var root = {
"name": "Root",
"type": "decision",
"children": [{
"name": "analytics",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "true",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
{
"name": "division",
"type": "action",
"value": "a-b",
"children": [],
}
]
};
var i = 0,
duration = 750,
rectW = 80,
rectH = 40;
var tree = d3.layout.tree().nodeSize([120, 90]);
//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
var source = {
x: d.source.x,
y: d.source.y + (rectH / 2)
};
var target = {
x: d.target.x + (rectW / 2),
y: d.target.y
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
if (source.x < target.x) {
// Child is to the right of the parent
result += ' H' + (inflection.x - radius);
} else {
result += ' H' + (inflection.x + radius);
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
result += 'V' + target.y;
return result;
}
// END OF LINK FUNC //
// DRAW TREE //
var svg = d3.select(".tree-diagram").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;
update(root);
d3.select(".tree-diagram").style("height", "1000px");
// END OF DRAW TREEE //
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 * 90;
});
// 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('path')
.attr("d", function(d){
if(d.type==='decision'){
return drawDiamond(d);
} else{
return drawRect(d);
}
}).attr("stroke-width", 1)
.attr('class','myPaths')
.style("fill", function(d) {
return "lightsteelblue";
});
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 + ")";
});
// 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;
}).classed('link1',true) ;
// 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", linkFunc)
.on('click', function(d, i) {
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x;
var y;
if (d.source.x < d.target.x) {
// Child is to the right of the parent
x= bbox.x + bbox.width;
y= bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
} else {
x = bbox.x;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
}
})
.on('blur', function(d, i) {
plusButton
.classed('hide', true);
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// ON CLICK OF NODES
function click(d) {
console.log(d);
selectedNode = d;
var x = d.x;
var y = d.y + 40;
var m = d.x + 50;
var h = d.y + 20;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 60;
var h = d.y - 10;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y + 20;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y - 10;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
//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 + ")");
}
// oN CALL
function addElement(d) {
console.log(d);
d.children = [];
d.children.push(emptyDecisionBox);
update(root);
}
// draw elements //
function drawDiamond(centroid) {
// Start at the top
console.log(centroid);
console.log("rectH", rectH,rectW)
// Start at the top
var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);
// Go right
result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;
// Bottom
result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);
// Left
result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;
// Close the shape
result += 'Z';
return result;
}
function drawRect(centroid) {
// Start at the top left
console.log(centroid);
var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);
// Go right
result += 'h' + rectW;
// Go down
result += 'v' + rectH;
// Left
result += 'h-' + rectW;
// Close the shape
result += 'Z';
console.log(result);
return result;
}
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("CLICKED");
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
addElement(selectedNode);
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// draw elements end ..
As you can see, the svg's are not exactly where they are supposed to be , but quite a bit lower than the actual position than where the nodes are.
I am also trying to add child nodes:-
If you click on the rectangle node to the right of the root node, you will be able to see orange squares and diamonds around the node position. If you click on the right bottom diamond - it draws a diamond connecting to the above node, and the links also transition well, but the nodes and SVG's are stuck at the same position.
You're right, when you change the nodes to be drawn statically, their positions are fine indeed, and the text can be easily changed too. One difference, I'd draw the nodes starting at the center, not at the top left, because that's prettier with transformations. I also changed the orange shape position a little bit to center the blue node that was clicked.
One problem with adding nodes: you add the same object every time. See this toturial for an explanation. A workaround is to copy the object or create it new every time.
You also deleted all children of the node you clicked on, with d.children = [];. Changing that to only execute if d has no children fixed that.
Finally, I added a case to the linkFunc just draw a vertical line if one node is straight below the other.
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
function emptyDecisionBox() {
return {
"name": "newDecision",
"id": "newId",
"value": "notSure",
"condition": "true",
};
}
var selectedNode;
var root = {
"name": "Root",
"type": "decision",
"children": [{
"name": "analytics",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "true",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
{
"name": "division",
"type": "action",
"value": "a-b",
"children": [],
}
]
};
var i = 0,
duration = 750,
rectW = 80,
rectH = 40;
var tree = d3.layout.tree().nodeSize([120, 90]);
//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
var source = {
x: d.source.x - (rectW / 2),
y: d.source.y
};
var target = {
x: d.target.x,
y: d.target.y - (rectH / 2)
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
// If the source and target are on the same position, just draw a straight vertical line
if (source.x !== target.x) {
if (source.x < target.x) {
// Child is to the right of the parent
result += ' H' + (inflection.x - radius);
} else {
result += ' H' + (inflection.x + radius);
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
}
result += 'V' + target.y;
return result;
}
// DRAW TREE //
var svg = d3.select(".tree-diagram").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;
update(root);
d3.select(".tree-diagram").style("height", "1000px");
// UPDATE TREE
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 * 90;
});
// 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('path')
.attr("d", function(d) {
if (d.type === 'decision') {
return drawDiamond();
} else {
return drawRect();
}
}).attr("stroke-width", 1)
.attr('class', 'myPaths')
.style("fill", "lightsteelblue");
nodeEnter.append("text")
.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 + ")";
});
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.remove();
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
}).classed('link1', true);
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", linkFunc)
.on('click', function(d, i) {
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x;
var y;
if (d.source.x < d.target.x) {
// Child is to the right of the parent
x = bbox.x + bbox.width;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
} else {
x = bbox.x;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
}
})
.on('blur', function(d, i) {
plusButton
.classed('hide', true);
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// ON CLICK OF NODES
function click(d) {
console.log(d);
selectedNode = d;
var m = d.x + 40;
var h = d.y;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 40;
var h = d.y - 30;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 70;
var h = d.y;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 70;
var h = d.y - 30;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
//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 + ")");
}
// oN CALL
function addElement(d) {
console.log(d);
if(d.children === undefined) { d.children = []; }
d.children.push(emptyDecisionBox());
update(root);
}
// draw elements //
function drawDiamond() {
// Start at the top
var result = 'M0,' + (-rectH / 2);
// Go right
result += 'L' + (rectW / 2) + ',0';
// Bottom
result += 'L0,' + (rectH / 2);
// Left
result += 'L' + (-rectW / 2) + ',0';
// Close the shape
result += 'Z';
return result;
}
function drawRect() {
// Start at the top left
var result = 'M' + (-rectW / 2) + ',' + (-rectH / 2);
// Go right
result += 'h' + rectW;
// Go down
result += 'v' + rectH;
// Left
result += 'h-' + rectW;
// Close the shape
result += 'Z';
return result;
}
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("CLICKED");
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
addElement(selectedNode);
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// draw elements end ..
.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;
} */
.button>path {
stroke: blue;
stroke-width: 1.5;
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.hide {
/* display: none; */
opacity: 0 !important;
/* pointer-events: none; */
}
.link:hover {
cursor: pointer;
stroke-width: 8px;
}
.scale {
/* transform: scale(0.4); */
}
.colorBlue {
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
I'm working on a D3 visualization that shows the parent and child relationship.
I'm able to visualize the data perfect when I've less number of children, but when the number of children is more, the child nodes get overlapped. how can I modify my chart so that all the nodes towards the left of the root (right) are visible? like having the nodes in a circular fashion around the root, having some nodes near to the root, and some far in some order. What would bee the best way to show it?
Here is my code.
var data = {
"name": "Root",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
"children": [{
"name": "3",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "4",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}, {
"name": "2",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}],
"parent": [{
"name": "1",
"img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}]
};
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.children))
};
// Right data
var data2 = {
"name": data.name,
"img": data.img,
"children": JSON.parse(JSON.stringify(data.parent))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
// draw single tree
function drawTree(root, pos) {
var refType;
if (pos == 'left')
refType = 'left';
else
refType = 'right';
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree()
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link")
.data(links)
.enter()
link.append("path")
.attr("class", "link")
.attr("d", function(d) {
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;
});
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.y + d.target.y) / 2) + "," +
((d.source.x + d.target.x) / 2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g.selectAll(".node")
.data(nodes)
.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 + ")";
})
node.append('circle')
.attr('class', 'icon-wrap')
.attr('x', 0)
.attr('y', 0)
.attr('r', 25)
.style('fill', 'black');
node.append('image')
.attr('href', d => d.data.img)
.attr('x', '-25')
.attr('y', '-25')
.attr('height', '50')
.attr('width', '50');
node.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text(d => d.data.name);
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="700" height="400"></svg>
Here is how it looks with less number of children.
Please let me know how can I optimize my code to fit a maximum of 30 child nodes.
Thanks
A possible solution is to create a recursive function which will adjust your root data coordinates in your drawTree function.
Here is an recursive function which will stagger the nodes left and right. Note the comment where it is mentioned which part of the code controls the calculations of the coordinates.
function adjustClashes(data, siblings = 1, index = 1, radius = 20, height = 400) {
//can the node fit in the current x level?
// if not adjust it
let heightneeded = siblings * radius * 2;
if (heightneeded > height) {
// the code in this if statement will control the calculations for your new coordinates
// In the simplest case we adjust the nodes by staggering odd and even nodes
if (index % 2 != 0){
data.y = data.y + (radius * 2)
} else {
data.y = data.y - (radius * 2)
}
}
// if there are children go deeper and perform same adjustment
if (data.children) {
data.children.forEach((f, i) => {
return adjustClashes( f, data.children.length, i )
})
} else {
return;
}
// finally return the data
return data
}
Full snippet:
var data = {
name: "Root",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
children: [
{
name: "3",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "4",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
},
{
name: "2",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}
],
parent: [
{
name: "1",
img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
}
]
};
var bgColors = [
"#fd90b5",
"#6ca1e9",
"#fa975c",
"#eb7092",
"#f88962",
"#a094ed",
"#7f8de1"
];
var dr = 0;
// Left data
var data1 = {
name: data.name,
img: data.img,
children: JSON.parse(JSON.stringify(data.children))
};
// Right data
var data2 = {
name: data.name,
img: data.img,
children: JSON.parse(JSON.stringify(data.parent))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
// Render both trees
drawTree(right, "right");
drawTree(left, "left");
// draw single tree
function drawTree(root, pos) {
var refType;
if (pos == "left") refType = "left";
else refType = "right";
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
var tree = d3.tree().size([height, (SWITCH_CONST * (width - 150)) / 2]);
tree(root);
function adjustClashes(
data,
siblings = 1,
index = 1,
radius = 20,
height = 400
) {
//can the node fit in the current x level?
// if not adjust it
let heightneeded = siblings * radius * 2;
if (heightneeded > height) {
// the code in this if statement will control the calculations for your new coordinates
// In the simplest case we adjust the nodes by staggering odd and even nodes
if (index % 2 != 0) {
data.y = data.y + radius * 2;
} else {
data.y = data.y - radius * 2;
}
}
// if there are children go deeper and perform same adjustment
if (data.children) {
data.children.forEach((f, i) => {
return adjustClashes(f, data.children.length, i);
});
} else {
return;
}
// finally return the data
return data;
}
root = adjustClashes(root);
var nodes = root.descendants();
var links = root.links();
nodes[0].x = height / 2;
// Create links
var link = g.selectAll(".link").data(links).enter();
link
.append("path")
.attr("class", "link")
.attr("d", function (d) {
//first return returns a curve and the second will return straight lines in
//return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
return (
"M" +
d.target.y +
"," +
d.target.x +
"A" +
dr +
"," +
dr +
" 1,0 0 " +
d.source.y +
"," +
d.source.x
);
});
link
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function (d) {
return (
"translate(" +
(d.source.y + d.target.y) / 2 +
"," +
(d.source.x + d.target.x) / 2 +
")"
);
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.data(nodes)
.text(refType);
// Create nodes
var node = g
.selectAll(".node")
.data(nodes)
.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 + ")";
});
node
.append("circle")
.attr("class", "icon-wrap")
.attr("x", 0)
.attr("y", 0)
.attr("r", 25)
.style("fill", "black");
node
.append("image")
.attr("href", (d) => d.data.img)
.attr("x", "-25")
.attr("y", "-25")
.attr("height", "50")
.attr("width", "50");
node
.append("text")
.attr("dy", 45)
.style("text-anchor", "middle")
.text((d) => d.data.name);
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="700" height="400"></svg>
Update
The following CodePen.io pen shows for example a 3 level staggering of child nodes and even adds a slight margin to space it out more evenly.
To do this simply add margin as a default parameter to the adjustClashes function and change the if statement as follows:
if (heightneeded > height) {
// the code in this if statement will control the calculations for your new coordinates
// In the simplest case we adjust the nodes by staggering odd and even nodes
if (index % 3 == 0) {
data.y = data.y - radius * 2 - margin ;
} else if (index % 3 == 1) {
data.y = data.y;
} else {
data.y = data.y + radius * 2 + margin;
}
}
var treeData = [
{
"name": "glucose_tol",
"directions": ">",
"thresholds": "126",
"exits": 0.0,
"children": [
{
"name": "age",
"directions": ">",
"thresholds": "29",
"exits": 1.0,
"children": [
{
"name": true
},
{
"name": "mass_index",
"directions": ">",
"thresholds": "29.7",
"exits": 0.5,
"children": [
{
"name": true
},
{
"name": false
}
]
}
]
},
{
"name": false
}
]
},
];
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
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 = d.depth * 120; });
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
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", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
// Add threshold and directions
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.x + d.target.x)/2) + "," +
((d.source.y + d.target.y)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
//check whether thresholds is not undefined && that target.thresholds is not undefined as it will print on both sides
if (d.source.thresholds !== undefined)
if(d.target.thresholds !== undefined)
return d.source.thresholds + ' ' + d.source.directions;
})
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
I am having a problem adding text in middle of the links to a tree, because the last level of the tree doesn't give me any text. This is the code I have to add the text in the links:
// Add threshold and directions
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.x + d.target.x)/2) + "," +
((d.source.y + d.target.y)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
//check whether thresholds is not undefined && that target.thresholds is not undefined as it will print on both sides
if (d.source.thresholds !== undefined)
if(d.target.thresholds !== undefined)
return d.source.thresholds + ' ' + d.source.directions;
})
if I comment these two lines : if (d.source.thresholds !== undefined) and if(d.target.thresholds !== undefined) then I get the text on all the links but on both sides, which I don't want. How do I get the text only on one side but also on the last level.
First of all you need to have a way to find out whether the current node is on the last level, or if there are more levels under it. For this we will implement a maxDepth variable that will store a maximum value of depth attribute for all nodes. This is done in the update(root) function:
// Normalize for fixed-depth.
var maxDepth = 0;
nodes.forEach(function(d) { d.y = d.depth * 120; maxDepth = (d.depth>maxDepth)?d.depth:maxDepth;});
Next we add two additional checks to the second if statement like this:
if(d.target.thresholds !== undefined || d.target.name === true && d.target.depth == maxDepth)
It will make sure the text is added to true node only on the last level.
var treeData = [
{
"name": "glucose_tol",
"directions": ">",
"thresholds": "126",
"exits": 0.0,
"children": [
{
"name": "age",
"directions": ">",
"thresholds": "29",
"exits": 1.0,
"children": [
{
"name": true
},
{
"name": "mass_index",
"directions": ">",
"thresholds": "29.7",
"exits": 0.5,
"children": [
{
"name": true
},
{
"name": false
}
]
}
]
},
{
"name": false
}
]
},
];
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
var maxDepth = 0;
nodes.forEach(function(d) { d.y = d.depth * 120; maxDepth = (d.depth>maxDepth)?d.depth:maxDepth;});
// Declare the nodes.
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
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", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
// Add threshold and directions
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.x + d.target.x)/2) + "," +
((d.source.y + d.target.y)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
//check whether thresholds is not undefined && that target.thresholds is not undefined as it will print on both sides
if (d.source.thresholds !== undefined)
if(d.target.thresholds !== undefined || d.target.name === true && d.target.depth == maxDepth)
return d.source.thresholds + ' ' + d.source.directions;
})
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
I am learning D3 on the fly today. Below is a sample set of code with a JSON data set. I am currently trying to create a second graph next to the main donut chart. This chart will be another donut chart of the thin_vols[] array which is nested in each object. If you see the commented code below, I am attempting to build the second chart with no such luck. Any advice or help is greatly appreciated. Thank you.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>D3 Donut Chart</title>
</head>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var sampleSet = {
"title":"DummyData",
"data":[
{
"origin":"Disk_Pool_1",
"volumeName":"Vol1",
"usage":15,
"type":"thick",
"thin_vols":[]
},
{
"origin":"Disk_Pool_1",
"volumeName":"Vol2",
"usage":25,
"type":"thick",
"thin_vols":[]
},
{
"origin":"Disk_Pool_1",
"volumeName":"Repo_06",
"usage":50,
"type":"thick",
"thin_vols":[
{
"origin":"Repo_06",
"volumeName":"thinVol1",
"usage":10,
"max":20,
"type":"thin"
},
{
"origin":"Repo_06",
"volumeName":"thinVol2",
"usage":10,
"max":30,
"type":"thin"
},
{
"origin":"Repo_06",
"volumeName":"freespace",
"usage":20,
"max":40,
"type":"freespace"
}]
}
]
};
var m = 10,
r = 100,
z = d3.scale.category20c();
var pie = d3.layout.pie()
.value(function(d) { return +d.usage; })
.sort(function(a, b) { return b.usage - a.usage; });
var arc = d3.svg.arc()
.innerRadius(r / 2)
.outerRadius(r);
//here
var disks = d3.nest()
.key(function(d) { return d.origin; })
.entries(sampleSet.data);
var svg = d3.select("body").selectAll("div")
.data(disks)
.enter().append("div")
.style("display", "inline-block")
.style("width", (r + m) * 2 + "px")
.style("height", (r + m) * 2 + "px")
.append("svg:svg")
.attr("width", (r + m) * 2)
.attr("height", (r + m) * 2)
.append("svg:g")
.attr("transform", "translate(" + (r + m) + "," + (r + m) + ")");
svg.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.key; });
var g = svg.selectAll("g")
.data(function(d) { return pie(d.values); })
.enter().append("svg:g");
g.append("svg:path")
.attr("d", arc)
.style("fill", function(d) { return z(d.data.volumeName); })
.append("svg:title")
.text(function(d) { return d.data.volumeName + ": " + d.data.usage; });
g.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.text(function(d) { return d.data.volumeName + ": " + d.data.usage; });
var disks2 = d3.nest()
.key(function(d) { return d.origin; })
.entries(sampleSet.data.thin_vols);
var svg2 = d3.select("body").selectAll("div")
.data(disks2)
.enter().append("div")
.style("display", "inline-block")
.style("width", (r + m) * 2 + "px")
.style("height", (r + m) * 2 + "px")
.append("svg:svg")
.attr("width", (r + m) * 2)
.attr("height", (r + m) * 2)
.append("svg:g")
.attr("transform", "translate(" + (r + m) + "," + (r + m) + ")");
svg2.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.key; });
var g2 = svg2.selectAll("g")
.data(function(d) { return pie(d.values); })
.enter().append("svg:g");
g2.append("svg:path")
.attr("d", arc)
.style("fill", function(d) { return z(d.data.volumeName); })
.append("svg:title")
.text(function(d) { return d.data.thinvolumeName + ": " + d.data.usage; });
g2.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.text(function(d) { return d.data.volumeName + ": " + d.data.usage; });
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
</script>
</body>
</html>
I was able to render the second chart by flattening the existing JSON. See the below source code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>D3 Donut Chart</title>
</head>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var sampleSet = {
"title": "DummyData",
"data": [
{
"origin": "Disk_Pool_1",
"volumeName": "Vol1",
"usage": 15,
"type": "thick"
},
{
"origin": "Disk_Pool_1",
"volumeName": "Vol2",
"usage": 25,
"type": "thick"
},
{
"origin": "Disk_Pool_1",
"volumeName": "Repo_06",
"usage": 50,
"type": "thick"
},
{
"origin": "Repo_06",
"volumeName": "thinVol1",
"usage": 10,
"max": 20,
"type": "thin"
},
{
"origin": "Repo_06",
"volumeName": "thinVol2",
"usage": 10,
"max": 30,
"type": "thin"
},
{
"origin": "Repo_06",
"volumeName": "freespace",
"usage": 20,
"max": 40,
"type": "freespace"
}
]
};
var m = 10, r = 100, z = d3.scale.category20c();
var pie = d3.layout.pie()
.value(function (d) {
return +d.usage;
})
.sort(function (a, b) {
return b.usage - a.usage;
});
var arc = d3.svg.arc()
.innerRadius(r / 2)
.outerRadius(r);
var disks = d3.nest()
.key(function (d) {
return d.origin;
})
.entries(sampleSet.data);
var svg = d3.select("body").selectAll("div")
.data(disks)
.enter().append("div")
.style("display", "inline-block")
.style("width", (r + m) * 2 + "px")
.style("height", (r + m) * 2 + "px")
.append("svg:svg")
.attr("width", (r + m) * 2)
.attr("height", (r + m) * 2)
.append("svg:g")
.attr("transform", "translate(" + (r + m) + "," + (r + m) + ")");
svg.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.key;
});
var g = svg.selectAll("g")
.data(function (d) {
return pie(d.values);
})
.enter().append("svg:g");
g.append("svg:path")
.attr("d", arc)
.style("fill", function (d) {
return z(d.data.volumeName);
})
.append("svg:title")
.text(function (d) {
return d.data.volumeName + ": " + d.data.usage;
});
g.filter(function (d) {
return d.endAngle - d.startAngle > .2;
}).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.text(function (d) {
return d.data.volumeName + ": " + d.data.usage;
});
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
</script>
</body>
</html>
I want to add Donut chart in each node of D3 tree chart (replace circle node in tree with Donut Chart)
Below is my Code. I want to replace the circle node with Donut chart in each node with different data that sholud be read from sample json.
// Sample JSON Data
var jsondata = {
"name": "CERT",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "DNS",
"children": [{
"name": "FW",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "ADC",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "SERVERS",
"children": [{
"name": ""
}, {
"name": ""
}]
}]
}]
}]
}, {
"name": "FW",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "DNS",
"children": [{
"name": ""
}, {
"name": ""
}, {
"name": "ADC",
"children": [{
"name": "SERVERS"
}]
}]
}]
}]
};
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
//width = 960 - margin.right - margin.left, height = 800 - margin.top - margin.bottom;
width = screen.width;
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.separation(function(a, b) {
return a.parent === b.parent ? 1 : 2;
})
.children(function(d) {
return d.children;
})
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var line = d3.svg.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zm = d3.behavior.zoom().scaleExtent([1, 13]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");;
// put JSON data to root variable
root = jsondata;
root.x0 = height / 2;
root.y0 = 0;
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
update(root);
//d3.select(self.frameElement).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.y0 + "," + source.x0 + ")";
})
.on("click", click)
.on("mouseover", function(d) {
var g = d3.select(this); // The node
// The class is used to remove the additional text later
var info = g.append('text')
.classed('info', true)
.attr('x', 30)
.attr('y', 10)
.text(function(d) {
return d.name;
});
})
// Remove the info text on mouse out.
.on("mouseout", function() {
d3.select(this).select('text.info').remove();
});
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
// .duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", function(d) {
return d.children ? 20 : 10;
})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// 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("d", function(d) {
return line([{
x: d.source.y,
y: d.source.x
}, {
x: d.target.y,
y: d.target.x
}]);
});
// Transition links to their new position.
link.transition()
//.duration(900)
.attr("d", function(d) {
return line([{
x: d.source.y,
y: d.source.x
}, {
x: d.target.y,
y: d.target.x
}]);
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
// .duration(duration)
.attr("d", function(d) {
return line([{
x: d.source.y,
y: d.source.x
}, {
x: d.target.y,
y: d.target.x
}]);
})
.remove();
// 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);
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function elbow(d, i) {
return "M" + d.source.y + "," + d.source.x + "H" + d.target.y + "V" + d.target.x + (d.target.children ? "" : "h" + margin.right);
}
// 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 graph after zoom
function redraw() {
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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.3/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>