D3.js 饼图标签覆盖的问题

首先说明一下,本文没有具体画饼图的源码,可以去看我之前写的源码文章:https://blog.csdn.net/m0_37777005/article/details/90693746
关于给饼图添加标签会产生覆盖的问题,没有找到完美的解决方案,只能解决一部分,需要应援,需要应援,需要应援
一般给饼图标签分为两种:
第一种是饼图内部的标签(红框)
第二种是饼图外部用弧线标记的标签(蓝框)
下图是最终成果的效果图。
在这里插入图片描述
从第一种情况开始说明:内部标签覆盖。
扇形区域过小不能完全显示标签内容,就算你缩小字体那也是不能解决滴。所以看起来就是这样的,如果不进行处理,设计师40m的大刀怕是已经准备好了。
在这里插入图片描述
所以第一种解决方案,就是把过小的区域的文字直接不显示。
只贴关键代码,需要全部源码的请留言(有时间我再改,画饼图的源码我也有写:https://blog.csdn.net/m0_37777005/article/details/90693746
或者左转出门百度Google)。

 .text(d => {
        const total = d3.sum(data, a => a.rate);
        if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return ""; }
        return `${(d.value / total * 100).toFixed(2)}%`;
      });

但是你会发现,即使你隐藏了,但是某个区域的标签离边界很近,还是会有部分溢出。
所以第二种解决方案就是对文字进行旋转。
那么计算文字的角度就是第一步了。
在这里插入图片描述
假设文字是一条直线,与圆心到弧线的中点的夹角(嫌弃我画图丑的,请左转->https://www.cnblogs.com/ystrdy/p/10324466.html)
A(x3,y3)
B(x1,y1)
C(x2,y2)
旋转角度α就Lab 与Lac 的夹角(正值锐角)。为什么要是正值呢,因为你会发现有的需要顺时针旋转有的需要逆时针旋转,为了好把控,所以以正值锐角为值,判断旋转方向。
第二象限需要顺时针旋转,其它三个象限逆时针旋转。
在这里插入图片描述

 getAngle({ x: x1, y: y1 }, { x: x2, y: y2 }) {
    const dot = x1 * x2 + y1 * y2;
    const det = x1 * y2 - y1 * x2;
    const angle = Math.abs(Math.atan2(det, dot) / Math.PI * 180);
    return angle;
    }
 arcs
      .append('text')
      .attr('text-anchor', 'middle')
      .attr('font-size', fonsize)
      .attr('transform', d => {
        const x1 = 0;
        const y1 = 0;

        const x3 = arc.centroid(d)[0];
        const y3 = arc.centroid(d)[1];

        const x2 = x3 + 100;
        const y2 = y3;
        const angle = this.getAngle({ x: Math.abs(x1 - x3), y: Math.abs(y1 - y3) },
          { x: Math.abs(x2 - x3), y: Math.abs(y2 - y3) });
        console.log(d, arc.centroid(d), angle);
        if (x3 < 0 && y3 < 0) {
          return `translate(${x3},${y3}) rotate(${angle})`;
        }
        return `translate(${x3},${y3}) rotate(${-angle})`;
      })
      .text(d => {
        const total = d3.sum(data, a => a.rate);
        if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return ''; }
        return `${(d.value / total * 100).toFixed(2)}%`;
      });

第二种情况:添加外部标签产生覆盖。
出现的原因依旧是扇形区域小而密集,到时外部标签聚合在一起了。
就像酱紫:
在这里插入图片描述
所以第一种解决的思路就是将小而密集的弧线的间距增大
核心就是:如果扇形区域较小,留出两倍fontsize的空间

 .attr('y2', (d, i) => {
        if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return arc.centroid(d)[1] * 2.5 + fontsize * i; }
        return arc.centroid(d)[1] * 2.5;
      });
添加外部标签的方法。这个方法并不完美,在某些情况下依然会遇到标签重叠的情况,但是有范围限制的情况下直接使用问题不大---- **求应援**

第二种解决方案:因为扇形区域小又密集才产生的问题,所以把小扇形区域打散。解决方案来源:https://blog.csdn.net/chancemagic/article/details/78872572

