From c3842c4a421c06e56cc0e70a52bb7744acdb1baf Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 18 Jul 2021 23:22:06 +0200 Subject: [PATCH] Support markevery on figure-level lines. ... except for `markevery=` or `(, )` which is a spec relative to the axes size (keeping handling of that specific case into _mark_every_path). --- lib/matplotlib/lines.py | 12 ++++-- lib/matplotlib/tests/test_lines.py | 59 ++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 2ddfbdbfe296..8f4ddaa5c6ea 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -104,7 +104,7 @@ def segment_hits(cx, cy, x, y, radius): return np.concatenate((points, lines)) -def _mark_every_path(markevery, tpath, affine, ax_transform): +def _mark_every_path(markevery, tpath, affine, ax): """ Helper function that sorts out how to deal the input `markevery` and returns the points where markers should be drawn. @@ -152,6 +152,10 @@ def _slice_or_none(in_v, slc): '`markevery` is a tuple with len 2 and second element is ' 'a float, but the first element is not a float or an int; ' 'markevery={}'.format(markevery)) + if ax is None: + raise ValueError( + "markevery is specified relative to the axes size, but " + "the line does not have a Axes as parent") # calc cumulative distance along path (in display coords): disp_coords = affine.transform(tpath.vertices) delta = np.empty((len(disp_coords), 2)) @@ -160,7 +164,7 @@ def _slice_or_none(in_v, slc): delta = np.hypot(*delta.T).cumsum() # calc distance between markers along path based on the axes # bounding box diagonal being a distance of unity: - (x0, y0), (x1, y1) = ax_transform.transform([[0, 0], [1, 1]]) + (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 @@ -812,8 +816,8 @@ def draw(self, renderer): # subsample the markers if markevery is not None markevery = self.get_markevery() if markevery is not None: - subsampled = _mark_every_path(markevery, tpath, - affine, self.axes.transAxes) + subsampled = _mark_every_path( + markevery, tpath, affine, self.axes) else: subsampled = tpath diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index def26456bb36..21cb2214051a 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -217,28 +217,57 @@ def test_step_markers(fig_test, fig_ref): fig_ref.subplots().plot([0, 0, 1], [0, 1, 1], "-o", markevery=[0, 2]) +@pytest.mark.parametrize("parent", ["figure", "axes"]) @check_figures_equal(extensions=('png',)) -def test_markevery(fig_test, fig_ref): +def test_markevery(fig_test, fig_ref, parent): np.random.seed(42) - t = np.linspace(0, 3, 14) - y = np.random.rand(len(t)) + x = np.linspace(0, 1, 14) + y = np.random.rand(len(x)) - casesA = [None, 4, (2, 5), [1, 5, 11], - [0, -1], slice(5, 10, 2), 0.3, (0.3, 0.4), - np.arange(len(t))[y > 0.5]] - casesB = ["11111111111111", "10001000100010", "00100001000010", - "01000100000100", "10000000000001", "00000101010000", - "11011011011110", "01010011011101", "01110001110110"] + cases_test = [None, 4, (2, 5), [1, 5, 11], + [0, -1], slice(5, 10, 2), + np.arange(len(x))[y > 0.5], + 0.3, (0.3, 0.4)] + cases_ref = ["11111111111111", "10001000100010", "00100001000010", + "01000100000100", "10000000000001", "00000101010000", + "01110001110110", "11011011011110", "01010011011101"] - axsA = fig_ref.subplots(3, 3) - axsB = fig_test.subplots(3, 3) + if parent == "figure": + # float markevery ("relative to axes size") is not supported. + cases_test = cases_test[:-2] + cases_ref = cases_ref[:-2] - for ax, case in zip(axsA.flat, casesA): - ax.plot(t, y, "-gD", markevery=case) + def add_test(x, y, *, markevery): + fig_test.add_artist( + mlines.Line2D(x, y, marker="o", markevery=markevery)) - for ax, case in zip(axsB.flat, casesB): + def add_ref(x, y, *, markevery): + fig_ref.add_artist( + mlines.Line2D(x, y, marker="o", markevery=markevery)) + + elif parent == "axes": + axs_test = iter(fig_test.subplots(3, 3).flat) + axs_ref = iter(fig_ref.subplots(3, 3).flat) + + def add_test(x, y, *, markevery): + next(axs_test).plot(x, y, "-gD", markevery=markevery) + + def add_ref(x, y, *, markevery): + next(axs_ref).plot(x, y, "-gD", markevery=markevery) + + for case in cases_test: + add_test(x, y, markevery=case) + + for case in cases_ref: me = np.array(list(case)).astype(int).astype(bool) - ax.plot(t, y, "-gD", markevery=me) + add_ref(x, y, markevery=me) + + +def test_markevery_figure_line_unsupported_relsize(): + fig = plt.figure() + fig.add_artist(mlines.Line2D([0, 1], [0, 1], marker="o", markevery=.5)) + with pytest.raises(ValueError): + fig.canvas.draw() def test_marker_as_markerstyle():