Define aspect ratio when using twinx in new version of matplotlib

狂风中的少年 提交于 2021-01-05 10:57:05

问题


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


回答1:


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 e.g. 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.




回答2:


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...



来源:https://stackoverflow.com/questions/57216993/define-aspect-ratio-when-using-twinx-in-new-version-of-matplotlib

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!