admin管理员组

文章数量:1404603

I have a little D3 script:

<script>
    d3.csv("../data/school_attendance.csv", function(data) {
        // Use d3.pie() to create and configure the data that we need in a format that we can enter into.
        let arc_data = d3.pie().value(d => d['YTD Enrollment(Avg)']).padAngle(d => 0.0115)(data);

        // Create the arc factory function that will render each data segment.
        let arc = d3.arc().innerRadius(75).outerRadius(160);

        // Run through each element of arc_data, creating and appening the arc for each one.
        d3.select("svg")
            .append("g")
            .attr("id", "transform")
            .attr("transform", "translate(400, 200)")
            .selectAll('path')
            .data(arc_data)
            .enter()
            .append('path')
            .attr('d', arc)
            .attr('fill', 'steelblue');

        // Use arc and arc_data to calculate centroids, and from there to calculate.
        arc_data.forEach(function(d, i) {
            [x, y] = arc.centroid(d);
            let label = d.data['District']
            // let rotation = d['startAngle'] * 180 / Math.PI;
            let rotation = d['startAngle'] / Math.PI / 2
            d3.select("#transform").append("text")
                .attr("x", x).attr("y", y)
                .attr("text-anchor", "middle").attr("alignment-baseline", "middle")
                .attr("transform", "rotate(" + rotation + ")")
                .text(label);
        })
    })
</script>

This produces the following output:

I'd like to rotate the text labels so that they appear in the middle of each of the arc segments.

However, what seems to me to be the obvious answer:

let rotation = d['startAngle'] / Math.PI / 2 * 360 - 90;

Doesn't work as expected:

What is my error here, and what should I do to fix it?

I have a little D3 script:

<script>
    d3.csv("../data/school_attendance.csv", function(data) {
        // Use d3.pie() to create and configure the data that we need in a format that we can enter into.
        let arc_data = d3.pie().value(d => d['YTD Enrollment(Avg)']).padAngle(d => 0.0115)(data);

        // Create the arc factory function that will render each data segment.
        let arc = d3.arc().innerRadius(75).outerRadius(160);

        // Run through each element of arc_data, creating and appening the arc for each one.
        d3.select("svg")
            .append("g")
            .attr("id", "transform")
            .attr("transform", "translate(400, 200)")
            .selectAll('path')
            .data(arc_data)
            .enter()
            .append('path')
            .attr('d', arc)
            .attr('fill', 'steelblue');

        // Use arc and arc_data to calculate centroids, and from there to calculate.
        arc_data.forEach(function(d, i) {
            [x, y] = arc.centroid(d);
            let label = d.data['District']
            // let rotation = d['startAngle'] * 180 / Math.PI;
            let rotation = d['startAngle'] / Math.PI / 2
            d3.select("#transform").append("text")
                .attr("x", x).attr("y", y)
                .attr("text-anchor", "middle").attr("alignment-baseline", "middle")
                .attr("transform", "rotate(" + rotation + ")")
                .text(label);
        })
    })
</script>

This produces the following output:

I'd like to rotate the text labels so that they appear in the middle of each of the arc segments.

However, what seems to me to be the obvious answer:

let rotation = d['startAngle'] / Math.PI / 2 * 360 - 90;

Doesn't work as expected:

What is my error here, and what should I do to fix it?

Share Improve this question asked Jan 31, 2017 at 4:49 Aleksey BilogurAleksey Bilogur 3,8564 gold badges35 silver badges61 bronze badges 3
  • A bit more context here. – Aleksey Bilogur Commented Jan 31, 2017 at 4:52
  • let rotation = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; rotation > 90 ? rotation = rotation - 180 : rotation; didn't do it. Why is this ternary math necessary? I saw that in some of the other answers to similar questions, and that's the piece of it that I get the least. – Aleksey Bilogur Commented Jan 31, 2017 at 5:28
  • The ternary math is necessary to make all the texts going from left to right. I created a demo in my answer. Keep only the truthy part of the ternary and you'll see what I mean. – Gerardo Furtado Commented Jan 31, 2017 at 12:59
Add a ment  | 

1 Answer 1

Reset to default 9

It seems to me that one of the problems here is setting the x and y position of the labels using attr. Instead of that, translate them:

.attr("transform", "translate(" + [x,y] + ")");

After that, es the math:

var rotation = (d.startAngle/2 + d.endAngle/2) * 180/Math.PI;

But the above variable has the problem of positioning all texts going from the center of the donut to the borders, and some labels (on the left side of the donut) end up upside down, going from the right to the left. As we read from left to write, it seems to me that this ternary is more elegant:

var rotation = d.endAngle < Math.PI ? 
    (d.startAngle/2 + d.endAngle/2) * 180/Math.PI : 
    (d.startAngle/2  + d.endAngle/2 + Math.PI) * 180/Math.PI ;

Here is a demo:

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

const totals = [{
    "name": "District A",
    "value": 20
}, {
    "name": "District B",
    "value": 50
}, {
    "name": "District C",
    "value": 30
}, {
    "name": "District D",
    "value": 20
}, {
    "name": "District E",
    "value": 50
}, {
    "name": "District F",
    "value": 30
}];

const color = d3.scaleOrdinal()
    .range(['#869099', '#8c7853', '#007d4a']);

const arc = d3.arc()
    .outerRadius(radius - 10)
    .innerRadius(40);

const pie = d3.pie()
    .sort(null)
    .value((d) => {
        return d.value
    });

const svg = d3.select('body').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', 'steelblue')
    .style('stroke', 'white');

pie(totals).forEach(function(d, i) {
    [x, y] = arc.centroid(d);
    let label = d.data.name;
    var rotation = d.endAngle < Math.PI ? (d.startAngle / 2 + d.endAngle / 2) * 180 / Math.PI : (d.startAngle / 2 + d.endAngle / 2 + Math.PI) * 180 / Math.PI;
    svg.append("text")
        .attr("text-anchor", "middle").attr("alignment-baseline", "middle")
        .attr("transform", "translate(" + [x, y] + ") rotate(-90) rotate(" + rotation + ")")
        .text(label);
})
<script src="https://d3js/d3.v4.min.js"></script>

本文标签: javascriptD3 text rotation in a pie chartStack Overflow