简体   繁体   中英

Define aspect ratio when using twinx in new version of matplotlib

Current version of matplotlib do not allow box-forced anymore, how should I do the same thing as the answer ?

I am using matplotlib 3.1.0. After I ploted another set of data on the same plot with twinx() function, I want to change the aspect ratio of the actual plot area to 1.

Normally I do this and it works for non-twinx axis

ratio = 1
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright - xleft) / (ybottom - ytop)) * ratio)

For twinx axis, the above code do not work, but will not raise any error either.
Then I found an answer here

The code basically used the same method to set aspect ratio to 1, only with box-forced option.

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 1.6, 50) + 50.0

fig, ax = plt.subplots()
ax2 = ax.twinx()

XLIM = [50.0, 51.6]
YLIM = [0.0, 1.1, 0.0, 11.0]

ax.plot(x, np.sin(x - 50.0), 'b')
ax2.plot(x, np.cos(x - 50.0) * 10., 'r')

# set aspect to 1
ax.set(adjustable='box-forced',
       xlim=XLIM, ylim=YLIM[:2],
       xticks=np.arange(XLIM[0], XLIM[1], 0.2),
       yticks=np.arange(YLIM[0], YLIM[1] + 0.1, 0.1)[:-1],
       aspect=(XLIM[1] - XLIM[0]) / (YLIM[1] - YLIM[0]))

ax2.set(adjustable='box-forced',
        ylim=YLIM[2:],
        yticks=np.arange(YLIM[2], YLIM[3] + 1.0, 1.0),
        aspect=(XLIM[1] - XLIM[0]) / (YLIM[3] - YLIM[2]))

ax.grid(True, which='major', linestyle='solid')

plt.show()

This code in my python don't work, raises

ValueError: 'box-forced' is not a valid value for adjustable; supported values are 'box', 'datalim'

And if I change that to 'box' , it gives

RuntimeError: Adjustable 'box' is not allowed in a twinned Axes.  Use 'datalim' instead.

I am not sure from when the box-forced was removed. Now how should we set the aspect ratio in a 'box' manner?

Thanks!

For reference: matplotlib.axes.Axes.set_adjustable

As I just commented on a respective matplotlib issue,

"aspect" in matplotlib always refers to the data, not the axes box. Therefore setting the aspect for twinned or shared axes and letting the box be adjustable actually only makes sense when the scales are the same - or differ by an offset (as opposed to any other linear or nonlinear function). Matplotlib does not perform any check on this, so it disallows for adjustable='box' in such case.

It seems to me that using aspect here is merely a workaround for getting a fixed ratio for the axes box. Matplotlib does not provide any clear codepath for that as of now, but one could eg force the axes box into a square space by adjusting the subplot parameters

import numpy as np
import matplotlib.pyplot as plt

def squarify(fig):
    w, h = fig.get_size_inches()
    if w > h:
        t = fig.subplotpars.top
        b = fig.subplotpars.bottom
        axs = h*(t-b)
        l = (1.-axs/w)/2
        fig.subplots_adjust(left=l, right=1-l)
    else:
        t = fig.subplotpars.right
        b = fig.subplotpars.left
        axs = w*(t-b)
        l = (1.-axs/h)/2
        fig.subplots_adjust(bottom=l, top=1-l)


x = np.linspace(0,1.6,50) + 50.0

fig, ax = plt.subplots()
ax2 = ax.twinx()

ax.set(xlim = [50.0, 51.6], ylim = [0.0, 1.1])
ax2.set(ylim = [0.0, 11.0])

ax.plot(x,np.sin(x-50.0),'b')
ax2.plot(x,np.cos(x-50.0)*10.,'r')

ax.grid(True, which='major',linestyle='solid')

squarify(fig)
fig.canvas.mpl_connect("resize_event", lambda evt: squarify(fig))

plt.show()

Also see this answer for more than one subplot.

If you want to use mpl_toolkits and make your hands dirty, this answer would be a good read.

Thanks to @ImportanceOfBeingErnest, but to make this work in several subplots, I found another way inspired by your answer:

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.axes_divider import AxesDivider

def make_patch_spines_invisible(ax):
    ax.set_frame_on(True)
    ax.patch.set_visible(False)
    for sp in ax.spines.values():
        sp.set_visible(False)

def demo_fixed_size_axes():
    fig, axs = plt.subplots(1, 2, figsize=(12, 9))
    axs[0].plot([1, 2, 3])
    axs[1].plot([1, 2, 3.5])
    ax3 = axs[1].twinx()
    ax3.plot([1, 2, 3], [1, 25, 30])

    axs[1].spines['right'].set_visible(False)
    make_patch_spines_invisible(ax4Alt)
    ax4Alt.spines['right'].set_visible(True)

    for ax in fig.get_axes():
        figPos = AxesDivider(ax).get_position()
        h = [Size.Fixed(4)] # has to be fixed
        v = h

        divider = Divider(fig, figPos, h, v, aspect=False)

        ax.set_axes_locator(divider.new_locator(nx=0, ny=0))


if __name__ == "__main__":
    demo_fixed_size_axes()

    plt.show()

The disadvantage is that one has to decide which size to use in inches. I do not fully understand my code though...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM