Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
90dce86
fix tightbbox to account for markeredgewidth
brunobeltran Feb 29, 2020
5ed49fd
cleanup definition of "point" marker
brunobeltran Mar 1, 2020
9088cf4
document unit_regular_polygon
brunobeltran Mar 1, 2020
fd6df9b
untested version of new code to get marker bbox
brunobeltran Mar 3, 2020
ef2fefd
fix for marker bbox now works except for on miter
brunobeltran Mar 3, 2020
db033e4
fixed mis-ordered PathEndAngles for ticks
brunobeltran Mar 3, 2020
7e41bf5
flake8 for new markers code
brunobeltran Mar 3, 2020
f1014b5
factor marker bbox code to be within MarkerStyles
brunobeltran Mar 3, 2020
42cc5db
bugfix, forgot self in MarkerStyle.get_centered_bbox
brunobeltran Mar 3, 2020
8fcb223
misc bugfixes after factoring get_centered_bbox
brunobeltran Mar 3, 2020
d95e4d8
markers bbox code visually tested, now works
brunobeltran Mar 3, 2020
4592cda
flake8 for new markers bbox code
brunobeltran Mar 3, 2020
c08826e
fixed formula for miter marker bbox, bevel broke
brunobeltran Mar 4, 2020
7f9db16
bugfix caret bbox calculation, incorrect angles
brunobeltran Mar 4, 2020
1805dc7
fixed star tip angle in marker bbox calculation
brunobeltran Mar 4, 2020
a13598d
test marker bbox. failing here, pass in jupyter
brunobeltran Mar 4, 2020
0f7300a
bugfix so markers bbox api stays in pts units
brunobeltran Mar 4, 2020
d6f1571
forgot to push new test references images up
brunobeltran Mar 4, 2020
2bee048
cleanup variable name consistency
brunobeltran Mar 6, 2020
66694a0
use conversion not magic nums for line get_extents
brunobeltran Mar 9, 2020
c4a45de
iter_curves: iterate over path more conveniently
brunobeltran Mar 9, 2020
c5bdd8d
helper functions for bezier curve zeros/tangents
brunobeltran Mar 10, 2020
41f268e
CornerInfo in bezier.py, should be path.py
brunobeltran Mar 10, 2020
637a7f2
update marker bbox to work for arbitrary paths
brunobeltran Mar 10, 2020
5ac9114
bugfix, new marker bbox code now runs, untested
brunobeltran Mar 10, 2020
ef36ec2
pyflake fixes for marker bbox code
brunobeltran Mar 10, 2020
82e3c12
cleanup path/bezier to prevent import triangle
brunobeltran Mar 12, 2020
8585eca
fix prev commit, make split_in_out method of Path
brunobeltran Mar 12, 2020
54e3a3e
generalized path bbox code tested on some markers
brunobeltran Mar 12, 2020
b390653
path bbox now works for all markers but "pixel"
brunobeltran Mar 12, 2020
85a3050
reorg'd path/bezier code now builds docs no errors
brunobeltran Mar 12, 2020
79aa3b7
cleanup docstrings of stroked path bbox code
brunobeltran Mar 12, 2020
1ea37be
fixed sphinx warnings in path.py's docstrings
brunobeltran Mar 12, 2020
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
Prev Previous commit
Next Next commit
cleanup path/bezier to prevent import triangle
  • Loading branch information
