Come calcolare il percorso SVG per un arco (di un cerchio)


191

Dato un cerchio centrato su (200.200), raggio 25, come posso disegnare un arco da 270 gradi a 135 gradi e uno che va da 270 a 45 gradi?

0 gradi significa che è proprio sull'asse x (il lato destro) (significa che è la posizione 3 ore) 270 gradi significa che è la posizione 12 e 90 indica che è la posizione 6

Più in generale, cos'è un percorso per un arco per parte di un cerchio

x, y, r, d1, d2, direction

senso

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

Risposte:


381

Espandendo la grande risposta di @wdebeaum, ecco un metodo per generare un percorso arcuato:

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

usare

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

e nel tuo HTML

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

Dimostrazione dal vivo


9
Questo è super! Si noti che la arcSweepvariabile sta effettivamente controllando il large-arc-flagparametro svg A. Nel codice sopra, il valore per il sweep-flagparametro è sempre zero. arcSweepdovrebbe probabilmente essere rinominato in qualcosa del genere longArc.
Steven Grosmark,

Grazie @PocketLogic, hai (finalmente) aggiornato secondo il tuo suggerimento.
opsb,

2
Davvero utile, grazie. L'unica cosa che ho scoperto è che la logica largeArc non funziona se si utilizzano angoli negativi. Funziona da -360 a +360: jsbin.com/kopisonewi/2/edit?html,js
output

Mi manca il punto per cui lo standard non definisce gli archi allo stesso modo.
polkovnikov.ph,

4
e non dimenticare di tagliare una piccola quantità di lunghezza dell'arco come: in endAngle - 0.0001caso contrario, non verrà eseguito il rendering dell'arco completo.
saike

128

Si desidera utilizzare il comando ellittico Arc . Sfortunatamente per te, questo richiede di specificare le coordinate cartesiane (x, y) dei punti iniziale e finale piuttosto che le coordinate polari (raggio, angolo) che hai, quindi devi fare un po 'di matematica. Ecco una funzione JavaScript che dovrebbe funzionare (anche se non l'ho ancora testata) e che spero sia abbastanza autoesplicativa:

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

Quali angoli corrispondono a quali posizioni dell'orologio dipenderanno dal sistema di coordinate; basta scambiare e / o negare i termini sin / cos secondo necessità.

Il comando arc ha questi parametri:

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

Per il tuo primo esempio:

rx= ry= 25 e x-axis-rotation= 0, poiché vuoi un cerchio e non un'ellisse. Puoi calcolare sia le coordinate iniziali (che dovresti eventualmente M) sia le coordinate finali (x, y) usando la funzione sopra, producendo (200, 175) e circa (182.322, 217.678), rispettivamente. Dati questi vincoli finora, in realtà ci sono quattro archi che potrebbero essere disegnati, quindi le due bandiere ne selezionano uno. Immagino che probabilmente vuoi disegnare un arco piccolo (significato large-arc-flag= 0), nella direzione dell'angolo decrescente (significato sweep-flag= 0). Tutti insieme, il percorso SVG è:

M 200 175 A 25 25 0 0 0 182.322 217.678

Per il secondo esempio (supponendo che intendi andare nella stessa direzione, e quindi un arco di grandi dimensioni), il percorso SVG è:

M 200 175 A 25 25 0 1 0 217.678 217.678

Ancora una volta, non li ho testati.

(modifica 2016-06-01) Se, come @clocksmith, ti stai chiedendo perché abbiano scelto questa API, dai un'occhiata alle note di implementazione . Descrivono due possibili parametrizzazioni dell'arco, la "parametrizzazione dell'endpoint" (quella che hanno scelto) e la "parametrizzazione centrale" (che è come ciò che utilizza la domanda). Nella descrizione di "parametrizzazione endpoint" si dice:

Uno dei vantaggi della parametrizzazione dell'endpoint è che consente una sintassi del percorso coerente in cui tutti i comandi del percorso terminano nelle coordinate del nuovo "punto corrente".

Quindi fondamentalmente è un effetto collaterale degli archi considerati come parte di un percorso più ampio piuttosto che il loro oggetto separato. Suppongo che se il tuo renderer SVG è incompleto potrebbe semplicemente saltare qualsiasi componente del percorso che non sa come eseguire il rendering, purché sappia quanti argomenti prendono. O forse abilita il rendering parallelo di blocchi diversi di un percorso con molti componenti. O forse lo hanno fatto per assicurarsi che gli errori di arrotondamento non si accumulassero lungo un percorso complesso.

Le note di implementazione sono utili anche per la domanda originale, poiché hanno più pseudocodici matematici per la conversione tra le due parametrizzazioni (cosa che non ho realizzato quando ho scritto per la prima volta questa risposta).


