Equal Height subplots in matplotlib - python

I have a script that generates two plots side by side of two aspects of the same quantity. It's fairly long, so this is only the relevant (I believe) portion. The left hand plot is called by the function "plot_shearsticks2"
Depending on what data I'm plotting, the left hand plot may have slightly different dimensions, but I would like the two to have the same height always, and if the left hand one is wider, that's fine (and usually it ought to). Here is the relevant portion:
import matplotlib.pyplot as plt
figr = plt.figure(i,figsize=(11, 5))
fig=figr.add_subplot(121,aspect='equal')
figr.subplots_adjust(wspace=0.3)
shearplot = plot_shearsticks2(cat.data,[imoments[0],imoments[1],imoments[2],imoments[3],imoments[4]],scale_factor,'-k')
fig=figr.add_subplot(122,aspect='equal')
figr = plt.figure(i,figsize=(11, 5))
And below this, there are some instructions for making the right hand plot, but nothing that seems to indicate anything about how tall it is. Tinkering with the options in add_subplot, doesn't seem to have any effect -- the left hand side is always smaller for some reason. Any idea how to make them equal heights?

I always find it easier to ise the axes funciton. For example:
import pylab as pl
pl.figure(figsize=(4,3))
pl.axes([0.2, 0.2, 0.75, 0.35 ])
pl.axes([0.2, 0.55, 0.75, 0.35 ])
This way you have the ability to control the axis properties very accurately, and even partially/fully overlay one over the other. This gives you significant advantage over the subplot function.

Ended up redoing everything with GridSpec, and specifying plot heights with gs = gridspec.GridSpec(1,2,height_ratios=[1],width_ratios=[ratioxy,1]), having defined ratioxy with ratioxy=widthx/heighty

Related

How to get the limits of plotted data from a Figure or Axes object in Matplotlib?

I'm trying to determine what the limits of data points are on a matplotlib Axes or Figure, but I can't find any way.
I'm gonna give an example, but the original image is much more complex:
By using Axes.get_xbound() or Axes.get_xlim() I get (-265.6, 6000.0) but I would want to get (0,5570).
I'm asking this because on this part of code I only have access to the Figure or Axes object.
Something like this:
def plot_detail():
fig, ax = plt.subplots(1)
# Code
# ...
return fig,ax
def main():
fig,ax = plot_detail()
print(ax.get_xbound())
print(ax.get_xlim())
# Here I would need the data limits
# Any Idea how?
First, just as a side note, from the fact that you want the data at point in the code where you only have the plot (Figure and Axes), it seems to me that there was at least one not-so-great design decision made while designing/writing your code. If I could see the whole code I could likely recommend a better approach. That said, it is understandable that sometimes we don't anticipate all the needs of our code, and then sometimes (depending on the size of the program) it may not be worth the effort for a redesign/rewrite of part of the code.
So to get the data (in order to know the x-limits of the data itself, and not just of the plot) ... You can do this by getting the lines.Line2D objects from the Axes object.
Even though it appears you are plotting a bar graph, there should still be a line2D object in there. That object contains the xy data.
xdata = ax.get_lines()[0].get_xdata()
print('xdata limits:',xdata[0],xdata[-1])
HTH.

mayavi: two surfaces obstruct each other, even though they are non-intersecting

I am trying to plot two surfaces which touch at exactly two points but are otherwise well separated. Depending on the viewing angle, this renders either just fine (figure 1) or it makes some mess with the top surface s2 (plasma, red) obstructing the lower one s1 (figure 2). I suppose that is due to the order in which the surfaces are plotted, so mayavi just puts one in front even though mathematically it should be in the back. How can I solve this issue? Note that I would like to have different colormaps for both surfaces, as they represent different things. Thanks a lot!
figure 1, correct plot
figure 2, wrong plot
Here the code to produce the plot. Viewing angles were chosen in the interactive window, not sure how to get the numerical values.
import numpy as np
import mayavi.mlab
x,y = np.mgrid[-np.pi:np.pi:0.01, -np.pi:np.pi:0.01]
def surface1(x,y):
return -np.sqrt((np.cos(x) + np.cos(y) - 1)**2 + np.sin(x)**2)
def surface2(x,y):
return np.sqrt((np.cos(x) + np.cos(y) - 1)**2 + np.sin(x)**2)
s1 = mayavi.mlab.surf(x,y,surface1, colormap='viridis')
s2 = mayavi.mlab.surf(x,y,surface2, colormap='plasma')
mayavi.mlab.show()
EDIT:
Finally found the issue: Need to specify the correct backend for rendering. Using ipython3 --gui=qt solves the issue. Thus the issue only appears when using the default backend (whichever that is). I wish this would be documented more clearly somewhere, would have saved me a lot of work.

