Drawing lines between two plots in Matplotlib - python

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?

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()

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()

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()

Related

Flexible way to add subplots to figure and one colorbar to figure

I have tried and tried to search for a solution, but I can't seem to make it work, so here is my problem:
I am trying to make a script, where I can set a size (i.e. number of subplots in a figure) of the figure. It should always be a N*2 grid structure (with equal dimensions) for the subplots in the figure. In the grids, I want to plot some lines (with a specific color) and add a horizontal colorbar at the bottom of the figure. I have tried following the solution to the problem, mentioned in Matplotlib 2 Subplots, 1 Colorbar. Here is my solution:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import matplotlib.colors as colors
import matplotlib.cm as cmx
import random
lines = np.random.rand(100, 10)
fig, ax = plt.subplots(nrows=2,ncols=2, sharex=True)
ranks = np.linspace(0,len(lines[0,:]), len(lines[0,:]))
norm = matplotlib.colors.Normalize(
vmin=np.min(ranks),
vmax=np.max(ranks))
c_m = matplotlib.cm.cool
s_m = matplotlib.cm.ScalarMappable(cmap=c_m, norm=norm)
s_m.set_array([])
for line, rank in zip(xrange(0,len(lines[0,:])), ranks):
try:
no = random.randint(0,1)
no2 =random.randint(0,1)
im =ax[no,no2].plot(lines[:, line], c=s_m.to_rgba(rank))
except IOError as error:
print error
fig.subplots_adjust(bottom=0.2)
fig.colorbar(s_m, orientation='horizontal')
plt.show()
So my question is: How do I make a gridbased subplot (for line- and scatterplots), where I can add one colorbar horizontally at the bottom to stretch across both subplots in the bottom?
You should provide the ax argument to the colorbar function,
fig.colorbar(s_m, ax = ax.flatten(), orientation='horizontal')
This would then produce
So I figured it out and both Thomas Kühn and and ImportanceOfBeingErnest helped me see the errors:
First off, I changed the question to be replicable and with a mappable instead of ax[1,1]. Once doing that, I could see how it was actually directly transferrable to the question I had linked to, in my question. Here is my result:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import matplotlib.colors as colors
import matplotlib.cm as cmx
import os
import random
fnames = np.random.rand(100, 10)
fig, ax = plt.subplots(nrows=2,ncols=2, sharex=True, figsize=(4,4))
parameters = np.linspace(0,len(fnames[0,:]), len(fnames[0,:]))
norm = matplotlib.colors.Normalize(
vmin=np.min(parameters),
vmax=np.max(parameters))
c_m = matplotlib.cm.cool
s_m = matplotlib.cm.ScalarMappable(cmap=c_m, norm=norm)
s_m.set_array([])
for filename, parameter in zip(xrange(0,len(fnames[0,:])), parameters):
try:
no = random.randint(0,1)
no2 =random.randint(0,1)
ax[no,no2].plot(fnames[:, filename], c=s_m.to_rgba(parameter))
except IOError as error:
print error
for ax1 in ax.flatten():
ax1.set_xlabel('Y')
ax1.set_ylabel('X')
plt.title('Title')
fig.subplots_adjust(bottom=0.2)
cbar_ax = fig.add_axes([0.13, 0.08, 0.74, 0.03])
cbar = fig.colorbar(s_m, cax=cbar_ax, orientation='horizontal')
cbar.set_label('Focus: ')
plt.show()
Which gave this plot. Thanks for the tips. Would you recommend changing the grid structure from plt.subplots, to AxesGrid, found in mpl_toolkits.axes_grid1?

Histogram at specific coordinates inside axes

What I want to achieve with Python 3.6 is something like this :
Obviously made in paint and missing some ticks on the xAxis. Is something like this possible? Essentially, can I control exactly where to plot a histogram (and with what orientation)?
I specifically want them to be on the same axes just like the figure above and not on separate axes or subplots.
fig = plt.figure()
ax2Handler = fig.gca()
ax2Handler.scatter(np.array(np.arange(0,len(xData),1)), xData)
ax2Handler.hist(xData,bins=60,orientation='horizontal',normed=True)
This and other approaches (of inverting the axes) gave me no results. xData is loaded from a panda dataframe.
# This also doesn't work as intended
fig = plt.figure()
axHistHandler = fig.gca()
axScatterHandler = fig.gca()
axHistHandler.invert_xaxis()
axHistHandler.hist(xData,orientation='horizontal')
axScatterHandler.scatter(np.array(np.arange(0,len(xData),1)), xData)
A. using two axes
There is simply no reason not to use two different axes. The plot from the question can easily be reproduced with two different axes:
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
xData = np.random.rand(1000)
fig,(ax,ax2)= plt.subplots(ncols=2, sharey=True)
fig.subplots_adjust(wspace=0)
ax2.scatter(np.linspace(0,1,len(xData)), xData, s=9)
ax.hist(xData,bins=60,orientation='horizontal',normed=True)
ax.invert_xaxis()
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.tick_params(axis="y", left=0)
plt.show()
B. using a single axes
Just for the sake of answering the question: In order to plot both in the same axes, one can shift the bars by their length towards the left, effectively giving a mirrored histogram.
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
xData = np.random.rand(1000)
fig,ax= plt.subplots(ncols=1)
fig.subplots_adjust(wspace=0)
ax.scatter(np.linspace(0,1,len(xData)), xData, s=9)
xlim1 = ax.get_xlim()
_,__,bars = ax.hist(xData,bins=60,orientation='horizontal',normed=True)
for bar in bars:
bar.set_x(-bar.get_width())
xlim2 = ax.get_xlim()
ax.set_xlim(-xlim2[1],xlim1[1])
plt.show()
You might be interested in seaborn jointplots:
# Import and fake data
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(2,1000)
# actual plot
jg = sns.jointplot(data[0], data[1], marginal_kws={"bins":100})
jg.ax_marg_x.set_visible(False) # remove the top axis
plt.subplots_adjust(top=1.15) # fill the empty space
produces this:
See more examples of bivariate distribution representations, available in Seaborn.

Matplotlib copy/duplicate a 3D figure?

I've tried to find a way to copy a 3D figure in matplotlib but I didn't find a solution which is appropriate in my case.
From these posts
How do I reuse plots in matplotlib?
and
How to combine several matplotlib figures into one figure?
Using fig2._axstack.add(fig2._make_key(ax),ax) as in the code below gives quite the good result but figure 2 is not interactive I can't rotate the figure etc :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(1)
ax = fig.gca(projection = '3d')
ax.plot([0,1],[0,1],[0,1])
fig2 = plt.figure(2)
fig2._axstack.add(fig2._make_key(ax),ax)
plt.show()
An alternative would be to copy objects from ax to ax2 using a copy method proposed in this post How do I reuse plots in matplotlib? but executing the code below returns RuntimeError: Can not put single artist in more than one figure :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np, copy
fig = plt.figure(1)
ax = fig.gca(projection = '3d')
ax.plot([0,1],[0,1],[0,1])
fig2 = plt.figure(2)
ax2 = fig2.gca(projection = '3d')
for n in range(len(ax.lines)) :
ax2.add_line(copy.copy(ax.lines[n]))
plt.show()
Those codes are pretty simple but I don't want to copy/paste part of my code for drawing similar figures
Thanks in advance for your reply !

Invert y axis on matplotlib trisurf 3d graph python

I'm using matplotlib to produce a 3d trisurf graph. I have everything working except that I would like to invert the y-axis, so that the origin is 0,0 not 0,100. I've looked through the matplotlib axes3d API and cannot figure out how to do this. Here is my code:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
# my data, xs=xaxis, ys=yaxis, zs=zaxis
mortar_xs = []
cycles_ys = []
score_zs = []
#... populate my data for the 3 arrays: mortar_xs, cycles_ys, score_zs
# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(mortar_xs,cycles_ys,score_zs,cmap=cm.coolwarm)
ax.set_zlim(bottom=0.0,top=1.0)
ax.legend()
ax.set_xlabel("# Mortar")
ax.set_ylabel("# Goals")
ax.set_zlabel("# Score")
plt.show()
My graph produced is the following, but I need the '# Goals' or the y-axis inverted, so that the origin is 0,0 not 0,100. If possible, I would like to do this without changing my data.
tmdavison's comment is what I was looking for:
ax.set_ylim(0,100)
Or
ax.set_ylim(100,0)
The simplest method would be to use ax.invert_yaxis()

Two different plots from same loop in matplotlib?

I would specifically like to create two different plots using one single loop. One plot should have four straight lines from x-y, and another plot should have four angled lines from x-y2. My code only shows everything in a single plot. I don't quite understand how plt works, how can I create two distinct plt objects?
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt2
x=[[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
y=[[1,2,3,4],[2,3,4,5],[3,4,5,6],[7,8,9,10]]
y2=[[11,12,13,24],[42,33,34,65],[23,54,65,86],[77,90,39,54]]
colours=['r','g','b','k']
for i in range(len(x)):
plt.plot(x[i],y2[i],colours[i])
plt2.plot(x[i],y[i],colours[i])
plt.show()
plt2.show()
Is that what you want to do?
import matplotlib.pyplot as plt
x=[[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
y=[[1,2,3,4],[2,3,4,5],[3,4,5,6],[7,8,9,10]]
y2=[[11,12,13,24],[42,33,34,65],[23,54,65,86],[77,90,39,54]]
colours=['r','g','b','k']
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
for i in range(len(x)):
ax1.plot(x[i],y2[i],colours[i])
ax2.plot(x[i],y[i],colours[i])
fig1.show()
fig2.show()

Categories