Javascript动态创建SVG圆弧

Javascript动态创建SVG圆弧

0. 起源

需要做一个展示统计数据(百分比)的小部件,默认情况下该小部件是隐藏的。页面右边放置一个圆形的按钮,当点击按钮时小部件从右边滑出显示。

本着尽可能多的展示数据而又不失简约的原则,希望在按钮上展示排名前几名数据的值,这样的话,不必手动点击按钮就知道数据的大概情况,增强了按钮的表现力。通过多个同心圆弧可以做到这一点。

要绘制多个圆弧,可先从绘制一个圆弧着手。

1. 问题描述

先看个草图,如下:
这里写图片描述

其中,点 A 是圆弧的起点,点 O 是圆心,点B是圆弧的终点, θ O B O A 的夹角,即 B O A Y 轴的箭头是朝下的,因为SVG使用的是屏幕坐标系。

已知
1. 圆弧起点 A 的坐标 ( s t a r t X , s t a r t Y )
2. 圆弧的半径 R
3. 圆弧终点 B 与起点 A 构成的圆心角 θ ,单位为度
4. 圆弧线条的宽度 w i d t h
5. 圆弧线条的颜色 c o l o r

为简化问题,先做一些假设
1. 圆弧起点 A 为圆弧上 Y 坐标值最小的点(在屏幕坐标系中)
2. 只按逆时针方向绘制圆弧(起点到终点)


用Javascript动态绘制出符合输入要求的SVG弧形。

2. SVG中的path

SVG中的path元素可以用来绘复杂的图形。它的形状是通过属性d来定义的,属性d的值是”命令+参数”序列。有过在AutoCAD中使用命令来绘图的应该能很快理解。

这里我们只需要用到两个命令:MA命令:

  • M x y
    把画笔移动到点 ( x , y ) (MMove的缩写)
  • A rx ry x-axis-rotation large-arc-flag sweep-flag x y
    按给定参数绘制圆弧,各部分参数的解释如下:
    • rx ry
      圆弧的 x 轴半径和 y 轴半径 A命令也可以用来绘制椭圆弧的,如果是圆弧则 r x = r y
    • x-axis-rotation
      x 轴的旋转角度,这里我们用不到,设为 0
    • large-arc-flag
      是否为大圆弧,1为是,0为否
    • sweep-flag
      圆弧绘制的方向(从起点到终点),1为顺时针,0为逆时针
    • x y
      圆弧终点的坐标 ( x , y )

关于图形样式的控制,pathstroke属性用来控制线条的颜色,stroke-width属性用来控制线条宽度,fill属性用来控制线条所包围部分的填充颜色,默认是黑色。

2.1 分析

根据以上对path元素的属性dMA命令的解说,结合第一部分的问题描述,可以有以下分析结果:

  1. M命令来移动到圆弧的起点 A ( s t a r t X , s t a r t Y ) ,即 M startX startY
  2. 圆弧的半径rxry可以从已知条件 R 中求得
  3. x 轴的旋转角度 x-axis-rotation,我们不管这个参数,这里设为0
  4. 是否为大圆弧 large-arc-flag 这个参数,我们可以根据 θ 这个参数计算得出,如果 θ >= 180 ,则为大圆弧,反之为小圆弧
  5. 圆弧绘制的方向 sweep-flag问题描述假设部分规定绘制方向为逆时针,所以该值为0
  6. 线条宽度,对应pathstroke-width属性
  7. 线条颜色,对应pathstroke属性
  8. pathfill属性应设为none,即无填充

现在就剩下圆弧的终点坐标 B ( x , y ) 为未知数了
回忆一下之前学过的平面几何知识,结合问题描述的草图,可得出如下关系式:

(1) { x = c x + R cos ( 90 + θ ) y = c y R sin ( 90 + θ )

为了与已知条件中的角度的单位一致,公式中用的是角度,实际在程序中,需要转换为弧度。可以用如下公式:
α = θ 180 π

其中, α 是以弧度为单位表示的角度, θ 是以度为单位表示的角度。

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>

效果如下:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/chunyuan314/article/details/81186980