Say I want to inset a plot to a figure, but the inset plot has different axis range than the one I am marking the inset to. For example:
fig, ax = plt.subplots()
axins = inset_axes(ax, 1,1 , loc=2, bbox_to_anchor=(0.35,0.85),bbox_transform=ax.figure.transFigure)
x = np.linspace(0, 3, 100)
y = x**2
ax.plot(x, y)
axins.plot(x, x**3)
x1, x2, y1, y2 = 2.,3, 6, 8 # specify the limits
axins.set_xlim(x1, x2) # apply the x-limits
axins.set_ylim(y1, y2) # apply the y-limits
plt.xticks(visible=False)
plt.yticks(visible=False)
mark_inset(ax, axins, loc1=4, loc2=1)#, fc="none")#, ec="0.5")
The result is an empty inset plot:
But this is obvious, since I set the limits of x and y to ranges where x**3 does not pass.
What I want to see is, for example, a plot of x**3 for 0 to 1 in the inset plot, while the mark_inset will still 'zoom' to the region boxed above, which is of different range.
How can I do this?
In that case you cannot use mark_inset directly, because that is exactly what this function does: synchronizing the marker with the axes limits of the inset.
Using a rectangle
Instead you may position some rectangle whereever you want it to be and use ConnectionPatches to draw some lines in between the inset and the rectangle.
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.axes_grid1.inset_locator as il
import matplotlib.patches as mpatches
fig, ax = plt.subplots()
axins = il.inset_axes(ax, 1,1 , loc=2, bbox_to_anchor=(0.35,0.85),bbox_transform=ax.figure.transFigure)
x = np.linspace(0, 3, 100)
y = x**2
ax.plot(x, y)
axins.plot(x, x**3)
x1, x2, y1, y2 = 2.,3, 6, 8 # specify the limits
rect = mpatches.Rectangle((x1,y1), width=x2-x1, height=y2-y1, facecolor="None", edgecolor="k", linewidth=0.8)
fig.canvas.draw()
p1 = mpatches.ConnectionPatch(xyA=(1,0), xyB=(x2,y1), coordsA="axes fraction", coordsB="data", axesA=axins, axesB=ax)
p2 = mpatches.ConnectionPatch(xyA=(1,1), xyB=(x2,y2), coordsA="axes fraction", coordsB="data", axesA=axins, axesB=ax)
ax.add_patch(rect)
ax.add_patch(p1)
ax.add_patch(p2)
plt.show()
Use dummy axes
You may also simply add an additional inset, just for the purpose of using mark_inset with it.
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.axes_grid1.inset_locator as il
fig, ax = plt.subplots()
axins_dummy = il.inset_axes(ax, 1,1 , loc=2, bbox_to_anchor=(0.35,0.85),bbox_transform=ax.figure.transFigure)
axins = il.inset_axes(ax, 1,1 , loc=2, bbox_to_anchor=(0.35,0.85),bbox_transform=ax.figure.transFigure)
x = np.linspace(0, 3, 100)
y = x**2
ax.plot(x, y)
axins.plot(x, x**3)
x1, x2, y1, y2 = 2.,3, 6, 8 # specify the limits
axins_dummy .set_xlim(x1, x2) # apply the x-limits
axins_dummy .set_ylim(y1, y2) # apply the y-limits
axins_dummy.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False )
il.mark_inset(ax,axins_dummy , loc1=4, loc2=1)#, fc="none")#, ec="0.5")
plt.show()
In both cases, the resulting plot would look like
Maybe it's worth noting that the resulting graph is of course incorrect. Any reader would assume that the inset shows part of the curve, which is not the case. Hence make sure not to use such graph in a publication or report.
Related
I want to write the operations and parameters that I usually use in drawing in a function. In the future, just pass x and y to draw according to the default parameters. But now the question I am facing is, how do I determine which picture plt.plot is drawing on? For example, I want to draw two curves on a picture.
def draw(x,y):
... %some operations
plt.plot(x,y) % draw picture operations
... %some operations
draw(x,y),
dray(x2,y2)
How to ensure that these two curves are drawn on a picture. That is, what parameters do I need to pass to make plt.plot focus on the picture I specify.
def plotLine(coordinate,figName='test',xylabel=[],ax=None):
# assert(len(coordinate)<=2)
if (len(coordinate)==2) :
x=coordinate[0]
y=coordinate[1]
assert(len(x)==len(y))
else:
y=coordinate
x =np.linspace(0,len(y)-1,len(y))
minn=min(y)
maxx=max(y)
plt.switch_backend('Agg')
if ax == None:
fig,ax = plt.subplots()
fig = plt.figure(num=None, figsize=(3.5, 1.5), dpi=300, facecolor='w')
plt.subplots_adjust(right = 0.98, top = 0.98, bottom=0.35,left=0.32,wspace=0, hspace=0.2)
ax.set_xlim([0,len(x)])
ax.set_ylim([0,maxx+maxx/3])
plt.xticks(fontsize=5)
plt.yticks(fontsize=5)
bar_width = 0.35
opacity = 0.8
lsmarkersize = 2.5
lslinewidth = 0.6
ax.plot(x,y,'-', linewidth=1, markersize=lsmarkersize, markeredgewidth=0)
plt.savefig(figName+".png",bbox_inches='tight',dpi=500)
# os.system("code "+figName+".png")
if ax!=None:
return ax
else:
return plt.gca()
x=[1,2,3,4,5,6]
y=[1,2,3,4,4,5]
ax = plotLine([x,y])
x=[1,2,3,4,5,6]
y=[12,13,14,15,16,17]
plotLine([x,y],ax=ax)
I tried to pass ax as a parameter. But the picture drawn at the end is blank.
You can use subplots to specify the axes to plot on. For example, create a figure with a single subplot:
fig, ax = plt.subplots()
ax.plot(x, y)
For your function you could do the following
fig, ax = plt.subplots()
def draw(x, y, ax):
ax.plot(x, y)
def dray(x2, y2, ax):
ax.plot(x2, y2)
I am not attempting to modify your code. This is more a general approach answer. Imho, it is better (in terms of keeping track of what's going on) to define the figure and plots outside the function and doing only the actual plotting inside the function.
import numpy as np
from matplotlib import pyplot as plt
np.random.seed(123)
#the plotting function, taking ax and label as optional parameters
def draw_the_line(x, y, current_ax=None, current_label=None):
if not current_ax:
current_ax=plt.gca()
if not current_label:
current_label="missing label"
current_ax.plot(x, y, label=current_label)
plt.sca(current_ax)
fig, (ax1, ax2) = plt.subplots(2, figsize=(6, 8))
#normal plot into panel 1
x1 = np.arange(6)
y1 = np.random.randint(1, 10, len(x1))
draw_the_line(x1, y1, ax1, "data1")
#normal plot into panel 2
x2 = np.arange(5)
y2 = np.random.randint(10, 20, len(x2))
draw_the_line(x2, y2, ax2, "data2")
#plot into panel 1 with missing label
x3 = np.arange(4)
y3 = np.random.randint(20, 30, len(x3))
draw_the_line(x3, y3, ax1)
#plot into the last panel used
x4 = np.arange(3)
y4 = np.random.randint(30, 40, len(x4))
draw_the_line(x4, y4, current_label="data4")
ax1.legend()
ax2.legend()
plt.show()
Sample output:
I have the issue that I am trying to make multiple plots that are supposed to have the same bbox size. As some of my plots have an additional colorbar or wider yticklabels the bbox size varies within multiple plots.
As I would like to use these plots in a LaTex document underneath each other, I would like to set the bbox for all plots to the same value instead of defining the figure size.
If it is not clear yet what I mean, here's an example:
As you can see the bbox sizes vary, as the width of the ylabel + ylabelticks and additionally the cbar is added. I thought the easisest way to approach this would be to find the image of the smallest drawn bbox and use that as a standard for all figures and keep the figsize constant, or to just set the bbox size constant and just add the rest and have varying figsizes.. the later would need me to do additional positioning in latex/illustrator/power point or whatever, but just about any solution that works would be great (even though I belive that the later is likely not possible with matplotlib). I tried changing the bbox size but unfortunately did not succeed. So I do not have some code to start from. But any help or pointers where to look at or start would help a lot.
Here a short code snippet to reproduce.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
np.random.seed(1)
mpl.rcParams['figure.figsize'] = (16.0, 12.0)
x = np.linspace(0, 100, 100)
y = np.random.randint(100, size=100)
z = np.random.randint(0, 1e6, size=100)/1e6
fig, ax = plt.subplots()
m = mpl.cm.ScalarMappable(cmap=mpl.cm.jet)
norm = plt.Normalize(min(z), max(z))
m.set_array(list(set(z)))
cbar = plt.colorbar(m, orientation="vertical", fraction=0.07, pad=0.02)
color = lambda c: m.cmap(norm(c))
ax.scatter(x, y, color=color(z))
fig, ax = plt.subplots()
ax.scatter(x, y)
pls see following code. I recommend you using ax1 and ax2, which have more flexibility.
Key points:
using get_position() to get bounds of axes.
using set_position() to set bounds of axes.
I highly recommend using ax1, ax2 ... instead of plt.stuff for multiple subplots.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
np.random.seed(1)
x = np.linspace(0, 100, 100)
y = np.random.randint(100, size=100)
z = np.random.randint(0, 1e6, size=100)/1e6
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 4))
m = mpl.cm.ScalarMappable(cmap=mpl.cm.jet)
norm = plt.Normalize(min(z), max(z))
m.set_array(list(set(z)))
cbar = fig.colorbar(m, orientation="vertical", fraction=0.07, pad=0.02)
color = lambda c: m.cmap(norm(c))
ax2.scatter(x, y, color=color(z))
ax1.scatter(x, y)
# get the bounds of ax1 and ax2
x1, y1, w1, h1 = ax1.get_position().bounds
x2, y2, w2, h2 = ax2.get_position().bounds
# set ax1 width to width of ax2
ax1.set_position([x1, y1, w2, h1])
I can create a scatter plot as follows:
fig, ax = plt.subplots()
x1 = [1, 1, 2]
y1 = [1, 2, 1]
x2 = [2]
y2 = [2]
ax.scatter(x1, y1, color="red", s=500)
ax.scatter(x2, y2, color="blue", s=500)
which gives
What I would like is something like the following (apologies for poor paint work):
I am plotting data that is all integer values, so they're all on a grid. I would like to be able to control the size of the scatter marker so that I could have white space around the points, or I could make the points large enough such that there would be no white space around them (as I have done in the above paint image).
Note - ideally the solution will be in pure matplotlib, using the OOP interface as they suggest in the documentation.
import matplotlib.pyplot as plt
import matplotlib as mpl
# X and Y coordinates for red circles
red_xs = [1,2,3,4,1,2,3,4,1,2,1,2]
red_ys = [1,1,1,1,2,2,2,2,3,3,4,4]
# X and Y coordinates for blue circles
blu_xs = [3,4,3,4]
blu_ys = [3,3,4,4]
# Plot with a small markersize
markersize = 5
fig, ax = plt.subplots(figsize=(3,3))
ax.plot(red_xs, red_ys, marker="o", color="r", linestyle="", markersize=markersize)
ax.plot(blu_xs, blu_ys, marker="o", color="b", linestyle="", markersize=markersize)
plt.show()
# Plot with a large markersize
markersize = 50
fig, ax = plt.subplots(figsize=(3,3))
ax.plot(red_xs, red_ys, marker="o", color="r", linestyle="", markersize=markersize)
ax.plot(blu_xs, blu_ys, marker="o", color="b", linestyle="", markersize=markersize)
plt.show()
# Plot with using patches and radius
r = 0.5
fig, ax = plt.subplots(figsize=(3,3))
for x, y in zip(red_xs, red_ys):
ax.add_patch(mpl.patches.Circle((x,y), radius=r, color="r"))
for x, y in zip(blu_xs, blu_ys):
ax.add_patch(mpl.patches.Circle((x,y), radius=r, color="b"))
ax.autoscale()
plt.show()
I'm not sure my wording is correct, but what I am trying to do is create a figure of two subplots, where the two plots have different limits, but their size is such that the physical scale (as in, y-distance per centimeter of figure height) is the same. To clarify, lets say subplot 1 shows data from -3 to 3 and subplot 2 shows data from -1 to 1. I want to have them below one another in such a way that the height of subplot2 (excluding ticks, just everything inside the frame) is exactly one third of subplot 1.
My attempt was as follows:
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2, 101)
y1 = 3*np.cos(x*np.pi)
y2 = np.cos(x*np.pi)
fig = plt.figure(figsize=(4, 6))
gs = gridspec.GridSpec(8, 1)
ax1 = plt.subplot(gs[0:6,0])
ax1.plot(x, y1, c='orange')
ax1.set_ylim(-3, 3)
ax1.set_xticks([], [])
ax2 = plt.subplot(gs[6:,0])
ax2.plot(x, y2, c='green')
ax2.set_ylim(-1,1)
ax2.set_xticks([0, 1, 2])
ax2.set_xticklabels([r'0', r'0.5', r'1'])
ax2.set_xlabel(r'$n_g$ (2e)')
plt.tight_layout()
fig.text(-0.025, 0.5, 'Frequency (GHz)', ha='center', va='center', rotation='vertical', size=18)
which produces the figure below, but as you can see (although you have to look closely) the range -1 to 1 in the second subplot is compressed (takes up less height) than the range -1 to 1 in subplot 1. I'm guessing this is because of the space between the two subplots.
Note that I'm using gridspec because I plan on adding another column of subplots with interesting aspect ratio's and its own labels and limits. I didn't know how to add a global ylabel in a more elegant way, if someone was wondering.
You can set the height_ratios of the gridspec to match the range of the limits.
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2, 101)
y1 = 3*np.cos(x*np.pi)
y2 = np.cos(x*np.pi)
ylim1 = -3,3
ylim2 = -1,1
fig = plt.figure(figsize=(4, 6), constrained_layout=True)
gs = gridspec.GridSpec(2, 1, height_ratios=[np.diff(ylim1)[0],
np.diff(ylim2)[0]], figure=fig)
ax1 = plt.subplot(gs[0,0])
ax1.plot(x, y1, c='orange')
ax1.set_ylim(ylim1)
ax1.set_xticks([], [])
ax2 = plt.subplot(gs[1,0])
ax2.plot(x, y2, c='green')
ax2.set_ylim(ylim2)
ax2.set_xticks([0, 1, 2])
ax2.set_xticklabels([r'0', r'0.5', r'1'])
ax2.set_xlabel(r'$n_g$ (2e)')
plt.show()
I would like to fill the area between the curve y1=x^3 and then line y2=3x-2.
Below is code I have that will do this, however, I want to place the restriction that y1 < y2 (which I have done with the where option of fill_between) and that x<1.
The problem that occurs with the code below is that the area between the curve is filled for x>1. I would like to plot these graphs on the range [-2.5,2.5]. How do I get matplotlib to stop filling between the curves for x>1?
My code:
import matplotlib.pyplot as plot
import numpy as np
x = np.linspace(-2.5, 2.5, 100)
y1 = np.array([i**3 for i in x])
y2 = np.array([3*i-2 for i in x])
fig = plot.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(x, y1, label=r"$y=x^3$")
ax.plot(x, y2, label=r"$y=3x-2$")
ax.spines['left'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('center')
ax.spines['top'].set_color('none')
ax.spines['left'].set_smart_bounds(True)
ax.spines['bottom'].set_smart_bounds(True)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.fill_between(x, y1, y2, where=y2<y1, facecolor='green')
ax.legend()
plot.show()
I got it. The easiest fix is to define 3 new variables, u,v, and w, where u holds the x values for v and w, and v = x^3, w=3x-2.
u=x[x<1]
v=y1[y1<1]
w=y2[y2<1]
Then plot these values with fill_between:
ax.fill_between(u, v, w, where=w<v, facecolor='green')