web-dev-qa-db-fra.com

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

Etant donné un cercle centré à (200 200), rayon 25, comment puis-je dessiner un arc de 270 degrés à 135 degrés et un autre allant de 270 à 45 degrés?

0 degré signifie que c'est à droite sur l'axe des x (le côté droit) (c'est-à-dire qu'il est à 3 heures) 270 degrés signifie qu'il est à 12 heures et 90 à 6 heures

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

x, y, r, d1, d2, direction

sens

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

Pour développer la réponse géniale de @ wdebeaum, voici une méthode permettant de générer un chemin arqué:

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

336
opsb

Vous souhaitez utiliser la commande commande elliptique Arc . Malheureusement pour vous, vous devez 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) dont vous disposez. Vous devez donc effectuer quelques calculs. Voici une fonction JavaScript qui devrait fonctionner (même si je ne l’ai pas testée), et que j’espère être 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 aux positions de l'horloge qui dépendent du système de coordonnées; Il suffit d’échanger et/ou d’annuler les termes sin/cos selon les besoins.

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, car vous voulez un cercle et non une ellipse. Vous pouvez calculer les coordonnées de départ (auxquelles vous devez Move to) et les coordonnées de fin (x, y) à l'aide de la fonction ci-dessus, donnant (200, 175) et environ (182.322, 217.678), respectivement. Compte tenu de ces contraintes jusqu’à présent, il est possible de tracer quatre arcs. Les deux drapeaux en sélectionnent un. Je suppose que vous voulez probablement dessiner un petit arc (signifiant large-arc-flag = 0), dans la direction de l’angle décroissant (signifiant sweep-flag = 0). Tous 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 aller dans la même direction, et donc dans un grand arc), le chemin SVG est:

M 200 175 A 25 25 0 1 0 217.678 217.678

Encore une fois, je n'ai pas testé ces derniers.

(edit 2016-06-01) Si, comme @clocksmith, vous vous demandez pourquoi ils ont choisi cette API, consultez le notes d'implémentation . Ils décrivent deux paramétrages d'arc possibles, le "paramétrage des points d'extrémité" (celui qu'ils ont choisi) et le "paramétrage du centre" (ce qui revient à utiliser la question). Dans la description du "paramétrage des points d'extrémité", ils disent:

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

Donc, fondamentalement, il s’agit d’un effet secondaire qui fait que les arcs sont 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 peut simplement ignorer tous les composants de chemin qu'il ne sait pas restituer, tant qu'il sait combien d'arguments ils prennent. Ou peut-être qu'il permet le rendu en parallèle de différentes parties d'un chemin comportant de nombreux composants. Ou peut-être l'ont-ils fait pour s'assurer que les erreurs d'arrondi ne se développent pas le long d'un chemin complexe.

Les notes d'implémentation sont également utiles pour la question initiale, car elles ont plus de pseudo-code 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).

123
wdebeaum

J'ai légèrement modifié la réponse de opsb et fait en sorte que le secteur cercle soit complété. 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>
17
Ahtenus

C'est une vieille question, mais j'ai trouvé le code utile et cela m'a évité trois minutes de réflexion :) J'ajoute donc une petite extension à la réponse de @ opsb.

Si vous voulez convertir cet arc en slice (pour permettre le remplissage), nous pouvons légèrement modifier 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à!

4
SumNeuron

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

La raison pour laquelle ce codepen ne fonctionne pas est que son code 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));
4
Alex

Les réponses de @ opsb sont bien nettes, mais le point central n'est pas précis. En outre, comme l'a noté @Jithin, si l'angle est 360, rien n'est tracé.

@Jithin a corrigé le problème 360, mais si vous avez sélectionné 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>
4
Hasan A Yousef

Une image et du python

Juste pour clarifier mieux et offrir une autre solution. La commande Arc [A] utilise la position actuelle comme point de départ; vous devez donc utiliser la commande Moveto [M] en premier.

Ensuite, les paramètres de Arc sont les suivants:

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

Si nous définissons par exemple le fichier svg suivant:

<svg width="5cm" height="5cm" viewBox="0 0 500 500"

xmlns="http://www.w3.org/2000/svg" version="1.1">

Le code suivant vous donnera ce résultat:

<g stroke="none" fill="Lime">
<path d="
M 250 250
A 100 100 0 0 0 450 250
Z"/> 
</g>

enter image description here

Vous définissez le point de départ avec M le point final avec les paramètres xf et yf de A.

Nous recherchons des cercles, donc rx est égal à ry. Essayons donc de trouver tout le cercle de rayon rx qui intersecte 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.

3
G M

Une légère modification de la réponse de @ opsb. Nous ne pouvons pas tracer 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 apportée pour résoudre ce problème. Il pourrait être utile d’afficher des scores atteignant 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));
2
Jithin B

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

La fonction polarToCartesian d'origine de wdebeaum est correcte:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Inversion des points de départ et d'arrivée à l'aide de:

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

Est déroutant (pour moi) parce que cela inversera le drapeau. En utilisant:

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

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

1
Wiley

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;
0
Guy

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

https://jsfiddle.net/tyw6nfee/

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

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