如何计算圆弧(圆)的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
181772 次浏览

你想要使用椭圆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,因为你想要一个圆而不是一个椭圆。你可以使用上面的函数计算起始坐标(你应该Move到)和结束坐标(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

同样,我还没有测试这些。

如果像@clocksmith一样,你想知道他们为什么选择这个API,看看实现注意事项。他们描述了两种可能的弧参数化,“端点参数化”(他们选择的那个)和“中心参数化”(就像这个问题使用的那样)。在“端点参数化”的描述中,他们说:

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

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

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

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

现场演示

我稍微修改了opsb的回答,并为圆形扇区做了支持填充。 http://codepen.io/anon/pen/AkoGx < / del >

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

超文本标记语言

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

由wdebeaum得到的原始极坐标笛卡尔函数是正确的:

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

with sweep-flag = "0"绘制"正常"逆时针弧线, 我认为这更直接。 看到https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths < / p >

我想评论@Ahtenus的回答,特别是Ray Hulha的评论,他说代码依赖没有显示出任何弧度,但我的声誉不够高。

这个代码依赖不工作的原因是它的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));

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

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

这是一个老问题,但我发现代码很有用,节省了我三分钟的思考:)所以我在< >强@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>

好了!

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

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

https://jsfiddle.net/tyw6nfee/

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

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

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

一个图像和一些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>

enter image description here

你将用M来设置起始点,用参数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))

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

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

将虚线数组分为两个元素,并将其范围缩放到所需的角度。可以使用stroke-dashoffset来调整起始角度。

看不见一个余弦。

完整的例子与解释: https://codepen.io/mjurczyk/pen/wvBKOvP < / p >

我会使用其他答案的代码,它们看起来都是互相复制的,但是我会让起点是开始角度的函数而终点是结束角度的函数。

我将使用绝对值使大圆弧标志与顺序无关,并通过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(" ");

向威利道歉;我还没读到最后,他也发现了同样的事情。如果你喜欢我的帖子,就给他投票吧!

PHP有人知道吗?

将接受的答案转换为PHP代码。帮助在服务器上生成弧。

function polarToCartesian($centerX, $centerY, $radius, $angleInDegrees) {
$angleInRadians = ($angleInDegrees-90) * pi() / 180.0;


return array(
"x"=> $centerX + ($radius * cos($angleInRadians)),
"y"=> $centerY + ($radius * sin($angleInRadians)),
);
}


function describeArc($x, $y, $radius, $startAngle, $endAngle){


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


$largeArcFlag = $endAngle - $startAngle <= 180 ? "0" : "1";


$d = implode(" ", array(
"M", $start["x"], $start["y"],
"A", $radius, $radius, 0, $largeArcFlag, 0, $end["x"], $end["y"]));


return $d;
}
<svg>
<path fill="none" stroke="#446688" stroke-width="20" d="<?= describeArc(150, 150, 100, 0, 30) ?>" />
</svg>