如何理解D3里的padAngle?

221 阅读4分钟

大年三十上午写文章,这绝对是第一次,哈哈哈,不知为何,此时的感觉异常的好,嘻嘻嘻。在这里祝新年快乐,身体健康,万事如意!

上篇文章,我们总结了D3画圆弧的方法,先是计算绘画的起点,然后逆时针画外圆弧,再是顺时针画内圆弧,最后stroke连线。

这篇文章我们来看看扇形之间的空隙是如何实现的。

一、padAngle

在D3里,它有一个生成器的概念,饼图有饼图生成器,圆弧有圆弧生成器。当你创建了生成器,生成器就可以调用padAngle函数来给你的图形添加“空隙”。

import d3 from 'd3';
// 圆弧生成器
const createArc = d3.arc();

// 饼图生成器
const createPie = d3.pie();

“padAngle”并不是真实绘画出来的“空隙弧度”,这点是要注意的。

不知道大家发现没有,D3画出来的图,“外圆弧的空隙弧的水平连线” 始终等于 “内圆弧的空隙弧的水平连线”。

“空隙弧”也是弧,也有起点、终点,如果将起点、终点连线,那么你会发现,外内“空隙弧”的水平连线是相等的。

而为了实现这样的效果,padAngle起到了类比的作用。

接下来我们一起来看看吧。

二、实现原理

首先,D3将内外圆弧的差值、用户输入的padAngle组成了一个三角形。

截屏2025-01-28 11.36.04.png

其中,ap是角度,就是角A,等于用户输入的padAngle的一半,AC就是内外半径的差值,根据正弦定理,BC真实的长度应该是:

BC = AC * sin(ap)

这个BC啊,就是咱们刚才说的“水平连线”,这个“水平连线”是要真实的绘画出来的。

因为BC是相等的,AC是不断变化的,它可以外圆弧的半径,也可以是内圆弧的半径,所以,真实绘画出来的ap它也是不断变化的。

根据下面的公式:

// 假设,AC的长度变化到了外圆弧的半径,无论在哪个三角形里,BC是一定相等的

内外半径 * sin(ap) = 外圆弧的半径 * sin(真实绘画出来的ap)

// 真实绘画出来的sin(ap)如下:

sin(真实绘画出来的ap) = 内外半径 / 外圆弧半径 * sin(ap)

根据“Math.sin”这个API来看的话,此时只是求出了角度对应的sin值,它并不是弧度。

那我们可以通过反正弦函数(Math.asin)来求出正弦值对应的(角度的弧度)。

上面通过反正弦函数求出来的弧度,就是真实要绘画出来的“空隙弧度”。

有了这个“空隙弧度”,那剩下来的工作就是每个弧度的加加减减。

下面给出了逻辑的一些关键代码

function arc(arcInfo) {
    const da = Math.abs(arcInfo.startAngle - arcInfo.endAngle);
    let da0 = da;
    let da1 = da;
    if (rp > 0.00000000000001) {
        // 外圆弧的空隙弧度
        var p0 = asin( rp / r0 * sin(ap) ),
        // 内圆弧的空隙弧度
        p1 = asin( rp / r1 * sin(ap) );
        da0 = da0 - p0 * 2
        // 外圆弧,如果当前数据的弧长占比足够
        if (da0 > 0) {
            p0 = p0 * (cw ? 1 : -1);
            // 绘画的起始弧度
            a00 = a00 + p0;
            // 绘画的终止弧度
            a10 = a10 - p0;
         } else {
            // 如果弧度不够,占比不够,直接塌陷成点        
         }
            // 内圆弧的空隙弧度的绘画方式与外圆弧一样
            da1 = da1 - 2 * p1
            if (da1 > 0 ) {
                p1 = p1 * (cw ? 1 : -1)
                a01 = a01 + p1;
                a11 = a11 - p1
             } else {
                // 依旧是弧度不够,塌陷成点
             }
          }
            // 外圆弧绘画起点的横坐标(a01:起始弧度)
            const x01 = r1 * cos(a01);
            const y01 = r1 * sin(a01);
            // a10: 终止弧度
            const x10 = r0 * cos(a10);
            const y10 = r0 * sin(a10)
            // 画外弧、内弧
            context.moveTo(x01, y01)
            context.arc(0, 0, r1, a01, a11, !cw);
            context.arc(0, 0, r0, a10, a00, cw);
}

下面给出了辅助图,方便大家理解d3的处理逻辑。

截屏2025-01-28 10.43.42.png

三、最后

本期的内容分享到这里就结束啦,下期我们继续分享,如何给扇形加圆角,那么,我们下期再见,希望我的分享能够对你有帮助