Cómo calcular la ruta SVG para un arco (de un círculo)

191

Dado un círculo centrado en (200.200), radio 25, ¿cómo dibujo un arco de 270 grados a 135 grados y uno que vaya de 270 a 45 grados?

0 grados significa que está justo en el eje x (el lado derecho) (lo que significa que es la posición de las 3 en punto) 270 grados significa que es la posición de las 12 en punto y 90 significa que es la posición de las 6 en punto

En términos más generales, ¿qué es un camino para un arco para parte de un círculo con

x, y, r, d1, d2, direction

sentido

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

Respuestas:

381

Ampliando la gran respuesta de @ wdebeaum, aquí hay un método para generar una ruta arqueada:

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

usar

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

y en tu html

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

Demo en vivo

opsb
fuente
9
Esto es super! Tenga en cuenta que la arcSweepvariable en realidad controla el large-arc-flagparámetro svg A. En el código anterior, el valor del sweep-flagparámetro siempre es cero. arcSweepprobablemente debería cambiarse de nombre a algo así longArc.
Steven Grosmark
Gracias @PocketLogic, he (finalmente) actualizado según su sugerencia.
opsb
2
Realmente útil, gracias. Lo único que encontré es que la lógica largeArc no funciona si usas ángulos negativos. Esto funciona de -360 a +360: jsbin.com/kopisonewi/2/edit?html,js,output
Xcodo
Extraño el punto por el cual el estándar no define los arcos de la misma manera.
polkovnikov.ph
44
y no olvide cortar una pequeña cantidad de longitud de arco como: endAngle - 0.0001si no, no se representará el arco completo.
Saike
128

Desea usar el comando elíptico Arc . Desafortunadamente para usted, esto requiere que especifique las coordenadas cartesianas (x, y) de los puntos inicial y final en lugar de las coordenadas polares (radio, ángulo) que tiene, por lo que debe hacer algunos cálculos. Aquí hay una función de JavaScript que debería funcionar (aunque no la he probado), y que espero sea bastante clara:

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

Qué ángulos corresponden a qué posiciones de reloj dependerán del sistema de coordenadas; simplemente intercambie y / o niegue los términos sin / cos según sea necesario.

El comando de arco tiene estos parámetros:

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

Para tu primer ejemplo:

rx= ry= 25 y x-axis-rotation= 0, ya que desea un círculo y no una elipse. Puede calcular tanto las coordenadas iniciales (a las que debe Mdirigirse) como las coordenadas finales (x, y) utilizando la función anterior, obteniendo (200, 175) y aproximadamente (182.322, 217.678), respectivamente. Dadas estas restricciones hasta ahora, en realidad hay cuatro arcos que podrían dibujarse, por lo que las dos banderas seleccionan uno de ellos. Supongo que probablemente quieras dibujar un arco pequeño (significado large-arc-flag= 0), en la dirección del ángulo decreciente (significado sweep-flag= 0). Todos juntos, la ruta SVG es:

M 200 175 A 25 25 0 0 0 182.322 217.678

Para el segundo ejemplo (suponiendo que se refiere a ir en la misma dirección, y por lo tanto un arco grande), la ruta SVG es:

M 200 175 A 25 25 0 1 0 217.678 217.678

De nuevo, no he probado estos.

(editar 2016-06-01) Si, como @clocksmith, se pregunta por qué eligieron esta API, eche un vistazo a las notas de implementación . Describen dos posibles parametrizaciones de arco, "parametrización de punto final" (la que eligieron) y "parametrización central" (que es como lo que usa la pregunta). En la descripción de "parametrización de punto final" dicen:

Una de las ventajas de la parametrización del punto final es que permite una sintaxis de ruta coherente en la que todos los comandos de ruta terminan en las coordenadas del nuevo "punto actual".

Básicamente, es un efecto secundario de los arcos que se consideran parte de una ruta más grande en lugar de su propio objeto separado. Supongo que si su renderizador SVG está incompleto, podría omitir cualquier componente de ruta que no sepa cómo renderizar, siempre que sepa cuántos argumentos toman. O tal vez permite la representación paralela de diferentes fragmentos de una ruta con muchos componentes. O tal vez lo hicieron para asegurarse de que los errores de redondeo no se acumularan a lo largo de una ruta compleja.

Las notas de implementación también son útiles para la pregunta original, ya que tienen más pseudocódigo matemático para la conversión entre las dos parametrizaciones (que no me di cuenta cuando escribí esta respuesta por primera vez).

wdebeaum
fuente
1
se ve bien, lo intentaremos a continuación. el hecho de que si es "más" del arco (cuando el arco es más de la mitad del círculo) y large-arc-flagse debe alternar de 0 a 1 podría introducir algún error.
nonopolaridad
uf, ¿por qué es esta la api para arcos svg?
Relojero
17

