Skip to content

Enh color shadding #8895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
9 changes: 9 additions & 0 deletions doc/users/whats_new/2015-05_color_shading.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Easy color shading
``````````````````
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be ----

Often we would like the ability add subtle color variation to our plots,
especially when similar element are plotted in close proximity to one another.
:func:`matplotlib.colors.shade_color` can be used to take an existing color and
slightly darken it, by giving a negative percentage, or to slightly lighten it,
by giving a positive percentage.

.. plot:: mpl_examples/color/color_shade_demo.py
21 changes: 21 additions & 0 deletions examples/color/color_shade_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Demo of shade_color utility.

"""
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

fig = plt.figure()
ax = fig.add_subplot(111)

lightened_color = mcolors.shade_color('blue', -50)
darkened_color = mcolors.shade_color('blue', +50)

ax.fill_between([0,1], [0,0], [1,1], facecolor=darkened_color)
ax.fill_between([0,1], [0,0], [.66, .66], facecolor='blue')
ax.fill_between([0,1], [0,0], [.33, .33], facecolor=lightened_color)

plt.xlim([0, 1])
plt.ylim([0, 1])

plt.show()
38 changes: 38 additions & 0 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@
from collections import Sized
import re
import warnings
from colorsys import rgb_to_hls, hls_to_rgb
Copy link
Contributor

@eric-wieser eric-wieser Jul 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing these publicly is a bad idea, because it makes matplotlib.colors.rgb_to_hsv and matplotlib.colors.rgb_to_hls look similar, when they have completely different argument conventions



import numpy as np
import matplotlib.cbook as cbook

from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS


Expand Down Expand Up @@ -1947,3 +1950,38 @@ def from_levels_and_colors(levels, colors, extend='neither'):

norm = BoundaryNorm(levels, ncolors=n_data_colors)
return cmap, norm


def shade_color(color, percent):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In almost all of the other color code we handle fractions as floats in range [0, 1], should do the same here.

"""A color helper utility to either darken or lighten given color.

This color utility function allows the user to easily darken or
lighten a color for plotting purposes. This function first
converts the given color to RGB using ColorConverter and then to
HSL. The saturation is modified according to the given percentage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genuine remark about what may be a typo: it is the lightness ("l" in "hls") that is modified by this function, not the saturation ("s" in "hls"), isn't it?

and converted back to RGB.

Parameters
----------
color : string, list, hexvalue
Any acceptable Matplotlib color value, such as 'red',
'slategrey', '#FFEE11', (1,0,0)

percent : the amount by which to brighten or darken the color.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specify units and range


Returns
-------
color : tuple of floats
tuple representing converted rgb values

"""

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should do some validation on the scaling

rgb = colorConverter.to_rgb(color)

h, l, s = rgb_to_hls(*rgb)

l *= 1 + float(percent)/100
Copy link
Contributor

@eric-wieser eric-wieser Jul 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use 0.5 instead of 50 here, and remove divide by 100s? Percentages are dumb units, and we already avoid them for [r,g,b] colors, so let's not introduce them here either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable.


l = np.clip(l, 0, 1)

return hls_to_rgb(h, l, s)
34 changes: 33 additions & 1 deletion lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import numpy as np
import pytest

from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
from numpy.testing.utils import (assert_array_equal,
assert_array_almost_equal, assert_equal)

from matplotlib import cycler
import matplotlib
Expand Down Expand Up @@ -708,3 +709,34 @@ def __add__(self, other):
mcolors.SymLogNorm(3, vmax=5, linscale=1),
mcolors.PowerNorm(1)]:
assert_array_equal(norm(data.view(MyArray)), norm(data))


def _shade_test_helper(color, shade, expected):
sc = mcolors.shade_color(color, shade)
assert_equal(sc, expected)


def test_color_shading():
test_colors = (
'red',
'red',
'red',
'red',
'red',
[0, .5, .9],
'slategrey',
)
test_shade = (0, 50, 100, -50, -100, 20, -20)
known_shaded_result = (
(1.0, 0.0, 0.0),
(1.0, 0.5, 0.5),
(1.0, 1.0, 1.0),
(0.5, 0.0, 0.0),
(0.0, 0.0, 0.0),
(0.080000000000000071, 0.59111111111111092, 1.0),
(0.35097730430754981, 0.40156862745098038, 0.45215995059441105)
)
for color, shade, expected in zip(test_colors,
test_shade,
known_shaded_result):
_shade_test_helper(color, shade, expected)