5
votes

I have some data plotted which I force to scientific notation to powers of 10 (instead of exponential). Heres a snippet of the code:

import matplotlib.ticker as mticker

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

However, the scale factor of x10^-4 appears on the top left hand corner of the graph.

Is there a simple method to force the position of this scale factor next to the y label as I have illustrated in the diagram below?

enter image description here

2

2 Answers

11
votes

You may set the offset to invisible, such that it does not appear in its original position.

ax.yaxis.offsetText.set_visible(False)

You may then get the offset from the formatter an update the label with it

offset = ax.yaxis.get_major_formatter().get_offset()
ax.yaxis.set_label_text("original label" + " " + offset)

such that it appears inside the label.

The following automates this using a class with a callback, such that if the offset changes, it will be updated in the label.

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

enter image description here

0
votes

Minimal example for a GUI, where draw() should only be called once per plot refresh. Keeps the correct scale factor. Can also be used with locked exponent, e.g. like here.

As an example I've just added a simple button to refresh the plot, but it could just as well be any other event.

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