Reputation: 428
I am using D3v5 and React.
I have this data
{
"name": "main",
"children": [
{
"name": "testedin",
"children": [
{
"name":"positive",
"value": 53,
"floor":2
},
{
"name":"negative",
"value": 24,
"floor":2
},
{
"name":"unknown",
"value": 23,
"floor":2
}
],
"floor":1,
"value":55
},
{
"name": "Not tested",
"value": 45,
"floor":1
}
],
"floor":0
}
And I want to draw this chart, where the data for the second column of rects is the percentage value of the rect parent from the first column.
I have follow this example: icecle Chart and everything works fine with it.
The Problem is that with that chart al values are just sum up due to this segment of code:
const partition = data => {
const root = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.height - a.height || b.value - a.value);
return d3.partition()
.size([height, (root.height + 1) * width / 4])
(root);
}
where the .sum function just adds the values of each children.
Having this result:
I tried to alter the sum function so I can have different usage of values, but cant find a way to edit it so I can have the percentages.
Upvotes: 1
Views: 880
Reputation: 38201
Sum won't work here. Instead we can create the hierarchy and then go through the tree and calculate a standardized value representing height for each node/bar:
root.value = 1;
root.eachBefore(d=>{
if(d.parent) d.value = d.data.value * d.parent.value / 100;
})
We set the total value to 1 for the root. In using root.eachBefore we visit nodes "such that a given node is only visited after all of its ancestors have already been visited (docs)". Whenever we visit a node we scale its value relative to its parents: if a node represents 55%, it's sizing value will be 55% of its parents. Now we have a set of nodes with a value property that represents a given node's relative height in comparison to the root, a standardized value we can pass to the partition.
In scaling children nodes, the parent's scaled value (d.value) is used along with the child's unscaled value (d.data.value). I also use d.data.value below to access the original percentage for labelling (as the original data is preserved in d.data).
const width = 500;
const height = 150;
const color = d3.scaleOrdinal()
.range(d3.schemeCategory10)
const data = {
"name": "main",
"children": [
{
"name": "testedin",
"children": [
{
"name":"positive",
"value": 53,
"floor":2
},
{
"name":"negative",
"value": 24,
"floor":2
},
{
"name":"unknown",
"value": 23,
"floor":2
}
],
"floor":1,
"value":55
},
{
"name": "Not tested",
"value": 45,
"floor":1
}
],
"floor":0
}
const partition = data => {
const root = d3.hierarchy(data)
.sort((a, b) => b.height - a.height || b.value - a.value);
root.value = 1;
root.eachBefore(d=>{
if(d.parent) d.value = d.data.value * d.parent.value / 100;
})
// or, slightly more compactly:
// root.eachBefore(d=>{d.value = d.parent ? d.data.value * d.parent.value / 100 : 1})
return d3.partition()
.size([height, (root.height + 1) * width / 4])
(root);
}
const root = partition(data);
const svg = d3.select("body").append("svg")
.attr("viewBox", [0, 0, width, height])
.style("font", "10px sans-serif");
const cell = svg
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", d => `translate(${d.y0},${d.x0})`);
cell.append("rect")
.attr("width", d => d.y1 - d.y0)
.attr("height", d => d.x1 - d.x0)
.attr("fill-opacity", 0.6)
.attr("fill", d => {
if (!d.depth) return "#ccc";
while (d.depth > 1) d = d.parent;
return color(d.data.name);
});
const text = cell.filter(d => (d.x1 - d.x0) > 16).append("text")
.attr("x", 4)
.attr("y", 13);
text.append("tspan")
.text(d => d.data.name);
text.append("tspan")
.attr("fill-opacity", 0.7)
.text(d =>Math.round(d.data.value) || "")
.attr("dx", 10);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
Upvotes: 1