@@ -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_threshold = 0.408 ):
867867 """
868868 Color the background in a gradient according to
869869 the data in each column (optionally row).
@@ -879,26 +879,39 @@ 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_threshold: 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.
886+
887+ .. versionadded:: 0.24.0
882888
883889 Returns
884890 -------
885891 self : Styler
886892
887893 Notes
888894 -----
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.
895+ Set ``text_color_threshold`` or tune ``low`` and ``high`` to keep the
896+ text legible by not using the entire range of the color map. The range
897+ of the data is extended by ``low * (x.max() - x.min())`` and ``high *
898+ (x.max() - x.min())`` before normalizing.
899+
900+ Raises
901+ ------
902+ ValueError
903+ If ``text_color_threshold`` is not a value from 0 to 1.
893904 """
894905 subset = _maybe_numeric_slice (self .data , subset )
895906 subset = _non_reducing_slice (subset )
896907 self .apply (self ._background_gradient , cmap = cmap , subset = subset ,
897- axis = axis , low = low , high = high )
908+ axis = axis , low = low , high = high ,
909+ text_color_threshold = text_color_threshold )
898910 return self
899911
900912 @staticmethod
901- def _background_gradient (s , cmap = 'PuBu' , low = 0 , high = 0 ):
913+ def _background_gradient (s , cmap = 'PuBu' , low = 0 , high = 0 ,
914+ text_color_threshold = 0.408 ):
902915 """Color background in a range according to the data."""
903916 with _mpl (Styler .background_gradient ) as (plt , colors ):
904917 rng = s .max () - s .min ()
@@ -909,8 +922,39 @@ def _background_gradient(s, cmap='PuBu', low=0, high=0):
909922 # https://github.com/matplotlib/matplotlib/issues/5427
910923 normed = norm (s .values )
911924 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 ]
925+ if (not isinstance (text_color_threshold , (float , int )) or
926+ not 0 <= text_color_threshold <= 1 ):
927+ msg = "`text_color_threshold` must be a value from 0 to 1."
928+ raise ValueError (msg )
929+
930+ def relative_luminance (color ):
931+ """
932+ Calculate relative luminance of a color.
933+
934+ The calculation adheres to the W3C standards
935+ (https://www.w3.org/WAI/GL/wiki/Relative_luminance)
936+
937+ Parameters
938+ ----------
939+ color : matplotlib color
940+ Hex code, rgb-tuple, or HTML color name.
941+
942+ Returns
943+ -------
944+ float
945+ The relative luminance as a value from 0 to 1
946+ """
947+ rgb = colors .colorConverter .to_rgba_array (color )[:, :3 ]
948+ rgb = np .where (rgb <= .03928 , rgb / 12.92 ,
949+ ((rgb + .055 ) / 1.055 ) ** 2.4 )
950+ lum = rgb .dot ([.2126 , .7152 , .0722 ])
951+ return lum .item ()
952+
953+ text_colors = ['#f1f1f1' if relative_luminance (x ) <
954+ text_color_threshold else '#000000' for x in c ]
955+
956+ return ['background-color: {color};color: {tc}' .format (
957+ color = color , tc = tc ) for color , tc in zip (c , text_colors )]
914958
915959 def set_properties (self , subset = None , ** kwargs ):
916960 """
0 commit comments