Jak obliczyć ścieżkę SVG dla łuku (koła)

191

Biorąc pod uwagę okrąg wyśrodkowany na (200,200), promień 25, jak narysować łuk od 270 stopni do 135 stopni i taki, który przechodzi od 270 do 45 stopni?

0 stopni oznacza, że ​​jest dokładnie na osi x (po prawej stronie) (co oznacza, że ​​jest to godzina trzecia) 270 stopni oznacza, że ​​jest godzina 12, a 90 oznacza, że ​​jest godzina 6

Mówiąc bardziej ogólnie, czym jest ścieżka dla łuku dla części koła

x, y, r, d1, d2, direction

znaczenie

center (x,y), radius r, degree_start, degree_end, direction
niepolarność
źródło

Odpowiedzi:

381

Rozwijając świetną odpowiedź na @ wdebeaum, oto metoda generowania łukowej ścieżki:

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

używać

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

i w html

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

Demo na żywo

opsb
źródło
9
To jest super! Zauważ, że arcSweepzmienna faktycznie kontroluje large-arc-flagparametr svg A. W powyższym kodzie wartość sweep-flagparametru jest zawsze równa zero. arcSweepnależy prawdopodobnie zmienić nazwę na coś takiego longArc.
Steven Grosmark
Dzięki @PocketLogic, (nareszcie) zaktualizowałem zgodnie z twoją sugestią.
opsb
2
Naprawdę pomocna, dzięki. Jedyne, co znalazłem, to to, że logika largeArc nie działa, jeśli używasz ujemnych kątów. Działa to od -360 do +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo
Nie rozumiem, dlaczego standard nie definiuje łuków w ten sam sposób.
polkovnikov.ph 27.10.16
4
i nie zapomnij wyciąć małej długości łuku, np .: endAngle - 0.0001jeśli nie, pełny łuk nie będzie renderowany.
saike
128

Chcesz użyć eliptycznego Apolecenia rc . Na nieszczęście dla ciebie wymaga to podania współrzędnych kartezjańskich (x, y) punktów początkowych i końcowych zamiast współrzędnych biegunowych (promień, kąt), które masz, więc musisz wykonać matematykę. Oto funkcja JavaScript, która powinna działać (choć jej nie testowałem) i mam nadzieję, że jest dość oczywista:

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

Które kąty odpowiadają pozycjom zegara zależą od układu współrzędnych; po prostu zamień i / lub neguj warunki sin / cos, jeśli to konieczne.

Polecenie łuku ma następujące parametry:

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

Dla twojego pierwszego przykładu:

rx= ry= 25 i x-axis-rotation= 0, ponieważ chcesz koła, a nie elipsy. Możesz obliczyć zarówno współrzędne początkowe (które powinieneś Mprzeoczyć), jak i końcowe (x, y), korzystając z powyższej funkcji, dając odpowiednio (200, 175) i około (182.322, 217.678). Biorąc pod uwagę dotychczasowe ograniczenia, można narysować cztery łuki, więc dwie flagi wybierają jedną z nich. Zgaduję, że prawdopodobnie chcesz narysować mały łuk (znaczenie large-arc-flag= 0), w kierunku malejącego kąta (znaczenie sweep-flag= 0). W sumie ścieżka SVG to:

M 200 175 A 25 25 0 0 0 182.322 217.678

W drugim przykładzie (zakładając, że masz na myśli pójście w tym samym kierunku, a tym samym duży łuk), ścieżka SVG to:

M 200 175 A 25 25 0 1 0 217.678 217.678

Znowu ich nie testowałem.

(edytuj 2016-06-01) Jeśli, podobnie jak @clocksmith, zastanawiasz się, dlaczego wybrali ten interfejs API, zapoznaj się z uwagami dotyczącymi implementacji . Opisują dwie możliwe parametryzacje łuku, „parametryzację punktu końcowego” (tę, którą wybrali) i „parametryzację środkową” (która jest podobna do tego, z której korzysta pytanie). W opisie „parametryzacji punktu końcowego” mówią:

Jedną z zalet parametryzacji punktu końcowego jest to, że pozwala ona na spójną składnię ścieżki, w której wszystkie polecenia ścieżki kończą się na współrzędnych nowego „bieżącego punktu”.

Zasadniczo jest to efekt uboczny, w którym łuki są traktowane jako część większej ścieżki, a nie jako oddzielny obiekt. Podejrzewam, że jeśli twój renderer SVG jest niekompletny, może po prostu pominąć komponenty ścieżki, których nie potrafi renderować, o ile wie, ile argumentów bierze. A może umożliwia równoległe renderowanie różnych fragmentów ścieżki z wieloma komponentami. A może zrobili to, aby upewnić się, że błędy zaokrąglania nie narastają wzdłuż złożonej ścieżki.

Uwagi dotyczące implementacji są również przydatne w przypadku pierwotnego pytania, ponieważ mają więcej pseudokodu matematycznego do konwersji między dwiema parametryzacjami (czego nie zdawałem sobie sprawy, kiedy pierwszy raz napisałem tę odpowiedź).

