¿Cómo borrar por encima de una línea en matplotlib?

Inauguramos sección nueva en el 2014 con las preguntas que nos llegan de nuestros lectores por las redes sociales o el correo electrónico 🙂 Nos llegan unas cuantas, ¡así que tenemos material para al menos una pregunta semanal! Estas serán entradas breves que publicaremos cada martes y que tratarán de responder vuestras dudas sin rodeos. ¡Si queréis mandar las vuestras no dudéis en contactar con nosotros!

Empezamos con Alberto, que me comenta:

¿Cómo puedo «borrar» lo que tengo por encima de una línea en matplotlib? He intentado con fill_between entre la línea de estabilidad funcional (línea roja) pero solo rellena con color, no sobreescribe que es lo que pretendo. ¿Se te ocurre alguna forma?

Mapa compresor - Primera versión

Alberto está escribiendo un programa para dibujar mapas de actuaciones de turbomáquinas en Python, similares a los que producen programas privativos como GSP (ejemplos) o GasTurb (ejemplos). En esos mapas aparece la línea de estabilidad funcional (surge line o stall line) por encima de la cual la turbomáquina no puede funcionar. Es preciso, por tanto, borrar todo lo que quede por encima de ella para suprimir información innecesaria del gráfico. El código es un poco complicado, así que voy a comentar solo los conceptos fundamentales.

Lo primero que hice (después de admitir que no tenía ni idea) fue intentar trabajar sobre lo que ya había intentado. Efectivamente, si usas la función fill_between el relleno se queda «por debajo» de las líneas que ya había, en lugar de taparlas. Consultando la documentación de fill_between vi que admitía un parámetro zorder, que controla la visibilidad de los elementos de la gráfica: por defecto vale 0, y cuanto mayor es más arriba aparece el elemento. Usando un valor lo suficientemente alto se llega a este resultado:

Mapa compresor - Segunda versión

Que es más o menos lo que se pretendía... pero en mi ordenador se vio el detalle fatal: el color de fondo y la rejilla quedan tapados. Esta solución no es suficiente.

A continuación me puse a pensar en si habría alguna manera de calcular la intersección de esas líneas con matplotlib. Ya Kiko escribió un artículo que utilizaba Shapely para calcular intersecciones entre formas geométricas, así que tenía un punto de partida, pero introducir Shapely para resolver algo tan aparentemente simple no me gustaba.

Otra opción era crear funciones interpolantes usando SciPy y calcular intersecciones entre funciones usando cualquiera de los métodos de optimización disponibles. El problema es que las curvas negras de la figura, que se obtienen con la función contour, no se pueden trasformar en una función tan fácilmente y eso me causaría problemas.

Estaba ya desempolvando mi ejemplar de «Computational Geometry: Algorithms and Applications» cuando se me ocurrió que tal vez matplotlib tuviese el concepto de máscaras, es decir, poder utilizar una forma geométrica para enmascarar otra, de la misma forma que usamos máscaras en arrays de NumPy. Y efectivamente, después de un rato buscando en Google encontré justo lo que buscaba: el método set_clip_path.

La palabra clave aquí era clip, que en inglés quiere decir algo así como «recortar»: hay que pasarle al método la forma que queremos usar para recortar la línea. En este caso, podemos extraer el área que resulta de fill_between (hacia abajo esta vez).

Si queremos además incluir etiquetas para las curvas de nivel, podemos definir manualmente su posición para que no queden fuera de la máscara. Esto se hace con el parámetro manual de la función clabel.

El código quedaría así:

from matplotlib.patches import PathPatch
# Relleno desde la línea hacia abajo
fillb = plt.fill_between(surge_line_x, surge_line_y, color='none')
# Extraemos el Path
path, = fillb.get_paths()
# Lo convertimos en un Patch
mask = PathPatch(path, fc='none')
# Y lo añadimos a la figura
plt.gca().add_patch(mask)
# Lo aplicamos a las curvas de nivel
cs = plt.contour(cx, cy, cz, colors="black")
for contour in cs.collections:
    cs.set_clip_path(mask)
# Posicionamos las etiquetas
labels_xy = [(10.8, 2.0), (12.4, 2.7), (12.4, 3.3)]
plt.clabel(cs, fmt='%1.2f', manual=labels_xy)
# Y aplicamos la máscara a las líneas normales
ll, = plt.plot(lx, ly, color="blue")
ll.set_clip_path(mask)

Y este sería el resultado:

Mapa compresor - Final

¡Ahora sí! 🙂

¡Y hasta aquí la pregunta de la semana! ¿Qué te ha parecido el método para llegar a la solución? ¿Se te ocurre una manera mejor? ¿Crees que te será útil para algo que estás haciendo ahora mismo? ¡Cuéntanos en los comentarios! Y si quieres mandarnos tu pregunta, ya sabes dónde estamos 😉

Juan Luis Cano

Estudiante de ingeniería aeronáutica y con pasión por la programación y el software libre. Obsesionado con mejorar los pequeños detalles y con ganas de cambiar el mundo. Divulgando Python en español a través de Pybonacci y la asociación Python España.

More Posts - Website

Follow Me:
TwitterLinkedIn

9 thoughts on “¿Cómo borrar por encima de una línea en matplotlib?

  1. Vaya, vaya… y parecía inocente el problemita… me lo apunto, porque seguro que en algún momento me hace falta algo así.

    Por cierto, no conocía Shapely y la verdad que me hubiese venido muy bien en una cosa que hice (tuve que acabar construyendo unos algoritmos caseros y malos…

    1. ¡Al final es una tontería, pero matplotlib todavía me intimida bastante!

      ¿Qué era lo que tenías que hacer, por curiosidad?

      1. Te acuerdas de las partículas de hielo que chocaban contra el perfil? pues al final, me tuve que hacer un algoritmo para hallar los puntos de corte y parar la integración. Funcionaba bastante bien, pero si hubiese tenido esto…

    1. ¡Me alegro de que hayas quedado satisfecho! 😛 Ha sido una pregunta muy interesante, me ha gustado darle vueltas al coco (¡aunque en el fondo era una tontería!). Un saludo y quedamos pendientes de futuras actualizaciones 😉

      1. Bueno, tanto como tontería… Matplotlib tiene una estructura de objetos bastante interesante, y se nota que está muy bien construida; con el conocimiento suficiente ¡puede hacerse prácticamente cualquier cosa! El problema es que es inmensa, y claro, convertirse en un auténtico gurú de la biblioteca puede llevar mucho, mucho tiempo jaja

Leave a Reply

Your email address will not be published. Required fields are marked *