brunobeltran committed Mar 12, 2020
commit 82e3c123f1bfe7f86090f6ebef9e99578ecea6c4
219 changes: 0 additions & 219 deletions lib/matplotlib/bezier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
"""

import math
from collections import namedtuple

import numpy as np

import matplotlib.cbook as cbook
from matplotlib.path import Path


class NonIntersectingPathException(ValueError):
Expand Down Expand Up @@ -282,68 +280,6 @@ def split_bezier_intersecting_with_closedpath(
# matplotlib specific


def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False):
"""
Divide a path into two segments at the point where ``inside(x, y)`` becomes
False.
"""
path_iter = path.iter_segments()

ctl_points, command = next(path_iter)
begin_inside = inside(ctl_points[-2:]) # true if begin point is inside

ctl_points_old = ctl_points

concat = np.concatenate

iold = 0
i = 1

for ctl_points, command in path_iter:
iold = i
i += len(ctl_points) // 2
if inside(ctl_points[-2:]) != begin_inside:
bezier_path = concat([ctl_points_old[-2:], ctl_points])
break
ctl_points_old = ctl_points
else:
raise ValueError("The path does not intersect with the patch")

bp = bezier_path.reshape((-1, 2))
left, right = split_bezier_intersecting_with_closedpath(
bp, inside, tolerance)
if len(left) == 2:
codes_left = [Path.LINETO]
codes_right = [Path.MOVETO, Path.LINETO]
elif len(left) == 3:
codes_left = [Path.CURVE3, Path.CURVE3]
codes_right = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
elif len(left) == 4:
codes_left = [Path.CURVE4, Path.CURVE4, Path.CURVE4]
codes_right = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]
else:
raise AssertionError("This should never be reached")

verts_left = left[1:]
verts_right = right[:]

if path.codes is None:
path_in = Path(concat([path.vertices[:i], verts_left]))
path_out = Path(concat([verts_right, path.vertices[i:]]))

else:
path_in = Path(concat([path.vertices[:iold], verts_left]),
concat([path.codes[:iold], codes_left]))

path_out = Path(concat([verts_right, path.vertices[i:]]),
concat([codes_right, path.codes[i:]]))

if reorder_inout and not begin_inside:
path_in, path_out = path_out, path_in

return path_in, path_out


def inside_circle(cx, cy, r):
"""
Return a function that checks whether a point is in a circle with center
Expand All @@ -361,139 +297,6 @@ def _f(xy):
return _f


CornerInfo = namedtuple('CornerInfo', 'apex incidence_angle corner_angle')
r"""Used to have a universal way to account for how much the bounding box of a
shape will grow as we increase its `markeredgewidth`.

Attributes
----------
`apex` : float
the vertex that marks the "tip" of the corner
`incidence_angle` : float
the angle that the corner bisector makes with the box edge (where
top/bottom box edges are horizontal, left/right box edges are
vertical).
`corner_angle` : float
the internal angle of the corner, where np.pi is a straight line, and 0
is retracing exactly the way you came. None can be used to signify that
the line ends there (i.e. no corner).

Notes
-----
$\pi$ and 0 are equivalent for `corner_angle`. Both $\theta$ and $\pi - \theta$
are equivalent for `incidence_angle` by symmetry."""


def _incidence_corner_from_angles(angle_1, angle_2):
"""Gets CornerInfo from direction of lines making up corner.

This function expects angle_1 and angle_2 (in radians) to be
the orientation of lines 1 and 2 (arbitrarily chosen to point
towards the corner where they meet) relative to the coordinate
system.

Helper function for iter_corners.

Returns
-------
incidence_angle : float in [0, 2*pi]
as described in CornerInfo docs
corner_angle : float in [0, pi]
as described in CornerInfo docs
"""
# get "interior" angle between tangents to joined curves' tips
corner_angle = np.abs(angle_1 - angle_2)
if corner_angle > np.pi:
corner_angle = 2*np.pi - corner_angle
# since [-pi, pi], we need to sort to avoid modulo
smaller_angle = min(angle_1, angle_2)
larger_angle = max(angle_1, angle_2)
if np.isclose(smaller_angle + corner_angle, larger_angle):
incident_angle = smaller_angle + corner_angle/2
else:
incident_angle = smaller_angle - corner_angle/2
return incident_angle, corner_angle