How does matplotlib 1.5.3's tight_layout() interact with subplots_adjust()?

I am using Matplotlib 1.5.3 in Python 3. I have a 3x3 subplot structure, or more generically an unspecified subplot structure that I'm trying to add a color bar to. As per this thread, an apparently good way to do this is to distort the subplots with subplots_adjust(), and add the colorbar as a new axes. Except, I have tight_layout() enabled, and that totally messes with things. Here is the function that, based on what I have read about subplots_adjust(), should work:
import matplotlib.pyplot as plt
def add_colorbar(last_im):
SPACE = 0.2 # portion of final width reserved for colorbar and its padding
PADDING = 0.5 # portion of reserved space reserved for padding
fig = plt.gcf()
# expand image to make room for colorbar
w,h = fig.get_size_inches()
fig.set_size_inches((w/(1-SPACE), h))
# shrink right side of subplot to create empty space on
# right hand side
fig.subplots_adjust(right=0.9*(1-SPACE)) # 0.9 being the original value
# create colorbar axes, place in empty space with padding
cbax = fig.add_axes([1-SPACE*(1-PADDING/2), 0.15,
SPACE*(1-PADDING), 0.7])
fig.colorbar(last_im, cax=cbax)
But the subplot configuration is kept centered, so this creates basically no space, and the color bar is drawn straight over the subplots. I have also tried using plt.tight_layout(rect=[0, 0, 1-SPACE, 1]) instead of subplots_adjust(), but this seems to do even less than the subplots_adjust() statement, and messes with basically just the sizes of the individual subplots. It seems neither of these functions work as advertised for me. What am I missing? Faulty plot shown below, with plot titles censored to be on the safe side.
Alternatively, I'd be fine with a solution for adding a colorbar that will generically work for a figure with any subplot configuration, but I'd prefer to understand the baffling behavior of subplots_adjust() and the tight_layout() rect.
EDIT: Problem ended up being that I made tight_layout() calls erroneously after running add_colorbar(). Correct behavior is observed now that I have removed the calls.

how to make easy and efficient plots on Python

