Cara menghitung Jalur SVG untuk busur (lingkaran)

191

Diberi lingkaran berpusat di (200.200), jari-jari 25, bagaimana cara menggambar busur dari 270 derajat ke 135 derajat dan satu yang bergerak dari 270 ke 45 derajat?

0 derajat berarti tepat di sumbu x (sisi kanan) (artinya adalah posisi jam 3 ') 270 derajat berarti posisi jam 12, dan 90 berarti posisi jam 6

Secara umum, apa yang dimaksud dengan jalur busur untuk bagian lingkaran

x, y, r, d1, d2, direction

berarti

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

Jawaban:

381

Memperluas jawaban hebat @ wdebeaum, berikut adalah metode untuk membuat jalur lengkung:

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

menggunakan

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

dan di html Anda

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

Demo langsung

opsb
sumber
9
Ini super! Perhatikan bahwa arcSweepvariabel sebenarnya mengendalikan large-arc-flagparameter svg A. Dalam kode di atas, nilai untuk sweep-flagparameter selalu nol. arcSweepmungkin harus diubah namanya menjadi sesuatu seperti longArc.
Steven Grosmark
Terima kasih @PocketLogic, telah (akhirnya) diperbarui sesuai saran Anda.
opsb
2
Sangat membantu, terima kasih. Satu-satunya hal yang saya temukan adalah bahwa logika largeArc tidak berfungsi jika Anda menggunakan sudut negatif. Ini berfungsi -360 hingga +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo
Saya kehilangan titik mengapa standar tidak mendefinisikan busur dengan cara yang sama.
polkovnikov.ph
4
dan jangan lupa untuk memotong sedikit panjang busur seperti:, endAngle - 0.0001jika tidak, busur penuh tidak akan diberikan.
saike
128

Anda ingin menggunakan perintah rc elipsA . Sayangnya untuk Anda, ini mengharuskan Anda untuk menentukan koordinat Cartesian (x, y) dari titik awal dan akhir daripada koordinat kutub (jari-jari, sudut) yang Anda miliki, sehingga Anda harus melakukan perhitungan. Inilah fungsi JavaScript yang harus berfungsi (meskipun saya belum mengujinya), dan yang saya harap cukup jelas:

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

Sudut mana yang sesuai dengan posisi jam mana yang akan bergantung pada sistem koordinat; hanya menukar dan / atau meniadakan persyaratan dosa / cos yang diperlukan.

Perintah arc memiliki parameter ini:

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

Untuk contoh pertama Anda:

rx= ry= 25 dan x-axis-rotation= 0, karena Anda ingin lingkaran dan bukan elips. Anda dapat menghitung koordinat awal (yang harus Anda Mhindari) dan mengakhiri koordinat (x, y) menggunakan fungsi di atas, menghasilkan (200, 175) dan sekitar (182.322, 217.678), masing-masing. Mengingat kendala-kendala ini sejauh ini, sebenarnya ada empat busur yang bisa ditarik, sehingga dua bendera memilih salah satunya. Saya kira Anda mungkin ingin menggambar busur kecil (artinya large-arc-flag= 0), ke arah sudut yang menurun (artinya sweep-flag= 0). Secara keseluruhan, jalur SVG adalah:

M 200 175 A 25 25 0 0 0 182.322 217.678

Untuk contoh kedua (dengan asumsi maksud Anda pergi ke arah yang sama, dan dengan demikian busur besar), jalur SVG adalah:

M 200 175 A 25 25 0 1 0 217.678 217.678

Sekali lagi, saya belum menguji ini.

(edit 2016-06-01) Jika, seperti @clocksmith, Anda bertanya-tanya mengapa mereka memilih API ini, lihatlah catatan implementasi . Mereka menggambarkan dua kemungkinan parameterisasi busur, "parameterisasi titik akhir" (yang mereka pilih), dan "parameterisasi tengah" (yang seperti apa yang digunakan pertanyaan). Dalam deskripsi "parameterisasi titik akhir" kata mereka:

Salah satu keuntungan dari parameterisasi titik akhir adalah bahwa hal itu memungkinkan sintaks jalur yang konsisten di mana semua perintah jalur berakhir dengan koordinat "titik saat ini" yang baru.

Jadi pada dasarnya itu adalah efek samping dari busur yang dianggap sebagai bagian dari jalur yang lebih besar daripada objek mereka sendiri yang terpisah. Saya kira bahwa jika renderer SVG Anda tidak lengkap, ia bisa melompati komponen path apa pun yang tidak direndernya, asalkan tahu berapa banyak argumen yang mereka ambil. Atau mungkin memungkinkan render paralel potongan yang berbeda dari jalan dengan banyak komponen. Atau mungkin mereka melakukannya untuk memastikan kesalahan pembulatan tidak menumpuk di sepanjang jalan yang rumit.

Catatan implementasi juga berguna untuk pertanyaan awal, karena mereka memiliki lebih banyak pseudocode matematis untuk mengkonversi antara dua parameterisasi (yang saya tidak sadari ketika saya pertama kali menulis jawaban ini).

wdebeaum
sumber
1
terlihat bagus, akan coba selanjutnya. fakta bahwa jika "lebih" dari busur (ketika busur lebih dari setengah dari lingkaran) dan large-arc-flagharus diubah dari 0 ke 1 dapat menimbulkan bug.
nonopolaritas
huh, mengapa ini api untuk busur svg?
tukang kunci
17

Saya sedikit mengubah jawaban opsb dan mengisi dukungan untuk sektor lingkaran. 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>
Ahtenus
sumber
5
Tautan codepen tampaknya tidak berfungsi untuk saya (Chrome)
Ray Hulha
Busur ditarik di luar batas SVG. Tingkatkan tinggi dan perubahan SVG centerX, centerYmenjadi 100 misalnya.
A.Akram
Anda juga dapat mengatur kotak tampilan secara eksplisit di elemen svg induk. MisalnyaviewBox="0 0 500 500"
Tutup Håken
7

Ini adalah pertanyaan lama, tetapi saya menemukan kode yang berguna dan menyelamatkan saya tiga menit berpikir :) Jadi saya menambahkan ekspansi kecil ke jawaban @opsb .