我们先看一个数据结构
未处理前的结构
在这里插入图片描述
处理后的数据结构
在这里插入图片描述
渲染效果:
在这里插入图片描述
学好数据结构还是很重要的。

reorderData(data) {
    /* ----------- Reorder data to avoid label overlap --------------------  */
    //Get the value list, sort it, then reord it.
    let value_only_list = []
    let value_list_temp = []

    // data.forEach((d, i) => { value_only_list.push(d.rate) });s
    value_only_list = data.sort(NumCompare('rate'));

    let length = value_only_list.length;
    const half = parseInt(length / 2);
    const left = length % 2;
    let tag = 0;
    while (tag < half) {
      value_list_temp.push(value_only_list[tag]);
      value_list_temp.push(value_only_list[length - 1]);
      tag += 1;
      length -= 1;
    }
    if (left === 1) { value_list_temp.push(value_only_list[half]); }

    console.log('value_only_list-->', value_only_list);
    console.log('value_list_temp-->', value_list_temp);
    //Re-construct the dataset for pie chart

    return value_list_temp;

    function NumCompare(pro) {
      return (a, b) => a[pro] - b[pro];
    }
  }

以下是添加外部标签的方法

 drawOutTip(arc, arcs, data) {
    const fontsize = 15;
    // 添加连接弧外文字的直线元素
    arcs
      .append('line')
      .attr('stroke', 'black')
      .attr('x1', d => {
        return arc.centroid(d)[0] * 2;
      })
      .attr('y1', d => {
        return arc.centroid(d)[1] * 2;
      })
      .attr('x2', (d, i) => {
        return arc.centroid(d)[0] * 2.5;
      })
      .attr('y2', (d, i) => {
        //第一种方案去掉注释
        //if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return arc.centroid(d)[1] * 2.5 + fontsize * i; }
        return arc.centroid(d)[1] * 2.5;
      });

    arcs
      .append('line')
      .style('stroke', 'black')
      .each(d => {
        d.textLine = {
          x1: 0,
          y1: 0,
          x2: 0,
          y2: 0,
        };
      })
      .attr('x1', d => {
        d.textLine.x1 = arc.centroid(d)[0] * 2.5;
        return d.textLine.x1;
      })
      .attr('y1', (d, i) => {
        d.textLine.y1 = arc.centroid(d)[1] * 2.5;
        //第一种方案去掉注释
       // if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { d.textLine.y1 = arc.centroid(d)[1] * 2.5 + fontsize * i; }

        return d.textLine.y1;
      })
      .attr('x2', d => {
        const strLen = getPixelLength(d.data.name, fontsize) * 1.5;
        const bx = arc.centroid(d)[0] * 2.5;
        d.textLine.x2 = bx >= 0 ? bx + strLen : bx - strLen;
        return d.textLine.x2;
      })
      .attr('y2', (d, i) => {
        d.textLine.y2 = arc.centroid(d)[1] * 2.5;
        //第一种方案去掉注释
        //if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { d.textLine.y2 = arc.centroid(d)[1] * 2.5 + fontsize * i; }
        return d.textLine.y2;
      });

    arcs
      .append('text')
      .attr('transform', (d) => {
        let x = 0;
        let y = 0;
        x = (d.textLine.x1 + d.textLine.x2) / 2;
        y = d.textLine.y1;
        y = y > 0 ? y + fontsize * 1.1 : y - fontsize * 0.4;
        return `translate(${x},${y})`;
      })
      .style('text-anchor', (d) => {
        return midAngle(d) < Math.PI ? 'start' : 'end';
      })

      .style('font-size', fontsize)
      .text((d) => {
        return d.data.name;
      });
    function getPixelLength(str, fontsize) {
      let curLen = 0;
      for (let i = 0; i < str.length; i += 1) {
        const code = str.charCodeAt(i);
        const pixelLen = code > 255 ? fontsize : fontsize / 2;
        curLen += pixelLen;
      }
      return curLen;
    }
    function midAngle(d) {
      return d.startAngle + (d.endAngle - d.startAngle) / 2;
    };
  }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值