背景
遇到了一个显示色块的问题,这些圆形色块如果顺序排开,横向空间会显得过长,九宫格和饼状统计图的样式就不太美观。因此,提出了如上图所示的排列方案。
解决思路
首先分析这种排列方式:小圆形相互堆叠,围成一个类似多边形的形状。通过观察和思考,我们发现这些小圆的圆心是平均分布在一个大圆的圆周上的,如下图所示:
基于这个思路,我们可以先根据小圆的数量计算出它们的圆心位置。
然后,通过控制绘制顺序,使后绘制的小圆覆盖先绘制的小圆,从而达到堆叠效果。
位置计算方法
原理
既然是圆形,而且是确定环形的位置,那肯定得选择极坐标系。
在极坐标系中,我们知道点的位置由以下两个参数确定:
r
(半径或径向距离): 从原点到点的距离。θ
(角度): 从极轴到点的方向,与极轴的夹角,通常以弧度radian
表示。
既然是平均分布的,那每个圆心所在的角度应该是2π/n
,其中n
就是圆的数量
也就是把360°进行均分
首先应该计算第一个小圆心在极坐标系中的角度,circleCount
是圆形的总数量,最后i
就是第几个圆,
第一个圆的i
就是0
const angle = (2 * Math.PI / circleCount) * i;
然后把极坐标转换成直角坐标,因为canvas不能直接传入极坐标。
极坐标和直角坐标转换(可以不看)
我们知道极坐标和直角坐标的转换公式是:
$$ x_i = centerX + largeRadius \cdot \cos\left(\frac{2\pi \cdot i}{n}\right) $$
$$ y_i = centerY + largeRadius \cdot \sin\left(\frac{2\pi \cdot i}{n}\right) $$
其中:
centerX
和centerY
是大圆的圆心坐标。largeRadius
是大圆的半径,决定了小圆心距大圆心的距离。i
是小圆的索引,n
是小圆的总数。
假设有 5 个小圆 (n=5
),这些小圆均匀分布在一个半径为 60 像素的大圆圆周上。对于第一个小圆(i=0
),它的角度$ θ_0 $为 0 度(即 0 弧度),因此圆心的坐标为:
$$ x_0 = centerX + 60 \cdot \cos(0) = centerX + 60 $$ $$ y_0 = centerY + 60 \cdot \sin(0) = centerY + 0 $$
对于第二个小圆(i=1
),它的角度 $ 0_1 $ 为:
$$ \theta_1 = \frac{2\pi \cdot 1}{5} = \frac{2\pi}{5} $$
相应的圆心坐标为:
$$ x_1 = centerX + 60 \cdot \cos\left(\frac{2\pi}{5}\right) $$
$$ y_1 = centerY + 60 \cdot \sin\left(\frac{2\pi}{5}\right) $$
轨迹圆(大圆)半径的确定
通过观察发现,如果两个小圆和五个小圆使用的大圆半径一样的画,美观度很低。
两个圆比例合适的时候,五个圆就显得有点紧凑。
五个圆比例合适的时候,而两个圆就显得有点间距过大。
所以就需要动态的调整圆的紧凑程度,也就是轨迹圆的半径。
经过手动调整后目测认为,在有 2 个小圆时候,轨迹圆的半径为小圆的70%最美观
5个圆的时候大圆和小圆半径相等最美观,所以2,3,4,5按比例调整百分比,
轨迹圆的半径分别是小圆半径的70%,80%,90%,100%,得到代码:
const largeRadius = smallRadius * (0.7 + (circleCount - 2) / 10);
代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas Circle Icon</title>
</head>
<body>
<canvas id="circleCanvas1" width="150" height="150" count="1" style="margin: 5px;"></canvas>
<canvas id="circleCanvas2" width="150" height="150" count="2" style="margin: 5px;"></canvas>
<canvas id="circleCanvas3" width="150" height="150" count="3" style="margin: 5px;"></canvas>
<canvas id="circleCanvas4" width="150" height="150" count="4" style="margin: 5px;"></canvas>
<canvas id="circleCanvas5" width="150" height="150" count="5" style="margin: 5px;"></canvas>
<script>
function drawCircles(canvasId) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
const count = parseInt(canvas.getAttribute('count'));
const colors = ['#1E90FF', '#800080', '#FFD700', '#32CD32', '#FF4500'];
const centerX = canvas.width / 2; // 画布中心X坐标
const centerY = canvas.height / 2; // 画布中心Y坐标
const smallRadius = 20; // 小圆的半径
const largeRadius = smallRadius * (0.7 + (count - 2) / 10); // 中心圆的半径,减小以使小圆重叠
for (let i = 0; i < count; i++) {
const angle = (2 * Math.PI / count) * i; // 计算每个小圆的角度
const x = centerX + largeRadius * Math.cos(angle); // 计算X坐标
const y = centerY + largeRadius * Math.sin(angle); // 计算Y坐标
ctx.beginPath();
ctx.arc(x, y, smallRadius, 0, Math.PI * 2);
ctx.fillStyle = colors[i];
ctx.fill();
}
}
drawCircles('circleCanvas1');
drawCircles('circleCanvas2');
drawCircles('circleCanvas3');
drawCircles('circleCanvas4');
drawCircles('circleCanvas5');
</script>
</body>
</html>