Skip to content

Change text color or display it on top of the path so the path won’t cover it

I am new to D3js and I have some sort of bug here. When the percentage of data is very low, the path will appear shorter too, logically. But that also “hides” my text which displays the data percentage. Is there any way possible to make my text white or at least make it appear on top of it’s path? Here’s my code snipped. Thank you in advance

    var data = [
        {"platform": "Yes", "percentage": 87.00}, //skyblue
        {"platform": "No", "percentage": 1.00},    //darkblue
        {"platform": "N/A", "percentage": 17.00} //orange
    ];
    
    var svgWidth = 200, svgHeight = 200, radius =  Math.min(svgWidth, svgHeight) / 2;
    var svg = d3.select('svg')
        .attr("width", svgWidth)
        .attr("height", svgHeight);
    
    //Create group element to hold pie chart    
    var g = svg.append("g")
        .attr("transform", "translate(" + radius + "," + radius + ")") ;
    
    var color = d3.scaleOrdinal(d3.schemeCategory20);
    
    var pie = d3.pie().value(function(d) { 
         return d.percentage; 
    });
    
    var path = d3.arc()
        .outerRadius(80)
        .innerRadius(40);
     
    var arc = g.selectAll("arc")
        .data(pie(data))
        .enter()
        .append("g");
    
    arc.append("path")
        .attr("d", path)
        .attr("fill", function(d) { return color(d.data.percentage); });
            
    var label = d3.arc()
        .outerRadius(20)
        .innerRadius(100);
                
    arc.append("text")
        .attr("transform", function(d) { 
            return "translate(" + label.centroid(d) + ")"; 
        })
        .attr("text-anchor", "middle")
        .text(function(d) { return +d.data.percentage+"%"; });
            .cnt-main-container
            {
                display:flex;
                flex-direction: column;
            }
            .charts{
                display:flex;
                justify-content: center;
                flex-direction: row;
                flex-wrap: nowrap;
            }
            .info{
                display:flex;
                justify-content: center;
                flex-direction: row;
            }

            #dot1{
                width:10px;
                height:10px;
                background:rgb(39, 81, 194);
                display:inline-block;
            }
            label{
                margin-left:3px;
                margin-top:-3px;
                margin-right:5px;
            }
            #dot2{
                width:10px;
                height:10px;
                background:rgb(233, 106, 3);
                display:inline-block;
            }

            #dot3{
                background-color:skyblue;
                width:10px;
                height:10px;
                display:inline-block;
            }
        <div class="cnt-main-container">
            <div class="charts">
                <svg class="pie-chart"></svg>
            </div>
            <div class="info">
                <div id="dot1"></div>
                <label for="dot1">Yes</label>
                <div id="dot2"></div>
                <label for="dot2">No</label>
                <div id="dot3"></div>
                <label for="dot3">N/A</label>
            </div>
    </div>
    
            <script src="https://d3js.org/d3.v4.min.js"></script>
        <script src="script.js"></script>

Answer

In a SVG the order of the elements defines the painting order. Thus, the simples solution is sorting your enter selection i such a way that small slices will be on the top:

.sort((a, b) => b.data.percentage - a.data.percentage);

Also, you can set a small white stroke to the texts, if the browser supports paint-order.

Here is the result:

var data = [{
    "platform": "Yes",
    "percentage": 87.00
  }, //skyblue
  {
    "platform": "No",
    "percentage": 1.00
  }, //darkblue
  {
    "platform": "N/A",
    "percentage": 17.00
  } //orange
];

var svgWidth = 200,
  svgHeight = 200,
  radius = Math.min(svgWidth, svgHeight) / 2;
var svg = d3.select('svg')
  .attr("width", svgWidth)
  .attr("height", svgHeight);

//Create group element to hold pie chart    
var g = svg.append("g")
  .attr("transform", "translate(" + radius + "," + radius + ")");

var color = d3.scaleOrdinal(d3.schemeCategory20);

var pie = d3.pie().value(function(d) {
  return d.percentage;
});

var path = d3.arc()
  .outerRadius(80)
  .innerRadius(40);

var arc = g.selectAll("arc")
  .data(pie(data))
  .enter()
  .append("g")
  .sort((a, b) => b.data.percentage - a.data.percentage);

arc.append("path")
  .attr("d", path)
  .attr("fill", function(d) {
    return color(d.data.percentage);
  });

var label = d3.arc()
  .outerRadius(20)
  .innerRadius(100);

arc.append("text")
  .attr("transform", function(d) {
    return "translate(" + label.centroid(d) + ")";
  })
  .attr("text-anchor", "middle")
  .text(function(d) {
    return +d.data.percentage + "%";
  });
.cnt-main-container {
  display: flex;
  flex-direction: column;
}

.charts {
  display: flex;
  justify-content: center;
  flex-direction: row;
  flex-wrap: nowrap;
}

.info {
  display: flex;
  justify-content: center;
  flex-direction: row;
}

#dot1 {
  width: 10px;
  height: 10px;
  background: rgb(39, 81, 194);
  display: inline-block;
}

label {
  margin-left: 3px;
  margin-top: -3px;
  margin-right: 5px;
}

#dot2 {
  width: 10px;
  height: 10px;
  background: rgb(233, 106, 3);
  display: inline-block;
}

#dot3 {
  background-color: skyblue;
  width: 10px;
  height: 10px;
  display: inline-block;
}

text {
  stroke: white;
  stroke-width: 2px;
  paint-order: stroke;
}
<div class="cnt-main-container">
  <div class="charts">
    <svg class="pie-chart"></svg>
  </div>
  <div class="info">
    <div id="dot1"></div>
    <label for="dot1">Yes</label>
    <div id="dot2"></div>
    <label for="dot2">No</label>
    <div id="dot3"></div>
    <label for="dot3">N/A</label>
  </div>
</div>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="script.js"></script>