如何将y轴比例尺移动到紧邻y轴标签的位置?

8

我有一些数据已经绘制出来,我强制将其转换为科学计数法的10次幂(而不是指数)。这是代码片段:

import matplotlib.ticker as mticker

formatter = mticker.ScalarFormatter(useMathText=True)
formatter.set_powerlimits((-3,2))
ax.yaxis.set_major_formatter(formatter)

然而,在图表的左上角出现了x10^-4的比例因子。

是否有一种简单的方法可以强制将这个比例因子的位置与y标签相邻,就像我在下面的图示中所示的那样?

enter image description here

2个回答

14

您可以将偏移量设置为不可见,这样它就不会出现在原来的位置。

ax.yaxis.offsetText.set_visible(False)
你可以从格式化程序中获取偏移量,然后更新标签。
offset = ax.yaxis.get_major_formatter().get_offset()
ax.yaxis.set_label_text("original label" + " " + offset)

通过使用一个具有回调函数的类来自动完成此操作,使得如果偏移量发生变化,它将在标签中更新。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker

class Labeloffset():
    def __init__(self,  ax, label="", axis="y"):
        self.axis = {"y":ax.yaxis, "x":ax.xaxis}[axis]
        self.label=label
        ax.callbacks.connect(axis+'lim_changed', self.update)
        ax.figure.canvas.draw()
        self.update(None)

    def update(self, lim):
        fmt = self.axis.get_major_formatter()
        self.axis.offsetText.set_visible(False)
        self.axis.set_label_text(self.label + " "+ fmt.get_offset() )


x = np.arange(5)
y = np.exp(x)*1e-6

fig, ax = plt.subplots()
ax.plot(x,y, marker="d")

formatter = mticker.ScalarFormatter(useMathText=True)
formatter.set_powerlimits((-3,2))
ax.yaxis.set_major_formatter(formatter)

lo = Labeloffset(ax, label="my label", axis="y")


plt.show()

输入图像描述


2
你如何避免在Labeloffset内调用draw,而是等待全局的draw命令? - komodovaran_
2
@komodovaran_ 我并不回避绘制图形,相反我是有意绘制它以便在展示之前能够应用标签更改。 - ImportanceOfBeingErnest
2
在我的情况下,当使用 ax.yaxis.get_major_formatter().get_offset() 时,我得到了一个空字符串。这里可能是什么问题? - Red Sparrow
1
对我来说,即使分两步进行,它也无法工作。这是一个错误吗? - Filipe
2
我认为空字符串是因为matplotlib尚未计算轴标签等。此答案中发布的“Labeloffset”类包括对ax.figure.canvas.draw()的调用,这必须导致matplotlib生成偏移标签。除非我首先调用ax.figure.canvas.draw()(或在我的情况下为plt.gcf().canvas.draw()),否则我将从get_offset()获得空字符串,在这种情况下,get_offset()的行为符合预期。 - Sam Van Kooten
显示剩余8条评论

0

GUI的最小示例,其中draw()每次绘图刷新时只应调用一次。保持正确的比例因子。也可以与锁定指数一起使用,例如像here

作为示例,我刚刚添加了一个简单的按钮来刷新绘图,但它也可以是任何其他事件。

from PyQt5.Qt import *
from PyQt5 import QtWidgets, QtCore # sorry about the Qt Creator mess
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.ticker import ScalarFormatter
import numpy as np


class WidgetPlot(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setLayout(QVBoxLayout())
        self.canvas = PlotCanvas(self)
        self.layout().addWidget(self.canvas)

class PlotCanvas(FigureCanvas):
    def __init__(self, parent = None, width = 5, height = 5, dpi = 100):
        self.fig = Figure(figsize = (width, height), dpi = dpi, tight_layout = True)
        self.ax = self.fig.add_subplot(111)

        FigureCanvas.__init__(self, self.fig)

    def majorFormatterInLabel(self, ax, axis, axis_label, major_formatter):
        if axis == "x":
            axis = ax.xaxis
        if axis == "y":
            axis = ax.yaxis

        axis.set_major_formatter(major_formatter)
        axis.offsetText.set_visible(False)
        exponent = axis.get_offset_text().get_text()
        axis.set_label_text(axis_label + " (" + exponent + ")")


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(674, 371)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.gridLayoutWidget.setGeometry(QtCore.QRect(50, 10, 601, 281))
        self.gridLayoutWidget.setObjectName("gridLayoutWidget")
        self.mpl_layoutBox = QtWidgets.QGridLayout(self.gridLayoutWidget)
        self.mpl_layoutBox.setContentsMargins(0, 0, 0, 0)
        self.mpl_layoutBox.setObjectName("mpl_layoutBox")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(280, 300, 113, 32))
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)

        self.w = WidgetPlot()
        self.canvas = self.w.canvas
        self.mpl_layoutBox.addWidget(self.w)

        self.pushButton.clicked.connect(self.refresh)



    def refresh(self):
        self.canvas.ax.clear()

        # Could've made it more beautiful. In any case, the cost of doing this is sub-ms.
        formatter = ScalarFormatter()
        formatter.set_powerlimits((-1, 1))
        self.canvas.majorFormatterInLabel(self.canvas.ax, "y", "label", major_formatter = formatter)

        r = np.random.choice((1e3, 1e5, 1e7, 1e9, 1e100))
        self.canvas.ax.plot(r)
        self.canvas.draw()


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    TraceWindow = QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(TraceWindow)
    TraceWindow.show()
    sys.exit(app.exec_())

这需要手动刷新(通过按按钮),对吗?在这种情况下,这显然是一个有效的解决方案,但可能不太人性化?我认为你也可以在这里简单地使用我的解决方案,因此这个答案可以从说明与现有答案不同的动机中受益。 - ImportanceOfBeingErnest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接