Reputation: 23
I need to create a sunburst chart with four separate targets. Each target has an assigned color and all of that target's children should inherit that color. I have it set up so that the children take their parent's color and that works fine. The issue is the children of a child do not inherit the color of the initial parent.
This is the code I am using to create the visualization:
var colors = d3.scale.ordinal()
.range(["#62ADDB", "#7568BA", "#FF8F2B", "#6BC96D"]);
var json = getData();
createVisualization(json);
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Basic setup of page elements.
initializeBreadcrumbTrail();
d3.select("#togglelegend").on("click", toggleLegend);
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json)
.filter(function (d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
var uniqueNames = (function (a) {
var output = [];
a.forEach(function (d) {
if (output.indexOf(d.name) === -1) {
output.push(d.name);
}
});
return output;
})(nodes);
// set domain of colors scale based on data
colors.domain(uniqueNames);
// make sure this is done after setting the domain
drawLegend();
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function (d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function (d) { return colors((d.children ? d : d.parent).name); })
.style("opacity", 1)
.on("mouseover", mouseover);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
};
function getData() {
return {
"name": "ref",
"children": [
{ "name": "EPIC",
"children": [
{ "name": "EPIC-a1", "size": 3 },
{ "name": "EPIC-a2", "size": 3 }
]
},
{ "name": "AD",
"children": [
{ "name": "AD-a1", "size": 3 },
{ "name": "AD-a2", "size": 3 }
]
},
{ "name": "SAP",
"children": [
{ "name": "SAP-a1", "size": 3 },
{ "name": "SAP-a2", "size": 3 }
]
},
{ "name": "Oracle",
"children": [
{ "name": "Oracle-a1", "size": 3 },
{ "name": "Oracle-a2", "size": 3,
"children": [
{ "name": "EPIC-b1", "size": 3 },
{ "name": "EPIC-b2", "size": 3 }
]
}
]
}
]
};
};
Upvotes: 2
Views: 1472
Reputation: 21578
Your logic for determining the node's name to be used for getting the color from the scale is flawed:
(d.children ? d : d.parent).name
Which translates to:
If I've got children then use my name, and if I've got no children then use my parent's name instead.
This, however, is not what you want. You are looking for
Give me the name of my ancestor which is closest to the root without being the root itself.
There are various ways to implement this logic:
1. By Recursion
To my eye this is the most elegant way to achieve what you are after.
function getRootmostAncestorByRecursion(node) {
return node.depth > 1 ? getRootmostAncestorByRecursion(node.parent) : node;
}
2. By Iteration
You may also apply various kinds of iterations using all sorts of loops known to JavaScript.
function getRootmostAncestorByWhileLoop(node) {
while (node.depth > 1) node = node.parent;
return node;
}
The call to either of these functions will then replace the above mentioned erroneous statement.
.style("fill", function (d) { return colors(getRootmostAncestorByRecursion(d).name); })
Depending on your amount of data and on how often this logic will get called, you might want to consider doing this once for your entire tree and store the rootmost parent's name on every node.
Upvotes: 3