Cách tính Đường dẫn SVG cho một cung (của hình tròn)


191

Cho một vòng tròn có tâm ở (200.200), bán kính 25, làm thế nào để tôi vẽ một vòng cung từ 270 độ đến 135 độ và một vòng tròn từ 270 đến 45 độ?

0 độ có nghĩa là nó nằm ngay trên trục x (phía bên phải) (có nghĩa là vị trí đồng hồ 3 giờ) 270 độ có nghĩa là vị trí 12 giờ và 90 có nghĩa là vị trí 6 giờ

Tổng quát hơn, đường dẫn cho một vòng cung cho một phần của vòng tròn là gì

x, y, r, d1, d2, direction

Ý nghĩa

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

Câu trả lời:


379

Mở rộng câu trả lời tuyệt vời của @ wdebeaum, đây là phương pháp tạo đường dẫn có vũ trang:

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

sử dụng

document.getElementById("arc1").setAttribute("d", describeArc(200, 400, 100, 0, 180));

và trong html của bạn

<path id="arc1" fill="none" stroke="#446688" stroke-width="20" />

Bản thử trực tiếp


9
Đây là một siêu nhân! Lưu ý rằng arcSweepbiến thực sự đang kiểm soát large-arc-flagtham số svg A. Trong đoạn mã trên, giá trị của sweep-flagtham số luôn bằng không. arcSweepcó lẽ nên được đổi tên thành một cái gì đó như longArc.
Steven Grosmark

Cảm ơn @PocketLogic, cuối cùng đã cập nhật theo đề xuất của bạn.
opsb

2
Thực sự hữu ích, cảm ơn. Điều duy nhất tôi tìm thấy là logic LargeArc không hoạt động nếu bạn sử dụng các góc âm. Điều này hoạt động -360 đến +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo

Tôi nhớ điểm tại sao tiêu chuẩn không xác định cung theo cùng một cách.
polkovnikov.ph

4
và đừng quên cắt một lượng nhỏ chiều dài cung như : endAngle - 0.0001, nếu không, toàn bộ cung sẽ không được hiển thị.
saike

128

Bạn muốn sử dụng lệnh RC hình elipA . Thật không may cho bạn, điều này đòi hỏi bạn phải xác định tọa độ Descartes (x, y) của điểm bắt đầu và điểm kết thúc thay vì tọa độ cực (bán kính, góc) mà bạn có, do đó bạn phải thực hiện một số phép toán. Đây là một hàm JavaScript sẽ hoạt động (mặc dù tôi chưa kiểm tra nó) và tôi hy vọng nó khá tự giải thích:

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

Những góc nào tương ứng với vị trí đồng hồ nào sẽ phụ thuộc vào hệ tọa độ; chỉ trao đổi và / hoặc phủ nhận các điều khoản sin / cos khi cần thiết.

Lệnh arc có các tham số sau:

rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y

Ví dụ đầu tiên của bạn:

rx= ry= 25 và x-axis-rotation= 0, vì bạn muốn có một hình tròn chứ không phải hình elip. Bạn có thể tính toán cả tọa độ bắt đầu (mà bạn nên Mchuyển sang) và tọa độ kết thúc (x, y) bằng cách sử dụng hàm trên, lần lượt là (200, 175) và khoảng (182.322, 217.678). Cho đến những hạn chế này cho đến nay, thực sự có bốn cung có thể được vẽ, vì vậy hai lá cờ chọn một trong số chúng. Tôi đoán có lẽ bạn muốn vẽ một vòng cung nhỏ (có nghĩa là large-arc-flag= 0), theo hướng giảm góc (nghĩa là sweep-flag= 0). Tất cả cùng nhau, đường dẫn SVG là:

M 200 175 A 25 25 0 0 0 182.322 217.678

Đối với ví dụ thứ hai (giả sử bạn có nghĩa là đi cùng hướng và do đó, một cung lớn), đường dẫn SVG là:

M 200 175 A 25 25 0 1 0 217.678 217.678

Một lần nữa, tôi đã không kiểm tra những thứ này.

(chỉnh sửa 2016-06-01) Nếu, như @clocksmith, bạn đang tự hỏi tại sao họ chọn API này, hãy xem các ghi chú triển khai . Họ mô tả hai tham số hóa cung có thể là "tham số hóa điểm cuối" (cái mà họ đã chọn) và "tham số hóa trung tâm" (giống như những gì câu hỏi sử dụng). Trong phần mô tả "tham số hóa điểm cuối", họ nói:

Một trong những lợi thế của tham số hóa điểm cuối là nó cho phép cú pháp đường dẫn nhất quán trong đó tất cả các lệnh đường dẫn kết thúc theo tọa độ của "điểm hiện tại" mới.

Vì vậy, về cơ bản, đó là tác dụng phụ của các cung được coi là một phần của đường dẫn lớn hơn là đối tượng riêng biệt của chúng. Tôi cho rằng nếu trình kết xuất SVG của bạn không đầy đủ, nó có thể bỏ qua mọi thành phần đường dẫn mà nó không biết cách kết xuất, miễn là nó biết có bao nhiêu đối số họ đưa ra. Hoặc có thể nó cho phép hiển thị song song các đoạn khác nhau của một đường dẫn có nhiều thành phần. Hoặc có lẽ họ đã làm điều đó để đảm bảo các lỗi làm tròn không tích tụ dọc theo chiều dài của một đường dẫn phức tạp.

Các ghi chú thực hiện cũng hữu ích cho câu hỏi ban đầu, vì chúng có nhiều mã giả toán học hơn để chuyển đổi giữa hai tham số hóa (điều mà tôi không nhận ra khi lần đầu tiên viết câu trả lời này).


1
Có vẻ tốt, sẽ thử tiếp theo. thực tế là nếu đó là "nhiều hơn" của vòng cung (khi vòng cung lớn hơn một nửa vòng tròn) và việc large-arc-flagphải chuyển từ 0 đến 1 có thể gây ra một số lỗi.
phân cực

ugh, tại sao đây là api cho Svg arcs?
thợ đồng hồ

16

Tôi sửa đổi một chút câu trả lời của opsb và thực hiện hỗ trợ điền cho khu vực vòng tròn. http://codepen.io/anon/pen/AkoGx

Mã não

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
Liên kết codepen dường như không hoạt động đối với tôi (Chrome)
Ray Hulha

Vòng cung được vẽ bên ngoài ranh giới SVG. Tăng chiều cao và thay đổi của SVG centerX, centerYlên 100 chẳng hạn.
A.Akram

Bạn cũng có thể đặt hộp xem rõ ràng trong phần tử cha mẹ. Ví dụviewBox="0 0 500 500"
Håken Nắp

7

Đây là một câu hỏi cũ, nhưng tôi thấy mã này hữu ích và tiết kiệm cho tôi ba phút suy nghĩ :) Vì vậy, tôi đang thêm một bản mở rộng nhỏ vào câu trả lời của @opsb .

Nếu bạn muốn chuyển đổi cung này thành một lát (để cho phép điền), chúng ta có thể sửa đổi mã một chút:

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>

Và ở đó bạn đi!


5

Câu trả lời của @ opsb là gọn gàng, nhưng điểm trung tâm không chính xác, hơn nữa, như @Jithin lưu ý, nếu góc là 360, thì không có gì được rút ra cả.

@Jithin đã khắc phục sự cố 360, nhưng nếu bạn chọn dưới 360 độ, thì bạn sẽ nhận được một dòng đóng vòng cung, không bắt buộc.

Tôi đã sửa nó và thêm một số hình ảnh động vào đoạn mã dưới đây:

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

Tôi muốn bình luận về câu trả lời của @Ahtenus, cụ thể là về bình luận của Ray Hulha nói rằng codepen không hiển thị bất kỳ cung nào, nhưng danh tiếng của tôi không đủ cao.

