Comment calculer le chemin SVG pour un arc (d'un cercle)

191

Étant donné un cercle centré à (200 200), rayon 25, comment dessiner un arc de 270 degrés à 135 degrés et un qui va de 270 à 45 degrés?

0 degré signifie qu'il est juste sur l'axe x (le côté droit) (ce qui signifie qu'il est en position 3 heures) 270 degrés signifie qu'il est en position 12 heures et 90 signifie qu'il est en position 6 heures

Plus généralement, qu'est-ce qu'un chemin pour un arc pour une partie d'un cercle avec

x, y, r, d1, d2, direction

sens

center (x,y), radius r, degree_start, degree_end, direction
non-polarité
la source

Réponses:

381

En développant sur la bonne réponse de @ wdebeaum, voici une méthode pour générer un chemin en arc:

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

utiliser

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

et dans votre html

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

Démo en direct

opsb
la source
9
C'est super! Notez que la arcSweepvariable contrôle en fait le large-arc-flagparamètre svg A. Dans le code ci-dessus, la valeur du sweep-flagparamètre est toujours zéro. arcSweepdevrait probablement être renommé en quelque chose comme longArc.
Steven Grosmark le
Merci @PocketLogic, ayez (enfin) mis à jour selon votre suggestion.
opsb
2
Vraiment utile, merci. La seule chose que j'ai trouvée est que la logique largeArc ne fonctionne pas si vous utilisez des angles négatifs. Cela fonctionne -360 à +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo
Je ne comprends pas pourquoi la norme ne définit pas les arcs de la même manière.
polkovnikov.ph
4
et n'oubliez pas de couper une petite quantité de longueur d'arc comme: endAngle - 0.0001sinon, l'arc complet ne sera pas rendu.
saike
128

Vous souhaitez utiliser la commande rc elliptiqueA . Malheureusement pour vous, cela vous oblige à spécifier les coordonnées cartésiennes (x, y) des points de départ et d'arrivée plutôt que les coordonnées polaires (rayon, angle) que vous avez, vous devez donc faire des calculs. Voici une fonction JavaScript qui devrait fonctionner (même si je ne l'ai pas testée), et qui, je l'espère, est assez explicite:

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

Quels angles correspondent à quelles positions d'horloge dépendront du système de coordonnées; échangez simplement et / ou annulez les termes sin / cos si nécessaire.

La commande arc a ces paramètres:

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

Pour votre premier exemple:

rx= ry= 25 et x-axis-rotation= 0, puisque vous voulez un cercle et non une ellipse. Vous pouvez calculer à la fois les coordonnées de départ (auxquelles vous devriez Maller) et les coordonnées de fin (x, y) en utilisant la fonction ci-dessus, ce qui donne respectivement (200, 175) et environ (182,322, 217,678). Compte tenu de ces contraintes jusqu'à présent, il y a en fait quatre arcs qui pourraient être dessinés, donc les deux drapeaux en sélectionnent l'un. Je suppose que vous voulez probablement dessiner un petit arc (signifiant large-arc-flag= 0), dans le sens de l'angle décroissant (signifiant sweep-flag= 0). Dans l'ensemble, le chemin SVG est:

M 200 175 A 25 25 0 0 0 182.322 217.678

Pour le deuxième exemple (en supposant que vous vouliez dire aller dans la même direction, et donc un grand arc), le chemin SVG est:

M 200 175 A 25 25 0 1 0 217.678 217.678

Encore une fois, je ne les ai pas testés.

(edit 2016-06-01) Si, comme @clocksmith, vous vous demandez pourquoi ils ont choisi cette API, jetez un œil aux notes d'implémentation . Ils décrivent deux paramétrisations possibles de l'arc, la «paramétrisation du point final» (celle qu'ils ont choisie) et la «paramétrage du centre» (qui est comme ce que la question utilise). Dans la description de "paramétrage des points de terminaison", ils disent:

L'un des avantages du paramétrage des points de terminaison est qu'il permet une syntaxe de chemin cohérente dans laquelle toutes les commandes de chemin se terminent par les coordonnées du nouveau "point courant".

Donc, fondamentalement, c'est un effet secondaire des arcs étant considérés comme faisant partie d'un chemin plus large plutôt que comme leur propre objet séparé. Je suppose que si votre moteur de rendu SVG est incomplet, il pourrait simplement ignorer tous les composants du chemin qu'il ne sait pas rendre, tant qu'il sait combien d'arguments ils prennent. Ou peut-être que cela permet le rendu parallèle de différents morceaux d'un chemin avec de nombreux composants. Ou peut-être l'ont-ils fait pour s'assurer que les erreurs d'arrondi ne se sont pas accumulées le long d'un chemin complexe.

Les notes d'implémentation sont également utiles pour la question originale, car elles ont un pseudo-code plus mathématique pour la conversion entre les deux paramétrisations (ce que je n'avais pas réalisé lorsque j'ai écrit cette réponse pour la première fois).

wdebeaum
la source
1
semble bon, essaiera ensuite. le fait que s'il est "plus" de l'arc (lorsque l'arc est plus de la moitié du cercle) et qu'il large-arc-flagdoit être basculé de 0 à 1 pourrait introduire un bug.
nonopolarité
ugh, pourquoi est-ce l'API pour les arcs svg?
horloger
17

J'ai légèrement modifié la réponse d'opsb et j'ai fait un remplissage de support pour le secteur du cercle. 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
la source
5
Le lien codepen ne semble pas fonctionner pour moi (Chrome)
Ray Hulha
L'arc est dessiné en dehors des limites SVG. Augmentez la hauteur de SVG et changez-la centerX, centerYà 100 par exemple.
A.Akram
Vous pouvez également définir une zone d'affichage explicitement dans l'élément svg parent. Par exempleviewBox="0 0 500 500"
Håken Lid
7

C'est une vieille question, mais je l' ai trouvé le code utile et m'a sauvé trois minutes de la pensée :) Je suis en ajoutant une petite extension à @opsb de réponse .

Si vous souhaitez convertir cet arc en une tranche (pour permettre le remplissage), nous pouvons modifier légèrement le code:

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>

et voilà!

SumNeuron
la source
5

Les réponses de @ opsb sont claires, mais le point central n'est pas précis, de plus, comme l'a noté @Jithin, si l'angle est de 360, alors rien n'est dessiné du tout.

@Jithin a résolu le problème de 360, mais si vous sélectionnez moins de 360 ​​degrés, vous obtiendrez une ligne fermant la boucle d'arc, ce qui n'est pas obligatoire.

J'ai corrigé cela et ajouté une animation dans le code ci-dessous:

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
la source
4

Je voulais commenter la réponse de @Ahtenus, en particulier le commentaire de Ray Hulha disant que le codepen ne montre aucun arc, mais ma réputation n'est pas assez élevée.

La raison pour laquelle ce codepen ne fonctionne pas est que son html est défectueux avec une largeur de trait de zéro.

Je l'ai corrigé et ajouté un deuxième exemple ici: http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

Le html:

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

Le CSS pertinent:

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

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

Le javascript:

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

Une image et du Python

Juste pour mieux clarifier et proposer une autre solution. ArcLa Acommande [ ] utilise la position actuelle comme point de départ, vous devez donc d'abord utiliser la Movetocommande [M].

Ensuite, les paramètres de Arcsont les suivants:

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

Si on définit par exemple le fichier svg suivant:

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

entrez la description de l'image ici

Vous définissez le point de départ avec Mle point d'arrivée avec les paramètres xfet yfde A.

Nous recherchons des cercles, donc nous définissons comme rxégal à le ryfaire essentiellement maintenant, il essaiera de trouver tous les cercles de rayon rxqui croisent le point de départ et le point final.

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

Vous pouvez avoir une explication plus détaillée dans ce post que j'ai écrit.

GM
la source
3

Remarque pour les demandeurs de réponses (que j'étais aussi) - si l'utilisation d'arc n'est pas obligatoire , une solution beaucoup plus simple pour dessiner un cercle partiel consiste à utiliser stroke-dasharraySVG <circle>.

Divisez le tableau de tirets en deux éléments et mettez leur plage à l'échelle à l'angle souhaité. L'angle de départ peut être ajusté à l'aide de stroke-dashoffset.

Pas un seul cosinus en vue.

Exemple complet avec explications: https://codepen.io/mjurczyk/pen/wvBKOvP

Maciek Jurczyk
la source
2

La fonction originale polarToCartesian de wdebeaum est correcte:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Inversion des points de départ et d'arrivée en utilisant:

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

Est déroutant (pour moi) car cela inversera le drapeau de balayage. En utilisant:

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

avec le sweep-flag = "0" dessine des arcs "normaux" dans le sens antihoraire, ce qui, je pense, est plus simple. Voir https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

Wiley
la source
2

Une légère modification de la réponse de @ opsb. Nous ne pouvons pas dessiner un cercle complet avec cette méthode. c'est-à-dire que si nous donnons (0, 360), il ne tirera rien du tout. Donc une légère modification a été apportée pour résoudre ce problème. Il peut être utile d'afficher des scores qui atteignent parfois 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
la source
2

Version 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
la source
2
Vous pourriez sans doute rendre l'exemple plus clair en tirant parti des littéraux de modèle ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins
0

Composant ReactJS basé sur la réponse sélectionnée:

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;
Gars
la source
0

vous pouvez utiliser le code JSFiddle que j'ai créé pour la réponse ci-dessus:

https://jsfiddle.net/tyw6nfee/

tout ce que vous avez à faire est de changer le code de la dernière ligne console.log et de lui donner votre propre paramètre:

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
javad bat
la source
1
J'ai fait quelques changements dans votre extrait de code juste pour afficher un résultat visuel. Jetez un oeil: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0