원호의 호에 대한 SVG 경로를 계산하는 방법


191

반지름 25 인 (200,200)을 중심으로 한 원이 주어지면 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

답변:


381

@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매개 변수 의 값 은 항상 0입니다. arcSweep아마도 같은 이름으로 바뀌어야 longArc합니다.
Steven Grosmark

감사합니다 @PocketLogic, 제안에 따라 (최종) 업데이트되었습니다.
opsb

2
정말 도움이되었습니다. 감사합니다. 내가 찾은 유일한 것은 음의 각도를 사용하면 largeArc 논리가 작동하지 않는다는 것입니다. 이것은 -360에서 +360까지 작동합니다. jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo

표준이 동일한 방식으로 호를 정의하지 않는 이유를 알 수 없습니다.
polkovnikov.ph

4
다음과 같이 소량의 호 길이를 자르는 것을 잊지 마십시오. endAngle - 0.0001그렇지 않으면 전체 호가 렌더링되지 않습니다.
saike

128

elliptical 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)을 산출 할 수 있습니다. 지금까지 이러한 제약 조건이 주어지면 실제로 그릴 수있는 4 개의 호가 있으므로 두 플래그가 그 중 하나를 선택합니다. large-arc-flag각도가 감소하는 방향 ( = 0) 으로 작은 호 ( = 0) 를 그리려고 할 것입니다 sweep-flag. 모두 함께 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 렌더러가 불완전하면 경로 구성 요소를 건너 뛸 수 있다고 가정합니다. 어떤 인수가 필요한지 알고있는 한 렌더링 방법을 모릅니다. 또는 많은 구성 요소가있는 경로의 다른 청크를 병렬 렌더링 할 수 있습니다. 또는 복잡한 경로의 길이에 따라 반올림 오류가 발생하지 않도록하기 위해 수행했을 수도 있습니다.

구현 노트는 두 개의 매개 변수화 사이에서 변환하기 위해 더 수학적 의사 코드가 있기 때문에 원래 질문에 유용합니다 (처음 에이 답변을 작성할 때 알지 못했습니다).


1
좋아 보인다 다음에 시도합니다. 그것이 호의 "더 많은"경우 (호가 원의 절반보다 큰 경우) large-arc-flag0에서 1로 토글해야하는 경우 버그가 발생할 수 있습니다.
nonopolarity

우, 왜 이것이 svg arcs의 API입니까?
clocksmith

17

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 링크가 작동하지 않는 것 같습니다 (Chrome)
Ray Hulha

호는 SVG 경계 외부에 그려집니다. SVG의 높이와 변화를 증가 centerX, centerY예를 들어 100.
A.Akram

부모 svg 요소에서 명시 적으로보기 상자를 설정할 수도 있습니다. 예 :viewBox="0 0 500 500"
Håken Lid

7

이것은 오래된 질문이지만 코드가 유용하다는 것을 알았고 3 분의 생각을 절약했습니다. :) @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의 의견에 대해 언급하고 싶었습니다.

이 코드 펜이 작동하지 않는 이유는 HTML에 획 너비가 0이므로 결함이 있기 때문입니다.

나는 그것을 고치고 여기에 두 번째 예를 추가했다 : 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;
}

자바 스크립트 :

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

3

이미지와 파이썬

더 명확하게하고 다른 솔루션을 제공하십시오. 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매개 변수 xfyf의 시작점으로 시작점을 설정합니다 A.

우리는 원을 찾고 있으므로 기본적으로 그렇게하는 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))

내가 쓴 이 게시물 에서 더 자세한 설명을 할 수 있습니다 .


3

답을 찾는 사람들을위한 참고 사항 (나도했던 사람) -arc를 사용해야 할 필요가 없다면 부분 원을 그리는 훨씬 간단한 해결책 stroke-dasharray은 SVG 를 사용하는 것 입니다 <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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.