1
sembra buono, proverò dopo. il fatto che se è "più" dell'arco (quando l'arco è più della metà del cerchio) e large-arc-flagdeve essere commutato da 0 a 1 potrebbe introdurre qualche bug.
polarità

ugh, perché questa è l'api per gli archi svg?
fabbro

17

Ho leggermente modificato la risposta di opsb e fatto il riempimento di supporto per il settore del cerchio. 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
Il link codepen non sembra funzionare per me (Chrome)
Ray Hulha,

L'arco viene disegnato al di fuori dei confini SVG. Aumenta l'altezza e la modifica di SVG centerX, centerYad esempio a 100.
A. Akram,

È inoltre possibile impostare esplicitamente una casella di visualizzazione nell'elemento svg parent. Ad esempioviewBox="0 0 500 500"
Håken Lid

7

Questa è una vecchia questione, ma ho trovato il codice utile e mi ha salvato tre minuti di pensiero :) Quindi io sono l'aggiunta di una piccola espansione per @opsb 's risposta .

Se si desidera convertire questo arco in una sezione (per consentire il riempimento), è possibile modificare leggermente il codice:

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>

e il gioco è fatto!


5

Le risposte di @ opsb sono pulite, ma il punto centrale non è preciso, inoltre, come notato da @Jithin, se l'angolo è 360, allora non viene disegnato nulla.

@Jithin ha risolto il problema di 360, ma se hai selezionato meno di 360 gradi, otterrai una linea che chiude il loop dell'arco, che non è richiesto.

Ho risolto questo problema e ho aggiunto alcune animazioni nel codice seguente:

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

Volevo commentare la risposta di @Ahtenus, in particolare sul commento di Ray Hulha dicendo che il codepen non mostra alcun arco, ma la mia reputazione non è abbastanza alta.

La ragione per cui questo codice non funziona è che il suo codice HTML è difettoso con una larghezza della corsa pari a zero.

L'ho corretto e ho aggiunto un secondo esempio qui: http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

Il codice HTML:

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

Il CSS pertinente:

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

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

Il javascript:

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

3

Un'immagine e un po 'di Python

Giusto per chiarire meglio e offrire un'altra soluzione. ArcIl Acomando [ ] usa la posizione corrente come punto di partenza, quindi devi prima usare il Movetocomando [M].

Quindi i parametri di Arcsono i seguenti:

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

Se definiamo ad esempio il seguente file svg:

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

inserisci qui la descrizione dell'immagine

Si imposterà il punto iniziale con Mil punto finale con i parametri xfe yfdi A.

Siamo alla ricerca di cerchi, quindi ci impegniamo rxa ryfarlo fondamentalmente ora cercheremo di trovare tutto il cerchio di raggio rxche interseca il punto iniziale e finale.

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

Puoi avere una spiegazione più dettagliata in questo post che ho scritto.


3

Nota per i cercatori di risposte (che ero anche io) - se l'uso dell'arco non è obbligatorio , una soluzione molto più semplice per disegnare un cerchio parziale è l'uso stroke-dasharraydi SVG <circle>.

Dividi la lineetta del trattino in due elementi e ridimensiona il loro raggio d'azione fino all'angolazione desiderata. L'angolo di partenza può essere regolato usando stroke-dashoffset.

Non un solo coseno in vista.

Esempio completo con spiegazioni: https://codepen.io/mjurczyk/pen/wvBKOvP


2

La funzione originale polarToCartesian di wdebeaum è corretta:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Inversione dei punti iniziale e finale utilizzando:

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

È fonte di confusione (per me) perché ciò invertirà la bandiera spazzata. usando:

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

con lo sweep-flag = "0" disegna archi "normali" in senso antiorario, che ritengo più diretti. Vedi https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths


2

Una leggera modifica alla risposta di @ opsb. Non possiamo tracciare un cerchio completo con questo metodo. cioè se diamo (0, 360) non disegnerà nulla. Quindi una leggera modifica fatta per risolvere questo problema. Potrebbe essere utile visualizzare punteggi che a volte raggiungono il 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

Versione 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
Probabilmente potresti rendere più chiaro l'esempio sfruttando i letterali del modello ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins

0

Componente ReactJS basato sulla risposta selezionata:

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

puoi usare il codice JSFiddle che ho creato per la risposta sopra:

https://jsfiddle.net/tyw6nfee/

tutto quello che devi fare è cambiare il codice console.log dell'ultima riga e assegnargli il tuo parametro:

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

1
Ho apportato alcune modifiche al tuo frammento solo per ottenere un risultato visivo. Dai un'occhiata: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.