How does python draw on the specified image? - python

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:

Related

Same bbox size for multiple plots

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

Having a single y axis values while plotting two variables on secondary axis

I am trying to plot three variables, in a graph using primary and secondary axis with one variable on primary axis and two on secondary axis. My code
vav = floor_data[floor_data['vavId'] == i]
vav = vav.reset_index()
x = vav.index
y1 = vav['nvo_temperature_sensor_pps']
y2 = vav['nvo_airflow']
y3 = vav['nvo_air_damper_position']
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, 'g-')
ax2.plot(x, y2, 'b-')
ax2.plot(x, y3, 'r-')
ax2 = ax1.twinx()
ax1.set_xlabel('VAV '+str(i))
ax1.set_ylabel('temperature ', color='g')
ax2.set_ylabel('air flow, temperature', color='b')
plt.show()
I have added all the three variables but I am facing problem in y-ticks of secondary axis. My plot looks like
Is it possible to have a single y tick values on secondary axis for better readability?
You need to create new twix axis on host and shrink subplot to create space for additional axis on right side. Then move new axis at right position. Some descriptions in code.
import matplotlib.pyplot as plt
import numpy as np
fig, host = plt.subplots()
# shrink subplot
fig.subplots_adjust(right=0.75)
# create new axis on host
par1 = host.twinx()
par2 = host.twinx()
# place second axis at far right position
par2.spines["right"].set_position(("axes", 1.2))
# define plot functions
def function_sin(x):
return np.sin(x)
def function_parabola(x):
return x**2
def function_line(x):
return x+1
# plot data
x = np.linspace(0, 10, 100)
y_sin = function_sin(x)
y_parabola = function_parabola(x)
y_line = function_line(x)
host.plot(x, y_sin, "b-")
par1.plot(x, y_parabola, "r-")
par2.plot(x, y_line, "g-")
# set labels for each axis
host.set_xlabel("VAV 16")
host.set_ylabel("Temperature")
par1.set_ylabel("Temperature")
par2.set_ylabel("Air Flow")
plt.show()
Output:

How to set all the four axes with matplotlib

I want to draw a picture like this one, the top and right axes have different labels and ticks, anyone can help me?
To double both axes you have to use ax1.twinx().twiny().
Here an example:
# Create some mock data
x1 = np.arange(0, 10, 1)
y1 = [random.randint(1,5) for n in x1]
#print(x1,y1)
x2 = np.arange(0, 100, 10)
y2 = [random.randint(10,50) for n in x2]
#print(x2,y2)
fig, ax1 = plt.subplots()
ax1.set_xlabel('x1', color='red')
ax1.set_ylabel('y1', color='red')
ax1.plot(x1, y1, color='red')
ax1.tick_params(axis='both', labelcolor='red')
ax2 = ax1.twinx().twiny() #here is the trick!
ax2.set_xlabel('x2', color='blue')
ax2.set_ylabel('y2', color='blue')
ax2.plot(x2, y2, color='blue')
ax2.tick_params(axis='both', labelcolor='blue') #y2 does not get blue... can't yet figure out why
plt.show()
Here the result:
Since both datasets are completely independent, one would probably not use twin axes here. Instead, just use two different axes.
import numpy as np
import matplotlib.pyplot as plt
# Create some mock data
x1 = np.linspace(0,1,11)
y1 = np.random.rand(11)
x2 = np.linspace(1,0,101)
y2 = np.random.rand(101)*20+20
fig, ax1 = plt.subplots()
ax2 = fig.add_subplot(111, label="second axes")
ax2.set_facecolor("none")
ax1.set_xlabel('x1', color='red')
ax1.set_ylabel('y1', color='red')
ax1.plot(x1, y1, color='red')
ax1.tick_params(colors='red')
ax2.set_xlabel('x2', color='blue')
ax2.set_ylabel('y2', color='blue')
ax2.plot(x2, y2, color='blue')
ax2.xaxis.tick_top()
ax2.xaxis.set_label_position('top')
ax2.yaxis.tick_right()
ax2.yaxis.set_label_position('right')
ax2.tick_params(colors='blue')
for which in ["top", "right"]:
ax2.spines[which].set_color("blue")
ax1.spines[which].set_visible(False)
for which in ["bottom", "left"]:
ax1.spines[which].set_color("red")
ax2.spines[which].set_visible(False)
plt.show()
You should use twinx and twiny functions, take a look at this link

Use mark_inset with different range plot

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.

Force square subplots when plotting a colorbar

I'm trying to generate two subplots side by side, sharing the y axis, with a single colorbar for both.
This is a MWE of my code:
import matplotlib.pyplot as plt
import numpy as np
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure()
cm = plt.cm.get_cmap('RdYlBu')
ax0 = plt.subplot(121)
plt.scatter(x1, y, c=z, cmap=cm)
ax1 = plt.subplot(122)
# make these y tick labels invisible
plt.setp(ax1.get_yticklabels(), visible=False)
plt.scatter(x2, y, c=z, cmap=cm)
cbar = plt.colorbar()
plt.show()
what this returns is a left subplot slightly larger horizontally than the right one since this last includes the colorbar, see below:
I've tried using ax.set_aspect('equal') but since the x axis are not in the same range the result looks awful.
I need both these plots to be displayed squared. How can I do this?
To expend my comment that one can make 3 plots, plot the colorbar() in the 3rd one, the data plots in the 1st and 2nd. This way, if necessary, we are free to do anything we want to the 1st and 2nd plots:
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure(figsize=(12,6))
gs=gridspec.GridSpec(1,3, width_ratios=[4,4,0.2])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])
ax3 = plt.subplot(gs[2])
cm = plt.cm.get_cmap('RdYlBu')
ax1.scatter(x1, y, c=z, cmap=cm)
SC=ax2.scatter(x2, y, c=z, cmap=cm)
plt.setp(ax2.get_yticklabels(), visible=False)
plt.colorbar(SC, cax=ax3)
plt.tight_layout()
plt.savefig('temp.png')
Updated - here is another option without using GridSpec.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x_vals = np.random.rand(N)
y_vals = np.random.rand(N)
z1_vals = np.random.rand(N)
z2_vals = np.random.rand(N)
minimum_z = min(np.min(z1_vals), np.min(z2_vals))
maximum_z = max(np.max(z1_vals), np.max(z2_vals))
fig, axis_array = plt.subplots(1,2, figsize = (20, 10), subplot_kw = {'aspect':1})
ax0 = axis_array[0].scatter(x_vals, y_vals, c = z1_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
ax1 = axis_array[1].scatter(x_vals, y_vals, c = z2_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
cax = fig.add_axes([0.95, 0.05, 0.02, 0.95]) #this locates the axis that is used for your colorbar. It is scaled 0 - 1.
fig.colorbar(ax0, cax, orientation = 'vertical') #'ax0' tells it which plot to base the colors on
plt.show()

Categories