I am trying to explore a subplot 2 plots with square in shape rotated by 45 degree.
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig, ax= plt.subplots(1,2)
ax[0].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[0].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[0].set_xticks(np.arange(-.5, 10, 1));
ax[0].set_yticks(np.arange(-.5, 10, 1));
ax[1].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[1].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[1].set_xticks(np.arange(-.5, 10, 1));
ax[1].set_yticks(np.arange(-.5, 10, 1));
plt.show()
Actual Result is :-
I want to rotate individual plot by 45 degree. Something like:-
I am trying to find in Matplotlib Documentation. Still not getting. Any help?
Please note this is NOT DUPLICATE OF
Is there a way to rotate a matplotlib plot by 45 degrees?
The mentioned URL is for a plot. and the solution is to rotate IMAGE. However this is pertaining to Subplot. I want to rotate PLOT not image as whole.
Based on this link and documentation about floating_axes, you can try something like this:
from mpl_toolkits.axisartist.grid_finder import DictFormatter
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib import colors
import numpy as np
def setup_axes1(fig, rect, angle):
tr = Affine2D().scale(2, 2).rotate_deg(angle)
#We create dictionarys to keep the xticks and yticks after the rotation
dictio={i:str(val) for i,val in enumerate(np.arange(-.5, 10, 1).tolist())}
reversedictio={i:dictio[val] for i,val in enumerate(list(reversed(sorted(dictio.keys()))))}
grid_helper = floating_axes.GridHelperCurveLinear(
tr, extremes=(-0.5, 9.5,-0.5, 9.5), tick_formatter1= DictFormatter(dictio),
tick_formatter2=DictFormatter(reversedictio))
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
aux_ax = ax1.get_aux_axes(tr)
grid_helper.grid_finder.grid_locator1._nbins = 10 #Number of rows
grid_helper.grid_finder.grid_locator2._nbins = 10 #Number of columns
return aux_ax
fig1, axes=plt.subplots(2,figsize=(20,20))
plt.rcParams.update({'font.size': 27})
#We erase the first previous axes
fig1.delaxes(axes[0])
fig1.delaxes(axes[1])
data = np.random.rand(10, 10) * 20
#We create the floating_axes
ax0 = setup_axes1(fig1, 121,-45)
ax1 = setup_axes1(fig1, 122,-45)
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
ax0.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax0.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax1.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax1.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.show()
Output:
Or, as an other alternative, I found a "tricky" way to do it, and it's about catching the figures in the buffer, rotate them -45 degrees, and then merge them into a single image, and since you have the same two images, you can try something like this:
import matplotlib
import io
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
##PLOTING THE FIGURE##
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
#We change style values to get the image with better quality
plt.rcParams.update({'font.size': 46})
plt.figure(figsize=(20,20))
plt.imshow(data, cmap=cmap, norm=norm)
# draw gridlines
plt.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.gca().set_xticks(np.arange(-.5, 10, 1));
plt.gca().set_yticks(np.arange(-.5, 10, 1));
##SAVING THE FIGURE INTO AN IMAGE##
#We save the current figure as a Image
buf = io.BytesIO()
plt.savefig(buf, format='png',bbox_inches='tight')
buf.seek(0)
im = Image.open(buf) #We open the current image saved in the buffer
#We rotate the image and fill the background with white
img_01=im.rotate(-45, Image.NEAREST, expand = 1, fillcolor = (255,255,255))
buf.close()
##MERGING THE TWO FIGURES##
new_im = Image.new('RGB', (2*img_01.size[0]+20,img_01.size[1]), 'white')
mouse_mask = img_01.convert('RGBA')
new_im.paste(img_01, (0,0))
new_im.paste(img_01, (img_01.size[0]+8,0))
new_im.save("merged_images.png", 'PNG') #Important(just to clarify): save the image, since the buffer is renewed every time you run the script
new_im.show()
Output:
I helped myself with these links:
How-to-merge-images-with-same-size-using-the-python-3-module-pillow
how-to-save-a-pylab-figure-into-in-memory-file-which-can-be-read-into-pil-image
python-pillow-rotate-image-90-180-270-degrees
specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand
Related
When creating a matplotlib colorbar, it is possible to set drawedges to True which separates the colors of the colorbar with black lines. However, when the colorbar is extended using extend='both', the black lines at the extremities do not show up. Is that a bug? Is there a possibility to draw those edges otherwise?
Here is the code:
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import from_levels_and_colors
my_cmap = mpl.cm.viridis
bounds = np.arange(10)
nb_colors = len(bounds) + 1
colors = my_cmap(np.linspace(100, 255, nb_colors).astype(int))
my_cmap, my_norm = from_levels_and_colors(bounds, colors, extend='both')
plt.figure(figsize=(5, 1))
ax = plt.subplot(111)
cbar = mpl.colorbar.ColorbarBase(ax, cmap=my_cmap, norm=my_norm, orientation='horizontal', drawedges=True)
plt.subplots_adjust(left=0.05, bottom=0.4, right=0.95, top=0.9)
plt.show()
and the figure it gives:
I looked into it from your question and found a way to change the color of the border and vertical lines of the color bar. I used that to change them to red. The result I got was that the extended outline was red, so my guess is that I just pulled the short sides of the normal color bar rectangle to the left and right.
I found this response helpful.
cbar.outline.set_edgecolor('red')
cbar.dividers.set_color('red')
So I think the only way to do this is to add vertical lines.
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import from_levels_and_colors
my_cmap = mpl.cm.viridis
bounds = np.arange(10)
nb_colors = len(bounds) + 1
colors = my_cmap(np.linspace(100, 255, nb_colors).astype(int))
my_cmap, my_norm = from_levels_and_colors(bounds, colors, extend='both')
plt.figure(figsize=(6, 2))
ax = plt.subplot(111)
cbar = mpl.colorbar.ColorbarBase(ax, cmap=my_cmap, norm=my_norm, orientation='horizontal', drawedges=True)
# update
cbar.outline.set_edgecolor('red')
cbar.dividers.set_color('red')
plt.axvline(max(bounds), color='red', alpha=0.3, linewidth=3.5)
plt.axvline(min(bounds), color='red', alpha=0.3, linewidth=3.5)
plt.subplots_adjust(left=0.05, bottom=0.4, right=0.95, top=0.9)
plt.show()
I want to create a violin plot, with either matplotlib or searborn, in which the plot is colored according to a colormap.
This is what I get:
This is what I would like to get (I used Photoshop here):
How can I obtain the desired plot?
I thought there would be a better was to do this, but, based on #ImportanceOfBeingErnest's comment, I guess this is actually the way to go:
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
x = [np.random.normal(loc=i, scale=1, size=(100,)) for i in range(5)]
fig, ax = plt.subplots()
violins = ax.violinplot(x)
ymin, ymax = ax.get_ylim()
xmin, xmax = ax.get_xlim()
# create a numpy image to use as a gradient
Nx,Ny=1,1000
imgArr = np.tile(np.linspace(0,1,Ny), (Nx,1)).T
cmap = 'hsv'
for violin in violins['bodies']:
path = Path(violin.get_paths()[0].vertices)
patch = PathPatch(path, facecolor='none', edgecolor='none')
ax.add_patch(patch)
img = ax.imshow(imgArr, origin="lower", extent=[xmin,xmax,ymin,ymax], aspect="auto",
cmap=cmap,
clip_path=patch)
# colorbar
ax_divider = make_axes_locatable(ax)
cax = ax_divider.append_axes("right", size="5%", pad="2%")
norm = matplotlib.colors.Normalize(vmin=ymin, vmax=ymax)
cb = matplotlib.colorbar.ColorbarBase(cax, cmap=matplotlib.cm.get_cmap(cmap),
norm=norm,
orientation='vertical')
I want to plot some scatter plots over the map of a country (an image). The idea is to depict the data visualization of the area at which the plot is plotted.
So, this is how I plot the image of the map of USA, where the circles I have drawn towards the top left and the middle are where I would like to display my scatter plots:
import numpy as np
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(10,15))
im = plt.imread("usa-states-map.jpg")
implot = plt.imshow(im, extent=[0, 200, 0, 150])
# A circle in the upper left region
theta=np.linspace(0,2*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 45
t=np.sin(theta)*faux_radius + 130
plt.plot(z,t)
# A circle in the middle region
theta=np.linspace(0,3*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 100
t=np.sin(theta)*faux_radius + 80
plt.plot(z,t)
This plots the image like so:
I proceed to plot the scatter plots like so:
import numpy as np
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(10,15))
im = plt.imread("usa-states-map.jpg")
implot = plt.imshow(im, extent=[0, 200, 0, 150])
# A circle in the upper left region
theta=np.linspace(0,2*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 45
t=np.sin(theta)*faux_radius + 130
plt.plot(z,t)
# A circle in the middle region
theta=np.linspace(0,3*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 100
t=np.sin(theta)*faux_radius + 80
plt.plot(z,t)
# Scatter plot 1
ax1 = plt.subplot(2,2,1)
ax1.scatter(x_1_a, y_1_a, marker="s")
ax1.scatter(x_1_b, y_1_b, marker="o")
# Scatter plot 2
ax1 = plt.subplot(2,2,2)
ax1.scatter(x_2_a, y_2_a, marker="s")
ax1.scatter(x_2_a, y_2_b, marker="o")
But the output it produces does not display the background image, and only plots the scatter plots:
[]
I even tried using zorder which is supposed to tell matplotlib which plot should come on top and which on bottom, but to no avail - it produces the same output as above:
implot = plt.imshow(im, extent=[0, 200, 0, 150], zorder=1)
...
...
...
# Scatter plot 1
ax1 = plt.subplot(2,2,1)
ax1.scatter(x_1_a, y_1_a, marker="s", zorder=2)
ax1.scatter(x_1_b, y_1_b, marker="o", zorder=2)
# Scatter plot 2
ax1 = plt.subplot(2,2,2)
ax1.scatter(x_2_a, y_2_a, marker="s", zorder=3)
ax1.scatter(x_2_a, y_2_b, marker="o", zorder=3)
How do I fix this to get the desired result? I don't even need the 2 circles to be present on the map actually - those were just to illustrate where I would like to plot the 2 scatter plots. Thanks.
I was able to solve the problem using the plt.axes suggestion in the comments:
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10, 15),facecolor='white')
ax = fig.add_axes([0, 0, 1, 1])
ax.axis('off')
im = plt.imread("usa-states-map.jpg")
implot = plt.imshow(im)
plt.xticks([])
plt.yticks([])
# this is an inset axes over the main axes for the top left region
a = plt.axes([.2, .6, .2, .1], facecolor='w')
plt.scatter(x_1_a, y_1_a, marker="s")
plt.scatter(x_1_b, y_1_b, marker="o")
plt.legend(['%.2f%%' %(100*len(x_1_a)/(len(x_1_a)+len(y_1_a))), '%.2f%%' %(100*len(y_1_a)/(len(x_1_a)+len(y_1_a)))], loc='upper right');
# this is an inset axes over the main axes for the middle region
a = plt.axes([.45, .45, .2, .1], facecolor='w')
plt.scatter(x_2_a, y_2_a, marker="s")
plt.scatter(x_2_b, y_2_b, marker="o")
plt.legend(['%.2f%%' %(100*len(x_2_b)/(len(x_2_b)+len(y_2_b))), '%.2f%%' %(100*len(y_2_b)/(len(x_2_b)+len(y_2_b)))], loc='upper right');
plt.show()
I'm trying to put a little arrow in the corner of each of my subplots. Below is the sample code I'm using:
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def multi_plot():
fig, axes = plt.subplots(4, 1)
x = 0
for axis in axes:
axis.plot(xs, xs**2)
axis.imshow(im, extent=(0.4, 0.6, .5, .7), zorder=-1, aspect='auto')
plt.show()
multi_plot()
Unfortunately, this produces 4 subplots that are entirely dominated by the arrows and the plots themselves are not seen.
Example output - Incorrect:
What do I need to do so that each individual subplot has a small image and the plot itself can be seen?
I think it's worthwhile thinking about putting the image in a box and place it similar to the legend, using a loc argument. The advantage is that you don't need to care about extents and data coordinates at all. You also wouldn't need to take care of what happens when zooming or panning the plot. Further it allows to keep the image in it's original resolution (zoom=1 in below code).
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
from matplotlib.offsetbox import OffsetImage,AnchoredOffsetbox
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def place_image(im, loc=3, ax=None, zoom=1, **kw):
if ax==None: ax=plt.gca()
imagebox = OffsetImage(im, zoom=zoom*0.72)
ab = AnchoredOffsetbox(loc=loc, child=imagebox, frameon=False, **kw)
ax.add_artist(ab)
def multi_plot():
fig, axes = plt.subplots(4, 1)
for axis in axes:
axis.plot(xs, xs**2)
place_image(im, loc=2, ax=axis, pad=0, zoom=1)
plt.show()
multi_plot()
You'll notice that the limits on the x and y axis have been set to the extent of the imshow, rather than 0-1, which your plot needs to see the line.
You can control this by using axis.set_xlim(0, 1) and axis.set_ylim(0, 1).
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def multi_plot():
fig, axes = plt.subplots(4, 1)
x = 0
for axis in axes:
axis.plot(xs, xs**2)
axis.imshow(im, extent=(0.4, 0.6, .5, .7), zorder=-1, aspect='auto')
axis.set_xlim(0, 1)
axis.set_ylim(0, 1)
plt.show()
multi_plot()
Alternatively, if you want to maintain the extra 5% margin around your data that matplotlib uses by default, you can move the imshow command to before the plot command, then the latter will control the axis limits.
import matplotlib.pyplot as plt
import matplotlib.image as image
from numpy import linspace
xs = linspace(0, 1, 100)
im = image.imread('arrow.png')
def multi_plot():
fig, axes = plt.subplots(4, 1)
x = 0
for axis in axes:
axis.imshow(im, extent=(0.4, 0.6, .5, .7), zorder=-1, aspect='auto')
axis.plot(xs, xs**2)
plt.show()
multi_plot()
Using matplotlib.pyplot, I have two plots. One is a waveform of an audio file. The second is a spectrogram of the same audio. I want the wave form to be directly above the spectrogram (same x-axis, and aligned together). I also want a colorbar for the spectrogram.
Problem - when I put the colorbar in, it attaches to the spectrogram row and the waveform extends over the colorbar (i.e. is no longer time-aligned with the spectrogram and is wider than the spectrogram).
I am close to the solution, I think, but I just can't quite figure out what I'm doing wrong or what to change to get it working the way I want. Hope someone can point me in the right direction!
Using the following python code (I made the code as MWE as possible):
import matplotlib
matplotlib.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
####
#Waveform
####
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(13.6, 7.68))
plt.subplot(211)
plt.plot(times, data, color='k')
plt.xlabel('time (s)')
plt.xlim(times[0], times[-1])
max_amp = max(abs(np.amin(data)), abs(np.amax(data)))
min_amp = (max_amp * -1) - abs(np.amin(data) - np.amax(data))/50
max_amp = max_amp + abs(np.amin(data) - np.amax(data))/50
plt.ylim(min_amp, max_amp)
ax = plt.gca()
ax.set_yticks(np.array([min_amp, min_amp/2, 0, max_amp/2, max_amp]))
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.xaxis.set_tick_params(pad=115)
####
#Spectrogram
####
Fs = 5000*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
plt.subplot(212)
Pxx, freqs, times, cax = plt.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
axes_spec = plt.gca()
axes_spec.set_xlim([0., max(times)])
axes_spec.set_ylim([0, 5000])
plt.xlabel("Time (s)")
plt.ylabel("Frequency (hz)")
plt.colorbar(cax, label='(dB)').ax.yaxis.set_label_position('left')
plt.tight_layout()
plt.show()
I can get the following plot:
Making these slight modifications below, I can get the plot to look almost how I want. The problem is, it creates a blank figure next to the colorbar. This version, minus the blank figure, is what I am trying to create.
#Replace this for waveform
plt.subplot(221)
#Replace this for spectrogram
plt.subplot(223)
#Add this before colorbar
plt.subplot(122)
New version of plot:
EDIT: There is another possibility that I am also OK with (or perhaps both, for good measure!)
Here is an example of colorbar based on one of the answers in matplotlib-2-subplots-1-colorbar. The parameter pad in fig.colorbar is used to specify the space between the plots and the colorbar, and aspect is used to specify the aspect ratio between the height and width of the colorbar. Specgram outputs the image as the 4th output parameter, so I'm using that for the colorbar.
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-');
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs.ravel().tolist(), pad=0.04, aspect = 30)
It is important to notice that when fig.colorbar function is called using the ax parameter, the original plots will be resized to make room for the colorbar. If it is only applied to one of the plots, only that axis will be resized. Se below:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs[1], pad=0.04, aspect = 30)
Below it is shown a way of controlling the resizing of your original axes in order to make room for a colorbar using fig.colorbar with the cax parameter that will not resize further your original plots. This approach requires to manually make some room for your colorbar specifying the right parameter inside the function fig.subplots_adjust :
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y11-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
And doing the same to span two rows by reading coordinates of the original two plots:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x00,y00],[x01,y01]] = axs[0].get_position().get_points()
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
The best solution I came up with is subplot2grid() function. This requies the use of subplots, which I was not using originally. Following this method, I needed to change everything from using plt (matplotlib.pyplot) to using the axes for the given plot for each .plot() or .specgram() invocation. The relevant changes are included here:
#No rows or columns need to be specified, because this is handled within a the `subplot2grid()` details
fig, axes = plt.subplots(figsize=(13.6, 7.68))
#Setup for waveform
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
####WAVEFORM PLOTTING
#Setup for spectrogram
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
####SPECTROGRAM PLOTTING
#Setup for colorbar
ax3 = plt.subplot2grid((2, 60), (0, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
And a MWE bringing it all together:
import matplotlib as mpl
mpl.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
fig, axes = plt.subplots(figsize=(13.6, 7.68))#nrows=2, ncols=2,
gs = gridspec.GridSpec(2, 60)
####
#Waveform
####
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
ax1.plot(times, data, color='k')
ax1.xaxis.set_ticks_position('none')
ax1.yaxis.set_ticks_position('none')
####
#Spectrogram
####
maxFrequency = 5000
Fs = maxFrequency*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
Pxx, freqs, times, cax = ax2.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
ax2.set_ylim([0, maxFrequency])
ax2.xaxis.set_ticks_position('none')
ax2.yaxis.set_ticks_position('none')
####
#Colorbar (for spectrogram)
####
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
cbar.ax.yaxis.set_tick_params(pad=3, left='off', right='off', labelleft='on', labelright='off')
plt.show()
Here's an example of the output from this MWE:
Best part! You need only change the 0 to 1 and the rowspan to be 1 in this line (i.e. :)
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
to make the colorbar span only the height of the spectrogram. Meaning that changing between the two options is incredibly simple. Here's an example of the output from this change:
EDIT: GridSpec actually was unused, and so I edited it out. The only relevant details that I needed involved calling subplot2grid() to set up the subplots.