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
infig.colorbar
is used to specify the space between the plots and the colorbar, andaspect
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.It is important to notice that when
fig.colorbar
function is called using theax
parameter, the original plots will be resized to make room for thecolorbar
. If it is only applied to one of the plots, only that axis will be resized. Se below: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 thecax
parameter that will not resize further your original plots. This approach requires to manually make some room for your colorbar specifying theright
parameter inside the functionfig.subplots_adjust
:And doing the same to span two rows by reading coordinates of the original two plots: