So berechnen Sie den SVG-Pfad für einen Bogen (eines Kreises)

191

Wie zeichne ich bei einem Kreis mit einem Radius von (200.200) und einem Radius von 25 einen Bogen von 270 bis 135 Grad und einen von 270 bis 45 Grad?

0 Grad bedeutet, dass es sich direkt auf der x-Achse (der rechten Seite) befindet (was bedeutet, dass es sich um eine 3-Uhr-Position handelt). 270 Grad bedeutet, dass es sich um eine 12-Uhr-Position handelt, und 90 Grad bedeutet, dass es sich um eine 6-Uhr-Position handelt

Allgemeiner gesagt, was ist ein Pfad für einen Bogen für einen Teil eines Kreises mit

x, y, r, d1, d2, direction

Bedeutung

center (x,y), radius r, degree_start, degree_end, direction
Unpolarität
quelle

Antworten:

379

Um die großartige Antwort von @ wdebeaum zu erweitern, finden Sie hier eine Methode zum Generieren eines Bogenpfads:

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

benutzen

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

und in Ihrem HTML

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

Live-Demo

opsb
quelle
9
Das ist super! Beachten Sie, dass die arcSweepVariable tatsächlich den large-arc-flagParameter svg A steuert . Im obigen Code ist der Wert für den sweep-flagParameter immer Null. arcSweepsollte wahrscheinlich in so etwas umbenannt werden longArc.
Steven Grosmark
Danke @PocketLogic, habe (endlich) nach deinem Vorschlag aktualisiert.
opsb
2
Wirklich hilfreich, danke. Das einzige, was ich gefunden habe, ist, dass die largeArc-Logik nicht funktioniert, wenn Sie negative Winkel verwenden. Dies funktioniert -360 bis +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo
Ich vermisse den Punkt, warum Standard Bögen nicht auf die gleiche Weise definiert.
polkovnikov.ph
4
und vergessen Sie nicht, eine kleine Menge der Bogenlänge wie endAngle - 0.0001folgt zu schneiden: Wenn nicht, wird der volle Bogen nicht gerendert.
Saike
128

Sie möchten den elliptischen ABefehl rc verwenden . Leider müssen Sie dafür die kartesischen Koordinaten (x, y) der Start- und Endpunkte und nicht die Polarkoordinaten (Radius, Winkel) angeben, die Sie haben. Sie müssen also etwas rechnen. Hier ist eine JavaScript-Funktion, die funktionieren sollte (obwohl ich sie noch nicht getestet habe) und die hoffentlich ziemlich selbsterklärend ist:

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

Welche Winkel welchen Taktpositionen entsprechen, hängt vom Koordinatensystem ab. Tauschen und / oder negieren Sie einfach die sin / cos-Begriffe nach Bedarf.

Der Befehl arc hat folgende Parameter:

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

Für Ihr erstes Beispiel:

rx= ry= 25 und x-axis-rotation= 0, da Sie einen Kreis und keine Ellipse wollen. MMit der obigen Funktion können Sie sowohl die Startkoordinaten (zu denen Sie übergehen sollten ) als auch die Endkoordinaten (x, y) berechnen. Dies ergibt (200, 175) bzw. ungefähr (182.322, 217.678). Angesichts dieser Einschränkungen können tatsächlich vier Bögen gezeichnet werden, sodass die beiden Flags einen davon auswählen. Ich vermute, Sie möchten wahrscheinlich einen kleinen Bogen (Bedeutung large-arc-flag= 0) in Richtung des abnehmenden Winkels (Bedeutung sweep-flag= 0) zeichnen . Insgesamt lautet der SVG-Pfad:

M 200 175 A 25 25 0 0 0 182.322 217.678

Für das zweite Beispiel (vorausgesetzt, Sie meinen die gleiche Richtung und damit einen großen Bogen) lautet der SVG-Pfad:

M 200 175 A 25 25 0 1 0 217.678 217.678

Auch diese habe ich nicht getestet.

(Bearbeiten des 01.06.2016) Wenn Sie sich wie bei @clocksmith fragen, warum sie diese API gewählt haben, lesen Sie die Implementierungshinweise . Sie beschreiben zwei mögliche Bogenparametrisierungen, "Endpunktparametrisierung" (die von ihnen gewählte) und "Mittelparametrisierung" (wie in der Frage verwendet). In der Beschreibung der "Endpunktparametrisierung" heißt es:

Einer der Vorteile der Endpunktparametrisierung besteht darin, dass eine konsistente Pfadsyntax ermöglicht wird, bei der alle Pfadbefehle in den Koordinaten des neuen "aktuellen Punkts" enden.

Im Grunde ist es ein Nebeneffekt, wenn Bögen als Teil eines größeren Pfades betrachtet werden und nicht als eigenständiges Objekt. Ich nehme an, wenn Ihr SVG-Renderer unvollständig ist, kann er einfach alle Pfadkomponenten überspringen, die er nicht rendern kann, solange er weiß, wie viele Argumente er benötigt. Oder es ermöglicht das parallele Rendern verschiedener Abschnitte eines Pfads mit vielen Komponenten. Oder vielleicht haben sie es getan, um sicherzustellen, dass sich auf der Länge eines komplexen Pfades keine Rundungsfehler gebildet haben.

Die Implementierungshinweise sind auch für die ursprüngliche Frage nützlich, da sie mehr mathematischen Pseudocode für die Konvertierung zwischen den beiden Parametrisierungen enthalten (was mir beim ersten Schreiben dieser Antwort nicht klar war).

wdebeaum
quelle
1
sieht gut aus, werde es als nächstes versuchen. Die Tatsache, dass wenn es "mehr" des Bogens ist (wenn der Bogen mehr als die Hälfte des Kreises ist) und der large-arc-flagvon 0 auf 1 umgeschaltet werden muss, könnte ein Fehler auftreten.
Unpolarität
ugh, warum ist dies die API für SVG-Bögen?
Uhrmacher
16

Ich habe die Antwort von opsb leicht modifiziert und zur Unterstützung des Kreissektors ausgefüllt . 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
quelle
5
Der Codepen-Link scheint für mich nicht zu funktionieren (Chrome)
Ray Hulha
Der Bogen wird außerhalb der SVG-Grenzen gezeichnet. Erhöhen Sie SVG Höhe und Veränderung centerX, centerYbis 100 zum Beispiel.
A.Akram
Sie können ein Ansichtsfeld auch explizit im übergeordneten svg-Element festlegen. Zum BeispielviewBox="0 0 500 500"
Håken Lid
7

Dies ist eine alte Frage, aber ich fand den Code nützlich und ersparte mir drei Minuten Nachdenken :) Also füge ich der Antwort von @opsb eine kleine Erweiterung hinzu .

Wenn Sie diesen Bogen in ein Slice konvertieren möchten (um das Füllen zu ermöglichen), können Sie den Code geringfügig ändern:

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>

und los geht's!

SumNeuron
quelle
5

Die Antworten von @ opsb sind ordentlich, aber der Mittelpunkt ist nicht genau, außerdem, wie @Jithin feststellte, wenn der Winkel 360 ist, wird überhaupt nichts gezeichnet.

@Jithin hat das 360-Problem behoben. Wenn Sie jedoch weniger als 360 Grad ausgewählt haben, wird eine Linie angezeigt, die die Bogenschleife schließt, was nicht erforderlich ist.

Ich habe das behoben und im folgenden Code einige Animationen hinzugefügt:

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
quelle
4

Ich wollte die Antwort von @Ahtenus kommentieren, insbesondere den Kommentar von Ray Hulha, wonach der Codepen keinen Bogen zeigt, aber mein Ruf ist nicht hoch genug.

Der Grund dafür, dass dieser Codepen nicht funktioniert, ist, dass sein HTML-Code mit einer Strichbreite von Null fehlerhaft ist.

Ich habe es behoben und hier ein zweites Beispiel hinzugefügt: http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

Das HTML:

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

Das relevante CSS:

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

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

Das Javascript:

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

Ein Bild und etwas Python

Nur um besser zu klären und eine andere Lösung anzubieten. ArcDer ABefehl [ ] verwendet die aktuelle Position als Ausgangspunkt, daher müssen Sie Movetozuerst den Befehl [M] verwenden.

Dann sind die Parameter von Arcden folgenden:

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

Wenn wir zum Beispiel die folgende SVG-Datei definieren:

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

Geben Sie hier die Bildbeschreibung ein

Sie setzen den Startpunkt mit Mdem Endpunkt mit den Parametern xfund yfvon A.

Wir suchen Kreise so setzten wir rxgleich ryso im Grunde jetzt tut es wird versuchen , alle Kreis mit dem Radius zu finden , rxdass sie schneiden den Anfangs- und Endpunkt.

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

Eine ausführlichere Erklärung finden Sie in diesem Beitrag , den ich geschrieben habe.

GM
quelle
3

Hinweis für die Antwortsuchenden (die ich auch war): Wenn die Verwendung von arc nicht obligatorisch ist , ist die Verwendung stroke-dasharrayvon SVG eine weitaus einfachere Lösung zum Zeichnen eines Teilkreises <circle>.

Teilen Sie das Stricharray in zwei Elemente und skalieren Sie deren Bereich auf den gewünschten Winkel. Der Startwinkel kann mit eingestellt werden stroke-dashoffset.

Kein einziger Kosinus in Sicht.

Vollständiges Beispiel mit Erläuterungen: https://codepen.io/mjurczyk/pen/wvBKOvP

Maciek Jurczyk
quelle
2

Die ursprüngliche polarToCartesian-Funktion von wdebeaum ist korrekt:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Umkehren von Start- und Endpunkten mit:

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

Ist (für mich) verwirrend, weil dies die Sweep-Flagge umkehren wird. Verwenden von:

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

mit dem Sweep-Flag = "0" zeichnet "normale" Bögen gegen den Uhrzeigersinn, was ich für einfacher halte. Siehe https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

Wiley
quelle
2

Eine leichte Änderung an der Antwort von @ opsb. Mit dieser Methode können wir keinen vollen Kreis zeichnen. dh wenn wir (0, 360) geben, wird überhaupt nichts gezeichnet. Also eine kleine Modifikation, um dies zu beheben. Es kann nützlich sein, Werte anzuzeigen, die manchmal 100% erreichen.

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.
quelle
2

ES6-Version:

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
quelle
2
Sie könnten das Beispiel wohl klarer machen, indem Sie ES6-Vorlagenliterale nutzen:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins
0

ReactJS-Komponente basierend auf der ausgewählten Antwort:

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;
Kerl
quelle
0

Sie können den JSFiddle-Code verwenden, den ich oben für die Antwort erstellt habe:

https://jsfiddle.net/tyw6nfee/

Alles, was Sie tun müssen, ist, den Code der Konsole in der letzten Zeile zu ändern und ihm Ihren eigenen Parameter zu geben:

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
Javad Fledermaus
quelle
1
Ich habe einige Änderungen an Ihrem Snippet vorgenommen, um ein visuelles Ergebnis auszugeben. Werfen
ed1nh0