diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 70c30eb42169b..93e0df54bd0bf 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -502,6 +502,7 @@ Plotting - :func:`set_option` now validates that the plot backend provided to ``'plotting.backend'`` implements the backend when the option is set, rather than when a plot is created (:issue:`28163`) - :meth:`DataFrame.plot` now allow a ``backend`` keyword arugment to allow changing between backends in one session (:issue:`28619`). - Bug in color validation incorrectly raising for non-color styles (:issue:`29122`). +- :func:`.plot` for line/bar now accepts color by dictonary (:issue:`8193`). Groupby/resample/rolling ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 5341dc3a6338a..412495e4154ba 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -741,7 +741,10 @@ def _apply_style_colors(self, colors, kwds, col_num, label): has_color = "color" in kwds or self.colormap is not None nocolor_style = style is None or re.match("[a-z]+", style) is None if (has_color or self.subplots) and nocolor_style: - kwds["color"] = colors[col_num % len(colors)] + if isinstance(colors, dict): + kwds["color"] = colors[label] + else: + kwds["color"] = colors[col_num % len(colors)] return style, kwds def _get_colors(self, num_colors=None, color_kwds="color"): @@ -1354,12 +1357,13 @@ def _make_plot(self): pos_prior = neg_prior = np.zeros(len(self.data)) K = self.nseries - for i, (label, y) in enumerate(self._iter_data(fillna=0)): ax = self._get_ax(i) kwds = self.kwds.copy() if self._is_series: kwds["color"] = colors + elif isinstance(colors, dict): + kwds["color"] = colors[label] else: kwds["color"] = colors[i % ncolors] diff --git a/pandas/plotting/_matplotlib/style.py b/pandas/plotting/_matplotlib/style.py index 927b9cf4e392a..21c8d907b4d14 100644 --- a/pandas/plotting/_matplotlib/style.py +++ b/pandas/plotting/_matplotlib/style.py @@ -27,7 +27,11 @@ def _get_standard_colors( warnings.warn( "'color' and 'colormap' cannot be used simultaneously. Using 'color'" ) - colors = list(color) if is_list_like(color) else color + colors = ( + list(color) + if is_list_like(color) and not isinstance(color, dict) + else color + ) else: if color_type == "default": # need to call list() on the result to copy so we don't diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index c51cd0e92eb3c..56ac205b99a32 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -416,3 +416,24 @@ def test_get_standard_colors_no_appending(self): color_list = cm.gnuplot(np.linspace(0, 1, 16)) p = df.A.plot.bar(figsize=(16, 7), color=color_list) assert p.patches[1].get_facecolor() == p.patches[17].get_facecolor() + + @pytest.mark.slow + def test_dictionary_color(self): + # issue-8193 + # Test plot color dictionary format + data_files = ["a", "b"] + + expected = [(0.5, 0.24, 0.6), (0.3, 0.7, 0.7)] + + df1 = DataFrame(np.random.rand(2, 2), columns=data_files) + dic_color = {"b": (0.3, 0.7, 0.7), "a": (0.5, 0.24, 0.6)} + + # Bar color test + ax = df1.plot(kind="bar", color=dic_color) + colors = [rect.get_facecolor()[0:-1] for rect in ax.get_children()[0:3:2]] + assert all(color == expected[index] for index, color in enumerate(colors)) + + # Line color test + ax = df1.plot(kind="line", color=dic_color) + colors = [rect.get_color() for rect in ax.get_lines()[0:2]] + assert all(color == expected[index] for index, color in enumerate(colors))