Javascript动态创建SVG圆弧
0. 起源
需要做一个展示统计数据(百分比)的小部件,默认情况下该小部件是隐藏的。页面右边放置一个圆形的按钮,当点击按钮时小部件从右边滑出显示。
本着尽可能多的展示数据而又不失简约的原则,希望在按钮上展示排名前几名数据的值,这样的话,不必手动点击按钮就知道数据的大概情况,增强了按钮的表现力。通过多个同心圆弧可以做到这一点。
要绘制多个圆弧,可先从绘制一个圆弧着手。
1. 问题描述
先看个草图,如下:
其中,点 是圆弧的起点,点 是圆心,点B是圆弧的终点, 是 与 的夹角,即 。 轴的箭头是朝下的,因为SVG使用的是屏幕坐标系。
已知:
1. 圆弧起点
的坐标
2. 圆弧的半径
3. 圆弧终点
与起点
构成的圆心角
,单位为度
4. 圆弧线条的宽度
5. 圆弧线条的颜色
为简化问题,先做一些假设:
1. 圆弧起点
为圆弧上
坐标值最小的点(在屏幕坐标系中)
2. 只按逆时针方向绘制圆弧(起点到终点)
求
用Javascript动态绘制出符合输入要求的SVG弧形。
2. SVG中的path
SVG中的path
元素可以用来绘复杂的图形。它的形状是通过属性d
来定义的,属性d
的值是”命令+参数”序列。有过在AutoCAD
中使用命令来绘图的应该能很快理解。
这里我们只需要用到两个命令:M
和A
命令:
M
x y
把画笔移动到点 (M
是 Move的缩写)A
rx ry x-axis-rotation large-arc-flag sweep-flag x y
按给定参数绘制圆弧,各部分参数的解释如下:
- rx ry
圆弧的 轴半径和 轴半径 (A
命令也可以用来绘制椭圆弧的,如果是圆弧则 ) - x-axis-rotation
轴的旋转角度,这里我们用不到,设为 0 - large-arc-flag
是否为大圆弧,1为是,0为否 - sweep-flag
圆弧绘制的方向(从起点到终点),1为顺时针,0为逆时针 - x y
圆弧终点的坐标
- rx ry
关于图形样式的控制,path
的stroke
属性用来控制线条的颜色,stroke-width
属性用来控制线条宽度,fill
属性用来控制线条所包围部分的填充颜色,默认是黑色。
2.1 分析
根据以上对path
元素的属性d
中M
和A
命令的解说,结合第一部分的问题描述,可以有以下分析结果:
- 用
M
命令来移动到圆弧的起点 ,即 M startX startY - 圆弧的半径rx 和 ry可以从已知条件 中求得
- 轴的旋转角度 x-axis-rotation,我们不管这个参数,这里设为0
- 是否为大圆弧 large-arc-flag 这个参数,我们可以根据 这个参数计算得出,如果 ,则为大圆弧,反之为小圆弧
- 圆弧绘制的方向 sweep-flag,问题描述的假设部分规定绘制方向为逆时针,所以该值为0
- 线条宽度,对应
path
的stroke-width
属性 - 线条颜色,对应
path
的stroke
属性 path
的fill
属性应设为none
,即无填充
现在就剩下圆弧的终点坐标
为未知数了。
回忆一下之前学过的平面几何知识,结合问题描述的草图,可得出如下关系式:
为了与已知条件中的角度的单位一致,公式中用的是角度,实际在程序中,需要转换为弧度。可以用如下公式:
其中, 是以弧度为单位表示的角度, 是以度为单位表示的角度。
2.2 注意点
SVG中,对于圆弧而言,线条的宽度不包含在半径中,线条宽度是向外延伸的。因此在后续参数的计算中需要考虑到这一点,相关参数需要根据线条宽度来调节。
3.编码实现
根据以上分析,编写一个JS函数,生成一个SVGpath
元素对象,如下:
function createSVGPath(startX, startY, R, theta, width, color) {
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var realR = R - width;
var dArr = ["M" + startX, startY + width, "A" + realR, realR, 0, theta>=180 ? 1 : 0, 0];
var cx = startX, cy = startY + R;
var theta2 = theta%360;
// 避免360度与0度一样的情况
theta = theta > 0 && theta2 == 0 ? 359.9 : theta2;
var alpha = (theta + 90)/180 * Math.PI;
var dx = realR * Math.cos(alpha);
var dy = realR * Math.sin(alpha);
var x = cx + dx, y = cy - dy;
dArr.push(x.toFixed(2));
dArr.push(y.toFixed(2));
var d = dArr.join(" ");
path.setAttribute('d', d);
path.setAttribute('stroke', color);
path.setAttribute('stroke-width', width);
path.setAttribute('fill', 'none');
return path;
}
4. 测试
创建一个html文档,其中包含一个空的svg元素,然后用javascript动态生成一系列圆弧,从360到0度,相邻间隔45度。
源代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title>动态生成SVG圆弧测试</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="150px" width="150px" id="svg-01">
</svg>
</body>
<script>
function createSVGPath(startX, startY, R, theta, width, color) {
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var realR = R - width;
var dArr = ["M" + startX, startY + width, "A" + realR, realR, 0, theta>=180 ? 1 : 0, 0];
var cx = startX, cy = startY + R;
var theta2 = theta%360;
// 避免360度与0度一样的情况
theta = theta > 0 && theta2 == 0 ? 359.9 : theta2;
var alpha = (theta + 90)/180 * Math.PI;
var dx = realR * Math.cos(alpha);
var dy = realR * Math.sin(alpha);
var x = cx + dx, y = cy - dy;
dArr.push(x.toFixed(2));
dArr.push(y.toFixed(2));
var d = dArr.join(" ");
path.setAttribute('d', d);
path.setAttribute('stroke', color);
path.setAttribute('stroke-width', width);
path.setAttribute('fill', 'none');
return path;
}
var svg = document.getElementById('svg-01');
var colors = ['red', 'orange', 'yellow', 'green', 'blue', 'cyan', 'purple', 'black'];
for(var i=0; i<8; i++){
var startY = i*5;
var R = 70 - startY;
var theta = 360 - 45* i;
var path = createSVGPath(75, startY, R, theta, 5, colors[i]);
svg.appendChild(path);
}
</script>
</html>
效果如下: