如何计算圆弧的SVG路径


191

给定一个以(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

Answers:


379

扩展@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" />

现场演示


9
太好了!请注意,该arcSweep变量实际上是在控制large-arc-flagsvg A参数。在上面的代码中,sweep-flag参数的值始终为零。 arcSweep应该应该重命名为诸如此类longArc
史蒂芬·格罗斯马克

感谢@PocketLogic,根据您的建议(最终)进行了更新。
opsb

2
真的很有帮助,谢谢。我发现的唯一问题是,如果使用负角,largeArc逻辑将不起作用。这适用于-360到+360
js,

我错过了为什么标准不以相同的方式定义圆弧的观点。
polkovnikov.ph

4
并且不要忘记像这样切割少量的弧长 endAngle - 0.0001,否则,将不会渲染完整的弧。
Saike

128

您要使用椭圆形Arc命令。不幸的是,这要求您指定起点和终点的笛卡尔坐标(x,y)而不是极坐标(半径,角度),因此必须进行一些数学运算。这是一个应该起作用的JavaScript函数(尽管我尚未测试过),并且我希望这是不言而喻的:

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偏向)和结束坐标(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,请查看实现说明。他们描述了两种可能的圆弧参数化,“端点参数化”(他们选择的一种)和“中心参数化”(就像问题使用的一样)。在“端点参数化”的描述中,他们说:

端点参数化的优点之一是,它允许使用一致的路径语法,其中所有路径命令都以新的“当前点”的坐标结尾。

因此,基本上,这是电弧的副作用,它被视为较大路径的一部分,而不是其自己的单独对象。我想如果您的SVG渲染器不完整,它可以跳过任何不知道如何渲染的路径组件,只要知道它们接受多少参数即可。或者,它可以并行渲染具有许多组件的路径的不同块。或者,也许他们这样做是为了确保舍入错误不会沿着复杂路径的长度累积。

这些实现笔记对于原始问题也很有用,因为它们具有更多的数学伪代码,可用于在两个参数化之间进行转换(当我第一次编写此答案时,我并没有意识到)。


1
看起来不错,请尝试下一个。如果它是圆弧的“更多”(当圆弧大于圆的一半时)并且large-arc-flag必须将其从0切换为1 的事实可能会引入一些错误。
nonopolarity 2011年

嗯,这为什么是svg arcs的api?
clocksmith '16

16

我稍微修改了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>

5
该codepen链接似乎并没有对我的工作(铬)不
雷Hulha

圆弧绘制在SVG边界之外。提高SVG的身高和变化centerXcenterY以100为例子。
A.Akram

您还可以在父svg元素中显式设置一个视图框。例如viewBox="0 0 500 500"
哈肯盖

7

这是一个老问题,但是我发现代码很有用,并节省了三分钟的时间:)因此,我在@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>

然后你去!


5

@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>


4

我想对@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));

3

图片和一些Python

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

然后的参数Arc如下:

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

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

<svg viewBox="0 0 500 500">
    <path fill="red" d="
    M 250 250
    A 100 100 0 0 0 450 250
    Z"/> 
</svg>

在此处输入图片说明

您将设置与起点M与参数的终点xfyfA

我们正在寻找圆,因此我们设置rxry基本上等于现在,它将尝试找到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))

在我写的这篇文章中,您可以有更详细的解释。


3

对于寻求答案的人(我也是),请注意- 如果不是必须使用arc,绘制局部圆的简单得多的解决方案是使用stroke-dasharraySVG <circle>

将破折号数组分为两个元素,并将其范围缩放到所需角度。起始角度可使用调节stroke-dashoffset

看不到一个余弦。

带有说明的完整示例:https : //codepen.io/mjurczyk/pen/wvBKOvP


2

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”,则会绘制“正常”的逆时针圆弧,我认为这更直接。参见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));

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;
};

2
您可以说可以通过利用ES6模板文字来使示例更清晰:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins

0

基于所选答案的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;

0

您可以使用我为上述答案编写的JSFiddle代码:

https://jsfiddle.net/tyw6nfee/

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

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

1
我对您的代码段做了一些更改,只是为了输出视觉效果。看一下: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.