Modifiqué ligeramente la respuesta de opsb e hice un relleno de soporte para el sector circular. 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
fuente
55
El enlace codepen no parece funcionar para mí (Chrome)
Ray Hulha
El arco se dibuja fuera de los límites de SVG. Aumente la altura y el cambio de SVG centerX, centerYpor ejemplo , a 100.
A.Akram
También puede establecer un cuadro de vista explícitamente en el elemento svg principal. Por ejemploviewBox="0 0 500 500"
tapa de Håken
7

Esta es una pregunta antigua, pero el código me pareció útil y me ahorró tres minutos para pensar :) Así que agrego una pequeña expansión a la respuesta de @opsb .

Si desea convertir este arco en un segmento (para permitir el relleno), podemos modificar ligeramente el código:

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>

y ahi tienes!

SumNeuron
fuente
5

Las respuestas de @ opsb son claras, pero el punto central no es exacto, además, como señaló @Jithin, si el ángulo es 360, entonces no se dibuja nada.

@Jithin solucionó el problema de 360, pero si seleccionó menos de 360 ​​grados, obtendrá una línea que cierra el bucle de arco, que no es obligatorio.

Lo arreglé y agregué algo de animación en el código a continuación:

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

Quería comentar sobre la respuesta de @Ahtenus, específicamente sobre el comentario de Ray Hulha diciendo que el codepen no muestra ningún arco, pero mi reputación no es lo suficientemente alta.

La razón de que este codepen no funcione es que su html está defectuoso con un ancho de trazo de cero.

Lo arreglé y agregué un segundo ejemplo aquí: http://codepen.io/AnotherLinuxUser/pen/QEJmkN .

El html:

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

El CSS relevante:

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

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

El javascript:

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

Una imagen y algo de Python

Solo para aclarar mejor y ofrecer otra solución. ArcEl Acomando [ ] usa la posición actual como punto de partida, por lo que Movetoprimero debe usar el comando [M].

Entonces los parámetros de Arcson los siguientes:

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

Si definimos por ejemplo el siguiente archivo svg:

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

ingrese la descripción de la imagen aquí

Establecerá el punto de inicio con Mel punto final con los parámetros xfy yfde A.

Estamos buscando círculos, así que establecemos la rxigualdad de ryhacerlo, básicamente, ahora intentaremos encontrar todos los círculos de radio rxque se cruzan con el punto inicial y 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))

Puedes tener una explicación más detallada en esta publicación que escribí.

GM
fuente
3

Nota para los buscadores de respuestas (quién también era yo): si usar el arco no es obligatorio , una solución mucho más simple para dibujar un círculo parcial es usar stroke-dasharraySVG <circle>.

Divida la matriz de guiones en dos elementos y escale su rango al ángulo deseado. El ángulo de inicio se puede ajustar usando stroke-dashoffset.

Ni un solo coseno a la vista.

Ejemplo completo con explicaciones: https://codepen.io/mjurczyk/pen/wvBKOvP

Maciek Jurczyk
fuente
2

La función original polarToCartesian de wdebeaum es correcta:

var angleInRadians = angleInDegrees * Math.PI / 180.0;

Inversión de los puntos de inicio y final utilizando:

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

Es confuso (para mí) porque esto revertirá la bandera de barrido. Utilizando:

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

con el sweep-flag = "0" dibuja arcos "normales" en sentido contrario a las agujas del reloj, lo que creo que es más sencillo. Ver https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

Wiley
fuente
2

Una ligera modificación a la respuesta de @ opsb. No podemos dibujar un círculo completo con este método. es decir, si damos (0, 360) no dibujará nada en absoluto. Entonces, se hizo una ligera modificación para arreglar esto. Podría ser útil mostrar puntuaciones que a veces alcanzan el 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
fuente
2

Versión 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
fuente
2
Podría decirse que podría aclarar el ejemplo aprovechando los literales de plantilla ES6:const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArc} 0 ${end.x} ${end.y}`
Roy Prins
0

Componente ReactJS basado en la respuesta seleccionada:

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;
Chico
fuente
0

puede usar el código JSFiddle que hice para la respuesta anterior:

https://jsfiddle.net/tyw6nfee/

todo lo que necesita hacer es cambiar el código de la última línea console.log y darle su propio parámetro:

  console.log(describeArc(255,255,220,30,180));
  console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
javad bat
fuente
1
Hice algunos cambios en su fragmento solo para generar un resultado visual. Echa un vistazo: jsfiddle.net/ed1nh0/96c203wj/3
ed1nh0