Dibujo circular con la trayectoria del arco de SVG


147

Pregunta corta: usando la ruta SVG, podemos dibujar el 99.99% de un círculo y aparece, pero cuando es el 99.99999999% de un círculo, entonces el círculo no aparecerá. ¿Cómo se puede arreglar?

La siguiente ruta SVG puede dibujar el 99.99% de un círculo: (pruébelo en http://jsfiddle.net/DFhUF/1381/ y vea si ve 4 arcos o solo 2 arcos, pero tenga en cuenta que si es IE, es renderizado en VML, no SVG, pero tiene el problema similar)

M 100 100 a 50 50 0 1 0 0.00001 0

Pero cuando es 99.99999999% de un círculo, ¿entonces nada se mostrará?

M 100 100 a 50 50 0 1 0 0.00000001 0    

Y eso es lo mismo con el 100% de un círculo (sigue siendo un arco, ¿no es así, solo un arco muy completo?)

M 100 100 a 50 50 0 1 0 0 0 

¿Cómo se puede arreglar eso? La razón es que uso una función para dibujar un porcentaje de un arco, y si necesito un "caso especial" de 99.9999% o 100% de arco para usar la función de círculo, sería una tontería.

Nuevamente, un caso de prueba en jsfiddle usando RaphaelJS está en http://jsfiddle.net/DFhUF/1381/
(y si es VML en IE 8, incluso el segundo círculo no se mostrará ... debe cambiarlo a 0,01)


Actualizar:

Esto se debe a que estoy representando un arco para una puntuación en nuestro sistema, por lo que 3.3 puntos obtienen 1/3 de un círculo. 0.5 obtiene medio círculo, y 9.9 puntos obtienen el 99% de un círculo. Pero, ¿qué pasa si hay puntajes de 9.99 en nuestro sistema? ¿Tengo que verificar si está cerca del 99.999% de un círculo y usar una arcfunción o una circlefunción en consecuencia? Entonces, ¿qué pasa con una puntuación de 9.9987? ¿Cuál usar? Es ridículo necesitar saber qué tipo de puntajes se asignarán a un "círculo demasiado completo" y cambiar a una función de círculo, y cuando sea "un cierto 99.9%" de un círculo o un puntaje de 9.9987, use la función de arco .


@minitech, por supuesto, funciona ... es entonces el 99% de un círculo (solo hablando más o menos). El caso es que puede dibujar 98%, 99%, 99.99% de un círculo, pero no 99.9999999% o 100%
polaridad

1
Ambos enlaces van a lo mismo, y funciona bien en Safari.
Mark Bessey

correcto, mismo enlace, solo quiero que la gente vea el caso de prueba antes, así que agrego el enlace al comienzo de la pregunta. El safari correcto lo hará, qué bueno ... Chrome y Firefox no lo harán ... un poco extraño porque Safari y Chrome son Webkit ... pero ¿el motor SVG depende de Webkit?
nonopolaridad

@Marcin se ve bien, ¿cómo? ves 4 arcos o 2 arcos? ¿siquiera miraste el código?
nonopolaridad

2
un jsfiddle actualizado con Raphael incluido como biblioteca, para evitar errores de origen cruzado al cargar raphael.js: jsfiddle.net/DFhUF/1381
ericsoco

Respuestas:


35

Lo mismo para el arco de XAML. ¡Solo cierra el arco del 99,99% con Zay tienes un círculo!


Entonces, ¿puede cerrar el tercer caso en la muestra jsfiddle y hacer que funcione?
nonopolaridad

con bajar el valor a 0.0001, sí. el problema suena relacionado con la implementación de varios navegadores de WebKit, no SVG en sí. Pero así es como se hace un círculo en el componente SVG / XAMLs Arc.
Todd Main

Ahhh, hacer trampa, siempre la mejor solución. Todavía necesito manejar el caso del 100%, pero simplemente "redondeando" al 99.999% en lugar de con una rama de código completamente separada.
SimonR

Cualquier demo? Esta respuesta no cumple
Medet Tleukabiluly

@MedetTleukabiluly tienes el arco de arriba en la pregunta, agrega un zal final y listo. Es posible que deba eliminar ceros, por ejemplo, en el último Chrome que tuve que escribir 0.0001para que funcione. Si está buscando dónde colocar la cadena de ruta, busque Rutas en MDN .
TWiStErRob

416

Sé que es un poco tarde en el juego, pero recordé esta pregunta de cuando era nuevo y tenía un dilema similar, y accidentalmente encontré la solución "correcta", si alguien todavía está buscando una:

<path 
    d="
    M cx cy
    m -r, 0
    a r,r 0 1,0 (r * 2),0
    a r,r 0 1,0 -(r * 2),0
    "
/>

En otras palabras, esto:

<circle cx="100" cy="100" r="75" />

se puede lograr como un camino con esto:

  <path 
        d="
        M 100, 100
        m -75, 0
        a 75,75 0 1,0 150,0
        a 75,75 0 1,0 -150,0
        "
  />

El truco consiste en tener dos arcos, el segundo retomando donde quedó el primero y usando el diámetro negativo para volver al punto de inicio del arco original.

La razón por la que no se puede hacer como un círculo completo en un arco (y solo estoy especulando) es porque le diría que dibuje un arco de sí mismo (digamos 150,150) para sí mismo (150,150), que representa como "¡oh, ya estoy allí, no es necesario un arco!".

Los beneficios de la solución que estoy ofreciendo son:

  1. es fácil traducir de un círculo directamente a un camino, y
  2. no hay superposición en las dos líneas de arco (lo que puede causar problemas si está utilizando marcadores o patrones, etc.). Es una línea continua limpia, aunque dibujada en dos piezas.

Nada de esto importaría si solo permitieran que las rutas de texto acepten formas. Pero creo que están evitando esa solución ya que los elementos de forma como el círculo técnicamente no tienen un punto de "inicio".

Demostración de jsfiddle: http://jsfiddle.net/crazytonyi/mNt2g/

Actualizar:

Si está utilizando la ruta para una textPathreferencia y desea que el texto se represente en el borde exterior del arco, usaría el mismo método exacto pero cambiaría el indicador de barrido de 0 a 1 para que trate el exterior del camino como la superficie en lugar del interior (piense 1,0como alguien sentado en el centro y dibujando un círculo a su alrededor, mientras 1,1que alguien caminando por el centro a una distancia de radio y arrastrando su tiza a su lado, si eso es de ayuda). Aquí está el código como arriba pero con el cambio:

<path 
    d="
    M cx cy
    m -r, 0
    a r,r 0 1,1 (r * 2),0
    a r,r 0 1,1 -(r * 2),0
    "
/>

1
+1, gracias por las fórmulas. Por supuesto, los dos primeros comandos podrían consolidarse.
John Gietzen

+1 por la claridad y practicidad de la solución en general, pero especialmente por los "elementos de forma como el círculo técnicamente no tienen un punto de" inicio " , una forma tan clara de expresar el problema.
David John Welsh

11
Creo que el problema aquí no es que "¡oh, ya estoy allí, no es necesario un arco!", Ya que al proporcionar 1 como bandera de arco grande, explícitamente le dice al motor que desea que avance más. El verdadero problema es que hay infinitos círculos que cumplen con las restricciones de pasar por su punto. Esto explica por qué cuando quieres un arco de 360 ​​*, el motor no sabe qué hacer. La razón por la que no funciona para 359.999 es que este es un cálculo numéricamente inestable, y aunque técnicamente hay exactamente un círculo que coincide con la solicitud, probablemente no sea el que desea de todos modos
qbolec

@qbolec - Tienes razón, estaba pensando en términos de que el arco grande y el barrido están desactivados y que el arco no tiene destino. O, al menos, que incluso con el arco grande y el barrido permitieron que hubiera un diseño fundamental que definiera un arco como la curva entre dos puntos (de modo que con un punto usado dos veces, lo vio como un solo punto colapsado). La visión del arco que potencialmente dibuja 360 círculos alrededor de un punto tiene sentido, aunque colapsaría en un círculo si se define un centro, lo que plantea la cuestión de si un solo arco podría hacer un círculo completo si existiera un centro.
Anthony

1
"los elementos de forma como el círculo técnicamente no tienen un" punto "de inicio". Esto no es realmente cierto. La especificación SVG especifica absolutamente dónde está el punto de inicio de todas las formas. Tiene que hacerlo para que las matrices de guiones funcionen en formas.
Paul LeBeau

17

En referencia a la solución de Anthony, aquí hay una función para obtener el camino:

function circlePath(cx, cy, r){
    return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0';
}

10

Un enfoque totalmente diferente:

En lugar de jugar con rutas para especificar un arco en svg, también puede tomar un elemento circular y especificar un trazo-dasharray, en pseudocódigo:

with $score between 0..1, and pi = 3.141592653589793238

$length = $score * 2 * pi * $r
$max = 7 * $r  (i.e. well above 2*pi*r)

<circle r="$r" stroke-dasharray="$length $max" />

Su simplicidad es la principal ventaja sobre el método de ruta de arco múltiple (p. Ej., Al crear scripts solo se conecta un valor y ya está listo para cualquier longitud de arco)

El arco comienza en el punto más a la derecha y se puede desplazar usando una transformación de rotación.

Nota: Firefox tiene un error extraño en el que se ignoran las rotaciones de más de 90 grados o más. Entonces, para comenzar el arco desde arriba, use:

<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />

tenga en cuenta que esta solución solo es aplicable para casos en los que no utiliza ningún tipo de relleno y solo necesita un segmento de arco sin desviaciones. En el momento en que desea dibujar sectores, incluso necesita un nuevo nodo de ruta svg, que de otro modo podría manejarse en una sola etiqueta de ruta.
mxfh

@mxfh no realmente, si tomas el ancho del trazo dos veces el valor de r obtienes sectores perfectos.
mvds

Con stroke-dasharray="0 10cm 5cm 1000cm"esto es posible evitar la transformación
stenci

@stenci sí, pero la desventaja es que no puede tener un parámetro en el dasharray para escalar de 0% a 100% de relleno (que era uno de mis objetivos)
mvds

5

Adobe Illustrator utiliza curvas bezier como SVG, y para círculos crea cuatro puntos. Puede crear un círculo con dos comandos de arco elíptico ... pero luego, para un círculo en SVG, usaría un <circle />:)