Lý do cho codepen này không hoạt động là do html của nó bị lỗi với độ rộng đột quỵ bằng không.

Tôi đã sửa nó và thêm một ví dụ thứ hai ở đây: http://codepen.io/AntherLinuxUser/pen/QEJmkN .

Các html:

<svg>
    <path id="theSvgArc"/>
    <path id="theSvgArc2"/>
</svg>

CSS có liên quan:

svg {
    width  : 500px;
    height : 500px;
}

path {
    stroke-width : 5;
    stroke       : lime;
    fill         : #151515;
}

Các javascript:

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

3

Một hình ảnh và một số Python

Chỉ cần làm rõ hơn và đưa ra một giải pháp khác. Lệnh Arc[ A] sử dụng vị trí hiện tại làm điểm bắt đầu để bạn phải sử dụng Movetolệnh [M] trước.

Sau đó, các tham số của Arcnhư sau:

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

Nếu chúng ta định nghĩa ví dụ tập tin svg sau:

<svg viewBox="0 0 500 500">
    <path fill="red" d="
    M 250 250
    A 100 100 0 0 0 450 250
    Z"/> 
</svg>

nhập mô tả hình ảnh ở đây

Bạn sẽ đặt điểm bắt đầu với Mđiểm kết thúc bằng các tham số xfyfcủa A.

Chúng tôi đang tìm kiếm các vòng tròn để chúng tôi thiết lập rxtương đương với ryviệc thực hiện về cơ bản bây giờ nó sẽ cố gắng tìm tất cả các vòng tròn bán kính rxgiao nhau với điểm bắt đầu và điểm kết thúc.

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

Bạn có thể có một lời giải thích chi tiết hơn trong bài viết này mà tôi đã viết.


3

Lưu ý cho những người tìm kiếm câu trả lời (tôi cũng là người) - nếu sử dụng cung không bắt buộc , một giải pháp đơn giản hơn nhiều để vẽ một vòng tròn là sử dụng stroke-dasharraySVG <circle>.

Chia mảng dash thành hai phần tử và chia tỷ lệ phạm vi của chúng thành góc mong muốn. Góc bắt đầu có thể được điều chỉnh bằng cách sử dụng stroke-dashoffset.

Không một cosin trong tầm nhìn.

Ví dụ đầy đủ với lời giải thích: https://codepen.io/mjurchot/pen/wvBKOvP


2

Hàm PolarToCartesian ban đầu của wdebeaum là chính xác:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Đảo ngược điểm bắt đầu và điểm kết thúc bằng cách sử dụng:

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

Là khó hiểu (với tôi) bởi vì điều này sẽ đảo ngược cờ quét. Sử dụng:

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

với cờ quét = "0" vẽ các cung tròn ngược chiều "bình thường", mà tôi nghĩ là thẳng hơn. Xem https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths


2

Một sửa đổi nhỏ cho câu trả lời của @ opsb. Chúng tôi không thể vẽ một vòng tròn đầy đủ với phương pháp này. tức là nếu chúng ta cho (0, 360) thì nó sẽ không rút ra được gì cả. Vì vậy, một sửa đổi nhỏ được thực hiện để khắc phục điều này. Nó có thể hữu ích để hiển thị điểm số đôi khi đạt 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

Phiên bản 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
Bạn có thể làm cho ví dụ rõ ràng hơn bằng cách tận dụng các mẫu chữ ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins

0

Thành phần ReactJS dựa trên câu trả lời đã chọn:

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

bạn có thể sử dụng mã JSFiddle tôi đã thực hiện để trả lời ở trên:

https://jsfiddle.net/tyw6nfee/

tất cả những gì bạn cần làm là thay đổi mã console.log cuối cùng và cung cấp cho nó tham số của riêng bạn:

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

1
Tôi đã thực hiện một số thay đổi trong đoạn trích của bạn chỉ để đưa ra kết quả trực quan. Hãy xem: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.