Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2479,8 +2479,7 @@ def _point_in_data_domain(self, x, y):
(e.g. negative coordinates with a log scale).
"""
for val, axis in zip([x, y], self._axis_map.values()):
vmin, vmax = axis.limit_range_for_scale(val, val)
if vmin != val or vmax != val:
if not axis._scale.val_in_range(val):
return False
return True

Expand Down
57 changes: 57 additions & 0 deletions lib/matplotlib/scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
""" # noqa: E501

import inspect
import math
import textwrap
from functools import wraps

Expand Down Expand Up @@ -114,6 +115,22 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
"""
return vmin, vmax

def val_in_range(self, val):
"""
Return whether the value(s) are within the valid range for this scale.

This method is a generic implementation. Subclasses may implement more
efficient solutions for their domain.
"""
try:
if not math.isfinite(val):
return False
else:
vmin, vmax = self.limit_range_for_scale(val, val, minpos=1e-300)
return vmin == val and vmax == val
except (TypeError, ValueError):
return False


def _make_axis_parameter_optional(init_func):
"""
Expand Down Expand Up @@ -196,6 +213,14 @@ def get_transform(self):
"""
return IdentityTransform()

def val_in_range(self, val):
"""
Return whether the value is within the valid range for this scale.

This is True for all values, except +-inf and NaN.
"""
Comment on lines +217 to +221
Copy link
Copy Markdown
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
Copy Markdown
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.

return math.isfinite(val)


class FuncTransform(Transform):
"""
Expand Down Expand Up @@ -400,6 +425,14 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
return (minpos if vmin <= 0 else vmin,
minpos if vmax <= 0 else vmax)

def val_in_range(self, val):
"""
Return whether the value is within the valid range for this scale.

This is True for value(s) > 0 except +inf and NaN.
"""
return math.isfinite(val) and val > 0


class FuncScaleLog(LogScale):
"""
Expand Down Expand Up @@ -581,6 +614,14 @@ def get_transform(self):
"""Return the `.SymmetricalLogTransform` associated with this scale."""
return self._transform

def val_in_range(self, val):
"""
Return whether the value is within the valid range for this scale.

This is True for all values, except +-inf and NaN.
"""
return math.isfinite(val)


class AsinhTransform(Transform):
"""Inverse hyperbolic-sine transformation used by `.AsinhScale`"""
Expand Down Expand Up @@ -707,6 +748,14 @@ def set_default_locators_and_formatters(self, axis):
else:
axis.set_major_formatter('{x:.3g}')

def val_in_range(self, val):
"""
Return whether the value is within the valid range for this scale.

This is True for all values, except +-inf and NaN.
"""
return math.isfinite(val)


class LogitTransform(Transform):
input_dims = output_dims = 1
Expand Down Expand Up @@ -820,6 +869,14 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
return (minpos if vmin <= 0 else vmin,
1 - minpos if vmax >= 1 else vmax)

def val_in_range(self, val):
"""
Return whether the value is within the valid range for this scale.

This is True for value(s) which are between 0 and 1 (excluded).
"""
return 0 < val < 1


_scale_mapping = {
'linear': LinearScale,
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/scale.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ScaleBase:
def limit_range_for_scale(
self, vmin: float, vmax: float, minpos: float
) -> tuple[float, float]: ...
def val_in_range(self, val: float) -> bool: ...

class LinearScale(ScaleBase):
name: str
Expand Down
62 changes: 62 additions & 0 deletions lib/matplotlib/tests/test_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,65 @@ def set_default_locators_and_formatters(self, axis):
# cleanup - there's no public unregister_scale()
del mscale._scale_mapping["custom"]
del mscale._scale_has_axis_parameter["custom"]


def test_val_in_range():

test_cases = [
# LinearScale: Always True (even for Inf/NaN)
('linear', 10.0, True),
('linear', -10.0, True),
('linear', 0.0, True),
('linear', np.inf, False),
('linear', np.nan, False),

# LogScale: Only positive values (> 0)
('log', 1.0, True),
('log', 1e-300, True),
('log', 0.0, False),
('log', -1.0, False),
('log', np.inf, False),
('log', np.nan, False),

# LogitScale: Strictly between 0 and 1
('logit', 0.5, True),
('logit', 0.0, False),
('logit', 1.0, False),
('logit', -0.1, False),
('logit', 1.1, False),
('logit', np.inf, False),
('logit', np.nan, False),

# SymmetricalLogScale: Valid for all real numbers
# Uses ScaleBase fallback. NaN returns False since NaN != NaN
('symlog', 10.0, True),
('symlog', -10.0, True),
('symlog', 0.0, True),
('symlog', np.inf, False),
('symlog', np.nan, False),
]

for name, val, expected in test_cases:
scale_cls = mscale._scale_mapping[name]
s = scale_cls(axis=None)

result = s.val_in_range(val)
assert result is expected, (
f"Failed {name}.val_in_range({val})."
f"Expected {expected}, got {result}"
)


def test_val_in_range_base_fallback():
# Directly test the ScaleBase fallback for custom scales.
# ScaleBase.limit_range_for_scale returns values unchanged by default
s = mscale.ScaleBase(axis=None)

# Normal values should be True
assert s.val_in_range(1.0) is True
assert s.val_in_range(-5.5) is True

# NaN and Inf returns False since they cannot be drawn in a plot
assert s.val_in_range(np.nan) is False
assert s.val_in_range(np.inf) is False
assert s.val_in_range(-np.inf) is False
Loading