"¿Puedes crear un círculo con dos comandos de arco elíptico"? ¿Qué quieres decir? ¿No puedes usar uno? Al menos haciendo pasar de (0,0) a (0.01, 0) para que produzca algo. Para usar circle, consulte la Actualización en la pregunta.
nonopolaridad

No, no creo que puedas usar solo uno. Has demostrado que no puedes y tienes un problema similar con los arcos de HTML5 Canvas.
Phrogz

¿Quieres decir, usar arcpara crear un círculo completo usando el arco izquierdo y el arco derecho? Por lo tanto, el arco no puede dibujar un círculo completo ... para hacerlo arc, se necesitan dos caminos
polaridad

2
@ 動靜 能量 Correcto, se necesitan dos caminos de arco (o el feo truco de un arco que va la mayor parte del camino y luego un comando de camino cerrado a línea recta hasta el final).
Phrogz

1
Ilustrador apesta. No se puede hacer un círculo con curvas bezier. Solo algo cercano a un círculo, pero aún no un círculo.
Adius

4

Es una buena idea usar dos comandos de arco para dibujar un círculo completo.

por lo general, uso elipse o elemento circular para dibujar un círculo completo.


55
Una limitación importante de svg es que los elementos de forma no se pueden usar para rutas de texto. Esta es probablemente la motivación principal para todos los que visitan esta pregunta.
Anthony

