44

I am drawing two subplots with Matplotlib, essentially following :

subplot(211); imshow(a); scatter(..., ...)
subplot(212); imshow(b); scatter(..., ...)

Can I draw lines between those two subplots? How would I do that?

F.X.
  • 6,809
  • 3
  • 49
  • 71

3 Answers3

58

The solution from the other answers are suboptimal in many cases (as they would only work if no changes are made to the plot after calculating the points).

A better solution would use the specially designed ConnectionPatch:

import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
import numpy as np

fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

x,y = np.random.rand(100),np.random.rand(100)

ax1.plot(x,y,'ko')
ax2.plot(x,y,'ko')

i = 10
xy = (x[i],y[i])
con = ConnectionPatch(xyA=xy, xyB=xy, coordsA="data", coordsB="data",
                      axesA=ax2, axesB=ax1, color="red")
ax2.add_artist(con)

ax1.plot(x[i],y[i],'ro',markersize=10)
ax2.plot(x[i],y[i],'ro',markersize=10)


plt.show()

enter image description here

Matt Hancock
  • 3,870
  • 4
  • 30
  • 44
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Good point. This actually works strictly better than the previously accepted answer, so I'll accept it instead. Thanks! – F.X. May 19 '17 at 07:38
  • 9
    It's worth a comment on why the `ax2.add_artist` is on `ax2` rather than `ax1` github.com/matplotlib/matplotlib/issues/8744 and why `axesA` is set to be `ax2` – Joel Oct 28 '17 at 00:53
  • 1
    Apparently, when using several `subplots`, I need to use the `fig.add_artist`, otherwise it seems to mess with `constrained_layout`. – Stefan Oct 29 '20 at 10:12
28

You could use fig.line. It adds any line to your figure. Figure lines are higher level than axis lines, so you don't need any axis to draw it.

This example marks the same point on the two axes. It's necessary to be careful with the coordinate system, but the transform does all the hard work for you.

import matplotlib.pyplot as plt
import matplotlib
import numpy as np

fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

x,y = np.random.rand(100),np.random.rand(100)

ax1.plot(x,y,'ko')
ax2.plot(x,y,'ko')

i = 10

transFigure = fig.transFigure.inverted()

coord1 = transFigure.transform(ax1.transData.transform([x[i],y[i]]))
coord2 = transFigure.transform(ax2.transData.transform([x[i],y[i]]))


line = matplotlib.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]),
                               transform=fig.transFigure)
fig.lines = line,

ax1.plot(x[i],y[i],'ro',markersize=20)
ax2.plot(x[i],y[i],'ro',markersize=20)


plt.show()

enter image description here

jb326
  • 1,345
  • 2
  • 14
  • 26
Pablo
  • 2,443
  • 1
  • 20
  • 32
  • 7
    probably better to do `fig.lines.append(line)` to not blow away anything already there. – tacaswell Jul 10 '13 at 05:17
  • Example is much appreciated, I had trouble understanding which Matplotlib transformation went where before! @tcaswell is right though, I just looked up the [docs](http://matplotlib.org/users/annotations_guide.html#using-connectorpatch) on `annotate`, and `ConnectorPatch` seems to be exactly what I'm looking for, so I'll try it out and come back later! – F.X. Jul 10 '13 at 08:28
  • 3
    Very nice solution. However I got line plotted at wrong coordinates with jupyter. The solution was to add `fig.canvas.draw()` before calling `transFigure = fig.transFigure.inverted()` in order to work with the correct coordinates. – scholi Feb 05 '18 at 14:15
2

I'm not sure if this is exactly what you are looking for, but a simple trick to plot across subplots.

import matplotlib.pyplot as plt
import numpy as np

ax1=plt.figure(1).add_subplot(211)
ax2=plt.figure(1).add_subplot(212)

x_data=np.linspace(0,10,20)
ax1.plot(x_data, x_data**2,'o')
ax2.plot(x_data, x_data**3, 'o')

ax3 = plt.figure(1).add_subplot(111)
ax3.plot([5,5],[0,1],'--')
ax3.set_xlim([0,10])
ax3.axis("off")
plt.show()
Peacefrog
  • 36
  • 3