Text Being Covered By Paths In D3 Pie Chart
Solution 1:
The texts are being covered by some paths because in your code each g
(group) has a path element and a text element. So, the paths inside the groups after a given group (in SVG order, "over it") will be rendered after that particular text, covering it. The "drawing order" in an SVG is described at the bottom of this post.
So, what you need to do is rendering the texts after rendering all the paths. There are several ways for doing this, but the simplest one (surely not the most elegant one) is creating another group for the texts.
For instance, this is your code right now. You can see some of the texts being covered by some slices:
const width = 400const height = 400;
const radius = Math.min(width, height) / 2.5;
const totals = [{"name":"Category A long label", "value":20},
{"name":"Category B long label", "value":50},
{"name":"Category C long label", "value":30},
{"name":"Category D long label", "value":20},
{"name":"Category E long label", "value":50},
{"name":"Category F long label", "value":30}];
const color = d3.scaleOrdinal()
.range(['#869099', '#8c7853', '#007d4a']);
const arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
const labelArc = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
const pie = d3.pie()
.sort(null)
.value((d) => { return d.value });
const svg = d3.select('#graph-pie').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width/2 + ',' + height/2 + ')');
const g = svg.selectAll('.arc')
.data(pie(totals))
.enter()
.append('g')
.attr('class', 'arc');
g.append('path')
.attr('d', arc)
.style('fill', (d) => { returncolor(d.data.name) });
g.append('text')
.attr("text-anchor", "middle")
.attr('transform', (d) => { return'translate(' + labelArc.centroid(d) + ')' })
.attr('dy', '.35em')
.text((d) => {
return d.data.name;
});
<scriptsrc="https://d3js.org/d3.v4.min.js"></script><divid="graph-pie"></div>
And this is the same code, but with a different group for the texts, that are rendered after the paths:
const width = 400const height = 400;
const radius = Math.min(width, height) / 2.5;
const totals = [{"name":"Category A long label", "value":20},
{"name":"Category B long label", "value":50},
{"name":"Category C long label", "value":30},
{"name":"Category D long label", "value":20},
{"name":"Category E long label", "value":50},
{"name":"Category F long label", "value":30}];
const color = d3.scaleOrdinal()
.range(['#869099', '#8c7853', '#007d4a']);
const arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
const labelArc = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
const pie = d3.pie()
.sort(null)
.value((d) => { return d.value });
const svg = d3.select('#graph-pie').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width/2 + ',' + height/2 + ')');
const g = svg.selectAll('.arc')
.data(pie(totals))
.enter()
.append('g')
.attr('class', 'arc');
const g2 = svg.selectAll('.arc2')
.data(pie(totals))
.enter()
.append('g')
.attr('class', 'arc');
g.append('path')
.attr('d', arc)
.style('fill', (d) => { returncolor(d.data.name) });
g2.append('text')
.attr("text-anchor", "middle")
.attr('transform', (d) => { return'translate(' + labelArc.centroid(d) + ')' })
.attr('dy', '.35em')
.text((d) => {
return d.data.name;
});
<scriptsrc="https://d3js.org/d3.v4.min.js"></script><divid="graph-pie"></div>
You can see that, now, there is no text being covered.
This works but, as I said, it's not the most elegant approach because, if you inspect the SVG, you're gonna see a bunch of groups with a single element (which makes no sense). An alternative is creating the paths and the texts independently, which involves modifying your code a little more.
SVG: the drawing order
This is something that can be frustrating: you make a visualisation using D3.js but the rectangle you want on top is hidden behind another rectangle, or the line you planned to be behind some circle is actually over it. You try to solve this using the z-index in your CSS, but it doesn't work (in SVG 1.1).
The explanation is simple: In an SVG, the order of the elements defines the order of the "painting", and the order of the painting defines who goes on top.
Elements in an SVG document fragment have an implicit drawing order, with the first elements in the SVG document fragment getting "painted" first. Subsequent elements are painted on top of previously painted elements.
So, suppose that we have this SVG:
<svgwidth="400"height=200><circlecy="100"cx="80"r="60"fill="blue"></circle><circlecy="100"cx="160"r="60"fill="yellow"></circle><circlecy="100"cx="240"r="60"fill="red"></circle><circlecy="100"cx="320"r="60"fill="green"z-index="-1"></circle></svg>
He have four circles. The blue circle is the first one "painted", so it will be bellow all the others. Then we have the yellow one, then the red one, and finally the green one. The green one is the last one, and it will be on the top.
This is how it looks:
Changing the order of SVG elements with D3
So, is it possible to change the order of the elements? Can I make the red circle in front of the green circle?
Yes. The first approach that you need to have in mind is the order of the lines in your code: draw first the elements of the background, and later in the code the elements of the foreground.
But we can dynamically change the order of the elements, even after they were painted. There are several plain JavaScript functions that you can write to do this, but D3 has already 2 nice features, selection.raise()
and selection.lower()
.
According to the API:
selection.raise(): Re-inserts each selected element, in order, as the last child of its parent. selection.lower(): Re-inserts each selected element, in order, as the first child of its parent.
So, to show how to manipulate the order of the elements in our previous SVG, here is a very small code:
d3.selectAll("circle").on("mouseover", function(){
d3.select(this).raise();
});
What does it do? It selects all the circles and, when the user hover over one circle, it selects that particular circle and brings it to the front. Very simple!
And here is the demo:
d3.selectAll("circle").on("mouseover", function(){
d3.select(this).raise()
});
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script><scriptsrc="https://d3js.org/d3.v4.min.js"></script><svgwidth="400"height=200><circlecy="100"cx="80"r="60"fill="blue"></circle><circlecy="100"cx="160"r="60"fill="yellow"></circle><circlecy="100"cx="240"r="60"fill="red"></circle><circlecy="100"cx="320"r="60"fill="green"></circle></svg>
Post a Comment for "Text Being Covered By Paths In D3 Pie Chart"