5

I'm trying to plot a 3D figure in Matplotlib with the scale in the offset text. For this purpose, I've used ImportanceOfBeingErnest's custom axis' major formatter in order to have this scale in a latex format:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from mpl_toolkits.mplot3d import Axes3D  
from matplotlib import cm
import numpy as np

class OOMFormatter(matplotlib.ticker.ScalarFormatter):
    def __init__(self, order=0, fformat="%1.1f", offset=True, mathText=True):
        self.oom = order
        self.fformat = fformat
        matplotlib.ticker.ScalarFormatter.__init__(self,useOffset=offset,useMathText=mathText)
    def _set_order_of_magnitude(self):
        self.orderOfMagnitude = self.oom
    def _set_format(self, vmin=None, vmax=None):
        self.format = self.fformat
        if self._useMathText:
             self.format = r'$\mathdefault{%s}$' % self.format


x = np.linspace(0, 22, 23)
y = np.linspace(-10, 10, 21)
X, Y = np.meshgrid(x, y)
V = -(np.cos(X/10)*np.cos(Y/10))**2*1e-4

fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
surf = ax.plot_surface(X, Y, V, cmap=cm.jet,
                       linewidth=0, antialiased=False)
ax.zaxis.set_major_formatter(OOMFormatter(int('{:.2e}'.format(np.min(V)).split('e')[1]), mathText=True))
ax.zaxis.set_rotate_label(False) 
ax.set_xlabel(r'$x$ (cm)', size=20, labelpad=10)
ax.set_ylabel(r'$y$ (cm)', size=20, labelpad=10)
ax.set_zlabel(r'$A_z$', size=20, labelpad=10)

This results in the following figure: Figure 1: initial plot

Note that the scale (zaxis off-set text, x 10^{-4}) is rotationed 90 degrees. To solve this, I've tried to acess the element of the off-set text and set its rotation to 0:

ax.zaxis.get_offset_text().set_rotation(0)
ax.zaxis.get_offset_text().get_rotation()
>>> 0

Which was of no use, since the off-set text didn't rotate an inch. I've then tried to print the text object when running the plot function:

surf = ax.plot_surface(X, Y, V, cmap=cm.jet,
                       linewidth=0, antialiased=False)
.
.
.
print(ax.zaxis.get_offset_text())
>>>Text(1, 0, '')

This made me think that perhaps the off-set text wasn't stored inside this variable, but when I run the same command without calling the plot function it returns exactly what I expected it to return:

print(ax.zaxis.get_offset_text())
>>>Text(-0.1039369506424546, 0.050310729257045626, '$\\times\\mathdefault{10^{−4}}\\mathdefault{}$')

What am I doing wrong?

1
  • Is it supposed to use the class you have introduced? You can also use the power notation string obtained by ax.zaxis.get_offset_text() and notate it with text2d(). Before doing so, the existing notation needs to be hidden. Commented Jul 1, 2021 at 14:32

1 Answer 1

3
+50

I have to say, this is an excellent and intriguing question and I scratched my head a while over it…

You can access the offset text with ot = ax.zaxis.get_offset_text(). It is easy to hide it ot.set_visible(False), but for some unknown reason it does not work to rotate ot.set_rotation(90). I tried to print the text value with print(ot.get_text()), but this outputs nothing, unless the plot was already drawn. Only after the plot is drawn, it returns '$\\times\\mathdefault{10^{-4}}\\mathdefault{}$'. This tells me that this is likely the source of the problem. Whatever you apply to the offset text, it gets overwritten in a final step of the graph generation and this fails.

I came to the conclusion that the best approach is to hide the offset and annotate yourself the graph. You can do it programmatically using the following snippet:

ax.zaxis.get_offset_text().set_visible(False)
exponent = int('{:.2e}'.format(np.min(V)).split('e')[1])
ax.text(ax.get_xlim()[1]*1.1, ax.get_ylim()[1], ax.get_zlim()[1],
        '$\\times\\mathdefault{10^{%d}}\\mathdefault{}$' % exponent)

Result:

graph with custom offset text

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.