วิธีการคำนวณเส้นทาง SVG สำหรับส่วนโค้ง (ของวงกลม)


191

เมื่อพิจารณาจากวงกลมที่มีศูนย์กลางที่ (200,200) รัศมี 25 ฉันจะวาดส่วนโค้งจาก 270 องศาถึง 135 องศาได้อย่างไรและวงกลมหนึ่งจาก 270 ถึง 45 องศา

0 องศาหมายถึงมันอยู่บนแกน x (ด้านขวา) (หมายถึงตำแหน่งนาฬิกา 3 o ') 270 องศาหมายความว่ามันเป็นตำแหน่ง 12 นาฬิกาและ 90 หมายถึงตำแหน่งนาฬิกา 6 โมง

โดยทั่วไปเส้นทางสำหรับอาร์คสำหรับส่วนหนึ่งของวงกลมด้วยคืออะไร

x, y, r, d1, d2, direction

ความหมาย

center (x,y), radius r, degree_start, degree_end, direction

คำตอบ:


381

การขยายคำตอบที่ยอดเยี่ยมของ @ wdebeaum ต่อไปนี้เป็นวิธีการสร้างเส้นทางแบบ arced:

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-flagพารามิเตอร์ svg A ในโค้ดด้านบนค่าsweep-flagพารามิเตอร์จะเป็นศูนย์เสมอ อาจจะต้องเปลี่ยนเพื่อสิ่งที่ต้องการ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

คุณต้องการที่จะใช้รูปไข่Aคำสั่ง RC น่าเสียดายสำหรับคุณคุณต้องระบุพิกัดคาร์ทีเซียน (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];
}

มุมใดที่สอดคล้องกับตำแหน่งของนาฬิกาที่จะขึ้นอยู่กับระบบพิกัด เพียงสลับและ / หรือคัดค้านเงื่อนไขบาป / 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

อีกครั้งฉันยังไม่ได้ทดสอบเหล่านี้

(แก้ไข 2016/06/01) ถ้าเช่น @clocksmith คุณสงสัยว่าทำไมพวกเขาเลือก API นี้มีลักษณะที่เป็นบันทึกการดำเนินงาน พวกเขาอธิบายการกำหนดค่าส่วนโค้งที่เป็นไปได้สองแบบคือ "การกำหนดพารามิเตอร์จุดสิ้นสุด" (อันที่พวกเขาเลือก) และ "การกำหนดพารามิเตอร์แบบกึ่งกลาง" (ซึ่งเหมือนกับคำถามที่ใช้) ในคำอธิบายของ "การกำหนดพารามิเตอร์จุดสิ้นสุด" พวกเขาพูดว่า:

ข้อดีอย่างหนึ่งของการปรับพารามิเตอร์ปลายทางคือการอนุญาตให้ใช้ไวยากรณ์เส้นทางที่สอดคล้องกันซึ่งคำสั่งเส้นทางทั้งหมดจะสิ้นสุดในพิกัดของ "จุดปัจจุบัน" ใหม่

ดังนั้นโดยทั่วไปแล้วมันเป็นผลข้างเคียงของส่วนโค้งที่ถูกพิจารณาว่าเป็นส่วนหนึ่งของเส้นทางที่ใหญ่กว่าแทนที่จะแยกวัตถุของพวกเขาเอง ฉันคิดว่าหากตัวแสดง SVG ของคุณไม่สมบูรณ์ก็สามารถข้ามไปยังส่วนประกอบของเส้นทางใด ๆ ก็ไม่ทราบวิธีการแสดงผลตราบใดที่มันรู้ว่าพวกเขาใช้เวลาหลายอาร์กิวเมนต์ หรืออาจช่วยให้การเรนเดอร์ของชิ้นส่วนของเส้นทางที่มีองค์ประกอบหลาย ๆ แบบขนานกัน หรือบางทีพวกเขาทำเพื่อให้แน่ใจว่าข้อผิดพลาดในการปัดเศษไม่ได้สร้างขึ้นตามความยาวของเส้นทางที่ซับซ้อน

บันทึกการใช้งานยังมีประโยชน์สำหรับคำถามเดิมเนื่องจากมีรหัสจำลองทางคณิตศาสตร์มากขึ้นสำหรับการแปลงระหว่างการกำหนดพารามิเตอร์สองแบบ (ซึ่งฉันไม่ทราบว่าเมื่อฉันเขียนคำตอบนี้ครั้งแรก)


1
ดูดีจะลองต่อไป ความจริงที่ว่าถ้ามันเป็น "มากกว่า" ของส่วนโค้ง (เมื่อส่วนโค้งมากกว่าครึ่งหนึ่งของวงกลม) และlarge-arc-flagจะต้องมีการสลับจาก 0 ถึง 1 สามารถแนะนำข้อผิดพลาดบางอย่าง
nonopolarity

เอ๊ะทำไมนี่คือ api สำหรับ svg arcs
กุญแจ

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)
เรย์ฮัลฮา

ส่วนโค้งถูกวาดนอกขอบเขต SVG เพิ่มความสูงและการเปลี่ยนแปลง SVG ของcenterX, centerY100 ตัวอย่างเช่น
A.Akram

คุณยังสามารถตั้งค่ากล่องมุมมองอย่างชัดเจนในองค์ประกอบ svg หลัก ตัวอย่างเช่นviewBox="0 0 500 500"
Håken Lid

7

นี่เป็นคำถามเก่า แต่ฉันพบว่าโค้ดมีประโยชน์และช่วยฉันคิดสามนาที :) ดังนั้นฉันจึงเพิ่มส่วนขยายเล็กน้อยในคำตอบของ@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 ว่า 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;
}

จาวาสคริปต์:

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จุดที่ลงท้ายด้วยพารามิเตอร์xfและของyfA

เรากำลังมองหาแวดวงดังนั้นเราตั้ง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

หมายเหตุสำหรับคำตอบแสวงหา (ที่ฉันยังเป็น) - ถ้าใช้โค้งจะไม่บังคับเป็นทางออกที่ไกลง่ายการวาดวงกลมส่วนหนึ่งคือการใช้stroke-dasharrayของ <circle>SVG

แบ่งอาเรย์เส้นประออกเป็นสององค์ประกอบและปรับช่วงของมันให้เป็นมุมที่ต้องการ stroke-dashoffsetมุมเริ่มต้นสามารถปรับใช้

ไม่ใช่โคไซน์เดี่ยวในการมองเห็น

ตัวอย่างแบบเต็มพร้อมคำอธิบาย: https://codepen.io/mjurczyk/pen/wvBKOvP


2

ฟังก์ชัน polarToCartesian เดิมโดย 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);

ด้วย sweep-flag = "0" ดึงส่วนโค้ง "counter" ธรรมดา "ฉลาด" ซึ่งฉันคิดว่าตรงไปข้างหน้ามากขึ้น ดู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

ทำซ้ำส่วนประกอบ JS ตามคำตอบที่เลือก:

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.