wdebeaum
źródło
1
wygląda dobrze, spróbuje dalej. fakt, że jeśli jest to „więcej” łuku (gdy łuk jest większy niż połowa koła) i large-arc-flagmusi zostać przełączony z 0 na 1, może wprowadzić błąd.
niepolarność
ugh, dlaczego jest to api dla łuków svg?
kowal
17

Lekko zmodyfikowałem odpowiedź opsb i wprowadziłem obsługę wypełnienia dla sektora koła. 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
źródło
5
Link do codepen wydaje się nie działać dla mnie (Chrome)
Ray Hulha
Łuk jest rysowany poza granicami SVG. Zwiększ wysokość i zmianę SVG centerX, centerYna przykład do 100.
A.Akram
Możesz także ustawić pole widoku jawnie w nadrzędnym elemencie svg. Na przykładviewBox="0 0 500 500"
Håken Lid
7

Jest to stare pytanie, ale okazało się, że kod użyteczne i zaoszczędziło mi trzy minuty myślenia :) Więc dodaję niewielką ekspansję na @opsb „s odpowiedź .

Jeśli chcesz przekonwertować ten łuk na plasterek (aby umożliwić wypełnienie), możemy nieznacznie zmodyfikować kod:

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>

i proszę bardzo!

SumNeuron
źródło
5

Odpowiedzi @ opsb są zgrabne, ale punkt środkowy nie jest dokładny, ponadto, jak zauważył @Jithin, jeśli kąt wynosi 360, to nic nie jest rysowane.

@Jithin naprawił problem 360, ale jeśli wybierzesz mniej niż 360 stopni, otrzymasz linię zamykającą pętlę łuku, co nie jest wymagane.

Naprawiłem to i dodałem animację w poniższym kodzie:

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
źródło
4

Chciałem skomentować odpowiedź @Ahtenus, szczególnie komentarz Raya Hulha, mówiąc, że codepen nie pokazuje żadnego łuku, ale moja reputacja nie jest wystarczająco wysoka.

Przyczyną tego, że codepen nie działa jest to, że jego HTML jest uszkodzony z szerokością obrysu równą zero.

Naprawiłem to i dodałem drugi przykład tutaj: http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

HTML:

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

Odpowiedni 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));
Alex
źródło
3

Obraz i trochę Python

Żeby lepiej wyjaśnić i zaoferować inne rozwiązanie. Komenda Arc[ A] użyj bieżącej pozycji jako punktu początkowego, więc musisz najpierw użyć Movetokomendy [M].

Następnie parametry Arcsą następujące:

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

Jeśli zdefiniujemy na przykład następujący plik svg:

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

wprowadź opis zdjęcia tutaj

Zostanie ustawiony punkt początkowy z Mpunktem kończącym się z parametrami xfi yfz A.

Szukamy okręgów, więc ustawiliśmy się rxna ryrobienie tego w zasadzie teraz spróbuje znaleźć cały okrąg o promieniu, rxktóry przecina punkt początkowy i końcowy.

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

Możesz napisać bardziej szczegółowe wyjaśnienie w tym poście .

GM
źródło
3

Uwaga dla osób poszukujących odpowiedzi (którym również byłem) - jeśli użycie łuku nie jest obowiązkowe , o wiele prostszym rozwiązaniem do narysowania koła częściowego jest użycie stroke-dasharraySVG<circle> .

Podziel tablicę rozdzielczą na dwa elementy i przeskaluj ich zakres do pożądanego kąta. Kąt początkowy można regulować za pomocą stroke-dashoffset.

Ani jednego cosinusa w zasięgu wzroku.

Pełny przykład z objaśnieniami: https://codepen.io/mjurczyk/pen/wvBKOvP

Maciek Jurczyk
źródło
2

Oryginalna funkcja polarToCartesian autorstwa wdebeaum jest poprawna:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Odwracanie punktów początkowych i końcowych za pomocą:

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

Jest to dla mnie mylące, ponieważ spowoduje to odwrócenie flagi zamiatania. Za pomocą:

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

with sweep-flag = "0" rysuje "normalne" łuki przeciwnie do ruchu wskazówek zegara, co moim zdaniem jest bardziej proste. Zobacz https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

Wiley
źródło
2

Drobna modyfikacja odpowiedzi @ opsb. Nie możemy narysować pełnego koła za pomocą tej metody. tzn. jeśli podamy (0, 360), to nic nie narysuje. Tak więc wprowadzono niewielką modyfikację, aby to naprawić. Przydatne może być wyświetlanie wyników, które czasami osiągają 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.
źródło
2

Wersja 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
źródło
2
Można by wyjaśnić przykład, wykorzystując literały szablonu ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins
0

Składnik ReactJS na podstawie wybranej odpowiedzi:

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;
Chłopak
źródło
0

możesz użyć kodu JSFiddle, który utworzyłem dla odpowiedzi powyżej:

https://jsfiddle.net/tyw6nfee/

wszystko, co musisz zrobić, to zmienić kod konsoli konsoli.log w ostatnim wierszu i nadać mu własny parametr:

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
javad bat
źródło
1
Zrobiłem kilka zmian w twoim fragmencie, aby uzyskać efekt wizualny. Spójrz: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0