pythonmatplotlibmatplotlib-gridspec

Sharing x axis and y axis at the same time for images


I have an image that I am displaying with imshow. Then I add all the rows and show maximum values. I do the same for the columns. On the display plot, I would like to make the x and y axis of the image coincide with the x axis of the addition of columns and the y axis with the adition of rows. However, despite setting sharex and sharey respectively, it just doesnt seem to work. I would I just seem to be able to do one at a time:

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import argrelextrema
import matplotlib.animation as animation

fig=        plt.figure()
gs=         fig.add_gridspec(2,2, height_ratios=[1, 0.1], width_ratios=[1, 0.1], hspace=0, wspace=0)
ax1=        fig.add_subplot(gs[0,0])
ax2=        fig.add_subplot(gs[1,0], sharex=ax1)
ax3=        fig.add_subplot(gs[0,1], sharey=ax1)
frameNumber= 10
imgs=   []

for i in range(frameNumber):
    np.random.seed(i)
    randomImage= np.random.random((5,5))
    sumX=   np.sum(randomImage, axis=0)
    sumY=   np.sum(randomImage, axis=1)
    dataRange=  np.arange(len(sumX))
    randomDataSet=  np.random.random((10))
    randomMaximalX= argrelextrema(sumX, np.greater)
    randomMaximalY= argrelextrema(sumY, np.greater)

    img1=   ax1.imshow(randomImage, animated=True)
    img2=   ax2.plot(dataRange, sumX,animated=True)[0]
    img3=   ax3.plot(sumY,dataRange,animated=True)[0]
    img4=   ax2.vlines(x=randomMaximalX, ymin=0, ymax=5, animated=True, linestyles="dashed")
    img5=   ax3.hlines(y=randomMaximalY, xmin=0, xmax=5, animated=True, linestyles="dashed")
    imgs.append([img1, img2, img3, img4, img5])
ani=    animation.ArtistAnimation(fig, imgs, interval=1000, blit=False)
plt.show()

The current result is this:

enter image description here

And indeally I want something like this:

enter image description here

where H is the same value for both plots. Many thanks!


Solution

  • There are two ways you can approach this problem:

    1. Use Axes.pcolormesh instead of Axes.imshow
    2. OR update the aspect ratios of the adjacent plots.

    ① Axes.pcolormesh

    Axes.pcolormesh does not force the resultant image to be square (1:1 aspect ratio), so your cells will be rectangular but they'll appropriately fill the space provided.

    from numpy.random import default_rng
    import matplotlib.pyplot as plt
    
    rng = default_rng(0)
    image = rng.uniform(1, 10, size=(5, 5))
    
    mosaic = [
        ['main',   'right'],
        ['bottom', '.'    ],
    ]
    
    fig, axd = plt.subplot_mosaic(
        mosaic,
        gridspec_kw={
            'height_ratios': [1, .1], 'width_ratios': [1, .1],
            'wspace': .05, 'hspace': .05,
        },
        sharex=True,
        sharey=True,
    )
    
    axd['main'].pcolormesh(image)
    
    plt.show()
    

    enter image description here

    ② Aspect Updating

    If you want to stick with Axes.imshow, then you'll need to adjust the aspect ratios of each plot manually. To get the correct ratios, you'll need to calculate from the supplied height_ratio and width_ratio fed into the GridSpec

    from numpy.random import default_rng
    import matplotlib.pyplot as plt
    
    rng = default_rng(0)
    image = rng.uniform(1, 10, size=(5, 5))
    
    mosaic = [
        ['main',   'right'],
        ['bottom', '.'    ],
    ]
    
    fig, axd = plt.subplot_mosaic(
        mosaic,
        sharex=True,
        sharey=True,
        gridspec_kw={
            'height_ratios': [1, .1], 'width_ratios': [1, .1],
    
            # change values to move adjacent plots closer to the main
            'wspace': .05, 'hspace': .05,
        },
    )
    
    axd['main'].imshow(image)
    axd['main'].set_anchor('SE') # move main plot to bottom-right of bounding-box
    
    # calculate the width and height scales
    gs = axd['main'].get_gridspec() # you can also save these values from your `gridspec_kw`
    width_scale = gs.get_width_ratios()[0]    / gs.get_width_ratios()[1]
    height_scale = gs.get_height_ratios()[0]  / gs.get_height_ratios()[1]
    
    # update the aspect ratios of the adjacent plots
    #   set their anchors so they correctly align with the main plot
    axd['right'].set_aspect(width_scale, anchor='SW')
    axd['bottom'].set_aspect(1/height_scale, anchor='NE')
    
    plt.show()
    

    enter image description here