椭圆动态分区以及扇形渐变处理

1,744 阅读6分钟

image.png demo链接

github地址

场景

一个可视化的关系网络,可以理解为中心是当前用户,周边根据权重分布与自己联系紧密的各个角色的人员。每个人的角色数量都不一样,沟通人数也不尽相同。

逻辑梳理:

  1. 椭圆动态分成不同数量的扇形;
  2. 扇形弧上每个区域的元素通过js计算坐标位置定位处理
  3. 背景单独通过处理通过定位层叠加的方式放在椭圆元素之下
  4. 背景渐变处理

实现

布局、背景实现方式

圆锥渐变

扇形背景色填充通过圆锥渐变实现conic-gradient

  1. 椭圆上元素的计算方式按照离心角方式计算(详细介绍见下文) image.png 问题: 明显看到不同颜色区域的元素有错位的情况,比如元素2-10 应该在黄色背景区域内,0-4应该在第一圈黄色背景区域的中间位置

解决方式:

  • 圆锥渐变中百分比是按照角度来处理的
  • 以短半轴为半径画圆
  • transform: scale(2.2, 1)把圆通过scale横向拉成一个椭圆,横向的比例: 椭圆的长半轴:短半轴
  • 渐变通过添加蒙版mask-image处理 mask-image
  1. 椭圆上元素的计算方式按照圆心角的方式计算 image.png
// 圆锥渐变
background: conic-gradient(rgb(223, 232, 2550%rgb(223, 232, 25520%rgb(221, 241, 25420%rgb(221, 241, 25440%rgb(223, 249, 23940%rgb(223, 249, 23960%rgb(245, 231, 25460%rgb(245, 231, 25480%rgb(254, 244, 21280%rgb(254, 244, 212100%);
// 添加蒙版
mask-image: radial-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));

变形函数

通过css绘制扇形,基础形状是一个宽为长半轴高为短半轴的长方形,用css3 的 变形函数 transform: skew来实现, 关于怎么倾斜需要自己多试一试 skew

第一步: 画椭圆,然后定义子元素, 每个子元素背景是一个长方形 image.png 第二步:对长方形做倾斜调整,倾斜相应的角度即下图的展示

image.png 依次通过transform: skewY倾斜完成:

image.png 第三步:通过transform: scale(2.2, 1);缩放处理,把圆形背景拉成椭圆

image.png 第四步:背景父元素宽高是以短半轴为半径的圆形,通过overflow: hidden;隐藏超出的显示即可

image.png

注意: 倾斜的角度最大为90度,如果所占的比例超过90度,需要用多个元素拼接成一个大的扇形区域

SVG实现

第一步: 根据坐标点画出扇形区域

image.png

// M:圆心坐标(a,b) L: 连接线初始坐标(240, 0) /
// A:长轴半径 短轴半径 / 开始角度: 0 / 是否是大圆弧标识 0 / 方向(1顺时针) / 终点(x y) /
// L: 连线结束坐标
<svg xmlns="http://www.w3.org/2000/svg" id="bg">
<!--M:圆心坐标(100,100) L: 链接线坐标(240, 0) / A:100长轴半径 50短轴半径 / 开始角度: 0 / 是否是大圆弧标识 0 / 方向(1顺时针) / 终点(x y) L: 连线结束坐标-->
  <path d="M 240 180 L 240 0 A 240 180 0 0 1 480 236 L 240 180" fill="#FFD2D2"></path>
</svg>

依次画出各个区域的扇形

image.png

第二步:渐变背景处理

image.png

        <svg xmlns="http://www.w3.org/2000/svg">
            <template v-for="(item, index) in pathList" :key="index">
                <defs>
                    <radialGradient
                        :id="`svg${index}`"
                        gradientUnits="userSpaceOnUse"
                        :gradientTransform="`scale(${item.a / item.b}, 1)`"
                        :cx="item.b"
                        :cy="item.b"
                        :r="item.b"
                        spreadMethod="pad">
                        <stop offset="0" :stop-color="item.color" stop-opacity="1"/>
                        <stop offset="0.2" :stop-color="item.color" stop-opacity="0.992" />
                        <stop offset="0.4" :stop-color="item.color" stop-opacity="0.936" />
                        <stop offset="0.6" :stop-color="item.color" stop-opacity="0.784" />
                        <stop offset="0.8" :stop-color="item.color" stop-opacity="0.488" />
                        <stop offset="1" :stop-color="item.color" stop-opacity="0.166"/>
                    </radialGradient>
                </defs>
                <path
                    :d="item.path"
                    :style="{'fill': `url(#svg${index})`}"/>
            </template>
        </svg>

背景也是以短半轴为半径的正圆通过scale横向缩放处理

JS动态计算节点位置

emmm 动态的肯定需要计算,什么椭圆方程、弧度,焦点,正弦、余弦全还给老师了o(╥﹏╥)o,重新捡起来

image.png

image.png

image.png

image.png

Math.sin(x) x 的正弦值。返回值在 -1.0 到 1.0 之间的数

Math.cos(x) x 的余弦值。返回的是 -1.0 到 1.0 之间的数

这两个函数中的X 都是指的弧度而非角度

弧度的计算公式为: 角度*(PI/180)

角度与弧度的转换

  1. 角度定义: 两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当弧长正好等于圆周长的360分之一时,两条射线的夹角的大小为1度。(单位: º)
  2. 弧度定义: 两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度(单位:rad) 可简单理解为: 弧度 = 弧长 / 半径

image.png

  1. 弧长与弧度
    1. 圆的周长C的计算公式为:C = 2πr = πd (r - 半径;d - 直径)
    2. 圆一周的弧长为:2πr (弧长 = 周长)
    3. 圆一周的弧度为:2πr / r = 2π (根据: 弧度 = 弧长 / 半径)
  2. 弧度与角度的转换 根据圆为360 º,弧度为2π,即 360º = 2π
    1. 角度转弧度:2π / 360 = π / 180 ≈ 0.0174rad, 即: 角度 * (π / 180) = 弧度 例如:将30º转为弧度rad 30º * (π / 180)= 0.523320 rad

    2. 弧度转角度: 360 / 2π = 180 / π ≈ 57.3º, 即: 弧度 * (180 / π) = 角度 例如:将0.523320rad转为度º 0.523320rad * (180 / π) = 29.9992352688º

计算椭圆上每个元素的坐标

圆心角的计算方式

实际上第一个扇形区域是蓝色背景

image.png

image.png

希望是从12点钟的方向开始顺时针旋转,需要处理下当前的角度 即 角度 - 90

image.png

// 圆心角方式计算,按照角度占比分配,但是弧长分布不均匀
// 矢量计算中都是以弧度作为计量单位, 弧度 = 角度 * Math.PI/180
// 初始值(起点)为12点钟方向, 角度 - 90
const radian = (angle - 90) * Math.PI / 180;
const r = a * b / (Math.sqrt(Math.pow(b * Math.cos(radian), 2)
    + Math.pow(a * Math.sin(radian), 2)));
const x = r * Math.cos(radian) + a;
const y = r * Math.sin(radian) + b;
离心角的计算方式

image.png 解决思路:根据三角形的正弦、余弦来得值 假设椭圆的圆心坐标是(a, b),a为长半轴,b为短半轴

初始值(起点)为12点钟方向, 角度增大时为顺时针方向, 求X坐标和Y坐标的方法是:

X坐标 = a + Math.sin(角度 * (Math.PI / 180)) * a

Y坐标 = b - Math.cos(角数 * (Math.PI / 180)) * b

// 离心角方式计算,弧长分布相对均匀但是角度有误差
// 矢量计算中都是以弧度作为计量单位, 弧度= 角度 * Math.PI/180
// 初始值(起点)为12点钟方向,圆心的坐标为(a, b)
const radian = angle * Math.PI / 180;
const x = a + Math.sin(radian) * a;
// 此处是'-'号,因为我们要得到的Y是相对于(0,0)而言的
const y = b - Math.cos(radian) * b;

注意: 离心角的方式视觉上看起来好看一些,但是如果区域分布不均匀,长半轴部分的分布跟短半轴部分的分布同一角度看起来差别有些大,两种方式都能实现,看合适哪一种即可

吭哧吭哧学了一通,又是画又是算的 加油你是最月半的

方案对比

圆锥渐变、变形函数

优点:通过css实现,conic-gradient, transform: skew

缺点:

  1. 渐变通过蒙版mask-image: radial-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));处理, 是对整体背景的处理,无法做到单独扇区区域的处理,过渡生硬
  2. 不管是锥形渐变还是倾斜都是按照以短半轴为半径圆的形状来处理的,需要通过scale缩放处理

SVG实现

优点:背景渐变过渡自然,实现效果最佳

缺点:需要js动态计算坐标位置来画扇形区域

参考文章