d3饼图中的路径覆盖了文本 [英] Text being covered by paths in d3 pie chart

查看:70
本文介绍了d3饼图中的路径覆盖了文本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在







如果有帮助,则输出的SVG代码如下:

 < div id = graph-pie class = graph pie> 
< svg width = 668 height = 256>
< g transform = translate(334,128)>
< g class = arc>
< path d = M7.225416114969383e-15,-118A118,118,0,0,1,98.56840842206363,-64.87117126383073L0,0Z style = fill:rgb(134,144,153); z -index:10;>
< text transform = translate(41.753394753534344,-77.46388853237056) dy =。35em> Sensitive< / text>
< / g>
< g class = arc>
< path d = M98.56840842206363,-64.87117126383073A118,118,0,0,1,-27.95807360901471,114.64007205193529L0,0Z style = fill:rgb(140,120,83);> ;
< text transform = translate(71.92851960216296,50.69800851947991) dy =。35em> Somewhat Sensitive< / text>
< / g>
< g class = arc>
< path d = M-27.95807360901471,114.64007205193529A118,118,0,0,1,-2.167624834490815e-14,-118L0,0Z style = fill:rgb(0,125,74); >
< text transform = translate(-87.37132713286675,-10.500056906587716) dy =。35em< Tolerant< / text>
< / g>
< / g>
< / svg>
< / div>

我尝试过弄乱z-index CSS样式,但是它没有任何样式效果,而且看起来SVG元素没有类似的属性。如何强制将文本元素呈现在路径的顶部?

解决方案

文本已被某些路径覆盖,因为在您的代码中,每个 g (组)都有一个path元素和一个text元素。因此,给定组之后(按照SVG顺序,在其之上)在组内部的路径将在该特定文本之后被覆盖。该绘图命令包括:


因此,您需要做的是在渲染所有路径后的之后渲染文本。有几种方法可以做到这一点,但是最简单的方法(肯定不是最优雅的方法)是为文本创建另一组。


例如,这是您现在的代码。您可以看到某些文本被某些切片覆盖:


  const width = 400 
const height = 400;
const radius = Math.min(width,height)/ 2.5;

const总计= [{ name:类别A长标签,值:20},
{ name:类别B长标签,值 :50},
{ name:类别C长标签,值:30},
{ name:类别D长标签,值:20},
{名称:类别E长标签,值:50},
{名称:类别F长标签,值: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)=> {返回颜色( 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;
});

 < script src = https://d3js.org/d3.v4.min.js>< / script> 
< div id = graph-pie>< / div>


这是相同的代码,但是具有不同的文本组,这些文本在路径后 呈现:


  const width = 400 
const height = 400;
const radius = Math.min(width,height)/ 2.5;

const总计= [{ name:类别A长标签,值:20},
{ name:类别B长标签,值 :50},
{ name:类别C长标签,值:30},
{ name:类别D长标签,值:20},
{名称:类别E长标签,值:50},
{名称:类别F长标签,值: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)=> {return color(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;
});

 < script src = https://d3js.org/d3.v4.min.js>< / script> 
< div id = graph-pie>< / div>


您现在可以看到没有文字被覆盖。


这是可行的,但是正如我说的,这不是最优雅的方法,因为如果您检查SVG ,您将看到一堆只有一个元素的组(这毫无意义)。另一种方法是独立创建路径和文本,这需要多修改一些代码。




SVG:绘制顺序


这可能令人沮丧:您使用D3.js进行可视化,但是您想要放在顶部的矩形隐藏在另一个矩形的后面,或者您计划在某些矩形后面的线圈子实际上已经过去了。您尝试在CSS中使用 z-index 解决此问题,但此方法不起作用(在SVG 1.1中)。


解释很简单:在SVG中,元素的顺序定义了绘画的顺序,绘画的顺序定义了谁在顶部。


SVG文档片段具有隐式的绘制顺序,其中SVG文档片段中的第一个元素被绘制为绘。第一。


因此,假设我们有以下SVG:

 < svg width = 400高度= 200> 
< circ cy = 100 cx = 80。 r = 60。 fill =蓝色< /圆圈
< circ cy = 100 cx = 160。 r = 60。填充=黄色< /圆圈>
< circ cy = 100 cx = 240。 r = 60。填充=红色 /圆。
< circ cy = 100 cx = 320。 r = 60。填充=绿色 z-index =-1< /圆形。
< / svg>

他有四个圈子。蓝色圆圈是第一个绘制的,因此将在所有其他圆圈下面显示。然后是黄色,然后是红色,最后是绿色。绿色的是最后一个,它将在顶部。


这是它的外观:



使用D3更改SVG元素的顺序


因此,是否有可能更改元素的顺序?我可以在绿色圆圈之前做一个红色圆圈吗?


是的。您需要记住的第一种方法是代码中各行的顺序:首先绘制背景元素,然后在代码中绘制前景元素。


但是,即使在绘制元素之后,我们也可以动态更改它们的顺序。您可以编写一些简单的JavaScript函数来执行此操作,但是D3已经具有2个不错的功能, selection.raise() selection.lower ()


根据API:


selection.raise():按顺序将每个选定元素重新插入为其父级的最后一个子级。
selection.lower():按顺序重新插入每个选定元素,作为其父级的第一个孩子。


显示了如何在我们之前的SVG中操作元素的顺序,这是一个非常小的代码:

  d3.selectAll( circle) .on( mouseover,function(){
d3.select(this).raise();
});

它是做什么的?它选择所有圆,并且当用户将鼠标悬停在一个圆上时,它将选择该特定圆并将其置于最前面。非常简单!


这是演示:


  d3.selectAll( circle)。on( mouseover,function(){
d3.select(this).raise()
});

 < script src = https://cdnjs.cloudflare.com/ajax/libs/d3 /5.7.0/d3.min.js\"</script> 
< script src = https://d3js.org/d3.v4.min.js>< / script>
< svg width = 400 height = 200>
< circ cy = 100 cx = 80 r = 60 fill = blue>< / circle>
< circ cy = 100 cx = 160 r = 60 fill = yellow>< / circle>
< circ cy = 100 cx = 240 r = 60 fill = red>< / circle>
< circ cy = 100 cx = 320 r = 60 fill = green>< / circle>
< / svg>


I'm attempting to follow along with the pie chart example for d3 at http://bl.ocks.org/mbostock/3887235.

A minimal sample of my code:

const container = $('#graph-pie');
const width = container.width() * 0.5;
const height = 256;
const radius = Math.min(width, height) / 2;

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) => { return color(d.data.name) });