Jika Anda ingin mengonversi busur ini menjadi slice (untuk memungkinkan diisi) kami dapat sedikit mengubah kode:

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>

dan di sana kamu pergi!

SumNeuron
sumber
5

Jawaban @ opsb rapi, tetapi titik pusatnya tidak akurat, apalagi, seperti yang dicatat oleh @Jithin, jika sudutnya 360, maka tidak ada yang ditarik sama sekali.

@Jithin memperbaiki masalah 360, tetapi jika Anda memilih kurang dari 360 derajat, maka Anda akan mendapatkan garis yang menutup loop arc, yang tidak diperlukan.

Saya memperbaikinya, dan menambahkan beberapa animasi dalam kode di bawah ini:

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>

Hasan A Yousef
sumber
4

Saya ingin mengomentari jawaban @Ahtenus, khususnya pada komentar Ray Hulha mengatakan codepen tidak menunjukkan busur, tetapi reputasi saya tidak cukup tinggi.

Alasan untuk codepen ini tidak berfungsi adalah karena htmlnya salah dengan lebar stroke nol.

Saya memperbaikinya dan menambahkan contoh kedua di sini: http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

Html:

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

CSS yang relevan:

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));
Alex
sumber
3

Gambar dan beberapa Python

Hanya untuk memperjelas dan menawarkan solusi lain. Perintah Arc[ A] menggunakan posisi saat ini sebagai titik awal sehingga Anda harus menggunakan Movetoperintah [M] terlebih dahulu.

Maka parameternya Arcadalah sebagai berikut:

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

Jika kita mendefinisikan misalnya file svg berikut:

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

masukkan deskripsi gambar di sini

Anda akan mengatur titik dimulai dengan Mtitik berakhir dengan parameter xfdan yfdari A.

Kami mencari lingkaran sehingga kami menyetel rxsama dengan rymelakukannya pada dasarnya sekarang ia akan mencoba menemukan semua lingkaran jari rx- jari yang memotong titik awal dan titik akhir.

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

Anda dapat memiliki penjelasan lebih rinci dalam posting ini yang saya tulis.

GM
sumber
3

Catatan untuk para pencari jawaban (siapa saya juga) - jika menggunakan busur tidak wajib , solusi yang jauh lebih sederhana untuk menggambar bagian-lingkaran adalah dengan menggunakan stroke-dasharraySVG<circle> .

Bagi array dasbor menjadi dua elemen, dan skala jangkauannya ke sudut yang diinginkan. Sudut awal dapat disesuaikan menggunakanstroke-dashoffset .

Tidak ada satu pun kosinus yang terlihat.

Contoh lengkap dengan penjelasan: https://codepen.io/mjurczyk/pen/wvBKOvP

Maciek Jurczyk
sumber
2

Fungsi asli polarToCartesian oleh wdebeaum benar:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Membalikkan titik awal dan akhir dengan menggunakan:

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

Membingungkan (bagi saya) karena ini akan membalikkan sweep-flag. Menggunakan:

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

dengan sweep-flag = "0" draw "normal" arc counter-clock-wise, yang saya pikir lebih lurus ke depan. Lihat https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

Wiley
sumber
2

Sedikit modifikasi pada jawaban @ opsb. Kami tidak dapat menggambar lingkaran penuh dengan metode ini. yaitu Jika kita memberi (0, 360) itu tidak akan menarik apa pun. Jadi sedikit modifikasi dilakukan untuk memperbaikinya. Mungkin bermanfaat untuk menampilkan skor yang terkadang mencapai 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));
Jithin B
sumber
2

Versi 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;
};
MrE
sumber
2
Anda bisa membuat contoh lebih jelas dengan memanfaatkan literal templat ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins
0

Komponen ReactJS berdasarkan jawaban yang dipilih:

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;
Orang
sumber
0

Anda dapat menggunakan kode JSFiddle yang saya buat untuk jawaban di atas:

https://jsfiddle.net/tyw6nfee/

yang perlu Anda lakukan adalah mengubah kode console.log baris terakhir dan berikan parameter Anda sendiri:

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
javad kelelawar
sumber
1
Saya melakukan beberapa perubahan pada cuplikan Anda hanya untuk menampilkan hasil visual. Lihatlah: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0