Two different methods can be used, depending on whether you want to keep the factor (order of magnitude) or not
One way could be to define your own formatter function
Own formatter function
def myform(x, pos):
e=0
while x>1e3 or x<-1e3:
x/=1000
e+=3
while x>-1 and x<1 and x!=0:
x*=1000
e-=3
if e==0: return f'${x:.1f}$'
return f'${x:.0f}.10^{{{e}}}$'
ax.xaxis.set_major_formatter(myform)
Advantage of this is that you have a total control on how the label is formatted.
Drawback is that you can't have the order of magnitude factor (at least I don't know how to combine it, other that with the second method, but if you use the second method, then, there is no point in still using it). Also, you need to reinvent the wheel (to decide how to display a number)
The way I compute the exponent is of course naive. You may want to replace that with some log10, or the like. But I wanted to make it explicit. Plus, as naive as is it, the computation cost is still negligible before anything related to plotting anyway (it even with very large of very small numbers, it is still just a handful multiplication)
Define your own variant of scalar formatter
Other method is to still let the control of all that to ScalarFormatter (this is what you are doing now. ScalarFormatter is the default one in your case). But subsume the way it decides the order of magnitude factor
# Just a subclass of ScalarFormatter that does almost nothing different
class MyForm(ticker.ScalarFormatter):
def __init__(self, *arg, **kw):
ticker.ScalarFormatter.__init__(self, *arg, **kw)
# I "hardcode" in it the two options you set with `style=sci` and `scilimits`
self.set_scientific(True)
self.set_powerlimits((0,0))
# And the only thing that this scalarformatter does differently as the parent one is the way it chooses the order of magnitude
def _set_order_of_magnitude(self):
# Rather than rewriting one, I rely on the original one
super()._set_order_of_magnitude()
# But after it has done its computation (leading to -4 in your example)
# I force it to choose one that is multiple of 3, my removing the remainder
self.orderOfMagnitude -= self.orderOfMagnitude%3
ax.yaxis.set_major_formatter(MyForm())
Advantage is that you don't reinvent the wheel. That is the same scalar formatter you were using. Drawback is the one of not reinventing the wheel generally speaking: you are stuck with the choices made by those who invented it (for example, see how in the first version, I used latex to have nice power notation).
Result
Following picture demonstrates it on your example, using the first one on x-axis and second one on yaxis

Whole code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
def myform(x, pos):
e=0
while x>1e3 or x<-1e3:
x/=1000
e+=3
while x>-1 and x<1 and x!=0:
x*=1000
e-=3
if e==0: return f'${x:.1f}$'
return f'${x:.0f}.10^{{{e}}}$'
class MyForm(ticker.ScalarFormatter):
def __init__(self, *args, **kw):
ticker.ScalarFormatter.__init__(self, *args, **kw)
self.set_scientific(True)
self.set_powerlimits((0,0))
#
def _set_order_of_magnitude(self):
super()._set_order_of_magnitude()
self.orderOfMagnitude -= self.orderOfMagnitude%3
T = 30e-6
N = 10
time = np.linspace(0, N*T, num=N)
plt.plot(time,np.sin(time))
ax = plt.gca()
ax.xaxis.set_major_formatter(myform)
ax.yaxis.set_major_formatter(MyForm())
plt.show()
(ok, the choice of myform for the function name and MyForm for the class name is not very good. But anyway, you are not expected to use both. And I am sure you'll come up with better names)
ax.ticklabel_format(style='sci',axis='both',scilimits=(-3,-3))for 10^(-3), and likewisescilimits=(-6,-6)for 10^(-6). Unfortunately you can only set the limits for the exponents to one fixed value (as I did here) or provide a lower and upper bound, but there is no direct way to explicitly provide a list of allowed exponents.