g.append('text')
    .attr('transform', (d) => { return 'translate(' + labelArc.centroid(d) + ')' })
    .attr('dy', '.35em')
    .text((d) => {
        return names[d.data.name];
    });

The issue is that sometimes the text for the labels appears hidden behind the pie slices. I've noticed this happening mostly on Chrome, but I don't know that it isn't happening in other browsers. A few examples:

If it helps, the outputted SVG code is as follows:

<div id="graph-pie" class="graph pie">
  <svg width="668" height="256">
    <g transform="translate(334,128)">
      <g class="arc">
        <path d="M7.225416114969383e-15,-118A118,118,0,0,1,98.56840842206363,-64.87117126383073L0,0Z" style="fill: rgb(134, 144, 153); z-index: 10;">
        <text transform="translate(41.753394753534344,-77.46388853237056)" dy=".35em">Sensitive</text>
      </g>
      <g class="arc">
        <path d="M98.56840842206363,-64.87117126383073A118,118,0,0,1,-27.95807360901471,114.64007205193529L0,0Z" style="fill: rgb(140, 120, 83);">
        <text transform="translate(71.92851960216296,50.69800851947991)" dy=".35em">Somewhat Sensitive</text>
      </g>
      <g class="arc">
        <path d="M-27.95807360901471,114.64007205193529A118,118,0,0,1,-2.167624834490815e-14,-118L0,0Z" style="fill: rgb(0, 125, 74);">
        <text transform="translate(-87.37132713286675,-10.500056906587716)" dy=".35em">Tolerant</text>
      </g>
    </g>
  </svg>
</div>

I've tried messing around with the z-index CSS style, but it doesn't have any effect, and it doesn't look like SVG elements have any similar attributes. How can I force my text elements to be rendered on top of my paths?

解决方案

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 = 400
const 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) => { return color(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;
    });

<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="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 = 400
const 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) => { return color(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;
    });

<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="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:

<svg width="400" height=200>
    <circle cy="100" cx="80" r="60" fill="blue"></circle>
    <circle cy="100" cx="160" r="60" fill="yellow"></circle>
    <circle cy="100" cx="240" r="60" fill="red"></circle>
    <circle cy="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()
});

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height=200>
    <circle cy="100" cx="80" r="60" fill="blue"></circle>
    <circle cy="100" cx="160" r="60" fill="yellow"></circle>
    <circle cy="100" cx="240" r="60" fill="red"></circle>
    <circle cy="100" cx="320" r="60" fill="green"></circle>
</svg>

这篇关于d3饼图中的路径覆盖了文本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