1
@ Anthony ahora pueden. Firefox admite textPath contra cualquier forma. Sospecho que Chrome también lo hace.
Robert Longson

@RobertLongson: ¿puede proporcionar un ejemplo o enlaces a un ejemplo? Curioso cómo decide dónde comienza la ruta de texto y si está en el exterior o en el interior (etc.) cuando se dibuja sin un punto de partida tradicional.
Anthony

@Anthony Vea la especificación SVG 2, por ejemplo , w3.org/TR/SVG2/shapes.html#CircleElement , también el nuevo atributo del lado
Robert Longson

4

Sobre la base de las respuestas de Anthony y Anton, incorporé la capacidad de rotar el círculo generado sin afectar su apariencia general. Esto es útil si está utilizando la ruta para una animación y necesita controlar dónde comienza.

function(cx, cy, r, deg){
    var theta = deg*Math.PI/180,
        dx = r*Math.cos(theta),
        dy = -r*Math.sin(theta);
    return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}

Esto es exactamente lo que necesitaba. Estaba usando el plugin greensock morph para transformar una ruta circular en una ruta rectangular y necesitaba una ligera rotación para que se viera bien.
RoboKozo

3

Para aquellos como yo que buscaban atributos de elipse para la conversión de ruta:

const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0`

2

Escrito como una función, se ve así:

function getPath(cx,cy,r){
  return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}

2
¡Esta es una respuesta genial! Solo una pequeña adición: este camino se dibuja este, norte, oeste y sur. Si desea que el círculo se comporte exactamente igual que una <circle>, usted tiene que cambiar la dirección de este, sur, oeste, norte: "M" + cx + "," + cy + "m" + (r) + ",0" + "a" + r + "," + r + " 0 1,1 " + (-r * 2) + ",0" + "a" + r + "," + r + " 0 1,1 " + (r * 2) + ",0" La ventaja es que stroke-dashoffset, y startOffsetahora funciona de la misma manera.
telar

2

Estas respuestas son demasiado complicadas.

Una forma más sencilla de hacer esto sin crear dos arcos o convertirlos a diferentes sistemas de coordenadas.

Esto supone que su área de lienzo tiene ancho wy alto h.

`M${w*0.5 + radius},${h*0.5}
 A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`

Simplemente use la bandera de "arco largo", para que se llene la bandera completa. Luego haga que los arcos sean 99.9999% del círculo completo. Visualmente es lo mismo. Evite la bandera de barrido simplemente comenzando el círculo en el punto más a la derecha del círculo (un radio directamente horizontal desde el centro).


1

Otra forma sería usar dos curvas Cubic Bezier. Eso es para personas con iOS que usan pocketSVG que no reconoce el parámetro svg arc.

C x1 y1, x2 y2, xy (o c dx1 dy1, dx2 dy2, dx dy)

Curva de Bezier cúbico

El último conjunto de coordenadas aquí (x, y) es donde desea que termine la línea. Los otros dos son puntos de control. (x1, y1) es el punto de control para el inicio de su curva, y (x2, y2) para el punto final de su curva.

<path d="M25,0 C60,0, 60,50, 25,50 C-10,50, -10,0, 25,0" />

1

Hice un jsfiddle para hacerlo aquí:

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;       
}
console.log(describeArc(255,255,220,134,136))

enlace

todo lo que necesita hacer es cambiar la entrada de console.log y obtener el resultado en la consola


Esto era exactamente lo que estaba buscando. ¡La mejor respuesta aquí! votó a favor de este tipo
Måns Dahlström
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.