Skip to content

[MNT]: Implement Scale.val_in_range and refactor _point_in_data_domain#31306

Merged
QuLogic merged 13 commits intomatplotlib:mainfrom
null-dreams:mnt
Mar 20, 2026
Merged

[MNT]: Implement Scale.val_in_range and refactor _point_in_data_domain#31306
QuLogic merged 13 commits intomatplotlib:mainfrom
null-dreams:mnt

Conversation

@null-dreams
Copy link
Contributor

@null-dreams null-dreams commented Mar 14, 2026

Closes #31286

PR summary

Following the suggestion by @timhoffm in #31061, this PR introduces a val_in_range method to the Scale hierarchy.

  • Why is this change necessary?
    The current implementation of _point_in_data_domain relies on a "clipping side-effect." It calls limit_range_for_scale, which is intended for setting axis limits, and then compares if the output value differs from the input value to determine validity. This approach is semantically indirect and makes the logic harder to maintain.

  • What problem does it solve?

    1. API Cleanliness: It decouples the logical question "is this value valid for this scale?" from the action of clipping a value to a supported range.
    2. Code Clarity: It removes the need for Axes logic to handle scale-specific details like minpos or equality checks against clipped values.
  • What is the reasoning for this implementation?

    • Fallback Support: ScaleBase implements val_in_range using the existing limit_range_for_scale logic to ensure third-party or custom scales continue to work without modification.
    • Vectorization: Although the original issue focused on the API shift, I identified a small gap in possible performance. I designed the new val_in_range implementations to be vectorized using NumPy.
      • In common scales like LogScale and LogitScale, this replaces iterative logic/clipping with direct boolean math (e.g., val > 0).
      • This allows for high-performance domain checking on large arrays, whereas the previous approach was primarily scalar-focused.
        Update: Vectorization has been removed after review since it was not deemed necessary for the current use case and added unneeded complexity without clear benefit.

Example

import numpy as np
from matplotlib.scale import LogScale

s = LogScale(axis=None)
data = np.array([-1, 0, 1, 2])

# New API
for val in data:
    print(s.val_in_range(val), end=" ")
# Expected: False False True True

AI Disclosure

I used an AI assistant to understand the codebase structure only.

PR checklist

Introduce a val_in_range method in ScaleBase to explicitly check whether
values lie within the valid domain of a scale. The default
implementation falls back to limit_range_for_scale for compatibility
with existing scales.

Specific scales (e.g., LogScale, LogitScale) override this method with
more efficient checks.
@github-actions
Copy link

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide.
Please let us know if (and how) you use AI, it will help us give you better feedback on your PR.

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@null-dreams
Copy link
Contributor Author

I would like some advice regarding implementing tests for the added function val_in_range and formatting the docstrings and documentations. Thank you.

@null-dreams null-dreams changed the title [MNT]: Implement Scale.val_in_range and refactor _point_in_data_domain [MNT]: Implement Scale.val_in_range and refactor _point_in_data_domain Mar 14, 2026
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your type usage is mixed up. In _pint_in_data_domain we just need a bool. You implementations return arrays. Your type stub claims bool | array return type. Since we only need single values, let's keep it simple and only implement that. We can always expand later.

Concerning tests: Write tests for all implemented subclasses. Consider edge cases like Inf and NaN

"""
return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())

def val_in_range(self, val):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation to expose this on the Axis?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed limit_range_in_scale being available in Axis class, so exposing the val_in_range seemed consistent to me. But I later forgot to implement the wrapper in _point_in_data_domain.

I have two possible approaches here:

  1. Keep the wrapper exposed in Axis and use it in the _point_in_data_domain.
  2. Remove the wrapper from Axis and keep the current _point_in_data_domain, using the _scale attribute, unchanged.

After going through other exposed apis, I feel removing the wrapper here would be fine as currently there is no other use of val_in_range except in _point_in_data_domain.

Comment on lines +119 to +120
Return whether the value(s) are within the valid range for this scale.
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Return whether the value(s) are within the valid range for this scale.
"""
Return whether the value(s) are within the valid range for this scale.
This method is a generic implementation. Subclasses may implement
more efficent solutions for their domain.
"""

Comment on lines +211 to +213
"""
Return `True` for all values.
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring should always descripe the concept, not just the imlementation. A docstring

