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

Skip to content

Commit 7c94eb0

Browse files
authored
Merge pull request #31306 from null-dreams/mnt
[MNT]: Implement `Scale.val_in_range` and refactor `_point_in_data_domain`
2 parents 1ab55c0 + 6b820a8 commit 7c94eb0

4 files changed

Lines changed: 121 additions & 2 deletions

File tree

lib/matplotlib/axes/_base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2479,8 +2479,7 @@ def _point_in_data_domain(self, x, y):
24792479
(e.g. negative coordinates with a log scale).
24802480
"""
24812481
for val, axis in zip([x, y], self._axis_map.values()):
2482-
vmin, vmax = axis.limit_range_for_scale(val, val)
2483-
if vmin != val or vmax != val:
2482+
if not axis._scale.val_in_range(val):
24842483
return False
24852484
return True
24862485

lib/matplotlib/scale.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
""" # noqa: E501
3131

3232
import inspect
33+
import math
3334
import textwrap
3435
from functools import wraps
3536

@@ -114,6 +115,22 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
114115
"""
115116
return vmin, vmax
116117

118+
def val_in_range(self, val):
119+
"""
120+
Return whether the value(s) are within the valid range for this scale.
121+
122+
This method is a generic implementation. Subclasses may implement more
123+
efficient solutions for their domain.
124+
"""
125+
try:
126+
if not math.isfinite(val):
127+
return False
128+
else:
129+
vmin, vmax = self.limit_range_for_scale(val, val, minpos=1e-300)
130+
return vmin == val and vmax == val
131+
except (TypeError, ValueError):
132+
return False
133+
117134

118135
def _make_axis_parameter_optional(init_func):
119136
"""
@@ -196,6 +213,14 @@ def get_transform(self):
196213
"""
197214
return IdentityTransform()
198215

216+
def val_in_range(self, val):
217+
"""
218+
Return whether the value is within the valid range for this scale.
219+
220+
This is True for all values, except +-inf and NaN.
221+
"""
222+
return math.isfinite(val)
223+
199224

200225
class FuncTransform(Transform):
201226
"""
@@ -400,6 +425,14 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
400425
return (minpos if vmin <= 0 else vmin,
401426
minpos if vmax <= 0 else vmax)
402427

428+
def val_in_range(self, val):
429+
"""
430+
Return whether the value is within the valid range for this scale.
431+
432+
This is True for value(s) > 0 except +inf and NaN.
433+
"""
434+
return math.isfinite(val) and val > 0
435+
403436

404437
class FuncScaleLog(LogScale):
405438
"""
@@ -581,6 +614,14 @@ def get_transform(self):
581614
"""Return the `.SymmetricalLogTransform` associated with this scale."""
582615
return self._transform
583616

617+
def val_in_range(self, val):
618+
"""
619+
Return whether the value is within the valid range for this scale.
620+
621+
This is True for all values, except +-inf and NaN.
622+
"""
623+
return math.isfinite(val)
624+
584625

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

751+
def val_in_range(self, val):
752+
"""
753+
Return whether the value is within the valid range for this scale.
754+
755+
This is True for all values, except +-inf and NaN.
756+
"""
757+
return math.isfinite(val)
758+
710759

711760
class LogitTransform(Transform):
712761
input_dims = output_dims = 1
@@ -820,6 +869,14 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
820869
return (minpos if vmin <= 0 else vmin,
821870
1 - minpos if vmax >= 1 else vmax)
822871

872+
def val_in_range(self, val):
873+
"""
874+
Return whether the value is within the valid range for this scale.
875+
876+
This is True for value(s) which are between 0 and 1 (excluded).
877+
"""
878+
return 0 < val < 1
879+
823880

824881
_scale_mapping = {
825882
'linear': LinearScale,

lib/matplotlib/scale.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ScaleBase:
1212
def limit_range_for_scale(
1313
self, vmin: float, vmax: float, minpos: float
1414
) -> tuple[float, float]: ...
15+
def val_in_range(self, val: float) -> bool: ...
1516

1617
class LinearScale(ScaleBase):
1718
name: str

lib/matplotlib/tests/test_scale.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,65 @@ def set_default_locators_and_formatters(self, axis):
371371
# cleanup - there's no public unregister_scale()
372372
del mscale._scale_mapping["custom"]
373373
del mscale._scale_has_axis_parameter["custom"]
374+
375+
376+
def test_val_in_range():
377+
378+
test_cases = [
379+
# LinearScale: Always True (even for Inf/NaN)
380+
('linear', 10.0, True),
381+
('linear', -10.0, True),
382+
('linear', 0.0, True),
383+
('linear', np.inf, False),
384+
('linear', np.nan, False),
385+
386+
# LogScale: Only positive values (> 0)
387+
('log', 1.0, True),
388+
('log', 1e-300, True),
389+
('log', 0.0, False),
390+
('log', -1.0, False),
391+
('log', np.inf, False),
392+
('log', np.nan, False),
393+
394+
# LogitScale: Strictly between 0 and 1
395+
('logit', 0.5, True),
396+
('logit', 0.0, False),
397+
('logit', 1.0, False),
398+
('logit', -0.1, False),
399+
('logit', 1.1, False),
400+
('logit', np.inf, False),
401+
('logit', np.nan, False),
402+
403+
# SymmetricalLogScale: Valid for all real numbers
404+
# Uses ScaleBase fallback. NaN returns False since NaN != NaN
405+
('symlog', 10.0, True),
406+
('symlog', -10.0, True),
407+
('symlog', 0.0, True),
408+
('symlog', np.inf, False),
409+
('symlog', np.nan, False),
410+
]
411+
412+
for name, val, expected in test_cases:
413+
scale_cls = mscale._scale_mapping[name]
414+
s = scale_cls(axis=None)
415+
416+
result = s.val_in_range(val)
417+
assert result is expected, (
418+
f"Failed {name}.val_in_range({val})."
419+
f"Expected {expected}, got {result}"
420+
)
421+
422+
423+
def test_val_in_range_base_fallback():
424+
# Directly test the ScaleBase fallback for custom scales.
425+
# ScaleBase.limit_range_for_scale returns values unchanged by default
426+
s = mscale.ScaleBase(axis=None)
427+
428+
# Normal values should be True
429+
assert s.val_in_range(1.0) is True
430+
assert s.val_in_range(-5.5) is True
431+
432+
# NaN and Inf returns False since they cannot be drawn in a plot
433+
assert s.val_in_range(np.nan) is False
434+
assert s.val_in_range(np.inf) is False
435+
assert s.val_in_range(-np.inf) is False

0 commit comments

Comments
 (0)