Skip to content

[ENH]: ticker.EngFormatter: allow offset #28463

Closed

Description

@doronbehar

Problem

I want to be able to generate this plot:

Screenshot from 2024-06-26 01-11-31

I managed to create this plot using the following code:

from itertools import count
from math import copysign

# https://pypi.org/project/prefixed/
from prefixed import Float

from matplotlib.ticker import ScalarFormatter

class FrequencyFormatter(ScalarFormatter):
    def format_data(self, value):
        # Add the Hz suffix
        return '{:H}Hz'.format(Float(value))
    def _set_order_of_magnitude(self):
        super()._set_order_of_magnitude()
        c = abs(self.orderOfMagnitude)
        # Search 0, -3, 3, -6, 6, -9, 9 etc until the best scientifically
        # prefixed oom is found, and then set self.orderOfMagnitude to it
        for sciOom in count(3,3):
            if c < sciOom:
                self.orderOfMagnitude = copysign(sciOom, self.orderOfMagnitude)
                break

# fig,ax...

ax.yaxis.set_major_formatter(FrequencyFormatter(
    # I'm not necessarily interested in this, but without this,
    # ScalarFormatter.get_offset doesn't use my own .format_data function to
    # format also the offset.
    useMathText=True
))

It is not ideal because it relies both upon modifying (the private) _set_order_of_magnitude method and the external library prefixed, which implements something already implemented in the EngFormatter.

I'm surprised that EngFormatter doesn't handle offsets so smartly like ScalarFormatter...

Where do you think this kind of enhancement would fit most? In EngFormatter with an additional __init__ argument? Or in a new formatter?

Proposed solution

Sort of proposed above...

Activity

  1. tacaswell commented on Jun 26, 2024

    @tacaswell
    Member

    I think mirroring the ScalarFormatter offset functionality to EngFormatter is a reasonable thing to do.

    I am pretty 👎🏻 on picking up a dependency though.

  2. doronbehar commented on Jun 26, 2024

    @doronbehar
    ContributorAuthor

    I am pretty 👎🏻 on picking up a dependency though.

    Yea sure, I was thinking about that the same, EngFormatter already includes most of the functionality of prefixed.Float, it's just that it wasn't accessible to me when I inherited functionality from ScalarFormatter, and I wanted a quick solution.

    I think mirroring the ScalarFormatter offset functionality to EngFormatter is a reasonable thing to do.

    OK I agree with that, in terms of the UX/UI of the library, I'd imagine this kind of usage:

    ax.yaxis.set_major_formatter(EngFormatter(
    	# Perhaps this should be the default value?
        useOffset=True
    ))

    The dilemma I'm facing though when I'm thinking of implementing this feature, is the fact that most of the code that computes constantly the data's orderOfMagnitude and the offset, is located now in "private" methods of ScalarFormatter, and EngFormatter's parent class is Formatter which doesn't include that functionality.

    Hence I'm not sure in to which class to put the functions that compute the order of magnitude and the offset. Perhaps I should make EngFormatter a child class of ScalarFormatter? what do you think?

  3. tacaswell commented on Jun 27, 2024

    @tacaswell
    Member

    Rather than go down the complexity of OO inheritance choices, I suggest we pull the re-usable code out into def _top_level_function(...): ... in the module and have each of the classes call them (passing in what ever state they need to pass in).

  4. doronbehar commented on Jul 1, 2024

    @doronbehar
    ContributorAuthor

    It was pretty hard to implement this without subclassing ScalarFormatter, because the decision what offset to use is heavily reliant upon many variables which I don't want to pass to external static functions. I hope you'll like the implementation at #28495 .

  5. 13 remaining items

  6. modified the milestones: v3.10.0, v3.11.0 on Oct 30, 2024
  7. QuLogic commented on Mar 19, 2026

    @QuLogic
    Member

    This was implemented in #28495.

  8. modified the milestones: v3.11.0, v3.10.0 on Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions