2011-10-09 23 views
5

¿Cuál es la herramienta adecuada para el trabajo si quiero escribir un script Python que produzca gráficos vectoriales en formato PDF? En particular, necesito dibujar polígonos rellenos con esquinas redondeadas (es decir, figuras planas que se componen de líneas rectas y arcos circulares ).Producir archivos PDF, dibujar polígonos con esquinas redondeadas

Parece que matplotlib hace que sea bastante fácil dibujar rectángulos con esquinas redondeadas y polígonos generales con esquinas agudas. Sin embargo, para dibujar polígonos con esquinas redondeadas, parece que primero tengo que calcular una curva de Bézier que se aproxime a la forma.

¿Hay algo más sencillo disponible? ¿O hay otra biblioteca que pueda usar para calcular la curva de Bézier que se aproxima a la forma que quiero producir? Idealmente, simplemente especificaría el par (ubicación, radio de la esquina) para cada vértice.

Aquí se muestra un ejemplo: me gustaría especificar el polígono rojo (+ el radio de cada esquina) y la biblioteca iba a emitir la figura gris:

example

(Para polígonos convexos que pude hacer trampa y utilice un bolígrafo grueso para dibujar el contorno del polígono. Sin embargo, esto no funciona en el caso no convexo.)

Respuesta

4

En cuanto a la producción de archivos PDF, sugiero echar un vistazo a la biblioteca cairo, gráficos vectoriales libaray que admiten "dibujar" en superficies de PDF. También tiene enlaces de Python.

En cuanto a dibujar polígonos con esquinas redondeadas, no conozco ninguna biblioteca de gráficos que admita esto de fábrica.

Pero no debería ser demasiado complicado calcular las coordenadas del arco en las esquinas del polígono dado el radio de la esquina. Básicamente debe encontrar el punto en la bisectriz de ángulo de dos bordes adyacentes que tiene la distancia r (es decir, el radio deseado) desde ambos bordes. Este es el centro del arco, para encontrar el punto inicial y final, proyectarás desde este punto hacia los dos bordes.

Puede haber casos no triviales, por ej. qué hacer, si los bordes del polígono son demasiado cortos para caber dos arcos (supongo que tendrá que seleccionar un radio más pequeño en este caso), y tal vez otros, actualmente no estoy al tanto ...

HTH

+0

Gracias por la respuesta! Claro, calcular las coordenadas del arco y las coordenadas del segmento de línea no es tan difícil; es decir, podría dibujar con bastante facilidad el * contorno * de la forma utilizando arcos y segmentos de línea separados. Sin embargo, ¿cómo obtengo una figura de avión * llena *? ¿Puedo usar la biblioteca de El Cairo para componer una ruta cerrada de arcos y segmentos de línea y luego decirle a la biblioteca que llene el interior de la ruta? –

+1

Sí, puedes :) – MartinStettner

13

Aquí hay una solución matplotlib algo hacky. Las principales complicaciones están relacionadas con el uso de objetos matplotlib Path para construir un compuesto Path.

#!/usr/bin/env python 

import numpy as np 
from matplotlib.path import Path 
from matplotlib.patches import PathPatch, Polygon 
from matplotlib.transforms import Bbox, BboxTransformTo 

def side(a, b, c): 
    "On which side of line a-b is point c? Returns -1, 0, or 1." 
    return np.sign(np.linalg.det(np.c_[[a,b,c],[1,1,1]])) 

def center((prev, curr, next), radius): 
    "Find center of arc approximating corner at curr." 
    p0, p1 = prev 
    c0, c1 = curr 
    n0, n1 = next 
    dp = radius * np.hypot(c1 - p1, c0 - p0) 
    dn = radius * np.hypot(c1 - n1, c0 - n0) 
    p = p1 * c0 - p0 * c1 
    n = n1 * c0 - n0 * c1 
    results = \ 
     np.linalg.solve([[p1 - c1, c0 - p0], 
         [n1 - c1, c0 - n0]], 
         [[p - dp, p - dp, p + dp, p + dp], 
         [n - dn, n + dn, n - dn, n + dn]]) 
    side_n = side(prev, curr, next) 
    side_p = side(next, curr, prev) 
    for r in results.T: 
     if (side(prev, curr, r), side(next, curr, r)) == (side_n, side_p): 
      return r 
    raise ValueError, "Cannot find solution" 

