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

Posted

技术标签:

【中文标题】如何计算(圆的)弧的 SVG 路径【英文标题】:How to calculate the SVG Path for an arc (of a circle) 【发布时间】:2011-08-09 20:00:38 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

Live demo

【讨论】:

这太棒了!注意arcSweep 变量实际上是控制large-arc-flag svg A 参数。在上面的代码中,sweep-flag 参数的值始终为零。 arcSweep 可能应该重命名为 longArc 感谢@PocketLogic,已(最终)根据您的建议进行了更新。 真的很有帮助,谢谢。我发现的唯一一件事是,如果您使用负角,大弧逻辑将不起作用。这适用于 -360 到 +360:jsbin.com/kopisonewi/2/edit?html,js,output 只有在 endAngle > startAngle 时才能正常工作。我不得不添加 while(endAngle 并且不要忘记切割少量的弧长如:endAngle - 0.0001,否则将不会渲染完整的弧。【参考方案2】:

您想使用elliptical Arc command。对您来说不幸的是,这需要您指定起点和终点的笛卡尔坐标 (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,因为你想要一个圆而不是椭圆。您可以使用上面的函数计算起始坐标(您应该 Move 到)和结束坐标 (x, y),分别产生 (200, 175) 和大约 (182.322, 217.678)。到目前为止,鉴于这些约束,实际上可以绘制四个弧,因此两个标志选择其中一个。我猜你可能想在角度减小的方向(意思是sweep-flag=0)画一个小弧(意思是large-arc-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。他们描述了两种可能的弧参数化,“端点参数化”(他们选择的那个)和“中心参数化”(就像问题使用的那样)。在“端点参数化”的描述中,他们说:

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

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

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

【讨论】:

看起来不错,下次试试。如果它是圆弧的“更多”(当圆弧超过圆的一半时)并且必须将 large-arc-flag 从 0 切换到 1,这一事实可能会引入一些错误。 呃,为什么这是 svg arcs 的 api?【参考方案3】:

我稍微修改了the answer of 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- />
</svg>

【讨论】:

codepen 链接似乎对我不起作用(Chrome) 弧线绘制在 SVG 边界之外。例如,增加 SVG 的高度并将 centerX,centerY 更改为 100。 您还可以在父 svg 元素中显式设置视图框。例如viewBox="0 0 500 500"【参考方案4】:

这是一个老问题,但我发现代码很有用,并为我节省了三分钟的思考时间:) 所以我在 @opsb's answer 中添加了一个小扩展。

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

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   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- />
</svg>

【讨论】:

我是否遗漏了什么,或者var e = circle.getAttribute("d");这行“什么都没有”?【参考方案6】:

对于寻求答案的人(我也是)的注意事项 - 如果使用弧不是强制性的,绘制部分圆的更简单的解决方案是使用 SVG @987654323 的 stroke-dasharray @。

将破折号数组分成两个元素,并将它们的范围缩放到所需的角度。可以使用stroke-dashoffset调整起始角度。

看不到一个余弦。

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

【讨论】:

感谢您的回答!【参考方案7】:

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

【讨论】:

【参考方案8】:

一张图片和一些 Python

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

那么Arc的参数如下:

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

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

<svg viewBox="0 0 500px 500px">
    <path fill="red" d="
    M 100 100
    A 40 40 0 0 0 180 100
    Z"/> 
</svg>

您将用M 设置起点,用A 的参数xfyf 设置终点。

我们正在寻找圆,所以我们将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有更详细的解释。

【讨论】:

【参考方案9】:

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

【讨论】:

您可以通过利用 ES6 模板文字使示例更清晰:const d = `M $start.x $start.y A $radius $radius 0 $largeArc 0 $end.x $end.y`【参考方案10】:

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

使用扫描标志 =“0”绘制“正常”逆时针弧线, 我认为这更直接。 见https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

【讨论】:

【参考方案11】:

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

【讨论】:

【参考方案12】:

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

【讨论】:

【参考方案13】:

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

https://jsfiddle.net/tyw6nfee/

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

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

【讨论】:

我在您的 sn-p 中做了一些更改,只是为了输出视觉结果。看一看:jsfiddle.net/ed1nh0/96c203wj/3【参考方案14】:

我会使用其他答案中的代码,它们似乎都在相互复制,但我会将起点设为起始角度的函数,将终点设为结束角度的函数。

我将通过使用绝对值使大弧标志与顺序无关,并通过模 360 度使角度与数值大小无关。

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

largeArcFlag = Math.abs((endAngle - startAngle) % 360) <= 180 ? "0" : "1";
clockwiseFlag = (endAngle > startAngle) ? "1" : "0";

var d = [
    "M", start.x, start.y, 
    "A", radius, radius, 0, largeArcFlag, clockwiseFlag, end.x, end.y

向威利道歉;我没有读到最后,看到他发现了同样的事情。如果你喜欢我的帖子,就给他点个赞吧!

【讨论】:

以上是关于如何计算(圆的)弧的 SVG 路径的主要内容,如果未能解决你的问题,请参考以下文章

理解SVG的缩放 偏移的计算公式

Canvas路径方向

svg路径到给定宽度和高度的预先计算比例

计算几何如何计算两个圆的交点坐标

d3.js画圆弧和圆的坐标、弧长计算方法

比较弧的角度和公共点