@@ -984,126 +984,63 @@ def set_properties(self, subset=None, **kwargs):
984984 return self .applymap (f , subset = subset )
985985
986986 @staticmethod
987- def _bar_left (s , color , width , base ):
988- """
989- The minimum value is aligned at the left of the cell
990- Parameters
991- ----------
992- color: 2-tuple/list, of [``color_negative``, ``color_positive``]
993- width: float
994- A number between 0 or 100. The largest value will cover ``width``
995- percent of the cell's width
996- base: str
997- The base css format of the cell, e.g.:
998- ``base = 'width: 10em; height: 80%;'``
999- Returns
1000- -------
1001- self : Styler
1002- """
1003- normed = width * (s - s .min ()) / (s .max () - s .min ())
1004- zero_normed = width * (0 - s .min ()) / (s .max () - s .min ())
1005- attrs = (base + 'background: linear-gradient(90deg,{c} {w:.1f}%, '
1006- 'transparent 0%)' )
1007-
1008- return [base if x == 0 else attrs .format (c = color [0 ], w = x )
1009- if x < zero_normed
1010- else attrs .format (c = color [1 ], w = x ) if x >= zero_normed
1011- else base for x in normed ]
1012-
1013- @staticmethod
1014- def _bar_center_zero (s , color , width , base ):
1015- """
1016- Creates a bar chart where the zero is centered in the cell
1017- Parameters
1018- ----------
1019- color: 2-tuple/list, of [``color_negative``, ``color_positive``]
1020- width: float
1021- A number between 0 or 100. The largest value will cover ``width``
1022- percent of the cell's width
1023- base: str
1024- The base css format of the cell, e.g.:
1025- ``base = 'width: 10em; height: 80%;'``
1026- Returns
1027- -------
1028- self : Styler
1029- """
1030-
1031- # Either the min or the max should reach the edge
1032- # (50%, centered on zero)
1033- m = max (abs (s .min ()), abs (s .max ()))
1034-
1035- normed = s * 50 * width / (100.0 * m )
1036-
1037- attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%'
1038- ', transparent {w:.1f}%, {c} {w:.1f}%, '
1039- '{c} 50%, transparent 50%)' )
1040-
1041- attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%'
1042- ', transparent 50%, {c} 50%, {c} {w:.1f}%, '
1043- 'transparent {w:.1f}%)' )
1044-
1045- return [attrs_pos .format (c = color [1 ], w = (50 + x )) if x >= 0
1046- else attrs_neg .format (c = color [0 ], w = (50 + x ))
1047- for x in normed ]
1048-
1049- @staticmethod
1050- def _bar_center_mid (s , color , width , base ):
1051- """
1052- Creates a bar chart where the midpoint is centered in the cell
1053- Parameters
1054- ----------
1055- color: 2-tuple/list, of [``color_negative``, ``color_positive``]
1056- width: float
1057- A number between 0 or 100. The largest value will cover ``width``
1058- percent of the cell's width
1059- base: str
1060- The base css format of the cell, e.g.:
1061- ``base = 'width: 10em; height: 80%;'``
1062- Returns
1063- -------
1064- self : Styler
1065- """
987+ def _bar (s , align , color = '#d65f5f' , width = 100 ):
988+ """Draw bar chart in dataframe cells"""
989+
990+ # Get input value range.
991+ smin = s .values .min ()
992+ smax = s .values .max ()
993+ if align == 'mid' :
994+ smin = min (0 , smin )
995+ smax = max (0 , smax )
996+ elif align == 'zero' :
997+ # For "zero" mode, we want the range to be symmetrical around zero.
998+ smax = max (abs (smin ), abs (smax ))
999+ smin = - smax
1000+ # Transform to percent-range of linear-gradient
1001+ normed = width * (s .values - smin ) / (smax - smin + 1e-12 )
1002+ zero = - width * smin / (smax - smin + 1e-12 )
1003+
1004+ def css_bar (start , end , color ):
1005+ """Generate CSS code to draw a bar from start to end."""
1006+ css = 'width: 10em; height: 80%;'
1007+ if end > start :
1008+ css += 'background: linear-gradient(90deg,'
1009+ if start > 0 :
1010+ css += ' transparent {s:.1f}%, {c} {s:.1f}%, ' .format (
1011+ s = start , c = color
1012+ )
1013+ css += '{c} {e:.1f}%, transparent {e:.1f}%)' .format (
1014+ e = end , c = color ,
1015+ )
1016+ return css
1017+
1018+ def css (x ):
1019+ if align == 'left' :
1020+ return css_bar (0 , x , color [x > zero ])
1021+ else :
1022+ return css_bar (min (x , zero ), max (x , zero ), color [x > zero ])
10661023
1067- if s .min () >= 0 :
1068- # In this case, we place the zero at the left, and the max() should
1069- # be at width
1070- zero = 0.0
1071- slope = width / s .max ()
1072- elif s .max () <= 0 :
1073- # In this case, we place the zero at the right, and the min()
1074- # should be at 100-width
1075- zero = 100.0
1076- slope = width / - s .min ()
1024+ if s .ndim == 1 :
1025+ return [css (x ) for x in normed ]
10771026 else :
1078- slope = width / (s .max () - s .min ())
1079- zero = (100.0 + width ) / 2.0 - slope * s .max ()
1080-
1081- normed = zero + slope * s
1082-
1083- attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%'
1084- ', transparent {w:.1f}%, {c} {w:.1f}%, '
1085- '{c} {zero:.1f}%, transparent {zero:.1f}%)' )
1086-
1087- attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%'
1088- ', transparent {zero:.1f}%, {c} {zero:.1f}%, '
1089- '{c} {w:.1f}%, transparent {w:.1f}%)' )
1090-
1091- return [attrs_pos .format (c = color [1 ], zero = zero , w = x ) if x > zero
1092- else attrs_neg .format (c = color [0 ], zero = zero , w = x )
1093- for x in normed ]
1027+ return pd .DataFrame (
1028+ [[css (x ) for x in row ] for row in normed ],
1029+ index = s .index , columns = s .columns
1030+ )
10941031
10951032 def bar (self , subset = None , axis = 0 , color = '#d65f5f' , width = 100 ,
10961033 align = 'left' ):
10971034 """
1098- Color the background ``color`` proportional to the values in each
1099- column.
1035+ Draw bars in the cell backgrounds, with a width proportional to
1036+ the values
11001037 Excludes non-numeric data by default.
11011038
11021039 Parameters
11031040 ----------
11041041 subset: IndexSlice, default None
11051042 a valid slice for ``data`` to limit the style application to
1106- axis: int
1043+ axis: int or None
11071044 color: str or 2-tuple/list
11081045 If a str is passed, the color is the same for both
11091046 negative and positive numbers. If 2-tuple/list is used, the
@@ -1125,33 +1062,22 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
11251062 -------
11261063 self : Styler
11271064 """
1128- subset = _maybe_numeric_slice (self .data , subset )
1129- subset = _non_reducing_slice (subset )
1130-
1131- base = 'width: 10em; height: 80%;'
1065+ if align not in ('left' , 'zero' , 'mid' ):
1066+ raise ValueError ("`align` must be one of {'left', 'zero',' mid'}" )
11321067
1133- if not (is_list_like (color )):
1068+ if not (is_list_like (color )):
11341069 color = [color , color ]
11351070 elif len (color ) == 1 :
11361071 color = [color [0 ], color [0 ]]
11371072 elif len (color ) > 2 :
1138- msg = ("Must pass `color` as string or a list-like"
1139- " of length 2: [`color_negative`, `color_positive`]\n "
1140- "(eg: color=['#d65f5f', '#5fba7d'])" )
1141- raise ValueError (msg )
1073+ raise ValueError ("`color` must be string or a list-like"
1074+ " of length 2: [`color_neg`, `color_pos`]"
1075+ " (eg: color=['#d65f5f', '#5fba7d'])" )
11421076
1143- if align == 'left' :
1144- self .apply (self ._bar_left , subset = subset , axis = axis , color = color ,
1145- width = width , base = base )
1146- elif align == 'zero' :
1147- self .apply (self ._bar_center_zero , subset = subset , axis = axis ,
1148- color = color , width = width , base = base )
1149- elif align == 'mid' :
1150- self .apply (self ._bar_center_mid , subset = subset , axis = axis ,
1151- color = color , width = width , base = base )
1152- else :
1153- msg = ("`align` must be one of {'left', 'zero',' mid'}" )
1154- raise ValueError (msg )
1077+ subset = _maybe_numeric_slice (self .data , subset )
1078+ subset = _non_reducing_slice (subset )
1079+ self .apply (self ._bar , subset = subset , axis = axis ,
1080+ align = align , color = color , width = width )
11551081
11561082 return self
11571083
0 commit comments