def proj((prev, curr, next), center): 
    "Project center onto lines prev-curr and next-curr." 
    p0, p1 = prev = np.asarray(prev) 
    c0, c1 = curr = np.asarray(curr) 
    n0, n1 = next = np.asarray(next) 
    pc = curr - prev 
    nc = curr - next 
    pc2 = np.dot(pc, pc) 
    nc2 = np.dot(nc, nc) 
    return (prev + np.dot(center - prev, pc)/pc2 * pc, 
      next + np.dot(center - next, nc)/nc2 * nc) 

def rad2deg(angle): 
    return angle * 180.0/np.pi 

def angle(center, point): 
    x, y = np.asarray(point) - np.asarray(center) 
    return np.arctan2(y, x) 

def arc_path(center, start, end): 
    "Return a Path for an arc from start to end around center." 
    # matplotlib arcs always go ccw so we may need to mirror 
    mirror = side(center, start, end) < 0 
    if mirror: 
     start *= [1, -1] 
     center *= [1, -1] 
     end *= [1, -1] 
    return Path.arc(rad2deg(angle(center, start)), 
        rad2deg(angle(center, end))), \ 
      mirror 

def path(vertices, radii): 
    "Return a Path for a closed rounded polygon." 
    if np.isscalar(radii): 
     radii = np.repeat(radii, len(vertices)) 
    else: 
     radii = np.asarray(radii) 
    pv = [] 
    pc = [] 
    first = True 
    for i in range(len(vertices)): 
     if i == 0: 
      seg = (vertices[-1], vertices[0], vertices[1]) 
     elif i == len(vertices) - 1: 
      seg = (vertices[-2], vertices[-1], vertices[0]) 
     else: 
      seg = vertices[i-1:i+2] 
     r = radii[i] 
     c = center(seg, r) 
     a, b = proj(seg, c) 
     arc, mirror = arc_path(c, a, b) 
     m = [1,1] if not mirror else [1,-1] 
     bb = Bbox([c, c + (r, r)]) 
     iter = arc.iter_segments(BboxTransformTo(bb)) 
     for v, c in iter: 
      if c == Path.CURVE4: 
       pv.extend([m * v[0:2], m * v[2:4], m * v[4:6]]) 
       pc.extend([c, c, c]) 
      elif c == Path.MOVETO: 
       pv.append(m * v) 
       if first: 
        pc.append(Path.MOVETO) 
        first = False 
       else: 
        pc.append(Path.LINETO) 
    pv.append([0,0]) 
    pc.append(Path.CLOSEPOLY) 

    return Path(pv, pc) 

if __name__ == '__main__': 
    from matplotlib import pyplot 
    fig = pyplot.figure() 
    ax = fig.add_subplot(111) 
    vertices = [[3,0], [5,2], [10,0], [6,9], [6,5], [3, 5], [0,2]] 

    patch = Polygon(vertices, edgecolor='red', facecolor='None', 
        linewidth=1) 
    ax.add_patch(patch) 

    patch = PathPatch(path(vertices, 0.5), 
         edgecolor='black', facecolor='blue', alpha=0.4, 
         linewidth=2) 
    ax.add_patch(patch) 

    ax.set_xlim(-1, 11) 
    ax.set_ylim(-1, 9) 
    fig.savefig('foo.pdf') 

output of script above

+0

Gracias, ¡esto es genial! Acepté la otra respuesta, ya que parece que El Cairo es la herramienta "correcta" para este tipo de tareas, pero es realmente agradable ver que también se puede hacer en matplotlib.En particular, el fragmento de código que construye una ruta compuesta a partir de fragmentos de ruta es muy útil. –