D3.js图表​​饼图带有标签旋转

时间:2020-09-04 20:21:49

标签: javascript d3.js

我需要单击每一块进行旋转,问题是标签开始未对准。并且它们应该像开始时那样对齐。

var width = 670,
  height = 400,
  radius = Math.min(width, height) / 2,
  raio = 180,
  active = !active,
  labelr = raio + 30;


const dataStructure = {
  data: [{
      label: 'Lorem Ipsum 1',
      value: 100,
      color: 'green'
    },
    {
      label: 'Lorem Ipsum 2',
      value: 80,
      color: 'red'
    },
    {
      label: 'Lorem Ipsum 3',
      value: 45,
      color: 'blue'
    },
    {
      label: 'Lorem Ipsum 5',
      value: 20,
      color: 'yellow'
    },
    {
      label: 'Lorem Ipsum 6',
      value: 70,
      color: 'orange'
    }
  ]
};


const arc = d3
  .arc()
  .outerRadius(raio)
  .innerRadius(8);

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


const pie = d3.pie().sort(null).value(100)(dataStructure.data);

const svg = d3
  .select('#chart-d3')
  .append('svg')
  .attr('width', width)
  .attr('height', 450)
  .attr('class', 'container-svg')
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ' ,' + height / 2 + ')');

svg.append('g').attr('class', 'labels');

svg.append('g').attr('class', 'slices');


const key = function(d) {
  return d.data.label;
};

const slice = svg.select('.slices').selectAll('path.slice').data(pie, key).enter();

// gera os 'pedaços' e coloca efeito
slice
  .append('path')
  .attr('d', arc)
  .attr('stroke', '#F0F1F5')
  .attr('stroke-width', '1')
  .style('fill', function(d) {
    return d.data.color;
  })

  .on('click', (d, i) => {

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

    svg
      .transition()
      .attr(
        'transform',
        'translate(' + width / 2 + ' ,' + height / 2 + ') rotate(' + rotate + ')'
      )
      .duration(1000);


    d3.selectAll('text')
      .transition()
      .attr('transform', function(d) {
        var c = labelArc.centroid(d),
          x = c[0],
          y = c[1],
          // pythagorean theorem for hypotenuse
          h = Math.sqrt(x * x + y * y);
        return (
          'translate(' + (x / h) * labelr + ',' + (y / h) * labelr + ') rotate(' + -rotate + ')'
        );

      })
      .attr('text-anchor', function(d) {
        return (d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start';
      })
      .duration(1000);
  });

// label
const text = svg.select('.labels').selectAll('text').data(pie, key).enter();

text
  .append('text')
  .attr('dy', '.35em')
  .text((d) => {
    return d.data.label
  })
  .style('fill', '#47484c')
  .attr('font-family', 'Bradesco Sans')
  .attr('font-size', '14px')
  .attr('font-weight', '500')
  .attr('id', function(d, i) {
    return 'legend-' + i;
  })
  .attr('transform', function(d) {
    var c = labelArc.centroid(d),
      x = c[0],
      y = c[1],
      // pythagorean theorem for hypotenuse
      h = Math.sqrt(x * x + y * y);
    return 'translate(' + (x / h) * labelr + ',' + (y / h) * labelr + ')';
  })
  .attr('text-anchor', function(d) {
    // are we past the center?
    return (d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start';
  });


// d3.select('#circle-icon-0').dispatch('click');
<script src="http://d3js.org/d3.v5.min.js"></script>
<div id="chart-d3"></div>

Example

代码jsfiddle

1 个答案:

答案 0 :(得分:0)

您忘记计算text-anchor的轮换。但是,当我解决该问题时,您会出现跳动的行为,因为该属性无法设置动画。

我看到一些解决方法:

  1. 增加labelr并持续使用text-anchor: middle;,请参见下面的示例;

var width = 670,
  height = 400,
  radius = Math.min(width, height) / 2,
  raio = 180,
  active = !active,
  labelr = raio + 45;


const dataStructure = {
  data: [{
      label: 'Lorem Ipsum 1',
      value: 100,
      color: 'green'
    },
    {
      label: 'Lorem Ipsum 2',
      value: 80,
      color: 'red'
    },
    {
      label: 'Lorem Ipsum 3',
      value: 45,
      color: 'blue'
    },
    {
      label: 'Lorem Ipsum 5',
      value: 20,
      color: 'yellow'
    },
    {
      label: 'Lorem Ipsum 6',
      value: 70,
      color: 'orange'
    }
  ]
};


const arc = d3
  .arc()
  .outerRadius(raio)
  .innerRadius(8);

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


const pie = d3.pie().sort(null).value(100)(dataStructure.data);

const svg = d3
  .select('#chart-d3')
  .append('svg')
  .attr('width', width)
  .attr('height', 450)
  .attr('class', 'container-svg')
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ' ,' + height / 2 + ')');

svg.append('g').attr('class', 'labels');

svg.append('g').attr('class', 'slices');


const key = function(d) {
  return d.data.label;
};

const slice = svg.select('.slices').selectAll('path.slice').data(pie, key).enter();

// gera os 'pedaços' e coloca efeito
slice
  .append('path')
  .attr('d', arc)
  .attr('stroke', '#F0F1F5')
  .attr('stroke-width', '1')
  .style('fill', function(d) {
    return d.data.color;
  })

  .on('click', (d, i) => {

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

    svg
      .transition()
      .attr(
        'transform',
        'translate(' + width / 2 + ' ,' + height / 2 + ') rotate(' + rotate + ')'
      )
      .duration(1000);


    d3.selectAll('text')
      .transition()
      .attr('transform', function(d) {
        var c = labelArc.centroid(d),
          x = c[0],
          y = c[1],
          // pythagorean theorem for hypotenuse
          h = Math.sqrt(x * x + y * y);
        return (
          'translate(' + (x / h) * labelr + ',' + (y / h) * labelr + ') rotate(' + -rotate + ')'
        );

      })
      .duration(1000);
  });

// label
const text = svg.select('.labels').selectAll('text').data(pie, key).enter();

text
  .append('text')
  .attr('dy', '.35em')
  .text((d) => {
    return d.data.label
  })
  .style('fill', '#47484c')
  .attr('font-family', 'Bradesco Sans')
  .attr('font-size', '14px')
  .attr('font-weight', '500')
  .attr('id', function(d, i) {
    return 'legend-' + i;
  })
  .attr('transform', function(d) {
    var c = labelArc.centroid(d),
      x = c[0],
      y = c[1],
      // pythagorean theorem for hypotenuse
      h = Math.sqrt(x * x + y * y);
    return 'translate(' + (x / h) * labelr + ',' + (y / h) * labelr + ')';
  })
  .attr('text-anchor', 'middle');


// d3.select('#circle-icon-0').dispatch('click');
<script src="http://d3js.org/d3.v5.min.js"></script>
<div id="chart-d3"></div>

  1. 如果,标签应更改位置,然后计算标签的宽度,并逐渐调整dx。动画完成后,重置dx并设置正确的text-anchor值。当然,这更麻烦,但是如果您真的不想使用text-anchor: middle,则可能是一种解决方法。

var width = 670,
  height = 400,
  radius = Math.min(width, height) / 2,
  raio = 180,
  active = !active,
  labelr = raio + 30;


const dataStructure = {
  data: [{
      label: 'Lorem Ipsum 1',
      value: 100,
      color: 'green'
    },
    {
      label: 'Lorem Ipsum 2',
      value: 80,
      color: 'red'
    },
    {
      label: 'Lorem Ipsum 3',
      value: 45,
      color: 'blue'
    },
    {
      label: 'Lorem Ipsum 5',
      value: 20,
      color: 'yellow'
    },
    {
      label: 'Lorem Ipsum 6',
      value: 70,
      color: 'orange'
    }
  ]
};


const arc = d3
  .arc()
  .outerRadius(raio)
  .innerRadius(8);

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


const pie = d3.pie().sort(null).value(100)(dataStructure.data);

const svg = d3
  .select('#chart-d3')
  .append('svg')
  .attr('width', width)
  .attr('height', 450)
  .attr('class', 'container-svg')
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ' ,' + height / 2 + ')');

svg.append('g').attr('class', 'labels');

svg.append('g').attr('class', 'slices');


const key = function(d) {
  return d.data.label;
};

const slice = svg.select('.slices').selectAll('path.slice').data(pie, key).enter();

// gera os 'pedaços' e coloca efeito
slice
  .append('path')
  .attr('d', arc)
  .attr('stroke', '#F0F1F5')
  .attr('stroke-width', '1')
  .style('fill', function(d) {
    return d.data.color;
  })

  .on('click', (d, i) => {

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

    var rotateRads = rotate / 360 * (Math.PI * 2);

    svg
      .transition()
      .attr(
        'transform',
        'translate(' + width / 2 + ' ,' + height / 2 + ') rotate(' + rotate + ')'
      )
      .duration(1000);


    d3.selectAll('text')
      .attr('dx', 0)
      .transition()
      .attr('transform', function(d) {
        var c = labelArc.centroid(d),
          x = c[0],
          y = c[1],
          // pythagorean theorem for hypotenuse
          h = Math.sqrt(x * x + y * y);
        return (
          'translate(' + (x / h) * labelr + ',' + (y / h) * labelr + ') rotate(' + -rotate + ')'
        );
      })
      .attr('dx', function(d) {
        // Will their text-anchor change?
        const oldAnchor = d3.select(this).attr('text-anchor');
        
        // + 2*PI to make it always positive, then modulo
        const newAngle = 
          ((d.endAngle + d.startAngle) / 2 + rotateRads + 2 * Math.PI) % (Math.PI * 2);
        const newAnchor = newAngle > Math.PI ? 'end' : 'start';
        if (oldAnchor === newAnchor) {
          return 0;
        }
        
        if (newAnchor === 'end') {
          return -this.getBBox().width;
        }
        
        return this.getBBox().width;
      })
      .duration(1000)
      .on('end', function(d, i) {
        d3.select(this)
          .attr('dx', 0)
          .attr('text-anchor', function(d) {
          const newAngle = 
            ((d.endAngle + d.startAngle) / 2 + rotateRads + 2 * Math.PI) % (Math.PI * 2);
            return newAngle > Math.PI ? 'end' : 'start';
          });
      });
  });

// label
const text = svg.select('.labels').selectAll('text').data(pie, key).enter();

text
  .append('text')
  .attr('dy', '.35em')
  .text((d) => {
    return d.data.label
  })
  .style('fill', '#47484c')
  .attr('font-family', 'Bradesco Sans')
  .attr('font-size', '14px')
  .attr('font-weight', '500')
  .attr('id', function(d, i) {
    return 'legend-' + i;
  })
  .attr('transform', function(d) {
    var c = labelArc.centroid(d),
      x = c[0],
      y = c[1],
      // pythagorean theorem for hypotenuse
      h = Math.sqrt(x * x + y * y);
    return 'translate(' + (x / h) * labelr + ',' + (y / h) * labelr + ')';
  })
  .attr('text-anchor', function(d) {
    return (d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start';
  });


// d3.select('#circle-icon-0').dispatch('click');
<script src="http://d3js.org/d3.v5.min.js"></script>
<div id="chart-d3"></div>