Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc7df9f0

Browse files
committed
BUGFIX: unique empty set and "full" set
1 parent9214657 commitc7df9f0

File tree

2 files changed

+131
-30
lines changed

2 files changed

+131
-30
lines changed

‎lib/matplotlib/tests/test_transforms.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ def test_bbox_intersection():
471471
# r3 contains r2
472472
assert_bbox_eq(inter(r1,r3),r3)
473473
# no intersection
474-
assertinter(r1,r4)isNone
474+
assert_bbox_eq(inter(r1,r4),mtransforms.Bbox.null())
475475
# single point
476476
assert_bbox_eq(inter(r1,r5),bbox_from_ext(1,1,1,1))
477477

@@ -569,8 +569,10 @@ def test_log_transform():
569569

570570
deftest_nan_overlap():
571571
a=mtransforms.Bbox([[0,0], [1,1]])
572-
b=mtransforms.Bbox([[0,0], [1,np.nan]])
573-
assertnota.overlaps(b)
572+
withpytest.warns(RuntimeWarning,
573+
match="invalid value encountered in less"):
574+
b=mtransforms.Bbox([[0,0], [1,np.nan]])
575+
assertnota.overlaps(b)
574576

575577

576578
deftest_transform_angles():

‎lib/matplotlib/transforms.py

Lines changed: 126 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ class BboxBase(TransformNode):
222222
is_bbox=True
223223
is_affine=True
224224

225+
@staticmethod
226+
def_empty_set_points():
227+
returnnp.array([[np.inf,np.inf], [-np.inf,-np.inf]])
228+
225229
ifDEBUG:
226230
@staticmethod
227231
def_check(points):
@@ -653,7 +657,7 @@ def rotated(self, radians):
653657
returnbbox
654658

655659
@staticmethod
656-
defunion(bboxes):
660+
defunion(bboxes,null_as_empty=True):
657661
"""Return a `Bbox` that contains all of the given *bboxes*."""
658662
ifnotlen(bboxes):
659663
raiseValueError("'bboxes' cannot be empty")
@@ -664,19 +668,51 @@ def union(bboxes):
664668
x1=np.max([bbox.x1forbboxinbboxes])
665669
y0=np.min([bbox.y0forbboxinbboxes])
666670
y1=np.max([bbox.y1forbboxinbboxes])
671+
ifnotnull_as_empty:
672+
cbook.warn_deprecated(
673+
3.4,message="Bboxs will soon change their behavior to "
674+
"correctly treat empty Bboxs as empty sets in unions and "
675+
"intersections. Explicitly set null_as_empty=True to enable "
676+
"this behavior now.")
677+
# needed for 1.14.4 < numpy_version < 1.16
678+
# can remove once we are at numpy >= 1.16
679+
withnp.errstate(invalid='ignore'):
680+
x0=np.min([bbox.xminforbboxinbboxes])
681+
x1=np.max([bbox.xmaxforbboxinbboxes])
682+
y0=np.min([bbox.yminforbboxinbboxes])
683+
y1=np.max([bbox.ymaxforbboxinbboxes])
684+
else:
685+
# needed for 1.14.4 < numpy_version < 1.16
686+
# can remove once we are at numpy >= 1.16
687+
withnp.errstate(invalid='ignore'):
688+
x0=np.min([bbox.x0forbboxinbboxes])
689+
x1=np.max([bbox.x1forbboxinbboxes])
690+
y0=np.min([bbox.y0forbboxinbboxes])
691+
y1=np.max([bbox.y1forbboxinbboxes])
667692
returnBbox([[x0,y0], [x1,y1]])
668693

669694
@staticmethod
670-
defintersection(bbox1,bbox2):
695+
defintersection(bbox1,bbox2,null_as_empty=True):
671696
"""
672697
Return the intersection of *bbox1* and *bbox2* if they intersect, or
673698
None if they don't.
674699
"""
675-
x0=np.maximum(bbox1.x0,bbox2.x0)
676-
x1=np.minimum(bbox1.x1,bbox2.x1)
677-
y0=np.maximum(bbox1.y0,bbox2.y0)
678-
y1=np.minimum(bbox1.y1,bbox2.y1)
679-
returnBbox([[x0,y0], [x1,y1]])ifx0<=x1andy0<=y1elseNone
700+
ifnotnull_as_empty:
701+
cbook.warn_deprecated(
702+
3.4,message="Bboxs will soon change their behavior to "
703+
"correctly treat empty Bboxs as empty sets in unions and "
704+
"intersections. Explicitly set null_as_empty=True to enable "
705+
"this behavior now.")
706+
x0=np.maximum(bbox1.xmin,bbox2.xmin)
707+
x1=np.minimum(bbox1.xmax,bbox2.xmax)
708+
y0=np.maximum(bbox1.ymin,bbox2.ymin)
709+
y1=np.minimum(bbox1.ymax,bbox2.ymax)
710+
else:
711+
x0=np.maximum(bbox1.x0,bbox2.x0)
712+
x1=np.minimum(bbox1.x1,bbox2.x1)
713+
y0=np.maximum(bbox1.y0,bbox2.y0)
714+
y1=np.minimum(bbox1.y1,bbox2.y1)
715+
returnBbox([[x0,y0], [x1,y1]])
680716

