@@ -984,126 +984,63 @@ def set_properties(self, subset=None, **kwargs):
984
984
return self .applymap (f , subset = subset )
985
985
986
986
@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 ])
1066
1023
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 ]
1077
1026
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
+ )
1094
1031
1095
1032
def bar (self , subset = None , axis = 0 , color = '#d65f5f' , width = 100 ,
1096
1033
align = 'left' ):
1097
1034
"""
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
1100
1037
Excludes non-numeric data by default.
1101
1038
1102
1039
Parameters
1103
1040
----------
1104
1041
subset: IndexSlice, default None
1105
1042
a valid slice for ``data`` to limit the style application to
1106
- axis: int
1043
+ axis: int or None
1107
1044
color: str or 2-tuple/list
1108
1045
If a str is passed, the color is the same for both
1109
1046
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,
1125
1062
-------
1126
1063
self : Styler
1127
1064
"""
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'}" )
1132
1067
1133
- if not (is_list_like (color )):
1068
+ if not (is_list_like (color )):
1134
1069
color = [color , color ]
1135
1070
elif len (color ) == 1 :
1136
1071
color = [color [0 ], color [0 ]]
1137
1072
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'])" )
1142
1076
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 )
1155
1081
1156
1082
return self
1157
1083
0 commit comments