Skip to content

Commit 36dd331

Browse files
committed
Color text based on background gradient
Close pandas-dev#21258
1 parent c85ab08 commit 36dd331

File tree

1 file changed

+38
-9
lines changed

1 file changed

+38
-9
lines changed

pandas/io/formats/style.py

+38-9
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ def highlight_null(self, null_color='red'):
863863
return self
864864

865865
def background_gradient(self, cmap='PuBu', low=0, high=0, axis=0,
866-
subset=None):
866+
subset=None, text_color=0.2):
867867
"""
868868
Color the background in a gradient according to
869869
the data in each column (optionally row).
@@ -879,26 +879,30 @@ def background_gradient(self, cmap='PuBu', low=0, high=0, axis=0,
879879
1 or 'columns' for columnwise, 0 or 'index' for rowwise
880880
subset: IndexSlice
881881
a valid slice for ``data`` to limit the style application to
882+
text_color: float or int
883+
luminance threshold for determining text color. Facilitates text
884+
visibility across varying background colors. From 0 to 1.
885+
0 = all text is dark colored, 1 = all text is light colored.
882886
883887
Returns
884888
-------
885889
self : Styler
886890
887891
Notes
888892
-----
889-
Tune ``low`` and ``high`` to keep the text legible by
890-
not using the entire range of the color map. These extend
891-
the range of the data by ``low * (x.max() - x.min())``
892-
and ``high * (x.max() - x.min())`` before normalizing.
893+
Set ``text_color`` or tune ``low`` and ``high`` to keep the text
894+
legible by not using the entire range of the color map. The range of
895+
the data is extended by ``low * (x.max() - x.min())`` and ``high *
896+
(x.max() - x.min())`` before normalizing.
893897
"""
894898
subset = _maybe_numeric_slice(self.data, subset)
895899
subset = _non_reducing_slice(subset)
896900
self.apply(self._background_gradient, cmap=cmap, subset=subset,
897-
axis=axis, low=low, high=high)
901+
axis=axis, low=low, high=high, text_color=text_color)
898902
return self
899903

900904
@staticmethod
901-
def _background_gradient(s, cmap='PuBu', low=0, high=0):
905+
def _background_gradient(s, cmap='PuBu', low=0, high=0, text_color=0.2):
902906
"""Color background in a range according to the data."""
903907
with _mpl(Styler.background_gradient) as (plt, colors):
904908
rng = s.max() - s.min()
@@ -909,8 +913,33 @@ def _background_gradient(s, cmap='PuBu', low=0, high=0):
909913
# https://github.com/matplotlib/matplotlib/issues/5427
910914
normed = norm(s.values)
911915
c = [colors.rgb2hex(x) for x in plt.cm.get_cmap(cmap)(normed)]
912-
return ['background-color: {color}'.format(color=color)
913-
for color in c]
916+
if (not isinstance(text_color, (float, int)) or
917+
not 0 <= text_color <= 1):
918+
msg = "`text_color` must be a value from 0 to 1."
919+
raise ValueError(msg)
920+
921+
def relative_luminance(color):
922+
"""Calculate the relative luminance of a color according to W3C
923+
standards, https://www.w3.org/WAI/GL/wiki/Relative_luminance
924+
Parameters
925+
----------
926+
color : matplotlib color. Hex code, rgb-tuple, or
927+
HTML color name.
928+
Returns
929+
-------
930+
luminance : float between 0 and 1
931+
"""
932+
rgb = colors.colorConverter.to_rgba_array(color)[:, :3]
933+
rgb = np.where(rgb <= .03928, rgb / 12.92,
934+
((rgb + .055) / 1.055) ** 2.4)
935+
lum = rgb.dot([.2126, .7152, .0722])
936+
return lum.item()
937+
938+
text_colors = ['#f1f1f1' if relative_luminance(x) < text_color
939+
else '#000000' for x in c]
940+
941+
return ['background-color: {color};color: {tc}'.format(
942+
color=color, tc=tc) for color, tc in zip(c, text_colors)]
914943

915944
def set_properties(self, subset=None, **kwargs):
916945
"""

0 commit comments

Comments
 (0)