【问题标题】:Is there a way to incorporate the extra artist generated by axes.ticklabel_format() into the axis label instead?有没有办法将 axes.ticklabel_format() 生成的额外艺术家合并到轴标签中?
【发布时间】:2020-06-22 00:35:09
【问题描述】:

简而言之,我想要右边的版本,而不是左边的版本。有什么方法可以做到这一点而不必画出拳头?您之前可以访问艺术家,但此时尚未设置文本。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.text import Text


image = np.random.uniform(10000000, 100000000, (100, 100))

fig, ax = plt.subplots()
image_artist = ax.imshow(image)
colorbar = fig.colorbar(image_artist)
colorbar.ax.ticklabel_format()

fig.show()

for artist in colorbar.ax.yaxis.get_children():
    if isinstance(artist, Text) and artist.get_text():
        exp = artist.get_text().split('e')[1].replace('+', '')
        colorbar.ax.set_ylabel(rf'Parameter [U${{\times}}10^{{{exp}}}$]')
        artist.set_visible(False)

fig.show()

【问题讨论】:

  • 您可以通过get_offset_text()访问偏移文本
  • 另见Adjust exponent text after setting scientific limits on matplotlib axis。有plt.tight_layout()建议强制填写offset_text。此后,您将获得所需的艺术家为offset_text = colorbar.ax.yaxis.get_offset_text()
  • 太好了,这基本上就是我想要的。尽管这些事情的记录如此糟糕,但真是太可惜了。你怎么知道ticklabel_format生成的额外文本被传递给offset_text?无论如何..如果您使用constrained_layout=True,您对如何执行此操作有任何想法吗?
  • 您也可以在绘图前将数据除以 10^6 或 10^7,然后不必担心偏移文本,只需将该信息添加到标签中即可。
  • 谢谢@PaulBrodersen,我也想过,但不幸的是,我不知道数量级,而且很难确定,因为对于colorbar,它直接依赖于图像的clim

标签: python matplotlib


【解决方案1】:

在触发平局之前,您无法获得任何刻度值,因为刻度是延迟评估的。因此,如果您需要来自定位器和格式化程序的信息,您必须致电fig.canvas.draw()。上面关于tight_layout 的所有内容都是红鲱鱼,因为它都调用fig.canvas.draw()

至于您的实际请求,这仍然调用fig.canvas.draw,但这只是为了方便获取格式化程序使用的指数。你可以很容易地从 vlim 值中得到它。否则,这只是将偏移文本设置为空白,而不是制作科学记数法标签。

import numpy as np
import matplotlib
matplotlib.use('qt5agg')
import matplotlib.pyplot as plt
from matplotlib.text import Text
import matplotlib.ticker as mticker

class NoOffsetFormatter(mticker.ScalarFormatter):
    def get_offset(self):
        return ''

formatter = NoOffsetFormatter()

image = np.random.uniform(10000000, 100000000, (100, 100))

fig, ax = plt.subplots()
image_artist = ax.imshow(image)
colorbar = fig.colorbar(image_artist)
colorbar.ax.yaxis.set_major_formatter(formatter)
fig.canvas.draw()
exp = formatter.orderOfMagnitude
colorbar.ax.set_ylabel(rf'Parameter [U${{\times}}10^{{{exp}}}$]')
plt.show()

【讨论】:

  • 谢谢,您所说的“您可以从 vlim 值中轻松获得它”是什么意思?我的意思是,最后,这就是我想要的。所以你是说有一种方法可以得到数量级而不用画图?绘制图形可能非常昂贵,我想尽可能避免它,另外因为我使用的是嵌入在 PyQt UI 中的画布,并且我不会在画布所在的位置“更新”画布还没准备好被看到。
  • 当然,您可以手动调用定位器或复制计算数量级的代码。
  • 对不起,定位器是什么?我已经多次遇到这个术语,但我一直不知道它是什么。
  • 谢谢!我知道这离题了,但这些定位器与 these guys 不同,是吗?
【解决方案2】:

好的,所以我遵循了 @JohanC 在 cmets 中指出的线索,您可以使用 fig.tight_layout() 来“欺骗”图形设置 offset_text 艺术家的文本,而无需绘制图形。 offset_text 艺术家正被ax.ticklabel_format() 方法用于显示数量级(同样,正如@JohanC 在 cmets 中指出的那样)。这个技巧在this post 中有解释,这与我的类似,对于大多数情况来说似乎是一个足够公平的解决方案。但是,如果您不想使用tight_layout,或者更糟糕的是,您正在使用不兼容的constrained_layout 来代替(比如我自己)怎么办?

总结:

所以我在tight_layout的踪迹之后,对matplotlib源代码进行了大量挖掘,幸运的是,我成功了。总之,这个问题的通用解决方案是调用ax.get_tightbbox(renderer),其中renderer是图形的渲染器。它也应该更便宜。下面的 MWE 表明即使使用 constrained_layout 也能正常工作:

import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.tight_layout import get_renderer
from matplotlib.backends.backend_qt5agg import \
    FigureCanvasQTAgg as FigureCanvas
# from matplotlib.transforms import Bbox
# from mpl_toolkits.axes_grid1 import make_axes_locatable

from PyQt5.QtWidgets import QDialog, QApplication, QGridLayout


class MainWindow(QDialog):
    def __init__(self):
        super().__init__()
        fig, ax = plt.subplots(constrained_layout=True)
        canvas = FigureCanvas(fig)
        lay = QGridLayout(self)
        lay.addWidget(canvas)
        self.setLayout(lay)

        image = np.random.uniform(10000000, 100000000, (100, 100))
        image_artist = ax.imshow(image)
        colorbar = fig.colorbar(image_artist)
        colorbar.ax.ticklabel_format()
        renderer = get_renderer(fig)
        colorbar.ax.get_tightbbox(renderer)
        colorbar.ax.yaxis.offsetText.set_visible(False)
        offset_text = colorbar.ax.yaxis.get_offset_text()
        exp = offset_text.get_text().split('e')[1].replace('+', '')
        colorbar.ax.set_ylabel(rf'Parameter [U${{\times}}10^{{{exp}}}$]')

        canvas.draw_idle()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    GUI = MainWindow()
    GUI.show()
    sys.exit(app.exec_())

分步说明:

这是我所做的:

  • 我看了tight_layoutsource code。通过消除,我意识到这个技巧起作用的重要一点是以下语句,

    kwargs = get_tight_layout_figure(
          self, self.axes, subplotspec_list, renderer,
          pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
    

    这很好,因为我还意识到“基本上”使tight_layoutconstrained_layout 不兼容的陈述是对subplots_adjust(**kwargs) 的调用。

  • 然后我看了get_tight_layout_figuresource code。为了使这对colorbar 产生任何影响,您需要使用解决方法,因为默认情况下,colorbar 是通过基本的Axes 实例添加的,而不是通过AxesSubplot 实例添加的。这是一个重要的区别,因为get_tight_layout_figure 需要subplotspec_list,而get_subplotspec_list 又是由get_subplotspec_list 生成的。后者在colorbar.ax 的情况下返回None,因为虽然AxesSubplot 实例带有locator,但常规Axes 实例没有。 locator 是在 get_subplotspec_list 中用于返回 subplotspec 的内容。解决方法是使用底部here 中描述的方法,通过使颜色条轴可定位:
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    
    arr = np.arange(100).reshape((10, 10))
    fig = plt.figure(figsize=(4, 4))
    im = plt.imshow(arr, interpolation="none")
    
    divider = make_axes_locatable(plt.gca())
    cax = divider.append_axes("right", "5%", pad="3%")
    plt.colorbar(im, cax=cax)
    
    plt.tight_layout()
    
  • 有了这个,我可以在我的colorbar.ax 上运行get_tight_layout_figure
    from matplotlib.tight_layout import get_renderer, get_tight_layout_figure
    renderer = get_renderer(fig)
    gridspec = colorbar.ax.get_axes_locator().get_subplotspec()
    get_tight_layout_figure(fig, [colorbar.ax], [gridspec], renderer)
    
  • 再次通过消除,我意识到get_tight_layout_figure 中的重要语句是这个技巧起作用的语句:

    kwargs = auto_adjust_subplotpars(fig, renderer,
                                   nrows_ncols=(max_nrows, max_ncols),
                                   num1num2_list=num1num2_list,
                                   subplot_list=subplot_list,
                                   ax_bbox_list=ax_bbox_list,
                                   pad=pad, h_pad=h_pad, w_pad=w_pad)
    

    这又让事情变得容易多了,因为对于这个函数,您只需要figrenderer,以及nrows_ncolsnum1num2_listsubplot_list。幸运的是,后三个参数很容易获得/模拟,其中nrows_ncolsnum1num2_list 是数字列表,在这个简单的情况下分别为(1, 1)[(0, 0)],而subplot_list 仅包含colorbar.ax。更重要的是,上面介绍的解决方法实际上不适用于constrained_laout,因为可以切断部分颜色条轴(特别是所有这一切所涉及的标签):

  • 那么,你猜对了,我查看了auto_adjust_subplotparssource code。再一次,通过排除,我这次很快找到了相关的代码行:

    tight_bbox_raw = union([ax.get_tightbbox(renderer) for ax in subplots
                            if ax.get_visible()])
    

    这里的重要部分当然是ax.get_tightbbox(renderer),你可以从我的解决方案中看出这一点。这是我所能追踪到的,尽管我相信它应该可以走得更远一点。实际上,要找到get_tightbbox-方法的相关酸代码并不是那么容易,因为即使代码表明正在调用的是Axes.get_tightbbox,至少也可以在docs中找到(尽管没有指向源代码的链接),实际上使用的是Artist.get_tightbbox,其中,出于某种原因,有no documentation,但它确实存在于source code .我提取了它并制作了我自己的“分离”版本,看看我是否可以更深入:

    from matplotlib.transforms import Bbox
    
    def get_tightbbox(artist, renderer):
        """
        Like `Artist.get_window_extent`, but includes any clipping.
    
        Parameters
        ----------
        renderer : `.RendererBase` instance
            renderer that will be used to draw the figures (i.e.
            ``fig.canvas.get_renderer()``)
    
        Returns
        -------
        bbox : `.BBox`
            The enclosing bounding box (in figure pixel co-ordinates).
        """
        bbox = artist.get_window_extent(renderer)
        if artist.get_clip_on():
            clip_box = artist.get_clip_box()
            if clip_box is not None:
                bbox = Bbox.intersection(bbox, clip_box)
            clip_path = artist.get_clip_path()
            if clip_path is not None and bbox is not None:
                clip_path = clip_path.get_fully_transformed_path()
                bbox = Bbox.intersection(bbox, clip_path.get_extents())
    
        return bbox
    

    但是在这里,发生了一件非常奇怪的事情,我无法解释,最终阻止了我进一步调查:

运行get_tightbbox(colorbar.ax, renderer) 与运行colorbar.ax.get_tightbbox(renderer)相同!

我不知道为什么。运行get_tightbbox(colorbar.ax, renderer)get_tightbbox 只执行一次(如您所料),但运行colorbar.ax.get_tightbbox(renderer) 时,它会运行多次,用于colorbar.ax 的一组(但不是全部)子级。我试图模拟它,但循环遍历孩子并为每个孩子单独运行get_tightbbox(特别是我在offset_text 艺术家上测试了这个,当然),但它没有相同的效果。它工作。所以现在,colorbar.ax.get_tightbbox(renderer) 是最好的选择。

【讨论】:

  • 使用紧凑布局确实使用 fig.canvas.draw 绘制图形。这就是它知道一切将有多大的方式。你自己也可以这样称呼。也就是说,您可以通过编写一个不将指数传递给偏移文本的新格式化程序来轻松获得所需的内容。
  • 哦,不,这一切都是徒劳的 :( 但感谢您告诉我!
猜你喜欢
  • 2020-06-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多