def some_func():
    """Return True."""
    return True

has zero information conent.

Instead do:

Suggested change
"""
Return `True` for all values.
"""
"""
Return whether the value(s) are within the valid range for this scale.
This is True for all inputs.
"""

The first sentence should be in all val_in_range docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually pretty enlightening. Kind of changed the way I thought about docstrings.
I'm making the needed changes. Thank you very much for the suggestion.

@null-dreams
Copy link
Contributor Author

I've made the changes which I think should fix the type ambiguity I caused earlier. I will need a little time to implement the tests as I have never written tests before and will need to study the current tests before I can implement mine and fix all the linting error with it as well.

@null-dreams
Copy link
Contributor Author

While working on this I noticed a couple of behaviors that I wanted to clarify.

For LinearScale, calling limit_range_for_scale with NaN values returns NaN unchanged:

Example code:

from matplotlib.scale import LinearScale

s = LinearScale(axis=None)

vmin, vmax = s.limit_range_for_scale(float('nan'), float('nan'), minpos=1e-100)
print(vmin, vmax)

which results in nan nan. This seems to indicate that limit_range_for_scale does not attempt to reject or normalize NaN for the linear scale. Is this intentional?


Similarly for LogScale, limit_range_for_scale allows +inf to pass through unchanged. As far as I know, the logarithm is defined for 0 < x < +inf, but +inf itself is not part of the domain. I just needed a small clarification on whether +inf will be allowed in val_in_range or not.

@timhoffm
Copy link
Member

This is a difficult question. Without having looked into this, I suspect, one of the two is correct:

  • it's unintentional, but does not matter for the current use cases limit_range_for_scale
  • it's intentional, because we want non-finite values to pass through for the current use cases

Which one it is needs to be found out by looking at the current usage contexts. In either case, the behavior should be specified.

@melissawm melissawm moved this to Waiting for author in First Time Contributors Mar 16, 2026
@timhoffm
Copy link
Member

Thinking about it, the primary use for val_in_range is to determine whether a value is "compatible" with the scale, e.g. whether i can draw it (under the boundary condition of properly chosen limits.
In that sense, NaN and Inf values are not compatible. I therefore propose to return False for these cases. For example LinearScale would return math.isfinite(val).

@null-dreams
Copy link
Contributor Author

I have added the finite bound check for the ScaleBase as well as LinearScale, LogScale and LogitScale and also edited the tests to match the new requirements.

@melissawm melissawm moved this from Waiting for author to Needs review in First Time Contributors Mar 18, 2026
@null-dreams null-dreams marked this pull request as ready for review March 19, 2026 03:53
Comment on lines +434 to +437
if not math.isfinite(val):
return False
else:
return val > 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logic, but more compact.

Suggested change
if not math.isfinite(val):
return False
else:
return val > 0
return math.isfinite(val) and val > 0

@melissawm melissawm moved this from Needs review to Waiting for author in First Time Contributors Mar 20, 2026
@QuLogic QuLogic added this to the v3.11.0 milestone Mar 20, 2026
@QuLogic QuLogic merged commit 7c94eb0 into matplotlib:main Mar 20, 2026
41 checks passed
@github-project-automation github-project-automation bot moved this from Waiting for author to Merged in First Time Contributors Mar 20, 2026
@QuLogic
Copy link
Member

QuLogic commented Mar 20, 2026

Thanks @null-dreams! Congratulations on your first PR to Matplotlib 🎉 We hope to hear from you again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

[MNT]: Scale val_in_range method

4 participants