饼图中的D3文本旋转

时间:2017-01-31 04:49:53

标签: javascript d3.js svg transform

我有一个小D3脚本:

<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>

这会产生以下输出:

enter image description here

我想旋转文字标签,使它们出现在每个弧段的中间。

然而,在我看来,显而易见的答案是:

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

不能按预期工作:

enter image description here

我的错误是什么,我应该怎么做才能解决它?

1 个答案:

答案 0 :(得分:3)

在我看来,其中一个问题是使用x设置标签的yattr位置。而不是那样,翻译它们:

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

之后,数学来了:

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

但是上述变量存在将所有文本从甜甜圈中心定位到边界的问题,并且一些标签(在甜甜圈的左侧)最终颠倒,从右到左。当我们从左边读到写字时,在我看来这个三元组更优雅:

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 ;

这是一个演示:

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.org/d3.v4.min.js"></script>