Python Matplotlib line plot aligned with contour/imshow
Question: How can I set the visual width of one subplot equal to the width of another
subplot using Python and Matplotlib? The first plot has a fixed aspect ratio
and square pixels from imshow. I'd then like to put a lineplot below that, but
am not able to do so and have everything aligned.

I'm fairly sure the solution involves the information on this [Transform
Tutorial](http://matplotlib.org/users/transforms_tutorial.html) page. I've
tried working with fig.transFigure, ax.transAxes, ax.transData, etc. but have
not been successful. I need to find the width and height and offsets of the
axes in the upper panel, and then be able to set the width, height, and
offsets of the axes in the lower panel. Axis labels and ticks and etc. should
not be included or change the alignment.

For example, the following code

    
    
    fig = plt.figure(1)
    fig.clf()
    
    data = np.random.random((3,3))
    xaxis = np.arange(0,3)
    yaxis = np.arange(0,3)
    
    ax = fig.add_subplot(211)
    ax.imshow(data, interpolation='none')
    c = ax.contour(xaxis, yaxis, data, colors='k')
    
    ax2 = fig.add_subplot(212)
    

![enter image description here](http://i.stack.imgur.com/NrKDd.png)


Answer: The outline of matplotlib axes are controlled by three things:

  1. The axes' bounding box within the figure (controlled by a subplot specification or a specific extent such as `fig.add_axes([left, bottom, width, height])`. The axes limits (not counting tick labels) will always be within this box.
  2. The `adjustable` parameter that controls whether changes in limits or aspect ratio are accomodated by changing data limits or the shape of the axes "box". This can be `"datalim"`, `"box"`, or `"box-forced"`. (The latter is for use with shared axes.)
  3. The axes limits and aspect ratio. For plots with a fixed aspect ratio, the axes box or data limits (depending on `adjustable`) will be changed to maintain the specified aspect ratio. The aspect ratio refers to data coordinates, _not_ the shape of the axes directly.

For the simplest case:

    
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(nrows=2)
    
    data = np.random.random((3,3))
    xaxis = np.arange(0,3)
    yaxis = np.arange(0,3)
    
    axes[0].imshow(data, interpolation='none')
    c = axes[0].contour(xaxis, yaxis, data, colors='k')
    
    axes[1].set_aspect(1)
    
    plt.show()
    

![enter image description here](http://i.stack.imgur.com/fDxoF.png)

* * *

## Shared Axes

However, if you want to ensure that it stays the same shape regardless, and
you're okay with both plots having the same data limits, you can do:

    
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(nrows=2), sharex=True, sharey=True)
    plt.setp(axes.flat, adjustable='box-forced')
    
    data = np.random.random((5,3))
    xaxis = np.arange(0,3)
    yaxis = np.arange(0,5)
    
    axes[0].imshow(data, interpolation='none')
    c = axes[0].contour(xaxis, yaxis, data, colors='k')
    
    axes[1].plot([-0.5, 2.5], [-0.5, 4.5])
    axes[1].set_aspect(1)
    
    plt.show()
    

![enter image description here](http://i.stack.imgur.com/aE9aq.png)

However, you may notice that this doesn't look quite right. That's because the
second subplot is controlling the extents of the first subplot due to the
order we plotted things in.

Basically, with shared axes, whatever we plot last will control the initial
extent, so if we just swap the order we're plotting in:

    
    
        import numpy as np
        import matplotlib.pyplot as plt
    
        fig, axes = plt.subplots(nrows=2, sharex=True, sharey=True)
        plt.setp(axes.flat, adjustable='box-forced')
    
        data = np.random.random((5,3))
        xaxis = np.arange(0,3)
        yaxis = np.arange(0,5)
    
        axes[1].plot([-0.5, 2.5], [-0.5, 4.5])
        axes[1].set_aspect(1)
    
        axes[0].imshow(data, interpolation='none')
        c = axes[0].contour(xaxis, yaxis, data, colors='k')
    
        plt.show()
    

![enter image description here](http://i.stack.imgur.com/IVzy2.png)

Of course, if you don't care about the interactive zooming/panning of the
plots being linked, you can skip the shared axes altogether and just to:

    
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(nrows=2)
    
    data = np.random.random((5,3))
    xaxis = np.arange(0,3)
    yaxis = np.arange(0,5)
    
    axes[0].imshow(data, interpolation='none')
    c = axes[0].contour(xaxis, yaxis, data, colors='k')
    
    axes[1].plot([-0.5, 2.5], [-0.5, 4.5])
    
    # Copy extents and aspect from the first axes...
    axes[1].set_aspect(axes[0].get_aspect())
    axes[1].axis(axes[0].axis())
    
    plt.show()
    

* * *

## Non-Shared Axes

If you don't want the two axes to have the same data extents, it is possible
to force them to be the same size (though if you interactively zoom, they
won't be linked). To do this, you need to calculate what the aspect ratio for
the second plot should be based on its extents and the extents/aspect of the
first plot.

    
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(nrows=2)
    
    data = np.random.random((3,3))
    xaxis = np.arange(0,3)
    yaxis = np.arange(0,3)
    
    axes[0].imshow(data, interpolation='none')
    c = axes[0].contour(xaxis, yaxis, data, colors='k')
    
    axes[1].plot(np.linspace(0, 10, 100), np.random.normal(0, 1, 100).cumsum())
    
    # Calculate the proper aspect for the second axes
    aspect0 = axes[0].get_aspect()
    if aspect0 == 'equal':
        aspect0 = 1.0
    dy = np.abs(np.diff(axes[1].get_ylim()))
    dx = np.abs(np.diff(axes[1].get_xlim()))
    
    aspect = aspect0 / (float(dy) / dx)
    axes[1].set_aspect(aspect)
    
    plt.show()
    

![enter image description here](http://i.stack.imgur.com/pOD3f.png)

