无心的专栏

对于代码有洁癖的人,才能写出更好的代码。

[SVG] 根据SVG椭圆弧路径参数计算中心点坐标、起始角度、结束角度的Javascript函数

SVG spec 1.2以及之前的版本标准中,都只有一种绘制椭圆弧的方式,即以起点、终点、长半轴、短半轴、大小弧标记、顺逆时针方向标记、倾角为参数来确定一段弧。这个方法很强大并灵活,可绘制任意的椭圆弧。有时候我们需要计算圆弧的圆心和起始角度、结束角度,虽然标准官方文档给出了计算公式的描述,但是没有给出直接的代码。我根据标准文档以及网上的资料,写了一个Javascript函数来做这件事。

根据SVG椭圆弧路径参数计算中心点坐标、起始角度、结束角度的Javascript函数:


    function  radian( ux, uy, vx, vy ) {
        var  dot = ux * vx + uy * vy;
        var  mod = Math.sqrt( ( ux * ux + uy * uy ) * ( vx * vx + vy * vy ) );
        var  rad = Math.acos( dot / mod );
        if( ux * vy - uy * vx < 0.0 ) {
            rad = -rad;
        }
        return rad;
    }

    // svg : [A | a] (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
    // x1 y1 rx ry φ fA fS x2 y2
    // sample :  svgArcToCenterParam(200,200,50,50,0,1,1,300,200)
    function svgArcToCenterParam(x1, y1, rx, ry, phi, fA, fS, x2, y2) {
        var cx, cy, startAngle, deltaAngle, endAngle;
        var PIx2 = Math.PI * 2.0;

        if (rx < 0) {
            rx = -rx;
        }
        if (ry < 0) {
            ry = -ry;
        }
        if (rx == 0.0 || ry == 0.0) { // invalid arguments
            throw Error('rx and ry can not be 0');
        }

        var s_phi = Math.sin(phi);
        var c_phi = Math.cos(phi);
        var hd_x = (x1 - x2) / 2.0; // half diff of x
        var hd_y = (y1 - y2) / 2.0; // half diff of y
        var hs_x = (x1 + x2) / 2.0; // half sum of x
        var hs_y = (y1 + y2) / 2.0; // half sum of y

        // F6.5.1
        var x1_ = c_phi * hd_x + s_phi * hd_y;
        var y1_ = c_phi * hd_y - s_phi * hd_x;

        // F.6.6 Correction of out-of-range radii
        //   Step 3: Ensure radii are large enough
        var lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
        if (lambda > 1) {
            rx = rx * Math.sqrt(lambda);
            ry = ry * Math.sqrt(lambda);
        }

        var rxry = rx * ry;
        var rxy1_ = rx * y1_;
        var ryx1_ = ry * x1_;
        var sum_of_sq = rxy1_ * rxy1_ + ryx1_ * ryx1_; // sum of square
        if (!sum_of_sq) {
            throw Error('start point can not be same as end point');
        }
        var coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
        if (fA == fS) { coe = -coe; }

        // F6.5.2
        var cx_ = coe * rxy1_ / ry;
        var cy_ = -coe * ryx1_ / rx;

        // F6.5.3
        cx = c_phi * cx_ - s_phi * cy_ + hs_x;
        cy = s_phi * cx_ + c_phi * cy_ + hs_y;

        var xcr1 = (x1_ - cx_) / rx;
        var xcr2 = (x1_ + cx_) / rx;
        var ycr1 = (y1_ - cy_) / ry;
        var ycr2 = (y1_ + cy_) / ry;

        // F6.5.5
        startAngle = radian(1.0, 0.0, xcr1, ycr1);

        // F6.5.6
        deltaAngle = radian(xcr1, ycr1, -xcr2, -ycr2);
        while (deltaAngle > PIx2) { deltaAngle -= PIx2; }
        while (deltaAngle < 0.0) { deltaAngle += PIx2; }
        if (fS == false || fS == 0) { deltaAngle -= PIx2; }
        endAngle = startAngle + deltaAngle;
        while (endAngle > PIx2) { endAngle -= PIx2; }
        while (endAngle < 0.0) { endAngle += PIx2; }

        var outputObj = { /* cx, cy, startAngle, deltaAngle */
            cx: cx,
            cy: cy,
            startAngle: startAngle,
            deltaAngle: deltaAngle,
            endAngle: endAngle,
            clockwise: (fS == true || fS == 1)
        }

        return outputObj;
    }

用法示例:

SVG:

    <path d="M 0 100 A 60 60 0 0 0 100 0"/>

JS:

    var result = svgArcToCenterParam(0, 100, 60, 60, 0, 0, 0, 100, 0);
    console.log(result);

将得到如下返回结果:

    {
        cx: 49.99999938964844,
        cy: 49.99999938964844,
        startAngle: 2.356194477985314,
        deltaAngle: -3.141592627780225,
        endAngle: 5.497787157384675,
        clockwise: false
    }
以上实现代码最早公布于2012年,但有bug,某些用例下无法得到正确结果,现已修正。如有疑问,可以留言给我。



阅读更多
个人分类: SVG JS/DHTML/CSS
上一篇[ASP技巧] 巧给FSO文件夹列表内容排序
下一篇一个在浏览器运行的华容道、&quot;移十五&quot;之类的滑块游戏自动求解与演示的GWT源码
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