(円の)弧の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

回答:


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
Steven Grosmark 2016年

@PocketLogicに感謝します。提案に従って(最終的に)更新しました。
opsb

2
本当に助かります、ありがとう。私が見つけた唯一のことは、負の角度を使用すると、largeArcロジックが機能しないことです。これは-360〜+360で機能します。jsbin.com
kopisonewi

標準がアークを同じように定義しない理由を私は見逃しています。
polkovnikov.ph 2016年

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)を生成できます。これまでのところこれらの制約を考えると、描画できるアークは実際には4つあるため、2つのフラグがいずれかを選択します。おそらくlarge-arc-flag、角度が減少する方向(= 0を意味する)に小さな弧(= 0を意味する)を描画したいと思いますsweep-flag。まとめると、SVGパスは次のとおりです。

M 200 175 A 25 25 0 0 0 182.322 217.678

2番目の例(同じ方向、つまり大きな円弧を想定していると仮定)の場合、SVGパスは次のとおりです。

M 200 175 A 25 25 0 1 0 217.678 217.678

繰り返しますが、私はこれらをテストしていません。

(編集2016-06-01)@clocksmithのように、なぜ彼らがこのAPIを選んだのか疑問に思っているなら、実装ノートを見てください。それらは、「エンドポイントのパラメーター化」(彼らが選択したもの)と「中央のパラメーター化」(これは質問が使用するものに似ています)の2つの可能なアークパラメーター化について説明しています。「エンドポイントのパラメーター化」の説明では、次のように述べています。

エンドポイントパラメータ化の利点の1つは、すべてのパスコマンドが新しい「現在のポイント」の座標で終了する一貫したパス構文を許可することです。

したがって、基本的には、それ自体の個別のオブジェクトではなく、より大きなパスの一部と見なされる弧の副作用です。SVGレンダラーが不完全な場合、引数の数がわかっている限り、レンダリングする方法がわからないパスコンポーネントをスキップするだけだと思います。あるいは、多くのコンポーネントを持つパスの異なるチャンクの並列レンダリングを可能にするかもしれません。あるいは、複雑なパスの長さに沿って丸め誤差が蓄積しないようにするために、そうしたのかもしれません。

実装ノートは、2つのパラメーター化の間で変換するためのより数学的な疑似コードを持っているので、元の質問にも役立ちます(最初にこの回答を書いたときに気が付きませんでした)。


1
よさそうだ、次にしよう。円弧の「より多く」である場合(円弧が円の半分を超えている場合)、large-arc-flag0から1に切り替える必要があるという事実は、いくつかのバグを引き起こす可能性があります。
非極性

うーん、なぜこれがSVGアークのAPIなのですか?
時計屋2016

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リンクが機能しない(Chrome)
Ray Hulha '23年

弧はSVG境界の外側に描かれます。SVGの高さと変化を大きくcenterXcenterY例えば100に。
A.Akram 2017年

親のsvg要素で明示的にビューボックスを設定することもできます。例えばviewBox="0 0 500 500"
クロス派遣蓋

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の回答、特にレイフルハのコメントで、コードペンには弧が表示されないというコメントがありましたが、私の評判は十分ではありません。

このcodepenが機能しない理由は、そのhtmlのストローク幅がゼロで欠陥があるためです。

私はそれを修正し、2番目の例をここに追加しました: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パラメータof xfyfを使用して、開始点と終了点を設定しますA

円を探し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

回答を求める人(私もそうでした)への注意- 円弧の使用が必須はない場合、部分円を描画するためのはるかに簡単な解決策stroke-dasharrayはSVG を使用することです<circle>

ダッシュ配列を2つの要素に分割し、それらの範囲を目的の角度にスケーリングします。開始角度はを使用して調整できます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);

スイープフラグ= "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}`
ロイ・プリンス

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.