Есть ли способ вместо этого включить дополнительного художника, сгенерированного axes.ticklabel_format (), в метку оси? - PullRequest
1 голос
/ 19 июня 2020

Короче вместо версии слева хотелось бы версию справа. Есть ли способ сделать это без рисования кулака фигуры? Вы можете получить доступ к исполнителю раньше, но на этом этапе текст не установлен.

enter image description here enter image description here

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()

Ответы [ 2 ]

1 голос
/ 21 июня 2020

Вы не можете получить ни одно из значений тиков, пока не активируете розыгрыш, потому что тики оцениваются лениво. Поэтому, если вам нужна информация от локаторов и форматеров, вы должны позвонить по номеру fig.canvas.draw(). Все, что выше о tight_layout, является отвлекающим маневром, потому что все это вызывает fig.canvas.draw().

Что касается вашего фактического запроса, он по-прежнему вызывает fig.canvas.draw, но это только для удобства получения экспоненты, которую использует форматтер. Вы можете легко получить это самостоятельно из значений vlim. В противном случае это просто устанавливает пустой текст смещения, а не делает научную c нотацию.

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()
0 голосов
/ 19 июня 2020

Хорошо, поэтому я последовал подсказке, указанной @ Johan C в комментариях, что вы могли бы использовать fig.tight_layout(), чтобы «обмануть» фигуру и установить текст offset_text художника без необходимости рисовать фигура. Исполнитель offset_text используется методом ax.ticklabel_format() для отображения порядка величины (опять же, как указано @ Johan C в комментариях). Этот трюк объясняется в этом посте , который похож на мой и кажется достаточно справедливым решением для большинства случаев. Однако что, если вы не хотите использовать tight_layout или, что еще хуже, вместо этого вы используете несовместимый constrained_layout (например, я)?

Резюме:

Так я и сделал много копался в исходном коде matplotlib после трассировки tight_layout, и, к счастью, мне это удалось. Короче говоря, универсальное решение этой проблемы - вызвать 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_layout исходный код . Путем исключения я понял, что важным моментом для работы этого трюка было следующее утверждение:

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

    , что здорово, потому что я также понял, что утверждение, которое «по существу» делает tight_layout несовместим с constrained_layout - это вызов subplots_adjust(**kwargs).

  • Затем я посмотрел на get_tight_layout_figure исходный код . Чтобы это повлияло на colorbar, вам нужно использовать обходной путь, поскольку по умолчанию colorbar добавляется через экземпляр basi c Axes, а не через экземпляр AxesSubplot . Это важное различие, потому что для get_tight_layout_figure требуется subplotspec_list, который, в свою очередь, генерируется как get_subplotspec_list. Последний возвращает None в случае colorbar.ax, потому что, хотя экземпляр AxesSubplot поставляется с locator, обычный Axes - нет. locator - это то, что используется в get_subplotspec_list для возврата subplotspec. Обходной путь состоит в том, чтобы использовать подход, описанный внизу здесь , сделав доступными оси цветовой шкалы:
    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()
    
  • С этим я смог запустить get_tight_layout_figure на моем colorbar.ax:
    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)
    

    Это снова упростило задачу, потому что для этой функции вам нужны только fig и renderer, а также nrows_ncols, num1num2_list и subplot_list. К счастью, последние три аргумента достаточно легко получить / смоделировать, где nrows_ncols и num1num2_list - это списки чисел, в этом простом случае (1, 1) и [(0, 0)] соответственно, а subplot_list содержит только colorbar.ax. Более того, описанный выше обходной путь на самом деле не работает с constrained_laout, поскольку часть осей шкалы палитры (в частности, метка, о которой идет речь) может быть отрезана:

    enter image description here

  • Итак, как вы уже догадались, я просмотрел исходный код auto_adjust_subplotpars . И снова, путем исключения, я на этот раз быстро нашел соответствующую строку кода:

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

    Важная часть здесь, конечно же, ax.get_tightbbox(renderer), как вы можете сказать по моему решению . Это насколько я мог его проследить, хотя я считаю, что go должно быть возможно даже немного дальше. На самом деле было не так просто найти соответствующий исходный код для метода get_tightbbox, потому что, хотя код предполагает, что вызывается Axes.get_tightbbox, который, по крайней мере, также можно найти в документах (хотя ссылки на исходный код нет), то, что на самом деле используется, это Artist.get_tightbbox, из которых по какой-то причине нет документации , однако он существует в исходном коде . Я извлек его и сделал свою собственную «отдельную» версию, чтобы посмотреть, могу ли я go еще глубже:

    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) - это путь к go.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...