首页 文章

如何计算圆弧(圆弧)的SVG路径

提问于
浏览
156

给定一个以(200,200),半径25为中心的圆,如何绘制从270度到135度的弧和一个从270度到45度的弧?

0度表示它在x轴(右侧)上(意味着它是3点钟位置)270度表示它是12点钟位置,90表示它是6点钟位置

更一般地说,圆弧的一部分的圆弧路径是什么

x, y, r, d1, d2, direction

含义

center (x,y), radius r, degree_start, degree_end, direction

12 回答

  • 297

    扩展@ wdebeaum的好答案,这是一个生成弧形路径的方法:

    function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
      var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
    
      return {
        x: centerX + (radius * Math.cos(angleInRadians)),
        y: centerY + (radius * Math.sin(angleInRadians))
      };
    }
    
    function describeArc(x, y, radius, startAngle, endAngle){
    
        var start = polarToCartesian(x, y, radius, endAngle);
        var end = polarToCartesian(x, y, radius, startAngle);
    
        var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
    
        var d = [
            "M", start.x, start.y, 
            "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
        ].join(" ");
    
        return d;       
    }
    

    使用

    document.getElementById("arc1").setAttribute("d", describeArc(200, 400, 100, 0, 180));
    

    并在你的HTML中

    <path id="arc1" fill="none" stroke="#446688" stroke-width="20" />
    

    Live demo

  • -1

    您想使用elliptical Arc command . 不幸的是,这需要你指定起点和终点的笛卡尔坐标(x,y)而不是你拥有的极坐标(半径,角度),所以你必须做一些数学运算 . 这里's a JavaScript function which should work (though I haven' t测试了它,我希望这是相当不言自明的:

    function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
      var angleInRadians = angleInDegrees * Math.PI / 180.0;
      var x = centerX + radius * Math.cos(angleInRadians);
      var y = centerY + radius * Math.sin(angleInRadians);
      return [x,y];
    }
    

    哪个角度对应于哪个时钟位置将取决于坐标系;只需交换和/或否定sin / cos术语 .

    arc命令具有以下参数:

    rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y
    

    第一个例子:

    rx = ry = 25和 x-axis-rotation = 0,因为您需要一个圆而不是椭圆 . 您可以使用上面的函数分别计算起始坐标(应该是 M ove到)和结束坐标(x,y),分别是屈服(200,175)和约(182.322,217.678) . 到目前为止,鉴于这些限制,实际上可以绘制四个弧,因此两个标志选择其中一个 . 我猜你可能想要在减小角度的方向上绘制一个小弧(意思是 large-arc-flag = 0)(意思是 sweep-flag = 0) . 总之,SVG路径是:

    M 200 175 A 25 25 0 0 0 182.322 217.678
    

    对于第二个示例(假设您指的是相同的方向,因此是一个大弧),SVG路径是:

    M 200 175 A 25 25 0 1 0 217.678 217.678
    

    我再次测试过这些 .

    (编辑2016-06-01)如果像@clocksmith一样,你想知道他们为什么选择这个API,那么看一下implementation notes . 它们描述了两种可能的弧参数化,"endpoint parameterization"(他们选择的那种)和"center parameterization"(这就像问题所用的那样) . 在"endpoint parameterization"的描述中,他们说:

    endpoints 参数化的一个优点是它允许一致的路径语法,其中所有路径命令都以新“当前点”的坐标结束 .

    所以基本上它是弧的副作用被视为更大路径的一部分而不是它们自己的独立对象 . 我想如果你的SVG渲染器不完整,它可以跳过它不知道如何渲染的任何路径组件,只要它知道它们采用了多少个参数 . 或者它可以实现具有许多组件的路径的不同块的并行渲染 . 或许他们这样做是为了确保舍入错误不会沿着复杂路径的长度积累 .

    实现说明对于原始问题也很有用,因为它们有更多的数学伪代码用于在两个参数化之间进行转换(我在第一次写这个答案时没有意识到) .

  • 1

    我略微修改了opsb的答案,并支持填充圆圈扇区 . http://codepen.io/anon/pen/AkoGx

    JS

    function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
      var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
    
      return {
        x: centerX + (radius * Math.cos(angleInRadians)),
        y: centerY + (radius * Math.sin(angleInRadians))
      };
    }
    
    function describeArc(x, y, radius, startAngle, endAngle){
    
        var start = polarToCartesian(x, y, radius, endAngle);
        var end = polarToCartesian(x, y, radius, startAngle);
    
        var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
    
        var d = [
            "M", start.x, start.y, 
            "A", radius, radius, 0, arcSweep, 0, end.x, end.y,
            "L", x,y,
            "L", start.x, start.y
        ].join(" ");
    
        return d;       
    }
    
    document.getElementById("arc1").setAttribute("d", describeArc(200, 400, 100, 0, 220));
    

    HTML

    <svg>
      <path id="arc1" fill="orange" stroke="#446688" stroke-width="0" />
    </svg>
    
  • 3

    我想评论一下@Ahtenus的答案,特别是关于Ray Hulha的评论说,codepen没有显示任何弧形,但我的声誉不够高 .

    这个codepen无法工作的原因是它的html错误,行程宽度为零 .

    我修复了它并在这里添加了第二个例子:http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

    html:

    <svg>
        <path id="theSvgArc"/>
        <path id="theSvgArc2"/>
    </svg>
    

    相关的CSS:

    svg {
        width  : 500px;
        height : 500px;
    }
    
    path {
        stroke-width : 5;
        stroke       : lime;
        fill         : #151515;
    }
    

    javascript:

    document.getElementById("theSvgArc").setAttribute("d", describeArc(150, 150, 100, 0, 180));
    document.getElementById("theSvgArc2").setAttribute("d", describeArc(300, 150, 100, 45, 190));
    
  • 1

    @ opsb的答案很整洁,但中心点不准确,而且,正如@Jithin所说,如果角度是360,那么根本就没有绘制 .

    @Jithin解决了360问题,但是如果你选择的程度低于360度,那么你将获得一条关闭弧循环的线,这不是必需的 .

    我解决了这个问题,并在下面的代码中添加了一些动画:

    function myArc(cx, cy, radius, max){       
           var circle = document.getElementById("arc");
            var e = circle.getAttribute("d");
            var d = " M "+ (cx + radius) + " " + cy;
            var angle=0;
            window.timer = window.setInterval(
            function() {
                var radians= angle * (Math.PI / 180);  // convert degree to radians
                var x = cx + Math.cos(radians) * radius;  
                var y = cy + Math.sin(radians) * radius;
               
                d += " L "+x + " " + y;
                circle.setAttribute("d", d)
                if(angle==max)window.clearInterval(window.timer);
                angle++;
            }
          ,5)
     }     
    
      myArc(110, 110, 100, 360);
    
    <svg xmlns="http://www.w3.org/2000/svg" style="width:220; height:220;"> 
        <path d="" id="arc" fill="none" stroke="red" stroke-width="2" />
    </svg>
    
  • 3

    wdebeaum的原始polarToCartesian函数是正确的:

    var angleInRadians = angleInDegrees * Math.PI / 180.0;
    

    使用以下内容反转起点和终点:

    var start = polarToCartesian(x, y, radius, endAngle);
    var end = polarToCartesian(x, y, radius, startAngle);
    

    令人困惑(对我而言),因为这会扭转扫描标志 . 使用:

    var start = polarToCartesian(x, y, radius, startAngle);
    var end = polarToCartesian(x, y, radius, endAngle);
    

    随着sweep-flag = "0"绘制"normal"反时钟弧,我认为这更直接 . 见https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

  • 2

    对@ opsb的回答略有修改 . 我们不能画一个完整的用这种方法圈起来 . 即如果我们给(0,360)它将不会绘制任何东西 . 所以稍作修改就可以解决这个问题 . 显示有时达到100%的分数可能很有用 .

    function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
      var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
    
      return {
        x: centerX + (radius * Math.cos(angleInRadians)),
        y: centerY + (radius * Math.sin(angleInRadians))
      };
    }
    
    function describeArc(x, y, radius, startAngle, endAngle){
    
        var endAngleOriginal = endAngle;
        if(endAngleOriginal - startAngle === 360){
            endAngle = 359;
        }
    
        var start = polarToCartesian(x, y, radius, endAngle);
        var end = polarToCartesian(x, y, radius, startAngle);
    
        var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
    
        if(endAngleOriginal - startAngle === 360){
            var d = [
                  "M", start.x, start.y, 
                  "A", radius, radius, 0, arcSweep, 0, end.x, end.y, "z"
            ].join(" ");
        }
        else{
          var d = [
              "M", start.x, start.y, 
              "A", radius, radius, 0, arcSweep, 0, end.x, end.y
          ].join(" ");
        }
    
        return d;       
    }
    
    document.getElementById("arc1").setAttribute("d", describeArc(120, 120, 100, 0, 359));
    
  • 0

    这是一个老问题,但我发现代码很有用并且让我节省了三分钟的思考:)所以我在 @opsb 的答案中添加了一个小扩展 .

    如果您想将此弧转换为切片(以允许填充),我们可以稍微修改代码:

    function describeArc(x, y, radius, spread, startAngle, endAngle){
        var innerStart = polarToCartesian(x, y, radius, endAngle);
      	var innerEnd = polarToCartesian(x, y, radius, startAngle);
        var outerStart = polarToCartesian(x, y, radius + spread, endAngle);
        var outerEnd = polarToCartesian(x, y, radius + spread, startAngle);
    
        var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
    
        var d = [
            "M", outerStart.x, outerStart.y,
            "A", radius + spread, radius + spread, 0, largeArcFlag, 0, outerEnd.x, outerEnd.y,
            "L", innerEnd.x, innerEnd.y, 
            "A", radius, radius, 0, largeArcFlag, 1, innerStart.x, innerStart.y, 
            "L", outerStart.x, outerStart.y, "Z"
        ].join(" ");
    
        return d;
    }
    
    function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
      var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
    
      return {
        x: centerX + (radius * Math.cos(angleInRadians)),
        y: centerY + (radius * Math.sin(angleInRadians))
      };
    }
    
    var path = describeArc(150, 150, 50, 30, 0, 50)
    document.getElementById("p").innerHTML = path
    document.getElementById("path").setAttribute('d',path)
    
    <p id="p">
    </p>
    <svg width="300" height="300" style="border:1px gray solid">
      <path id="path" fill="blue" stroke="cyan"></path>
    </svg>
    

    你去吧!

  • 2

    ES6版本:

    const angleInRadians = angleInDegrees => (angleInDegrees - 90) * (Math.PI / 180.0);
    
    const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
        const a = angleInRadians(angleInDegrees);
        return {
            x: centerX + (radius * Math.cos(a)),
            y: centerY + (radius * Math.sin(a)),
        };
    };
    
    const arc = (x, y, radius, startAngle, endAngle) => {
        const fullCircle = endAngle - startAngle === 360;
        const start = polarToCartesian(x, y, radius, endAngle - 0.01);
        const end = polarToCartesian(x, y, radius, startAngle);
        const arcSweep = endAngle - startAngle <= 180 ? '0' : '1';
    
        const d = [
            'M', start.x, start.y,
            'A', radius, radius, 0, arcSweep, 0, end.x, end.y,
        ].join(' ');
    
        if (fullCircle) d.push('z');
        return d;
    };
    
  • 118

    图像和一些Python

    只是为了更好地澄清并提供另一种解决方案 . Arc [ A ]命令使用当前位置作为起点,因此必须先使用Moveto [M]命令 .

    那么 Arc 的参数如下:

    rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, xf, yf
    

    如果我们定义例如以下svg文件:

    <svg width="5cm" height="5cm" viewBox="0 0 500 500"
    
    xmlns="http://www.w3.org/2000/svg" version="1.1">
    

    以下代码将为您提供以下结果:

    <g stroke="none" fill="lime">
    <path d="
    M 250 250
    A 100 100 0 0 0 450 250
    Z"/> 
    </g>
    

    enter image description here

    您将使用参数 xfyfA 设置 M 结束点的起点 .

    我们正在寻找圆圈所以我们设置 rx 等于 ry 这样做基本上现在它将尝试找到与起点和终点相交的半径 rx 的所有圆圈 .

    import numpy as np
    
    def write_svgarc(xcenter,ycenter,r,startangle,endangle,output='arc.svg'):
        if startangle > endangle: 
            raise ValueError("startangle must be smaller than endangle")
    
        if endangle - startangle < 360:
            large_arc_flag = 0
            radiansconversion = np.pi/180.
            xstartpoint = xcenter + r*np.cos(startangle*radiansconversion)
            ystartpoint = ycenter - r*np.sin(startangle*radiansconversion)
            xendpoint = xcenter + r*np.cos(endangle*radiansconversion)
            yendpoint = ycenter - r*np.sin(endangle*radiansconversion)
            #If we want to plot angles larger than 180 degrees we need this
            if endangle - startangle > 180: large_arc_flag = 1
            with open(output,'a') as f:
                f.write(r"""<path d=" """)
                f.write("M %s %s" %(xstartpoint,ystartpoint))
                f.write("A %s %s 0 %s 0 %s %s" 
                        %(r,r,large_arc_flag,xendpoint,yendpoint))
                f.write("L %s %s" %(xcenter,ycenter))
                f.write(r"""Z"/>""" )
    
        else:
            with open(output,'a') as f:
                f.write(r"""<circle cx="%s" cy="%s" r="%s"/>"""
                        %(xcenter,ycenter,r))
    

    你可以在我写的this post中有更详细的解释 .

  • 15

    您可以使用我为上面的答案做的JSFiddle代码:

    https://jsfiddle.net/tyw6nfee/

    您需要做的就是更改最后一行console.log代码并为其指定自己的参数:

    console.log(describeArc(255,255,220,30,180));
      console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
    
  • 1

    ReactJS组件基于所选答案:

    import React from 'react';
    
    const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
        const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
    
        return {
            x: centerX + (radius * Math.cos(angleInRadians)),
            y: centerY + (radius * Math.sin(angleInRadians))
        };
    };
    
    const describeSlice = (x, y, radius, startAngle, endAngle) => {
    
        const start = polarToCartesian(x, y, radius, endAngle);
        const end = polarToCartesian(x, y, radius, startAngle);
    
        const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
    
        const d = [
            "M", 0, 0, start.x, start.y,
            "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
        ].join(" ");
    
        return d;
    };
    
    const path = (degrees = 90, radius = 10) => {
        return describeSlice(0, 0, radius, 0, degrees) + 'Z';
    };
    
    export const Arc = (props) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
        <g transform="translate(150,150)" stroke="#000" strokeWidth="2">
            <path d={path(props.degrees, props.radius)} fill="#333"/>
        </g>
    
    </svg>;
    
    export default Arc;
    

相关问题