def iter_corners(path, **kwargs):
"""Iterate over a mpl.path.Path object and return information about every
cap and corner.

Parameters
----------
path : mpl.path.Path
the path to extract corners from
kwargs : Dict[str, object]
passed onto Path.iter_curves

Yields
------
corner : CornerInfo
Measure of the corner's position, orientation, and angle. Useful in
order to determine how the corner affects the bbox of the curve.
"""
first_tan_angle = None
first_vertex = None
prev_tan_angle = None
prev_vertex = None
is_capped = False
for bcurve, code in path.iter_curves(**kwargs):
bcurve = BezierSegment(bcurve)
if code == Path.MOVETO:
# deal with capping ends of previous polyline, if it exists
if prev_tan_angle is not None and is_capped:
for cap_angle, cap_vertex in [(first_tan_angle, first_vertex),
(prev_tan_angle, prev_vertex)]:
yield CornerInfo(cap_vertex, cap_angle, None)
first_tan_angle = None
prev_tan_angle = None
first_vertex = bcurve.cpoints[0]
prev_vertex = first_vertex
# lines will end in a cap by default unless a CLOSEPOLY is observed
is_capped = True
continue
if code == Path.CLOSEPOLY:
is_capped = False
if prev_tan_angle is None:
raise ValueError("Misformed path, cannot close poly with "
"single vertex!")
tan_in = prev_vertex - first_vertex
# often CLOSEPOLY is used when the curve has already reached it's
# initial point in order to prevent there from being a stray
# straight line segment if it's used this way, then we more or less
# ignore the current bcurve
if np.isclose(np.linalg.norm(tan_in), 0):
incident_angle, corner_angle = _incidence_corner_from_angles(
prev_tan_angle, first_tan_angle)
yield CornerInfo(prev_vertex, incident_angle, corner_angle)
continue
# otherwise, we have to calculate both the corner from the
# previous line segment to the current straight line, and from the
# current straight line to the original starting line. The former
# is taken care of by the
# non-special-case code below. the latter looks like:
tan_out = bcurve.tan_out
angle_end = np.arctan2(tan_out[1], tan_out[0])
incident_angle, corner_angle = _incidence_corner_from_angles(
angle_end, first_tan_angle)
yield CornerInfo(first_vertex, incident_angle, corner_angle)
# finally, usual case is when two curves meet at an angle
tan_in = -bcurve.tan_in
angle_in = np.arctan2(tan_in[1], tan_in[0])
if first_tan_angle is None:
first_tan_angle = angle_in
if prev_tan_angle is not None:
incident_angle, corner_angle = _incidence_corner_from_angles(
angle_in, prev_tan_angle)
yield CornerInfo(prev_vertex, incident_angle, corner_angle)
tan_out = bcurve.tan_out
prev_tan_angle = np.arctan2(tan_out[1], tan_out[0])
prev_vertex = bcurve.cpoints[-1]
if prev_tan_angle is not None and is_capped:
for cap_angle, cap_vertex in [(first_tan_angle, first_vertex),
(prev_tan_angle, prev_vertex)]:
yield CornerInfo(cap_vertex, cap_angle, None)

# quadratic Bezier lines


Expand Down Expand Up @@ -669,25 +472,3 @@ def make_wedged_bezier2(bezier2, width, w1=1., wm=0.5, w2=0.):
c3x_right, c3y_right)

return path_left, path_right


def make_path_regular(p):
"""
If the ``codes`` attribute of `.Path` *p* is None, return a copy of *p*
with ``codes`` set to (MOVETO, LINETO, LINETO, ..., LINETO); otherwise
return *p* itself.
"""
c = p.codes
if c is None:
c = np.full(len(p.vertices), Path.LINETO, dtype=Path.code_type)
c[0] = Path.MOVETO
return Path(p.vertices, c)
else:
return p


def concatenate_paths(paths):
"""Concatenate a list of paths into a single path."""
vertices = np.concatenate([p.vertices for p in paths])
codes = np.concatenate([make_path_regular(p).codes for p in paths])
return Path(vertices, codes)
23 changes: 13 additions & 10 deletions lib/matplotlib/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
from . import cbook, rcParams
from .path import Path
from .transforms import IdentityTransform, Affine2D, Bbox
from .bezier import BezierSegment, iter_corners
from .bezier import BezierSegment

