matplotlib: axis tick label alignment not enforced with multiple subplots - python

I've come across a number of posts such as this and this talking about how to align tick labels to avoid overlaps. In fact, the second link is basically exactly what I want to do, which is move the first and last tick (on both X and Y axis) into the plot area. Unfortunately, I'm encountering some odd behavior that I'm hoping someone can explain to me.
The code below generates 3 figures (also shown below). The first is a figure with 1 subplot, and everything works as intended. All tick labels are center-justified except the first and last on each axis, which is properly adjusted to be within the plot area.
Figure 2 has 2 subplots vertically stacked. In this plot the horizontal axis has tick labels properly justified, but on the vertical axis all the positive labels (0-10) have been justified "bottom" when only the last label (10) should have been justified "top". All others should be justified "center" still.
Figure 3 is similar to Figure 2, only with horizontally stacked subplots. In this case, it's the tick labels on the positive horizontal axis that are not correctly justified, with all labels justified "left" when only the final label should be justified "right".
Any clue why on a figure with multiple subplots the tick label justification is not being set correctly? I've made multiple versions of these plots, including embedded in a Tkinter window (my actual application) and I get the exact same result every time.
EDIT: I've added a screenshot of the plots from my actual application, which is a GUI made using Tkinter. The overall window size is 1024x768 and the plots are made using 2 figures, one for the top plot (XY) and one with two subplots for the bottom plots (XZ and YZ). This screenshot is without any resizing of the window.
import matplotlib.pyplot as plt
import matplotlib
def stylize_plot(ax=None, fig=None):
if ax is None:
ax = plt.gca()
if fig is None:
fig = plt.gcf()
ax.axis([-10, 10, -10, 10])
ax.grid(True)
fig.set_tight_layout(True)
ax.spines['left'].set_position('zero')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Appears to be shifting all tick labels on positive horizontal axis for Figure with 2 subplots
xTick_objects = ax.xaxis.get_major_ticks()
xTick_objects[0].label1.set_horizontalalignment('left') # left align first tick
xTick_objects[-1].label1.set_horizontalalignment('right') # right align last tick
yTick_objects = ax.yaxis.get_major_ticks()
yTick_objects[0].label1.set_verticalalignment('bottom') # bottom align first tick
yTick_objects[-1].label1.set_verticalalignment('top') # top align last tick
ax.xaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%0.1f'))
ax.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%0.1f'))
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111)
stylize_plot(ax=ax, fig=fig)
fig2 = plt.figure()
ax2 = fig2.add_subplot(211)
ax3 = fig2.add_subplot(212)
stylize_plot(ax=ax2, fig=fig2)
stylize_plot(ax=ax3, fig=fig2)
fig3 = plt.figure()
ax4 = fig3.add_subplot(121)
ax5 = fig3.add_subplot(122)
stylize_plot(ax=ax4, fig=fig3)
stylize_plot(ax=ax5, fig=fig3)
plt.show()

Related

Removing ticks when using grid with imshow matplotlib

In this question, they answer how to correctly use grid with imshow with matplotlib. I am trying to do the same as they do, but I want to remove all ticks (x and y). When I try to do it, it also eliminates the grid and I just the image displayed without grid and ticks. My code is:
fig, ax = plt.subplots()
data = np.random.rand(20,20)
ax.imshow(data)
ax.set_xticks(np.arange(20))
ax.set_xticklabels(np.arange(20))
ax.set_xticks(np.arange(20)+0.5, minor=True)
ax.grid(which='minor',color='w',axis='x',linewidth=6)
ax.axes.xaxis.set_visible(False)
ax.axes.yaxis.set_visible(False)
plt.show()
Does anyone how to remove the ticks while keeping the grid (along the x axis in my case)?
Removing the axes (via set_visible(False)) will also remove the grid.
However, there's a workaround setting both spines and tick marks/labels to be invisible individually:
fig, ax = plt.subplots()
data = np.random.rand(20,20)
ax.imshow(data)
ax.set_xticks(np.arange(20))
ax.set_xticklabels(np.arange(20))
ax.set_xticks(np.arange(20)+0.5, minor=True)
ax.grid(which='minor',color='w',axis='x',linewidth=6)
# set axis spines (the box around the plot) to be invisible
plt.setp(ax.spines.values(), alpha = 0)
# set both tick marks and tick labels to size 0
ax.tick_params(which = 'both', size = 0, labelsize = 0)
plt.show()
Gives you output as:
Note, you might need to adjust xlim/ylim and grid parameters to fit your needs.
This is not perfect, but you can just set the tick label as an empty list.
ax.axes.get_xaxis().set_ticks([])
ax.axes.get_yaxis().set_ticks([])
Only the minor xticks, used in the grid, remain.

Non-overlapping legend and axes (e.g. in matplotlib)

I need the plot legend to appear side-by-side to the plot axes, i.e. outside of the axes and non-overlapping.
The width of the axes and the legend should adjust "automatically" so that they both fill the figure w/o them to overlap or the legend to be cut, even when using tight layout. The legend should occupy a minor portion of the figure (let's say max to 1/3 of figure width so that the remaining 2/3 are dedicated to the actual plot).
Eventually, the font of the legend entries can automatically reduce to meet the requirements.
I've read a number of answers regarding legend and bbox_to_anchor in matplotlib with no luck, among which:
how to put the legend out of the plot
moving matplotlib legend outside of the axis makes it cutoff by the figure box
I tried by creating a dedicated axes in which to put the legend so that plt.tight_layout() would do its job properly but then the legend only takes a minor portion of the dedicated axes, with the result that a lot of space is wasted. Or if there isn't enough space (the figure is too small), the legend overlaps the first axes anyway.
import matplotlib.pyplot as plt
import numpy as np
# generate some data
x = np.arange(1, 100)
# create 2 side-by-side axes
fig, ax = plt.subplots(1,2)
# create a plot with a long legend
for ii in range(20):
ax[0].plot(x, x**2, label='20201110_120000')
ax[0].plot(x, x, label='20201104_110000')
# grab handles and labels from the first ax and pass it to the second
hl = ax[0].get_legend_handles_labels()
leg = ax[1].legend(*hl, ncol=2)
plt.tight_layout()
I'm open to use a package different from matplotlib.
Instead of trying to plot the legend in a separate axis, you can pass loc to legend:
# create 2 side-by-side axes
fig, ax = plt.subplots(figsize=(10,6))
# create a plot with a long legend
for ii in range(20):
ax.plot(x, x**2, label='20201110_120000')
ax.plot(x, x, label='20201104_110000')
# grab handles and labels from the first ax and pass it to the second
ax.legend(ncol=2, loc=[1,0])
plt.tight_layout()
Output:

Properly adding a second set of ticks to python matplotlib colorbar

I have a figure with three subplots. The top two subplots share a similar data range, while the bottom one shows data with a different data range. I'd like to use only one colorbar for the whole figure by having ticks for the top two subplots to the left of the colorbar and having ticks for the bottom subplot to the right of the colorbar (see fig bellow).
I have been able to do this using a dirty hack, namely by displaying two colorbars on top of each other and moving the ticks of one of them to the left. As an example I've modified this matplotlib example:
import matplotlib.pyplot as plt
import numpy as np
# Fixing random state for reproducibility
np.random.seed(19680801)
# create three subplots
fig, axes = plt.subplots(3)
# filling subplots with figures and safing the map of the first and third figure.
# fig 1-2 have a data range of 0 - 1
map12 =axes[0].imshow(np.random.random((100, 100)), cmap=plt.cm.BuPu_r)
axes[1].imshow(np.random.random((100, 100)), cmap=plt.cm.BuPu_r)
# figure 3 has a larger data range from 0 - 5
map3 = axes[2].imshow(np.random.random((100, 100))*5, cmap=plt.cm.BuPu_r)
# Create two axes for the colorbar on the same place.
# They have to be very slightly missplaced, else a warning will appear and only the second colorbar will show.
cax12 = plt.axes([0.85, 0.1, 0.075, 0.8])
cax3 = plt.axes([0.85, 0.100000000000001, 0.075, 0.8])
# plot the two colorbars
cbar12 = plt.colorbar(map12, cax=cax12, label='ticks for top two figs')
cbar3 = plt.colorbar(map3, cax=cax3, label='ticks for bottom fig')
# move ticks and label of second plot to the left
cbar12.ax.yaxis.set_ticks_position('left')
cbar12.ax.yaxis.set_label_position('left')
## display image
plt.show()
While I'm happy with the visual result, i think there has to be a better way to do this. One problem is that if you save it as vector graphic, you will end up with overlapping shapes. Also if you make a mistake with the colors of the lower colorbar you might not realize it because the colors are hidden, or it might give you a headache if you want to make the colorbar sightly transpartent for some reason. I therefore wonder how one would do this properly, or if this is not possible, if there is a better hack?
You can achieve the same result without drawing the second colorbar, you just need to create a new axes with the ticks to the right, and adjust the range of the y-axis to the range of data of your 3rd plot.
import matplotlib.pyplot as plt
import numpy as np
# Fixing random state for reproducibility
np.random.seed(19680801)
# create three subplots
fig, axes = plt.subplots(3)
# filling subplots with figures and safing the map of the first and third figure.
# fig 1-2 have a data range of 0 - 1
map12 =axes[0].imshow(np.random.random((100, 100)), cmap=plt.cm.BuPu_r)
axes[1].imshow(np.random.random((100, 100)), cmap=plt.cm.BuPu_r)
# figure 3 has a larger data range from 0 - 5
map3 = axes[2].imshow(np.random.random((100, 100))*5, cmap=plt.cm.BuPu_r)
# Create two axes for the colorbar on the same place.
cax12 = plt.axes([0.85, 0.1, 0.075, 0.8])
cax3 = cax12.twinx()
# plot first colorbar
cbar12 = plt.colorbar(map12, cax=cax12, label='ticks for top two figs')
# move ticks and label of colorbar to the left
cbar12.ax.yaxis.set_ticks_position('left')
cbar12.ax.yaxis.set_label_position('left')
# adjust limits of right axis to match data range of 3rd plot
cax3.set_ylim(0,5)
cax3.set_ylabel('ticks for bottom fig')
## display image
plt.show()
For some reason, the above answer did not work for me. I do not know why. What worked for me is as follows:
cax2 = fig.add_axes([<xposition>, <yposition>, <xlength>, <ylegth>])
cax21 = cax2.twinx()
cax2.set_ylabel('right-label',size=<right_lable_size>)
cax2.tick_params(labelsize=<right_tick_size>)
'''
These did not work for me
cbar21.ax.yaxis.set_ticks_position('left')
cbar21.ax.yaxis.set_label_position('left')
'''
# This worked.
cax21.yaxis.tick_left()
cax21.yaxis.label_position='left'
cax21.set_ylim(<minVal>,<maxVal>,<step>)
cax21.set_ylabel("left-label",size=<left_lable_size>)
cax21.tick_params(labelsize=<left_tick_size>)
Hopefully, this helps.

Pyplot won't stop showing decimal percentages of X and Y axes

I'm trying to use pyplot to do linear modeling, and I've run across a problem. When I make a plot of the data, pyplot wants to put decimal percentages along the X and Y axis. I've tried a few different things to make it go away. I want to keep some tick labels, so I've tried various methods of adding my own tick labels, and that works, however, it still prints its own tick labels on top.
So at the origin it says 0.0, then a fifth of the way along the axis it says 0.2, so on until the end of the axis it says 1.0.
Example image of the problem:
fig = plt.figure(figsize = (10,10))
big_plot = fig.add_subplot(111)
data_plot = fig.add_subplot(211)
residual_plot = fig.add_subplot(212)
data_plot.plot(x,y,'ro')
data_plot.errorbar(x,model,sigma)
residual_plot.plot(x,residuals,'b*')
data_plot.set_title("Data")
data_plot.set_ylabel(y_label)
residual_plot.set_title("Residuals")
residual_plot.set_ylabel("Residual Value")
big_plot.set_xlabel(x_label)
plt.show()
Does anyone know how to clear those tick labels and add my own? Thank you.
In your case, you are creating three plots, but only plotting data on two of them. The big_plot is axis is plotted with the default tick marks and it is the source of the extra tick marks that you don't want.
Instead, just remove this axis and label the bottom x-axis by assigning the label to data_plot.
fig = plt.figure(figsize = (10,10))
data_plot = fig.add_subplot(211)
residual_plot = fig.add_subplot(212)
data_plot.plot(x,y,'ro')
data_plot.errorbar(x,model,sigma)
residual_plot.plot(x,residuals,'b*')
data_plot.set_title("Data")
data_plot.set_ylabel(y_label)
residual_plot.set_title("Residuals")
residual_plot.set_ylabel("Residual Value")
data_plot.set_xlabel(x_label)
plt.show()

Need to add space between SubPlots for X axis label, maybe remove labelling of axis notches

Looking to add in vertical space between plotted graphs to allow a X-Axis label to show:
Each graph needs to have space to show the day, currently the last 2 graphs are the only one's that show simply because the graphs are overlapping it.
Also curious if I could actually remove the notch labels for the X-Axis for the graphs above the one's marked Thursday/Friday, i.e. the bottom X-axis is the only one that shows. Same for the Y-Axis, but only the graphs on the left having the scale shown.
*Unfortunately I can't post an image to show this since I don't have enough rep.
Code snippet:
import mathlib.pyplot as pyplot
fig = pyplot.figure()
ax1 = fig.add_subplot(4,2,1)
ax1.set_yscale('log')
ax2 = fig.add_subplot(4,2,2, sharex=ax1, sharey=ax1)
ax3 = fig.add_subplot(4,2,3, sharex=ax2, sharey=ax2)
ax4 = fig.add_subplot(4,2,4, sharex=ax3, sharey=ax3)
ax5 = fig.add_subplot(4,2,5, sharex=ax4, sharey=ax4)
ax6 = fig.add_subplot(4,2,6, sharex=ax5, sharey=ax5)
ax7 = fig.add_subplot(4,2,7, sharex=ax6, sharey=ax6)
ax1.plot(no_dict["Saturday"],'k.-',label='Saturday')
ax1.set_xlabel('Saturday')
ax1.axis([0,24,0,10000])
pyplot.suptitle('Title')
pyplot.xlabel('Hour in 24 Hour Format')
ax2.plot(no_dict["Sunday"],'b.-',label='Sunday')
ax2.set_xlabel('Sunday')
...
Use subplots_adjust. In your case this looks good:
fig.subplots_adjust(hspace=.5)
to remove the tick labels do this:
ax1.set_xticklabels([])
Similar for the yticklabels. However, you cannot share the x-axis with the plots that do have tick labels.
To change the spacing around a certain subplot, instead of all of them, you can adjust the position of the axes of that subplot using:
bbox=plt.gca().get_position()
offset=-.03
plt.gca().set_position([bbox.x0, bbox.y0 + offset, bbox.x1-bbox.x0, bbox.y1 - bbox.y0])
If offset < 0, the subplot is moved down. If offset > 0, the subplot is moved up.
Note that the subplot will disappear if offset is so big that the new position of the subplot overlaps with another subplot.

Categories

Resources