681717

682718
classBbox(BboxBase):
@@ -734,33 +770,54 @@ class Bbox(BboxBase):
734770
default value of ``ignore`` can be changed at any time by code with
735771
access to your Bbox, for example using the method `~.Bbox.ignore`.
736772
737-
**Properties of the ``null`` bbox**
773+
**Create from a set of constraints**
738774
739-
.. note::
775+
The null object for accumulating Bboxs from constrains is the entire plane
776+
777+
>>> Bbox.unbounded()
778+
Bbox([[-inf, -inf], [inf, inf]])
779+
780+
By repeatedly intersecting Bboxs, we can refine the Bbox as needed
781+
782+
>>> constraints = Bbox.unbounded()
783+
>>> for box in [Bbox([[0, 0], [1, 1]]), Bbox([[-1, 1], [1, 1]])]:
784+
... constraints = Bbox.intersection(box, constraints)
785+
>>> constraints
786+
Bbox([[0.0, 1.0], [1.0, 1.0]])
787+
788+
**Algebra of Bboxs**
740789
741-
The current behavior of `Bbox.null()` may be surprising as it does
742-
not have all of the properties of the "empty set", and as such does
743-
not behave like a "zero" object in the mathematical sense. We may
744-
change that in the future (with a deprecation period).
790+
The family of all BBoxs forms a ring of sets, once we include the empty set
791+
(`Bbox.null`) and the full space (`Bbox.unbounded`).
745792
746-
The null bbox is the identity for intersections
793+
The unbounded bbox is the identity for intersections (the "multiplicative"
794+
identity)
747795
748-
>>> Bbox.intersection(Bbox([[1, 1], [3, 7]]), Bbox.null())
796+
>>> Bbox.intersection(Bbox([[1, 1], [3, 7]]), Bbox.unbounded())
749797
Bbox([[1.0, 1.0], [3.0, 7.0]])
750798
751-
exceptwithitself, where itreturns thefull space.
799+
and unionwiththe unbounded Bbox alwaysreturns theunbounded Bbox
752800
753-
>>> Bbox.intersection(Bbox.null(), Bbox.null())
801+
>>> Bbox.union([Bbox([[0, 0], [0, 0]]), Bbox.unbounded()])
754802
Bbox([[-inf, -inf], [inf, inf]])
755803
756-
A union containing null will always return the full space (not the other
757-
set!)
804+
The null Bbox is the identity for unions (the "additive" identity)
758805
759-
>>> Bbox.union([Bbox([[0, 0], [0, 0]]), Bbox.null()])
760-
Bbox([[-inf, -inf], [inf, inf]])
806+
>>> Bbox.union([Bbox.null(), Bbox([[1, 1], [3, 7]])])
807+
Bbox([[1.0, 1.0], [3.0, 7.0]])
808+
809+
and intersection with the null Bbox always returns the null Bbox
810+
811+
>>> Bbox.intersection(Bbox.null(), Bbox.unbounded())
812+
Bbox([[inf, inf], [-inf, -inf]])
813+
814+
.. note::
815+
816+
In order to ensure that there is a unique "empty set", all empty Bboxs
817+
are automatically converted to ``Bbox([[inf, inf], [-inf, -inf]])``.
761818
"""
762819

763-
def__init__(self,points,**kwargs):
820+
def__init__(self,points,null_as_empty=True,**kwargs):
764821
"""
765822
Parameters
766823
----------
@@ -772,6 +829,15 @@ def __init__(self, points, **kwargs):
772829
ifpoints.shape!= (2,2):
773830
raiseValueError('Bbox points must be of the form '
774831
'"[[x0, y0], [x1, y1]]".')
832+
ifnotnull_as_empty:
833+
cbook.warn_deprecated(
834+
3.4,message="Bboxs will soon change their behavior to "
835+
"correctly treat empty Bboxs as empty sets in unions and "
836+
"intersections. Explicitly set null_as_empty=True to enable "
837+
"this behavior now.")
838+
ifnull_as_emptyandnp.any(np.diff(points,axis=0)<0):
839+
points=self._empty_set_points()
840+
self._null_as_empty=null_as_empty
775841
self._points=points
776842
self._minpos=np.array([np.inf,np.inf])
777843
self._ignore=True
@@ -798,25 +864,33 @@ def unit():
798864
@staticmethod
799865
defnull():
800866
"""Create a new null `Bbox` from (inf, inf) to (-inf, -inf)."""
801-
returnBbox([[np.inf,np.inf], [-np.inf,-np.inf]])
867+
returnBbox(Bbox._empty_set_points())
868+
869+
@staticmethod
870+
defunbounded():
871+
"""Create a new unbounded `Bbox` from (-inf, -inf) to (inf, inf)."""
872+
returnBbox([[-np.inf,-np.inf], [np.inf,np.inf]])
802873

803874
@staticmethod
804-
deffrom_bounds(x0,y0,width,height):
875+
deffrom_bounds(x0,y0,width,height,null_as_empty=True):
805876
"""
806877
Create a new `Bbox` from *x0*, *y0*, *width* and *height*.
807878
808879
*width* and *height* may be negative.
809880
"""
810-
returnBbox.from_extents(x0,y0,x0+width,y0+height)
881+
ifnull_as_emptyandwidth<0orheight<0:
882+
returnBbox.null()
883+
returnBbox.from_extents(x0,y0,x0+width,y0+height,
884+
null_as_empty=null_as_empty)
811885

812886
@staticmethod
813-
deffrom_extents(*args):
887+
deffrom_extents(*args,null_as_empty=True):
814888
"""
815889
Create a new Bbox from *left*, *bottom*, *right* and *top*.
816890
817891
The *y*-axis increases upwards.
818892
"""
819-
returnBbox(np.reshape(args, (2,2)))
893+
returnBbox(np.reshape(args, (2,2)),null_as_empty=null_as_empty)
820894

821895
def__format__(self,fmt):
822896
return (
@@ -908,41 +982,57 @@ def update_from_data_xy(self, xy, ignore=None, updatex=True, updatey=True):
908982
@BboxBase.x0.setter
909983
defx0(self,val):
910984
self._points[0,0]=val
985+
ifself._null_as_emptyandself.x0>self.x1:
986+
self._points=self._empty_set_points()
911987
self.invalidate()
912988

913989
@BboxBase.y0.setter
914990
defy0(self,val):
915991
self._points[0,1]=val
992+
ifself._null_as_emptyandself.y0>self.y1:
993+
self._points=self._empty_set_points()
916994
self.invalidate()
917995

918996
@BboxBase.x1.setter
919997
defx1(self,val):
920998
self._points[1,0]=val
999+
ifself._null_as_emptyandself.x0>self.x1:
1000+
self._points=self._empty_set_points()
9211001
self.invalidate()
9221002

9231003
@BboxBase.y1.setter
9241004
defy1(self,val):
9251005
self._points[1,1]=val
1006+
ifself._null_as_emptyandself.y0>self.y1:
1007+
self._points=self._empty_set_points()
9261008
self.invalidate()
9271009

9281010
@BboxBase.p0.setter
9291011
defp0(self,val):
9301012
self._points[0]=val
1013+
ifself._null_as_emptyand (self.y0>self.y1orself.x0>self.x1):
1014+
self._points=self._empty_set_points()
9311015
self.invalidate()
9321016

9331017
@BboxBase.p1.setter
9341018
defp1(self,val):
9351019
self._points[1]=val
1020+
ifself._null_as_emptyand (self.y0>self.y1orself.x0>self.x1):
1021+
self._points=self._empty_set_points()
9361022
self.invalidate()
9371023

9381024
@BboxBase.intervalx.setter
9391025
defintervalx(self,interval):
9401026
self._points[:,0]=interval
1027+
ifself._null_as_emptyandself.x0>self.x1:
1028+
self._points=self._empty_set_points()
9411029
self.invalidate()
9421030

9431031
@BboxBase.intervaly.setter
9441032
defintervaly(self,interval):
9451033
self._points[:,1]=interval
1034+
ifself._null_as_emptyandself.y0>self.y1:
1035+
self._points=self._empty_set_points()
9461036
self.invalidate()
9471037

9481038
@BboxBase.bounds.setter
@@ -951,6 +1041,9 @@ def bounds(self, bounds):
9511041
points=np.array([[l,b], [l+w,b+h]],float)
9521042
ifnp.any(self._points!=points):
9531043
self._points=points
1044+
ifself._null_as_emptyand \
1045+
(self.y0>self.y1orself.x0>self.x1):
1046+
self._points=self._empty_set_points()
9541047
self.invalidate()
9551048

9561049
@property
@@ -981,6 +1074,9 @@ def set_points(self, points):
9811074
"""
9821075
ifnp.any(self._points!=points):
9831076
self._points=points
1077+
ifself._null_as_empty \
1078+
and (self.y0>self.y1orself.x0>self.x1):
1079+
self._points=self._empty_set_points()
9841080
self.invalidate()
9851081

9861082
defset(self,other):
@@ -989,6 +1085,9 @@ def set(self, other):
9891085
"""
9901086
ifnp.any(self._points!=other.get_points()):
9911087
self._points=other.get_points()
1088+
ifself._null_as_empty \
1089+
and (self.y0>self.y1orself.x0>self.x1):
1090+
self._points=self._empty_set_points()
9921091
self.invalidate()
9931092

9941093
defmutated(self):

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp