From d4443cce48d41250df9c4b017235e8f292fe7447 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:18:17 -0800 Subject: [PATCH 1/3] Change get_subplotspec and handles changes --- pandas/plotting/_matplotlib/core.py | 4 +-- pandas/plotting/_matplotlib/tools.py | 34 +++++++++++-------- .../tests/plotting/frame/test_frame_color.py | 2 +- .../tests/plotting/frame/test_frame_legend.py | 4 +-- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 455dd1f75b225..a238c45f22558 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -784,8 +784,8 @@ def _make_legend(self) -> None: if not self.subplots: if leg is not None: title = leg.get_title().get_text() - # Replace leg.LegendHandles because it misses marker info - handles = leg.legendHandles + # Replace leg.legend_handles because it misses marker info + handles = leg.legend_handles labels = [x.get_text() for x in leg.get_texts()] if self.legend: diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index 7d3c857eea2dd..e19ac9865717a 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -390,24 +390,26 @@ def handle_shared_axes( sharey: bool, ) -> None: if nplots > 1: - row_num = lambda x: x.get_subplotspec().rowspan.start - col_num = lambda x: x.get_subplotspec().colspan.start - - is_first_col = lambda x: x.get_subplotspec().is_first_col() - if nrows > 1: try: # first find out the ax layout, # so that we can correctly handle 'gaps" layout = np.zeros((nrows + 1, ncols + 1), dtype=np.bool_) for ax in axarr: - layout[row_num(ax), col_num(ax)] = ax.get_visible() + spec = ax.get_subplotspec() + if spec is not None: + layout[ + spec.rowspan.start, spec.colspan.start + ] = ax.get_visible() for ax in axarr: + spec = ax.get_subplotspec() + if spec is None: + continue # only the last row of subplots should get x labels -> all # other off layout handles the case that the subplot is # the last in the column, because below is no subplot/gap. - if not layout[row_num(ax) + 1, col_num(ax)]: + if not layout[spec.rowspan.start + 1, spec.colspan.start]: continue if sharex or _has_externally_shared_axis(ax, "x"): _remove_labels_from_axis(ax.xaxis) @@ -415,19 +417,23 @@ def handle_shared_axes( except IndexError: # if gridspec is used, ax.rowNum and ax.colNum may different # from layout shape. in this case, use last_row logic - is_last_row = lambda x: x.get_subplotspec().is_last_row() - for ax in axarr: - if is_last_row(ax): - continue - if sharex or _has_externally_shared_axis(ax, "x"): - _remove_labels_from_axis(ax.xaxis) + spec = ax.get_subplotspec() + if spec is not None: + for ax in axarr: + if spec.is_last_row(): + continue + if sharex or _has_externally_shared_axis(ax, "x"): + _remove_labels_from_axis(ax.xaxis) if ncols > 1: for ax in axarr: + spec = ax.get_subplotspec() + if spec is None: + continue # only the first column should get y labels -> set all other to # off as we only have labels in the first column and we always # have a subplot there, we can skip the layout test - if is_first_col(ax): + if spec.is_first_col(): continue if sharey or _has_externally_shared_axis(ax, "y"): _remove_labels_from_axis(ax.yaxis) diff --git a/pandas/tests/plotting/frame/test_frame_color.py b/pandas/tests/plotting/frame/test_frame_color.py index 2d8b3b14c8c68..4c5ccdd45680b 100644 --- a/pandas/tests/plotting/frame/test_frame_color.py +++ b/pandas/tests/plotting/frame/test_frame_color.py @@ -643,7 +643,7 @@ def test_colors_of_columns_with_same_name(self): df1 = DataFrame({"a": [2, 4, 6]}) df_concat = pd.concat([df, df1], axis=1) result = df_concat.plot() - for legend, line in zip(result.get_legend().legendHandles, result.lines): + for legend, line in zip(result.get_legend().legend_handles, result.lines): assert legend.get_color() == line.get_color() def test_invalid_colormap(self): diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index d759d753d4320..720488a5049db 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -28,7 +28,7 @@ def test_mixed_yerr(self): df.plot("x", "b", c="blue", yerr=None, ax=ax, label="blue") legend = ax.get_legend() - result_handles = legend.legendHandles + result_handles = legend.legend_handles assert isinstance(result_handles[0], LineCollection) assert isinstance(result_handles[1], Line2D) @@ -41,7 +41,7 @@ def test_legend_false(self): ax = df.plot(legend=True, color={"a": "blue", "b": "green"}, secondary_y="b") df2.plot(legend=True, color={"d": "red"}, ax=ax) legend = ax.get_legend() - result = [handle.get_color() for handle in legend.legendHandles] + result = [handle.get_color() for handle in legend.legend_handles] expected = ["blue", "green", "red"] assert result == expected From 8401799e505071b9e05687ad8302bf26437f75ea Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:04:33 -0800 Subject: [PATCH 2/3] version gate legend_handles --- pandas/plotting/_matplotlib/compat.py | 15 --------------- pandas/plotting/_matplotlib/core.py | 6 +++++- pandas/tests/plotting/frame/test_frame_color.py | 10 +++++++++- pandas/tests/plotting/frame/test_frame_legend.py | 15 +++++++++++++-- 4 files changed, 27 insertions(+), 19 deletions(-) delete mode 100644 pandas/plotting/_matplotlib/compat.py diff --git a/pandas/plotting/_matplotlib/compat.py b/pandas/plotting/_matplotlib/compat.py deleted file mode 100644 index 7314f05e9f19c..0000000000000 --- a/pandas/plotting/_matplotlib/compat.py +++ /dev/null @@ -1,15 +0,0 @@ -# being a bit too dynamic -from __future__ import annotations - -from pandas.util.version import Version - - -def _mpl_version(version, op): - def inner(): - try: - import matplotlib as mpl - except ImportError: - return False - return op(Version(mpl.__version__), Version(version)) - - return inner diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index a238c45f22558..754cc94b6ded6 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -54,6 +54,7 @@ import pandas.core.common as com from pandas.core.frame import DataFrame +from pandas.util.version import Version from pandas.io.formats.printing import pprint_thing from pandas.plotting._matplotlib import tools @@ -785,7 +786,10 @@ def _make_legend(self) -> None: if leg is not None: title = leg.get_title().get_text() # Replace leg.legend_handles because it misses marker info - handles = leg.legend_handles + if Version(mpl.__version__) < Version("3.7"): + handles = leg.legendHandles + else: + handles = leg.legend_handles labels = [x.get_text() for x in leg.get_texts()] if self.legend: diff --git a/pandas/tests/plotting/frame/test_frame_color.py b/pandas/tests/plotting/frame/test_frame_color.py index 4c5ccdd45680b..a2ab72ecb690e 100644 --- a/pandas/tests/plotting/frame/test_frame_color.py +++ b/pandas/tests/plotting/frame/test_frame_color.py @@ -13,6 +13,7 @@ TestPlotBase, _check_plot_works, ) +from pandas.util.version import Version @td.skip_if_no_mpl @@ -639,11 +640,18 @@ def test_rcParams_bar_colors(self): def test_colors_of_columns_with_same_name(self): # ISSUE 11136 -> https://github.com/pandas-dev/pandas/issues/11136 # Creating a DataFrame with duplicate column labels and testing colors of them. + import matplotlib as mpl + df = DataFrame({"b": [0, 1, 0], "a": [1, 2, 3]}) df1 = DataFrame({"a": [2, 4, 6]}) df_concat = pd.concat([df, df1], axis=1) result = df_concat.plot() - for legend, line in zip(result.get_legend().legend_handles, result.lines): + legend = result.get_legend() + if Version(mpl.__version__) < Version("3.7"): + handles = legend.legendHandles + else: + handles = legend.legend_handles + for legend, line in zip(handles, result.lines): assert legend.get_color() == line.get_color() def test_invalid_colormap(self): diff --git a/pandas/tests/plotting/frame/test_frame_legend.py b/pandas/tests/plotting/frame/test_frame_legend.py index 720488a5049db..bad42ebc85cc8 100644 --- a/pandas/tests/plotting/frame/test_frame_legend.py +++ b/pandas/tests/plotting/frame/test_frame_legend.py @@ -8,6 +8,7 @@ date_range, ) from pandas.tests.plotting.common import TestPlotBase +from pandas.util.version import Version class TestFrameLegend(TestPlotBase): @@ -19,6 +20,7 @@ class TestFrameLegend(TestPlotBase): ) def test_mixed_yerr(self): # https://github.com/pandas-dev/pandas/issues/39522 + import matplotlib as mpl from matplotlib.collections import LineCollection from matplotlib.lines import Line2D @@ -28,20 +30,29 @@ def test_mixed_yerr(self): df.plot("x", "b", c="blue", yerr=None, ax=ax, label="blue") legend = ax.get_legend() - result_handles = legend.legend_handles + if Version(mpl.__version__) < Version("3.7"): + result_handles = legend.legendHandles + else: + result_handles = legend.legend_handles assert isinstance(result_handles[0], LineCollection) assert isinstance(result_handles[1], Line2D) def test_legend_false(self): # https://github.com/pandas-dev/pandas/issues/40044 + import matplotlib as mpl + df = DataFrame({"a": [1, 1], "b": [2, 3]}) df2 = DataFrame({"d": [2.5, 2.5]}) ax = df.plot(legend=True, color={"a": "blue", "b": "green"}, secondary_y="b") df2.plot(legend=True, color={"d": "red"}, ax=ax) legend = ax.get_legend() - result = [handle.get_color() for handle in legend.legend_handles] + if Version(mpl.__version__) < Version("3.7"): + handles = legend.legendHandles + else: + handles = legend.legend_handles + result = [handle.get_color() for handle in handles] expected = ["blue", "green", "red"] assert result == expected From a36f00790a5468186869b8e0dc186cc6189bb672 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 17 Feb 2023 17:10:26 -0800 Subject: [PATCH 3/3] Undo tools change --- pandas/plotting/_matplotlib/tools.py | 34 ++++++++++++---------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index e19ac9865717a..7d3c857eea2dd 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -390,26 +390,24 @@ def handle_shared_axes( sharey: bool, ) -> None: if nplots > 1: + row_num = lambda x: x.get_subplotspec().rowspan.start + col_num = lambda x: x.get_subplotspec().colspan.start + + is_first_col = lambda x: x.get_subplotspec().is_first_col() + if nrows > 1: try: # first find out the ax layout, # so that we can correctly handle 'gaps" layout = np.zeros((nrows + 1, ncols + 1), dtype=np.bool_) for ax in axarr: - spec = ax.get_subplotspec() - if spec is not None: - layout[ - spec.rowspan.start, spec.colspan.start - ] = ax.get_visible() + layout[row_num(ax), col_num(ax)] = ax.get_visible() for ax in axarr: - spec = ax.get_subplotspec() - if spec is None: - continue # only the last row of subplots should get x labels -> all # other off layout handles the case that the subplot is # the last in the column, because below is no subplot/gap. - if not layout[spec.rowspan.start + 1, spec.colspan.start]: + if not layout[row_num(ax) + 1, col_num(ax)]: continue if sharex or _has_externally_shared_axis(ax, "x"): _remove_labels_from_axis(ax.xaxis) @@ -417,23 +415,19 @@ def handle_shared_axes( except IndexError: # if gridspec is used, ax.rowNum and ax.colNum may different # from layout shape. in this case, use last_row logic - spec = ax.get_subplotspec() - if spec is not None: - for ax in axarr: - if spec.is_last_row(): - continue - if sharex or _has_externally_shared_axis(ax, "x"): - _remove_labels_from_axis(ax.xaxis) + is_last_row = lambda x: x.get_subplotspec().is_last_row() + for ax in axarr: + if is_last_row(ax): + continue + if sharex or _has_externally_shared_axis(ax, "x"): + _remove_labels_from_axis(ax.xaxis) if ncols > 1: for ax in axarr: - spec = ax.get_subplotspec() - if spec is None: - continue # only the first column should get y labels -> set all other to # off as we only have labels in the first column and we always # have a subplot there, we can skip the layout test - if spec.is_first_col(): + if is_first_col(ax): continue if sharey or _has_externally_shared_axis(ax, "y"): _remove_labels_from_axis(ax.yaxis)