line, polyline y polygon: los vértices
La sintaxis tolerante del atributo points, por qué una polyline se rellena de negro sin que se lo pidas, el cierre implícito del polígono, y el papel de los marcadores.
Los tres elementos de vértices son el punto donde SVG deja de parecer un lenguaje de dibujo y empieza a parecer un formato de datos: una lista de números y una forma. Son también el sitio donde más gente se estrella, porque polyline tiene un valor por omisión que contradice su nombre y porque el parser de points acepta tantas variantes de separador que oculta los errores hasta que la forma sale rota.
- Escribir
line,polylineypolygoncon su geometría y saber qué hace cada uno con el relleno. - Enumerar los separadores válidos del atributo
pointsy anticipar qué ocurre con una lista impar. - Explicar la diferencia entre el cierre implícito de
polygony volver al primer punto a mano. - Colocar marcadores en los vértices con
marker-start,marker-midymarker-end.
line: la forma que solo existe con trazo
line toma cuatro números: x1, y1, x2, y2. Y tiene una particularidad que no comparte con ninguna otra forma: nunca se rellena. Un segmento no encierra área, así que fill no tiene nada que pintar. Si escribes un line sin stroke, el elemento existe y no se ve.
<svg viewBox="0 0 200 60" width="400">
<line x1="10" y1="30" x2="190" y2="30" stroke="#89b4fa" stroke-width="2" />
</svg>
Esa propiedad tiene una consecuencia que sale de aquí y va a parar al nivel de gradientes: la caja envolvente de un line horizontal tiene altura cero. Y una caja envolvente con una dimensión nula hace que cualquier gradiente o patrón declarado en unidades de caja no se renderice en absoluto. Es la causa más común del gradiente invisible, y se trata a fondo en gradientUnits y los dos sistemas de coordenadas.
Para líneas de rejilla en un gráfico, line es exactamente la herramienta. Para una línea de datos con muchos puntos, no: serían n elementos donde uno bastaría.
points y su parser tolerante
polyline y polygon comparten un único atributo geométrico: points, una lista de pares de coordenadas. La gramática es más permisiva de lo que la mayoría imagina. Son válidos y equivalentes:
<polyline points="0,0 10,20 20,0" />
<polyline points="0 0 10 20 20 0" />
<polyline points="0,0,10,20,20,0" />
<polyline points="0 , 0 10,20
20,0" />
Coma, espacio, tabulador y salto de línea son intercambiables como separadores, y una coma puede llevar espacios a los lados. Esa tolerancia es deliberada, porque estos atributos se generan a máquina y nadie quería obligar a un formato exacto. El precio es que un error de un solo número no se nota: si te comes una coordenada, la lista sigue siendo válida y todos los puntos posteriores se desplazan una posición, así que la forma sale retorcida en lugar de no salir.
Qué pasa con una lista de longitud impar depende de la versión: SVG 1.1 declaraba el atributo entero en error y no se renderizaba nada; SVG 2 dice que se descarte el número sobrante y se dibuje el resto. Los motores actuales siguen el segundo comportamiento. Dicho de otra manera: no confíes en que un error de paridad te avise. Si generas points desde datos, valida la longitud tú.
function puntos(pares) {
if (pares.length % 2 !== 0) throw new Error('lista de coordenadas impar');
return pares.join(' ');
}
El relleno que nadie pidió
Aquí está la trampa principal del nivel. polyline significa «línea múltiple», pero se rellena por omisión, porque fill es una propiedad heredada cuyo valor inicial es negro y polyline no la exceptúa. Dibujar una serie temporal con polyline sin más produce una mancha negra con la forma del área bajo la curva, cerrada por una recta imaginaria del último punto al primero.
<svg viewBox="0 0 200 80" width="400">
<!-- Mal: mancha negra -->
<polyline points="10,70 50,30 90,50 130,20 170,45" stroke="#f38ba8" stroke-width="2" />
<!-- Bien -->
<polyline points="10,70 50,30 90,50 130,20 170,45"
fill="none" stroke="#a6e3a1" stroke-width="2"
stroke-linejoin="round" stroke-linecap="round" />
</svg>
fill="none" no es una decoración: es obligatorio en toda polyline que represente una línea. La misma regla vale para cualquier path abierto, y es la primera cosa que hay que comprobar cuando una curva sale como un borrón.
polygon sí encierra área por definición, así que su relleno tiene sentido. Su diferencia con polyline es exactamente una: cierra la ruta. El último punto se une al primero con un segmento recto, y ese cierre es un cierre de verdad, no un segmento más.
La distinción entre un polygon y una polyline cuyo último punto coincide con el primero es invisible mientras el trazo es fino, y salta a la vista en cuanto engordas stroke-width. Un cierre real produce una unión en ese vértice, con el stroke-linejoin que hayas declarado: si es miter, sale una punta limpia. Una ruta que simplemente vuelve al origen produce dos terminaciones superpuestas con el stroke-linecap correspondiente, y con butt se ve una muesca en la esquina, como si le faltara un trocito. Es un bug clásico de los polígonos generados desde código: alguien añade el primer punto al final de la lista pensando que así lo cierra, y con trazo de 8 píxeles aparece una hendidura que nadie sabe explicar. El mismo fenómeno, con el comando Z, se ve en path. Regla: si la forma es cerrada, díselo al formato; no lo simules con coordenadas.
Marcadores en los vértices
Los tres elementos de este nivel comparten una capacidad que las formas curvas también tienen pero que aquí luce especialmente: los marcadores. Un marker es un trozo de SVG que se instancia en los vértices de una forma, y se conecta con tres propiedades: marker-start, marker-mid y marker-end.
<svg viewBox="0 0 200 90" width="400">
<defs>
<marker id="punto" viewBox="0 0 10 10" refX="5" refY="5"
markerWidth="5" markerHeight="5">
<circle cx="5" cy="5" r="4" fill="#f9e2af" />
</marker>
<marker id="flecha" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f38ba8" />
</marker>
</defs>
<polyline points="15,70 55,30 95,55 135,20 180,40"
fill="none" stroke="#89b4fa" stroke-width="2"
marker-mid="url(#punto)" marker-end="url(#flecha)" />
</svg>
Tres detalles que importan. refX y refY dicen qué punto del marcador se alinea con el vértice, así que son las coordenadas del «agujero de la chincheta». markerWidth y markerHeight son el tamaño en unidades de usuario, y por omisión escalan con stroke-width porque markerUnits vale strokeWidth; si quieres un tamaño fijo pon markerUnits="userSpaceOnUse". Y orient="auto" rota el marcador según la tangente; auto-start-reverse hace lo mismo pero invierte el de inicio, que es lo que quieres para una flecha doble.
Los marcadores son la forma correcta de poner los puntos de una serie temporal: un solo elemento polyline con un marker-mid produce un punto en cada vértice, en lugar de n elementos circle. Con quinientos puntos, la diferencia en número de nodos del DOM es la diferencia entre un gráfico fluido y uno que se arrastra.
Un aviso: marker-mid no se aplica al primero ni al último vértice. Si quieres puntos en todos, declara los tres:
<polyline points="..." fill="none" stroke="#89b4fa"
marker-start="url(#punto)"
marker-mid="url(#punto)"
marker-end="url(#punto)" />
Toma una serie de treinta valores y píntala dos veces en el mismo svg: una como polyline con fill="none" y otra como polygon que cierra contra la línea base para formar el área. Comprueba que el polygon necesita dos vértices extra (las esquinas inferiores) y que el orden en que los añades determina si el área queda debajo de la curva o encima. Después mide cuántos nodos del DOM tiene cada solución frente a la alternativa de un circle por punto.