From db133b0696b6e66a1c58ded53c6bcbed0ad20a35 Mon Sep 17 00:00:00 2001 From: henryhu123 Date: Mon, 9 Mar 2020 23:58:29 -0400 Subject: [PATCH 1/5] Resolve inconsistent NaN handling behaviour Combine array masks rather than deleting masked points to maintain consistency across the project. Add appropriate test cases for validating color correctness for hlines and vlines. Fixes issue #13799. --- lib/matplotlib/axes/_axes.py | 36 ++++++++-------- lib/matplotlib/tests/test_axes.py | 68 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 5a0e7d0ce3c7..19cff5a24a98 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1122,15 +1122,17 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', if not np.iterable(xmax): xmax = [xmax] - y, xmin, xmax = cbook.delete_masked_points(y, xmin, xmax) - + # Create and combine masked_arrays from input + y, xmin, xmax = cbook._combine_masks(y, xmin, xmax) y = np.ravel(y) - xmin = np.resize(xmin, y.shape) - xmax = np.resize(xmax, y.shape) - - verts = [((thisxmin, thisy), (thisxmax, thisy)) - for thisxmin, thisxmax, thisy in zip(xmin, xmax, y)] - lines = mcoll.LineCollection(verts, colors=colors, + xmin = np.ma.resize(xmin, y.shape) + xmax = np.ma.resize(xmax, y.shape) + + masked_verts = [np.ma.array([y_xmin, y_xmax]) + for y_xmin, y_xmax in + zip(np.ma.array([xmin, y]).T, + np.ma.array([xmax, y]).T)] + lines = mcoll.LineCollection(masked_verts, colors=colors, linestyles=linestyles, label=label) self.add_collection(lines, autolim=False) lines.update(kwargs) @@ -1200,15 +1202,17 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', if not np.iterable(ymax): ymax = [ymax] - x, ymin, ymax = cbook.delete_masked_points(x, ymin, ymax) - + # Create and combine masked_arrays from input + x, ymin, ymax = cbook._combine_masks(x, ymin, ymax) x = np.ravel(x) - ymin = np.resize(ymin, x.shape) - ymax = np.resize(ymax, x.shape) - - verts = [((thisx, thisymin), (thisx, thisymax)) - for thisx, thisymin, thisymax in zip(x, ymin, ymax)] - lines = mcoll.LineCollection(verts, colors=colors, + ymin = np.ma.resize(ymin, x.shape) + ymax = np.ma.resize(ymax, x.shape) + + masked_verts = [np.ma.array([xymin, xymax]) + for xymin, xymax in + zip(np.ma.array([x, ymin]).T, + np.ma.array([x, ymax]).T)] + lines = mcoll.LineCollection(masked_verts, colors=colors, linestyles=linestyles, label=label) self.add_collection(lines, autolim=False) lines.update(kwargs) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 6355b88de91a..903564b8de7d 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3992,6 +3992,74 @@ def test_hlines(): ax5.set_ylim(0, 15) +def generate_hlines_with_colors_inputs(): + colors = cycler('colors', [['red', 'green', 'blue', 'purple', 'orange']]) + base_y = cycler( + 'y', [[1, 2, 3, np.nan, 5], np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) + base_max = cycler('xmax', [np.ones(5)]) + base_min = cycler('xmin', [0]) + base_line_width = cycler('linewidth', [5]) + original = base_y * base_min * base_max * colors * base_line_width + + expect = { + 'y': [1, 2, 3, 5], + 'xmin': 0, 'xmax': [1, 2, 3, 5], + 'colors': ['red', 'green', 'blue', 'orange']} + + result = [] + + for i in original: + result.append({'original': i, 'expect': expect}) + + return result + + +@pytest.mark.parametrize('kwargs', generate_hlines_with_colors_inputs()) +@check_figures_equal(extensions=["png"]) +def test_hlines_with_colors(fig_test, fig_ref, kwargs): + fig_test, ax0 = plt.subplots() + test = kwargs.pop('original') + ax0.hlines(**test) + + fig_ref, ax1 = plt.subplots() + ref = kwargs.pop('expect') + ax1.hlines(**ref) + + +def generate_vlines_with_colors_inputs(): + colors = cycler('colors', [['red', 'green', 'blue', 'purple', 'orange']]) + base_x = cycler( + 'x', [[1, 2, 3, np.nan, 5], np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) + base_max = cycler('ymax', [np.ones(5)]) + base_min = cycler('ymin', [0]) + base_line_width = cycler('linewidth', [5]) + original = base_x * base_min * base_max * colors * base_line_width + + expect = { + 'x': [1, 2, 3, 5], + 'ymin': 0, + 'ymax': [1, 2, 3, 5], + 'colors': ['red', 'green', 'blue', 'orange']} + + result = [] + for i in original: + result.append({'original': i, 'expect': expect}) + + return result + + +@pytest.mark.parametrize('kwargs', generate_vlines_with_colors_inputs()) +@check_figures_equal(extensions=["png"]) +def test_vlines_with_colors(fig_test, fig_ref, kwargs): + fig_test, ax0 = plt.subplots() + test = kwargs.pop('original') + ax0.vlines(**test) + + fig_ref, ax1 = plt.subplots() + ref = kwargs.pop('expect') + ax1.vlines(**ref) + + @image_comparison(['step_linestyle', 'step_linestyle'], remove_text=True) def test_step_linestyle(): x = y = np.arange(10) From 8f9ce77cd0f39519c7becbbdc48aa6a0830a35f1 Mon Sep 17 00:00:00 2001 From: henryhu123 Date: Fri, 13 Mar 2020 18:58:18 -0400 Subject: [PATCH 2/5] Fix test cases based on feedback --- lib/matplotlib/tests/test_axes.py | 99 +++++++++++++------------------ 1 file changed, 42 insertions(+), 57 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 903564b8de7d..8759b861e082 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3992,72 +3992,57 @@ def test_hlines(): ax5.set_ylim(0, 15) -def generate_hlines_with_colors_inputs(): - colors = cycler('colors', [['red', 'green', 'blue', 'purple', 'orange']]) - base_y = cycler( - 'y', [[1, 2, 3, np.nan, 5], np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) - base_max = cycler('xmax', [np.ones(5)]) - base_min = cycler('xmin', [0]) - base_line_width = cycler('linewidth', [5]) - original = base_y * base_min * base_max * colors * base_line_width +def generate_lines_with_colors_inputs(): + colors = ['red', 'green', 'blue', 'purple', 'orange'] + xy_nan = [1, 2, 3, np.nan, 5] + xy_mask = np.ma.masked_equal([1, 2, 3, 4, 5], 4) + lines_nan = [{'base_xy': xy_nan, + 'base_max': np.ones(5), + 'base_min': [0], + 'linewidth': 5, + 'colors': colors}] + lines_mask = [{'base_xy': xy_mask, + 'base_max': np.ones(5), + 'base_min': [0], + 'linewidth': 5, + 'colors': colors}] + + return [*lines_nan, *lines_mask] + + +@pytest.mark.parametrize('kwargs', generate_lines_with_colors_inputs()) +@check_figures_equal(extensions=["png"]) +def test_vlines_with_colors(fig_test, fig_ref, kwargs): + kwargs['x'] = kwargs.pop('base_xy') + kwargs['ymin'] = kwargs.pop('base_min') + kwargs['ymax'] = kwargs.pop('base_max') + fig_test.subplots().vlines(**kwargs) expect = { - 'y': [1, 2, 3, 5], - 'xmin': 0, 'xmax': [1, 2, 3, 5], - 'colors': ['red', 'green', 'blue', 'orange']} - - result = [] - - for i in original: - result.append({'original': i, 'expect': expect}) + 'x': [1, 2, 3, 5], + 'ymin': [0], + 'ymax': np.ones(4), + 'colors': ['red', 'green', 'blue', 'orange'], + 'linewidth': 5} + fig_ref.subplots().vlines(**expect) - return result - -@pytest.mark.parametrize('kwargs', generate_hlines_with_colors_inputs()) +@pytest.mark.parametrize('kwargs', generate_lines_with_colors_inputs()) @check_figures_equal(extensions=["png"]) def test_hlines_with_colors(fig_test, fig_ref, kwargs): - fig_test, ax0 = plt.subplots() - test = kwargs.pop('original') - ax0.hlines(**test) - - fig_ref, ax1 = plt.subplots() - ref = kwargs.pop('expect') - ax1.hlines(**ref) - - -def generate_vlines_with_colors_inputs(): - colors = cycler('colors', [['red', 'green', 'blue', 'purple', 'orange']]) - base_x = cycler( - 'x', [[1, 2, 3, np.nan, 5], np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) - base_max = cycler('ymax', [np.ones(5)]) - base_min = cycler('ymin', [0]) - base_line_width = cycler('linewidth', [5]) - original = base_x * base_min * base_max * colors * base_line_width + kwargs['y'] = kwargs.pop('base_xy') + kwargs['xmin'] = kwargs.pop('base_min') + kwargs['xmax'] = kwargs.pop('base_max') + fig_test.subplots().hlines(**kwargs) expect = { - 'x': [1, 2, 3, 5], - 'ymin': 0, - 'ymax': [1, 2, 3, 5], - 'colors': ['red', 'green', 'blue', 'orange']} - - result = [] - for i in original: - result.append({'original': i, 'expect': expect}) - - return result - - -@pytest.mark.parametrize('kwargs', generate_vlines_with_colors_inputs()) -@check_figures_equal(extensions=["png"]) -def test_vlines_with_colors(fig_test, fig_ref, kwargs): - fig_test, ax0 = plt.subplots() - test = kwargs.pop('original') - ax0.vlines(**test) + 'y': [1, 2, 3, 5], + 'xmin': [0], + 'xmax': np.ones(4), + 'colors': ['red', 'green', 'blue', 'orange'], + 'linewidth': 5} - fig_ref, ax1 = plt.subplots() - ref = kwargs.pop('expect') - ax1.vlines(**ref) + fig_ref.subplots().hlines(**expect) @image_comparison(['step_linestyle', 'step_linestyle'], remove_text=True) From 6d93d102dc8a59661fba8e55199de86f1de58b9f Mon Sep 17 00:00:00 2001 From: henryhu123 Date: Thu, 19 Mar 2020 13:50:49 -0400 Subject: [PATCH 3/5] Improved test cases --- lib/matplotlib/tests/test_axes.py | 65 +++++++------------------------ 1 file changed, 15 insertions(+), 50 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 8759b861e082..5f8a1a514357 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3992,57 +3992,22 @@ def test_hlines(): ax5.set_ylim(0, 15) -def generate_lines_with_colors_inputs(): - colors = ['red', 'green', 'blue', 'purple', 'orange'] - xy_nan = [1, 2, 3, np.nan, 5] - xy_mask = np.ma.masked_equal([1, 2, 3, 4, 5], 4) - lines_nan = [{'base_xy': xy_nan, - 'base_max': np.ones(5), - 'base_min': [0], - 'linewidth': 5, - 'colors': colors}] - lines_mask = [{'base_xy': xy_mask, - 'base_max': np.ones(5), - 'base_min': [0], - 'linewidth': 5, - 'colors': colors}] - - return [*lines_nan, *lines_mask] - - -@pytest.mark.parametrize('kwargs', generate_lines_with_colors_inputs()) +@pytest.mark.parametrize('data', [[1, 2, 3, np.nan, 5], + np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) @check_figures_equal(extensions=["png"]) -def test_vlines_with_colors(fig_test, fig_ref, kwargs): - kwargs['x'] = kwargs.pop('base_xy') - kwargs['ymin'] = kwargs.pop('base_min') - kwargs['ymax'] = kwargs.pop('base_max') - fig_test.subplots().vlines(**kwargs) - - expect = { - 'x': [1, 2, 3, 5], - 'ymin': [0], - 'ymax': np.ones(4), - 'colors': ['red', 'green', 'blue', 'orange'], - 'linewidth': 5} - fig_ref.subplots().vlines(**expect) - - -@pytest.mark.parametrize('kwargs', generate_lines_with_colors_inputs()) -@check_figures_equal(extensions=["png"]) -def test_hlines_with_colors(fig_test, fig_ref, kwargs): - kwargs['y'] = kwargs.pop('base_xy') - kwargs['xmin'] = kwargs.pop('base_min') - kwargs['xmax'] = kwargs.pop('base_max') - fig_test.subplots().hlines(**kwargs) - - expect = { - 'y': [1, 2, 3, 5], - 'xmin': [0], - 'xmax': np.ones(4), - 'colors': ['red', 'green', 'blue', 'orange'], - 'linewidth': 5} - - fig_ref.subplots().hlines(**expect) +def test_lines_with_colors(fig_test, fig_ref, data): + test_colors = ['red', 'green', 'blue', 'purple', 'orange'] + fig_test.add_subplot(2, 1, 1).vlines(data, 0, 1, + colors=test_colors, linewidth=5) + fig_test.add_subplot(2, 1, 2).hlines(data, 0, 1, + colors=test_colors, linewidth=5) + + expect_xy = [1, 2, 3, 5] + expect_color = ['red', 'green', 'blue', 'orange'] + fig_ref.add_subplot(2, 1, 1).vlines(expect_xy, 0, 1, + colors=expect_color, linewidth=5) + fig_ref.add_subplot(2, 1, 2).hlines(expect_xy, 0, 1, + colors=expect_color, linewidth=5) @image_comparison(['step_linestyle', 'step_linestyle'], remove_text=True) From 5dc55ee21e58d517602661c6303074539cd07f35 Mon Sep 17 00:00:00 2001 From: Dennis Tismenko Date: Fri, 20 Mar 2020 18:52:58 -0400 Subject: [PATCH 4/5] Refactor masked vertices creation --- lib/matplotlib/axes/_axes.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 19cff5a24a98..a22801f2756b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1125,13 +1125,15 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', # Create and combine masked_arrays from input y, xmin, xmax = cbook._combine_masks(y, xmin, xmax) y = np.ravel(y) - xmin = np.ma.resize(xmin, y.shape) - xmax = np.ma.resize(xmax, y.shape) + xmin = np.ravel(xmin) + xmax = np.ravel(xmax) + + masked_verts = np.ma.empty((len(y), 2, 2)) + masked_verts[:, 0, 1] = y + masked_verts[:, 1, 1] = y + masked_verts[:, 0, 0] = xmin + masked_verts[:, 1, 0] = xmax - masked_verts = [np.ma.array([y_xmin, y_xmax]) - for y_xmin, y_xmax in - zip(np.ma.array([xmin, y]).T, - np.ma.array([xmax, y]).T)] lines = mcoll.LineCollection(masked_verts, colors=colors, linestyles=linestyles, label=label) self.add_collection(lines, autolim=False) @@ -1205,13 +1207,15 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', # Create and combine masked_arrays from input x, ymin, ymax = cbook._combine_masks(x, ymin, ymax) x = np.ravel(x) - ymin = np.ma.resize(ymin, x.shape) - ymax = np.ma.resize(ymax, x.shape) + ymin = np.ravel(ymin) + ymax = np.ravel(ymax) + + masked_verts = np.ma.empty((len(x), 2, 2)) + masked_verts[:, 0, 0] = x + masked_verts[:, 1, 0] = x + masked_verts[:, 0, 1] = ymin + masked_verts[:, 1, 1] = ymax - masked_verts = [np.ma.array([xymin, xymax]) - for xymin, xymax in - zip(np.ma.array([x, ymin]).T, - np.ma.array([x, ymax]).T)] lines = mcoll.LineCollection(masked_verts, colors=colors, linestyles=linestyles, label=label) self.add_collection(lines, autolim=False) From 583d17550ff1639198014296f15fe30a0620680a Mon Sep 17 00:00:00 2001 From: Dennis Tismenko Date: Sun, 22 Mar 2020 02:03:24 -0400 Subject: [PATCH 5/5] Refactor hlines and vlines --- lib/matplotlib/axes/_axes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a22801f2756b..2902a15fea0b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1129,10 +1129,10 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid', xmax = np.ravel(xmax) masked_verts = np.ma.empty((len(y), 2, 2)) - masked_verts[:, 0, 1] = y - masked_verts[:, 1, 1] = y masked_verts[:, 0, 0] = xmin + masked_verts[:, 0, 1] = y masked_verts[:, 1, 0] = xmax + masked_verts[:, 1, 1] = y lines = mcoll.LineCollection(masked_verts, colors=colors, linestyles=linestyles, label=label) @@ -1212,8 +1212,8 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid', masked_verts = np.ma.empty((len(x), 2, 2)) masked_verts[:, 0, 0] = x - masked_verts[:, 1, 0] = x masked_verts[:, 0, 1] = ymin + masked_verts[:, 1, 0] = x masked_verts[:, 1, 1] = ymax lines = mcoll.LineCollection(masked_verts, colors=colors,