Search code examples
pythonmatplotlibaxesmultiple-axes

Figure appearance is not preserved when its axes are added to new figure


I have several figures generated by independent scripts that I would like to lump in one complex figure with custom axes (position and size) for each panel. I know that this approach seems not to be well supported by matplotlib so far but I decided to give it a try.

Right now, I am able to copy each figure in to the panel that I want, but the "copied" figure is somehow clipped and the axes are messed up. And frankly, as I am relatively new to matplotlib I don't know what I am missing (in Matlab copyobj() would have done the trick for example).

Following is the sample code, I am using for testing:

def customfigure(flist,axlist):
    # Margins
    lm = 0.05
    rm = 0.05
    bm = 0.05
    tm = 0.05

    # basic grid
    nrow = 2
    ncol = 2

    # rulers
    hr = 0.02
    vr = 0.02

    # axis width
    aw = (1-lm-rm-(ncol-1)*hr)/ncol
    # axis height
    ah = (1-tm-bm-(nrow-1)*vr)/nrow

    # Make axis box
    ax = [None]*(ncol*nrow)
    ax[0] = [lm,bm,aw,ah]
    ax[1] = [lm,bm+ah+vr,aw,ah]
    ax[2] = [lm+aw+hr,bm,aw,ah]
    ax[3] = [lm+aw+hr,bm+ah+vr,aw,ah]

    fig = plt.figure(figsize=(10,6),dpi=80)
    # Solution 1
    for i in xrange(len(axlist)):
        axlist[i].set_position(ax[i])
        axlist[i].set_figure(fig)
        flist[i].delaxes(axlist[i])        
        fig.add_axes(axlist[i])

close('all')
fig = [None]*4
ax = [None]*4
x = array([range(10)])

close('all')
fig = [None]*4
ax = [None]*4

t = arange(0.0, 2.0, 0.01)
color = ['k','r','g','b']
for i in xrange(4):
    fig[i],ax[i] = plt.subplots()
    s = sin(2*(i+1)*pi*t)
    plt.plot(t, s,'-',color=color[i])
    show()

customfigure(fig,ax)

The final figure, built by customfigure() (which regretfully I cannot paste here as a newbie) has at least two problems in this figure: (1) - the size of the copied figures is not the one that I would expect by the set_position() instance (2) - the original figure when lumped in the main figure, seems clipped for data ranges to 1.0 (while the x-axis shall go up to 2.0) (3) - Then ok, position of axes is totally screwed...

What am I doing wrong?

Thanks in advance for your help.

M


Solution

  • I think the most natural way of doing this is to create the custom axes first and then plot on them directly. By the sounds of it, for you this would involve changing your independent scripts to take an axes instance as an argument and do their plotting on that (rather than by using the state-ful interface).

    One way of doing this would be to return a list of axes instances from customfigure:

    for i in xrange(len(axlist)):
        axlist[i] = fig.add_axes(ax[i])
    return axlist
    

    you can then use these to plot on in the main section of your script:

    color = ['k', 'r','g', 'b']
    alist = customfigure() # (customfigure doesn't need to take any
                           # arguments now)
    for i in xrange(4):
        s = np.sin(2*(i+1) * np.pi * t)
        alist[i].plot(t, s, '-', color = color[i])
    

    and that should do what you want. This sort of thing is what I tend to do when doing custom plots.

    If you really don't want to change your independent scripts then you can achieve much the same thing in the state-ful interface by using plt.sca to set the current axes of the state machine to be the axes you've created and want to plot on. This would involve doing something like:

    color = ['k', 'r','g', 'b']
    alist = customfigure() # (customfigure doesn't need to take any
                           # arguments now)
    for i in xrange(4):
        s = np.sin(2*(i+1) * np.pi * t)
        plt.sca(alist[i])
        plt.plot(t, s, '-', color = color[i])
    

    Hope this helps and is understandable!