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
32 changes: 27 additions & 5 deletions lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,33 @@ def _slice_or_none(in_v, slc):
# bounding box diagonal being a distance of unity:
(x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
scale = np.hypot(x1 - x0, y1 - y0)
marker_delta = np.arange(start * scale, delta[-1], step * scale)
# find closest actual data point that is closest to
# the theoretical distance along the path:
inds = np.abs(delta[np.newaxis, :] - marker_delta[:, np.newaxis])
inds = inds.argmin(axis=1)
marker_start = start * scale
marker_step = step * scale
if marker_step <= 0:
raise ValueError(
f"'markevery' step must be positive, but got {step!r}")
# A theoretical marker can select a vertex only if the marker
# immediately before or after that vertex selects it. Limit
# the candidates to those markers instead of materializing
# every marker position, which may be arbitrarily large when
# zoomed in far enough.
marker_delta = delta - np.remainder(delta - marker_start, marker_step)
marker_delta = np.union1d(marker_delta, marker_delta + marker_step)
marker_delta = marker_delta[
(marker_delta >= marker_start) & (marker_delta < delta[-1])]

# Find each candidate's closest actual data point without
# constructing a len(marker_delta) x len(delta) array.
right = np.searchsorted(delta, marker_delta, side="left")
left = np.maximum(right - 1, 0)
right = np.minimum(right, len(delta) - 1)
inds = np.where(
np.abs(delta[right] - marker_delta)
< np.abs(marker_delta - delta[left]),
right, left)
# If there are multiple vertices at a given distance, use the
# first one.
inds = np.searchsorted(delta, delta[inds], side="left")
inds = np.unique(inds)
# return, we are done here
return Path(fverts[inds], _slice_or_none(codes, inds))
Expand Down
54 changes: 54 additions & 0 deletions lib/matplotlib/tests/test_lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,60 @@ def test_markevery_figure_line_unsupported_relsize():
fig.canvas.draw()


def test_markevery_extreme_zoom():
x = np.linspace(0, 10, 200)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), marker="o", markevery=0.05)
ax.set_xlim(5.000000, 5.000001)

# Calculating marker positions must not allocate an unbounded distance
# matrix when the axes are zoomed in extremely far.
fig.canvas.draw()


@pytest.mark.parametrize("markevery", [0.0, -0.1])
def test_markevery_float_nonpositive_spacing(markevery):
fig, ax = plt.subplots()
ax.plot([0, 1], marker="o", markevery=markevery)

with pytest.raises(ValueError, match="'markevery' step must be positive"):
fig.canvas.draw()


@pytest.mark.parametrize(
("markevery", "expected"),
[(1.5, [0, 3, 4, 5, 6, 7, 8]),
((-0.2, 1.5), [0, 3, 4, 5, 6, 7]),
((11.0, 1.5), [])])
def test_markevery_float_vertex_selection(markevery, expected):
fig, ax = plt.subplots()
(x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
scale = np.hypot(x1 - x0, y1 - y0)
path = Path(np.column_stack([
[0, 0.2, 0.8, 1.6, 2.7, 4.1, 5.8, 7.8, 10], np.zeros(9)]))

actual = mlines._mark_every_path(
markevery, path, mtransforms.Affine2D().scale(scale), ax)

assert_array_equal(actual.vertices, path.vertices[expected])


@pytest.mark.parametrize(
("x", "expected"), [([0, 0.5, 1.5], [0, 1]), ([0, 0, 2], [0])])
def test_markevery_float_ties_and_repeated_vertices(x, expected):
fig, ax = plt.subplots()
(x0, y0), (x1, y1) = ax.transAxes.transform([[0, 0], [1, 1]])
scale = np.hypot(x1 - x0, y1 - y0)
path = Path(np.column_stack([x, np.zeros(len(x))]))

actual = mlines._mark_every_path(
1.0, path, mtransforms.Affine2D().scale(scale), ax)

# Match np.argmin's preference for the first vertex on ties, including
# repeated cumulative distances.
assert_array_equal(actual.vertices, path.vertices[expected])


def test_marker_as_markerstyle():
fig, ax = plt.subplots()
line, = ax.plot([2, 4, 3], marker=MarkerStyle("D"))
Expand Down
Loading