ChatGPT解决这个技术问题 Extra ChatGPT

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

svg

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

o
opsb

扩展@wdebeaum's great answer,这是一种生成弧形路径的方法:

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


这太棒了!请注意,arcSweep 变量实际上是控制 large-arc-flag svg A 参数。在上面的代码中,sweep-flag 参数的值始终为零。 arcSweep 可能应该重命名为 longArc
真的很有帮助,谢谢。我发现的唯一一件事是,如果您使用负角,大弧逻辑将不起作用。这适用于 -360 到 +360:jsbin.com/kopisonewi/2/edit?html,js,output
这只有在 endAngle > startAngle 时才能正常工作。我不得不添加 while(endAngle < startAngle) endAngle += 360;可能有更聪明的方法来处理它。
并且不要忘记切割少量的弧长,例如:endAngle - 0.0001,如果没有,则不会渲染完整的弧。
这是同样方便的函数,但经过压缩:function describeArc(x,y,r,sAng,eAng){var M=Math,f=eAng-sAng<=180?0:1,q, cXY=(x,y,a)=>{q=(a-90)*M.PI/180;return[x+r*M.cos(q),y+r*M.sin(q)]}; return["M",...cXY(x,y,eAng), "A",r,r,0,f,0, ...cXY(x,y,sAng)].join(" ")} 示例用法: d=describeArc(200,200,100,-45,45); 返回半径为 100 像素(基于 200,200 的中心)从 -45° 到 45° 的圆弧的路径。 . 然后用 d 的值填充 <path> 标记的 d 属性,例如 myPath.setAttribute('d',d); 或 jQuery $('#myPath').attr('d',d);
g
gsnedders

您想使用 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,因为您需要的是圆而不是椭圆。您可以使用上面的函数计算起始坐标(您应该M转向)和结束坐标 (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?
a
ashleedawg

我稍微修改了 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-width="0" />
</svg>

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

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

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

function describeArc(x, y, radius, spread, startAngle, endAngle){ var innerStart = polarToCartesian(x, y, radius, endAngle); var innerEnd = polarToCartesian(x, y, 半径, 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(" ");返回 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)

< /路径>

你去吧!


M
Maciek Jurczyk

对于寻求答案的人(我也是)的注意事项 - 如果不是必须使用圆弧,绘制部分圆的更简单的解决方案是使用 SVG <circle>stroke-dasharray

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

看不到一个余弦。

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


谢谢你的回答!
H
Hasan A Yousef

@opsb 的答案很简洁,但中心点不准确,此外,正如@Jithin 指出的那样,如果角度是 360,则根本没有绘制任何内容。

@Jithin 修复了 360 度问题,但如果您选择的角度小于 360 度,那么您将得到一条关闭弧环的线,这不是必需的。

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

函数 myArc(cx, cy, 半径, max){ var circle = document.getElementById("arc"); var e = circle.getAttribute("d"); var d = " M "+ (cx + 半径) + " " + cy;变量角度=0; window.timer = window.setInterval( function() { var radians= angle * (Math.PI / 180); // 将度数转换为弧度 var x = cx + Math.cos(radians) * radius; var y = cy + Math.sin(弧度) * 半径; d += " L "+x + " " + y; circle.setAttribute("d", d) if(angle==max)window.clearInterval(window.timer); angle++ ; } ,5) } myArc(110, 110, 100, 360);


我是否遗漏了什么,或者var e = circle.getAttribute("d");行“什么都不做”?
A
Alex

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

T
Teocci

一张图片和一些 Python

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

那么Arc的参数如下:

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

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

https://i.stack.imgur.com/oqPXx.png

您将使用 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中有更详细的解释。


M
MrE

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}`
W
Wiley

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


S
Syntax Error

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

G
Guy

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

I
Ivan

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

我将通过使用绝对值使大弧标志独立于顺序,并通过工作模 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
  ].join(" ");

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


j
javad bat

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

https://jsfiddle.net/tyw6nfee/

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

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

我在您的代码段中做了一些更改,只是为了输出视觉结果。看看:jsfiddle.net/ed1nh0/96c203wj/3