I use matplotlib for my plots, I find it great, but sometimes too much complicated. Here an example:
import matplotlib.pyplot as plt
import numpy as np
idx1 = -3
idx2 = 3
x = np.arange(-3, 3, 0.01)
y = np.sin(np.pi*x*7)/(np.pi*x*7)
major_ticks = np.arange(idx1, idx2, 1)
minor_ticks = np.arange(idx1, idx2, 0.1)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_ylim(-0.3, 1.2)
ax.set_xlim(idx1, idx2)
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, minor = True)
ax.grid(True, which = 'both')
ax.tick_params(axis = 'x', labelsize = 18)
ax.tick_params(axis = 'y', labelsize = 18)
ax.plot(x, y)
plt.show()
Is there anything implemented on matplotlib and/or seaborn in which I can provide all these plot settings just as argument of a function only? It may considerably reduce the number of code lines and make the script easier both to write and understand.
Matplotlib provides an object oriented API. This means that all the elements of the figure are acutally objects for which one can get and set properties and which can be easily manipulated. This makes matplotlib really flexible such that it can produce almost any plot you'd imagine.
Since a plot may consist of a hundred or more elements, a function that would allow the same flexibility would need that amount of possible arguments. It is not necessarily easier to remember all possible arguments of a function than all possible attributes of a class.
Having a single function call that does all of this, does not necessarily mean that you have to type in less characters. The commands would just be ordered differently.
Furthermore the object oriented approach allows to keep things seperate. Some properties of the axes, like the grid or the axis labels are completely independend on what you plot to the axes. So you wouldn't want to set the xticks in the call to plot, because they are simply not related and it may be very confusing to set twice the same ticklabels when plotting two lines in the same axes.
On the other hand, matplotlib is really easy. In order to produce a plot you need two lines
import matplotlib.pyplot as plt
plt.plot([1,2,3],[2,1,3])
which sets most of the parameters exactly as they are needed. The more you want to customize this plot, the more settings you have to apply. Which is fine as it allows the user himself to determine how much in depth he wants to control the appearance of the plot.
Most matplotlib codes can be separated into three parts.
Setting the style
Creating the plot
Customizing the plot
Setting the style in the case of the code from the question involves e.g. the ticklabel size and the use of a grid. Those properties can set as it's done in the code but it may indeed be that one always wants to use the same properities here and finds it annoying to type the same parameters in every time one creates a plot. Therefore matplotlib provides general style settings, called rcParams. They can be set at the beginning of a script, e.g.
plt.rcParams['lines.linewidth'] = 2
plt.rcParams['axes.grid '] = True
plt.rcParams['axes.labelsize'] = 18
and will be applied to all plots within the script. It is also possible to define a complete stylesheet using those parameters. For more information see the Customizing matplotlib article.
It is equally possible to use predefined stylesheets for certain applications.
Simply importing import seaborn is also a possible way to change the style.
Creating the plot can not be simplified much more. It's clear that one needs as many plotting commands as items to plot. Creating the figure and axes like
fig, ax = plt.subplots()
saves one line though.
Equally no simplification is possible if customizing ticks or tickmarks are required. One may however consider to use Tickers and Formatters for this purpose.
At the end one may of course consider to write a custom function which performs much of those tasks, but everyone can decide if that is useful for himself.
Browsing around I saw this wabe page.
This line of code can summarise many settings
import matplotlib as mpl
mpl.rc('lines', linewidth=2, color='r')
ax.set is very useful for this:
ax.set(xlim=(idx1, idx2), ylim=(-0.3, 1.2),
xticks=major_ticks, ...)
You can only set simple single-argument properties (e.g. those which don't need further keywords), but it's a nice timesaver.

Using matplotlib, how do I whiten the background of the axis label?

Is there a way to whiten out the background of the axis label so that when it crosses the axis line itself, the latter does not run through it?
For example, this script (the best I managed so far)
#!/usr/bin/python
import matplotlib.pyplot as plt
xx=[1,2,3]
yy=[2,3,4]
dy=[0.1,0.2,0.05]
fig=plt.figure()
ax=fig.add_subplot(111)
ax.errorbar(xx,yy,dy,fmt='ro-',ms=6,elinewidth=4)
ax.set_xlim([0.,3.4])
ax.set_ylim([0.,4.4])
ax.set_xlabel(r'$T/t$',fontsize=16)
ax.set_ylabel(r'$S(\mathbf{Q})L^{1+\eta}$',fontsize=16)
# position the axis labels
ax.xaxis.set_label_coords(1,0)
ax.yaxis.set_label_coords(0.1,0.93)
ax.yaxis.get_label().set_rotation('horizontal')
ax.yaxis.get_label().set_backgroundcolor('w')
#ax.yaxis.get_label().set_zorder(222) #doesn't do the trick
plt.show()
produces almost what I'm looking for, but still the y-axis runs over the label: .
By default, the left spine has a zorder of 2.5. For some reason this seems to cause problems; maybe there's something in the code which only works if they're integral? Anyway, if you add
ax.spines['left'].set_zorder(2)
or more generally
ax.spines['left'].set_zorder(ax.yaxis.get_label().get_zorder()-1)
before the show, it should work. Also, set_ylabel returns the ylab object itself, so if you use "ylab = ax.set_ylabel(stuff)" you can avoid all the ax.yaxis.get_label() calls later.
Does this link help you?
http://matplotlib.sourceforge.net/faq/howto_faq.html#automatically-make-room-for-tick-labels
You can simply shift the y-axis to the right to allows some space for the $S(\mathbf{Q})L^{1+\eta}$ mark be fully placed before the axis line.

Categories

Resources