# special-purpose marker identifiers:
(TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN,
Expand Down Expand Up @@ -1023,6 +1023,9 @@ def get_stroked_bbox(self, markersize, markeredgewidth=0, **kwargs):
markeredgewidth : float, optional, default: 0
Width, in points, of the stroke used to create the marker's edge.

kwargs : Dict[str, object]
forwarded to iter_curves and iter_corners

Returns
-------
bbox : matplotlib.transforms.Bbox
Expand Down Expand Up @@ -1061,16 +1064,16 @@ def get_stroked_bbox(self, markersize, markeredgewidth=0, **kwargs):
unit_extents[dim] = potential_extrema
if potential_extrema > unit_extents[maxi+dim]:
unit_extents[maxi+dim] = potential_extrema
for corner in iter_corners(unit_path, **kwargs):
for corner in unit_path.iter_corners(**kwargs):
x, y = corner.apex
# now for each of up/down/left/right, convert the absolute
# incidence angle into the incidence angle relative to that
# respective side of the bbox, and see if the corner expands the
# extents...
if np.cos(corner.incidence_angle) > 0:
incidence_angle = corner.incidence_angle + np.pi/2
x += _get_padding_due_to_angle(markeredgewidth,
incidence_angle, corner.corner_angle,
x += _get_padding_due_to_angle(
markeredgewidth, incidence_angle, corner.corner_angle,
joinstyle=self._joinstyle, capstyle=self._capstyle)
if x > unit_extents[xmax]:
unit_extents[xmax] = x
Expand All @@ -1079,22 +1082,22 @@ def get_stroked_bbox(self, markersize, markeredgewidth=0, **kwargs):
incidence_angle = 3*np.pi/2 + corner.incidence_angle
else:
incidence_angle = corner.incidence_angle - np.pi/2
x -= _get_padding_due_to_angle(markeredgewidth,
incidence_angle, corner.corner_angle,
x -= _get_padding_due_to_angle(
markeredgewidth, incidence_angle, corner.corner_angle,
joinstyle=self._joinstyle, capstyle=self._capstyle)
if x < unit_extents[xmin]:
unit_extents[xmin] = x
if np.sin(corner.incidence_angle) > 0:
incidence_angle = corner.incidence_angle
y += _get_padding_due_to_angle(markeredgewidth,
incidence_angle, corner.corner_angle,
y += _get_padding_due_to_angle(
markeredgewidth, incidence_angle, corner.corner_angle,
joinstyle=self._joinstyle, capstyle=self._capstyle)
if y > unit_extents[ymax]:
unit_extents[ymax] = y
else:
incidence_angle = corner.incidence_angle + np.pi
y -= _get_padding_due_to_angle(markeredgewidth,
incidence_angle, corner.corner_angle,
y -= _get_padding_due_to_angle(
markeredgewidth, incidence_angle, corner.corner_angle,
joinstyle=self._joinstyle, capstyle=self._capstyle)
if y < unit_extents[ymin]:
unit_extents[ymin] = y
Expand Down
11 changes: 5 additions & 6 deletions lib/matplotlib/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
import matplotlib as mpl
from . import artist, cbook, colors, docstring, lines as mlines, transforms
from .bezier import (
NonIntersectingPathException, concatenate_paths, get_cos_sin,
get_intersection, get_parallels, inside_circle, make_path_regular,
make_wedged_bezier2, split_bezier_intersecting_with_closedpath,
split_path_inout)
NonIntersectingPathException, get_cos_sin, get_intersection, get_parallels,
inside_circle, make_wedged_bezier2,
split_bezier_intersecting_with_closedpath, split_path_inout)
from .path import Path


Expand Down Expand Up @@ -3187,7 +3186,7 @@ def __call__(self, path, mutation_size, linewidth,
and takes care of the aspect ratio.
"""

path = make_path_regular(path)
path = path.make_path_regular()

if aspect_ratio is not None:
# Squeeze the given height by the aspect_ratio
Expand Down Expand Up @@ -4174,7 +4173,7 @@ def get_path(self):
"""
_path, fillable = self.get_path_in_displaycoord()
if np.iterable(fillable):
_path = concatenate_paths(_path)
_path = Path.make_compound_path(_path)
return self.get_transform().inverted().transform_path(_path)

def get_path_in_displaycoord(self):
Expand Down
Loading