5

I have recently switched from making illutrations with TeX (PGF/TikZ) to using Matplotlib for the same purpose. The main reason is that a lot of my scientific code is in python and some of the illustrations should directly use the output of python calculations.

I have been using annotate and the fancy arrow patch, which already does a lot of what I need, such as drawing curved arrows (see e.g. here). Compared to TikZ, there is one particular thing I have been missing: path decorations (see e.g. https://tex.stackexchange.com/a/193451/96546 for an easy TikZ example). My question is if there is a way to do such TikZ-style path decorations in matplotlib?

Since this is rather broad I will set a particular task (taken from the TeX.SE question https://tex.stackexchange.com/questions/193444/sketching-simple-arrows-with-different-direction): How would I draw the following picture in Matplotlib?

enter image description here

I am aware of this question, but the implementation is not really a decoration of a line between endpoints, but rather a plotted function.

CDJB
  • 14,043
  • 5
  • 29
  • 55
Wolpertinger
  • 1,169
  • 2
  • 13
  • 30
  • Well, it needs to be some kind of function, right? Or are you looking for Bezier curves? In both cases, there is quite a bit of work involved, especially if this is to be drawn in screen coordinates instead of data coordinates. I suppose you can then implement it as a PathEffect to be easily reusable. – ImportanceOfBeingErnest Feb 17 '20 at 13:41
  • @ImportanceOfBeingErnest I was hoping for something simple, but figured it might just be too much work to ask for. My main motivation was that it is an existing and easy-to-use feature in Tikz. So I thought there might already be something in matplotlib or one might be able to port the algorithm from Tikz. But then that might just be too much work. For that reason I made the question more specific: the task is just to reproduce the given picture. I would accept an answer that does. Bonus points (bounty) if one of the wiggly lines can be curved/an arch. – Wolpertinger Feb 17 '20 at 15:58

1 Answers1

4

I created this in matplotlib with the help of the feynman package by GkAntonius (docs). This has capabilities for neat plotting of various arrow types using vertices and lines, including a style='wiggly' line style. I also added a straight start and end segment to these wiggly lines, as in the example diagram in the OP. In addition, given the comments/bounty asks for a curved/arch wiggly line, I have also included a demonstration of this functionality.

Diagram in question

enter image description here

Code:

import matplotlib.pyplot as plt
from feynman import Diagram

# Set up diagram
fig = plt.figure(figsize=(10.,10.))
ax = fig.add_axes([0,0,1,1], frameon=False)
diagram = Diagram(ax)

# Mirror line
m1 = diagram.vertex(xy=(.1,.5), marker='')
m2 = diagram.vertex(xy=(.9,.5), marker='')
mirror = diagram.line(m1, m2, arrow=False, style='double')


# Top left P-line
p1 = diagram.vertex(xy=(.2, .8), marker='')
p2 = diagram.vertex(xy=(.5, .5), marker='')
P1 = diagram.line(p1, p2, arrow=True, arrow_param={'t':0.91})
P1.text("$P$", fontsize=60, y=-.1, t=.1)


# Bottom right P-line
p3 = diagram.vertex(xy=(.5, .5), marker='')
p4 = diagram.vertex(xy=(.8, .2), marker='')
P2 = diagram.line(p3, p4, arrow=True, arrow_param={'t':0.91})
P2.text("$P$", fontsize=60, y=.05, t=.9)


# Top right P-line
p5 = diagram.vertex(xy=(.5, .5), marker='')
p6 = diagram.vertex(xy=(.8, .8), marker='')
P3 = diagram.line(p5, p6, arrow=True, arrow_param={'t':0.91})
P3.text("$P$", fontsize=60, y=-.05, t=.9)


# Top right SV-line start
sv1_s1 = diagram.vertex(xy=(.5, .5), marker='')
sv1_s2 = diagram.vertex(xy=(.51, .5175), marker='')
SV1_start = diagram.line(sv1_s1, sv1_s2, arrow=False)

# Top right SV-line middle
sv1_1 = diagram.vertex(xy=(.51, .5175), marker='')
sv1_2 = diagram.vertex(xy=(.69, .8325), marker='')
SV1 = diagram.line(sv1_1, sv1_2, arrow=False, style='wiggly', nwiggles=5)
SV1.text("$SV$", fontsize=60, y=.15, t=.9)

# Top right SV-line end
sv1_e1 = diagram.vertex(xy=(.69, .8325), marker='')
sv1_e2 = diagram.vertex(xy=(.7, .85), marker='')
SV1_end = diagram.line(sv1_e1, sv1_e2, arrow=True, arrow_param={'t':0.91})


# Bottom right SV-line start
sv2_s1 = diagram.vertex(xy=(.5, .5), marker='')
sv2_s2 = diagram.vertex(xy=(.51, .4825), marker='')
SV2_start = diagram.line(sv2_s1, sv2_s2, arrow=False)

# Bottom right SV-line middle
sv2_1 = diagram.vertex(xy=(.51, .4825), marker='')
sv2_2 = diagram.vertex(xy=(.69, .1675), marker='')
SV2 = diagram.line(sv2_1, sv2_2, arrow=False, style='wiggly', nwiggles=5)
SV2.text("$SV$", fontsize=60, y=-.15, t=.9)

# Bottom right SV-line end
sv2_e1 = diagram.vertex(xy=(.69, .1675), marker='')
sv2_e2 = diagram.vertex(xy=(.7, .15), marker='')
SV2_end = diagram.line(sv2_e1, sv2_e2, arrow=True, arrow_param={'t':0.91})

diagram.plot()
plt.show()

Curved/arched lines

enter image description here

Code

import matplotlib.pyplot as plt
from feynman import Diagram

# Set up diagram
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0,0,1,1], frameon=False)
diagram = Diagram(ax)

# Curvy wiggly line
sv1_1 = diagram.vertex(xy=(.205, .205), marker='')
sv1_2 = diagram.vertex(xy=(.79, .79), marker='')
SV1 = diagram.line(sv1_1, sv1_2, arrow=False, style='wiggly elliptic ', nwiggles=8)
SV1.text("$1$", fontsize=50, y=.15, t=.5)

# Curvy wiggly line end
sv1_e1 = diagram.vertex(xy=(.79, .79), marker='')
sv1_e2 = diagram.vertex(xy=(.8, .8), marker='')
SV1_end = diagram.line(sv1_e1, sv1_e2, arrow=True, arrow_param={'t':0.91})


# Circular wiggly line
sv2_1 = diagram.vertex(xy=(.75, .25), marker='')
SV2 = diagram.line(sv2_1, sv2_1, arrow=False, style='wiggly circular', nwiggles=8)
SV2.text("$2$", fontsize=50, y=.15, t=.5)

diagram.plot()
plt.show()
CDJB
  • 14,043
  • 5
  • 29
  • 55
  • 1
    Beautiful, works great! Thank you very much! For other readers I'll mention the parameters `ellipse_spread` and `ellipse_excentricity`, which are useful for controlling the ellipse shape. – Wolpertinger Feb 18 '20 at 16:17