Monday, 16 October 2023

matplotlib set_major_formatter taking into account range of values

I've written a UI that includes a matplotlib figure. It shows graphs and I'd like to use engineering notation (exponents that are multiples of 3) on the axes. Note that I want the powers to be written out (e.g. "123×10⁶", not "123 M", hence not using EngFormatter).

I can do this with a formatting function (format_eng) I wrote many years ago and the set_major_formatter & fmt_x_data features of matplotlib:

def axes_eng_format(ax, x=True, y=True, **kwargs):
    if x:
        if 'use_si' not in kwargs and 'use_latex' not in kwargs:
            ax.xaxis.set_major_formatter(lambda v, pos: format_eng(v, use_latex=True, **kwargs))
        else:
            ax.xaxis.set_major_formatter(lambda v, pos: format_eng(v, **kwargs))
        ax.fmt_xdata = lambda v: format_eng(v, **kwargs)
    if y:
        if 'use_si' not in kwargs and 'use_latex' not in kwargs:
            ax.yaxis.set_major_formatter(lambda v, pos: format_eng(v, use_latex=True, **kwargs))
        else:
            ax.yaxis.set_major_formatter(lambda v, pos: format_eng(v, **kwargs))
        ax.fmt_ydata = lambda v: format_eng(v, **kwargs)

axes_eng_format(ax)

The formatting function itself shouldn't be relevant to this question (as it is just a dumb formatter that only takes a single parameter and hence cannot consider the range of values), however for reference it is here: https://gist.github.com/abudden/3252252632bd8271c49c886771d8f82d

The formatters only get two parameters: the value (v) and the position on the graph (pos). Using just the value v is okay and works, but the 0.5 is rendered as 500×10⁻³. If the range of values on the major axis is (say) 0 to 700×10⁻³, then that's exactly how I'd like it to be rendered, but if the range of values is (like the second image below) 0 to 3.5, I'd rather it were rendered as 0.5 for consistency with the other values on the axis.

The only way I can think of doing this at the moment is to post-process all the tick values, but that doesn't seem like it would be very robust given the user can zoom in and out etc.

Is there a better way of achieving the effect I'm after? I'm guessing I can do something with pos, but I haven't been able to get my head round what would work. How should I go about using pos to tweak the formatting of a number (or is there a better way)?

Y-axis looks good:

enter image description here

Y axis looks not-so-good:

enter image description here



from matplotlib set_major_formatter taking into account range of values

No comments:

Post a Comment