@@ -993,132 +993,67 @@ def set_properties(self, subset=None, **kwargs):
993
993
return self .applymap (f , subset = subset )
994
994
995
995
@staticmethod
996
- def _bar_left (s , color , width , base ):
997
- """
998
- The minimum value is aligned at the left of the cell
999
- Parameters
1000
- ----------
1001
- color: 2-tuple/list, of [``color_negative``, ``color_positive``]
1002
- width: float
1003
- A number between 0 or 100. The largest value will cover ``width``
1004
- percent of the cell's width
1005
- base: str
1006
- The base css format of the cell, e.g.:
1007
- ``base = 'width: 10em; height: 80%;'``
1008
- Returns
1009
- -------
1010
- self : Styler
1011
- """
1012
- normed = width * (s - s .min ()) / (s .max () - s .min ())
1013
- zero_normed = width * (0 - s .min ()) / (s .max () - s .min ())
1014
- attrs = (base + 'background: linear-gradient(90deg,{c} {w:.1f}%, '
1015
- 'transparent 0%)' )
1016
-
1017
- return [base if x == 0 else attrs .format (c = color [0 ], w = x )
1018
- if x < zero_normed
1019
- else attrs .format (c = color [1 ], w = x ) if x >= zero_normed
1020
- else base for x in normed ]
1021
-
1022
- @staticmethod
1023
- def _bar_center_zero (s , color , width , base ):
1024
- """
1025
- Creates a bar chart where the zero is centered in the cell
1026
- Parameters
1027
- ----------
1028
- color: 2-tuple/list, of [``color_negative``, ``color_positive``]
1029
- width: float
1030
- A number between 0 or 100. The largest value will cover ``width``
1031
- percent of the cell's width
1032
- base: str
1033
- The base css format of the cell, e.g.:
1034
- ``base = 'width: 10em; height: 80%;'``
1035
- Returns
1036
- -------
1037
- self : Styler
1038
- """
1039
-
1040
- # Either the min or the max should reach the edge
1041
- # (50%, centered on zero)
1042
- m = max (abs (s .min ()), abs (s .max ()))
1043
-
1044
- normed = s * 50 * width / (100.0 * m )
1045
-
1046
- attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%'
1047
- ', transparent {w:.1f}%, {c} {w:.1f}%, '
1048
- '{c} 50%, transparent 50%)' )
1049
-
1050
- attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%'
1051
- ', transparent 50%, {c} 50%, {c} {w:.1f}%, '
1052
- 'transparent {w:.1f}%)' )
1053
-
1054
- return [attrs_pos .format (c = color [1 ], w = (50 + x )) if x >= 0
1055
- else attrs_neg .format (c = color [0 ], w = (50 + x ))
1056
- for x in normed ]
996
+ def _bar (s , align , colors , width = 100 ):
997
+ """Draw bar chart in dataframe cells"""
998
+
999
+ # Get input value range.
1000
+ smin = s .values .min ()
1001
+ smax = s .values .max ()
1002
+ if align == 'mid' :
1003
+ smin = min (0 , smin )
1004
+ smax = max (0 , smax )
1005
+ elif align == 'zero' :
1006
+ # For "zero" mode, we want the range to be symmetrical around zero.
1007
+ smax = max (abs (smin ), abs (smax ))
1008
+ smin = - smax
1009
+ # Transform to percent-range of linear-gradient
1010
+ normed = width * (s .values - smin ) / (smax - smin + 1e-12 )
1011
+ zero = - width * smin / (smax - smin + 1e-12 )
1012
+
1013
+ def css_bar (start , end , color ):
1014
+ """Generate CSS code to draw a bar from start to end."""
1015
+ css = 'width: 10em; height: 80%;'
1016
+ if end > start :
1017
+ css += 'background: linear-gradient(90deg,'
1018
+ if start > 0 :
1019
+ css += ' transparent {s:.1f}%, {c} {s:.1f}%, ' .format (
1020
+ s = start , c = color
1021
+ )
1022
+ css += '{c} {e:.1f}%, transparent {e:.1f}%)' .format (
1023
+ e = end , c = color ,
1024
+ )
1025
+ return css
1057
1026
1058
- @staticmethod
1059
- def _bar_center_mid (s , color , width , base ):
1060
- """
1061
- Creates a bar chart where the midpoint is centered in the cell
1062
- Parameters
1063
- ----------
1064
- color: 2-tuple/list, of [``color_negative``, ``color_positive``]
1065
- width: float
1066
- A number between 0 or 100. The largest value will cover ``width``
1067
- percent of the cell's width
1068
- base: str
1069
- The base css format of the cell, e.g.:
1070
- ``base = 'width: 10em; height: 80%;'``
1071
- Returns
1072
- -------
1073
- self : Styler
1074
- """
1027
+ def css (x ):
1028
+ if align == 'left' :
1029
+ return css_bar (0 , x , colors [x > zero ])
1030
+ else :
1031
+ return css_bar (min (x , zero ), max (x , zero ), colors [x > zero ])
1075
1032
1076
- if s .min () >= 0 :
1077
- # In this case, we place the zero at the left, and the max() should
1078
- # be at width
1079
- zero = 0.0
1080
- slope = width / s .max ()
1081
- elif s .max () <= 0 :
1082
- # In this case, we place the zero at the right, and the min()
1083
- # should be at 100-width
1084
- zero = 100.0
1085
- slope = width / - s .min ()
1033
+ if s .ndim == 1 :
1034
+ return [css (x ) for x in normed ]
1086
1035
else :
1087
- slope = width / (s .max () - s .min ())
1088
- zero = (100.0 + width ) / 2.0 - slope * s .max ()
1089
-
1090
- normed = zero + slope * s
1091
-
1092
- attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%'
1093
- ', transparent {w:.1f}%, {c} {w:.1f}%, '
1094
- '{c} {zero:.1f}%, transparent {zero:.1f}%)' )
1095
-
1096
- attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%'
1097
- ', transparent {zero:.1f}%, {c} {zero:.1f}%, '
1098
- '{c} {w:.1f}%, transparent {w:.1f}%)' )
1099
-
1100
- return [attrs_pos .format (c = color [1 ], zero = zero , w = x ) if x > zero
1101
- else attrs_neg .format (c = color [0 ], zero = zero , w = x )
1102
- for x in normed ]
1036
+ return pd .DataFrame (
1037
+ [[css (x ) for x in row ] for row in normed ],
1038
+ index = s .index , columns = s .columns
1039
+ )
1103
1040
1104
1041
def bar (self , subset = None , axis = 0 , color = '#d65f5f' , width = 100 ,
1105
1042
align = 'left' ):
1106
1043
"""
1107
- Color the background ``color`` proportional to the values in each
1108
- column.
1109
- Excludes non-numeric data by default.
1044
+ Draw bar chart in the cell backgrounds.
1110
1045
1111
1046
Parameters
1112
1047
----------
1113
- subset: IndexSlice, default None
1048
+ subset: IndexSlice, optional
1114
1049
a valid slice for ``data`` to limit the style application to
1115
- axis: int
1050
+ axis: int, default 0, meaning column-wise
1116
1051
color: str or 2-tuple/list
1117
1052
If a str is passed, the color is the same for both
1118
1053
negative and positive numbers. If 2-tuple/list is used, the
1119
1054
first element is the color_negative and the second is the
1120
1055
color_positive (eg: ['#d65f5f', '#5fba7d'])
1121
- width: float
1056
+ width: float, default 100
1122
1057
A number between 0 or 100. The largest value will cover ``width``
1123
1058
percent of the cell's width
1124
1059
align : {'left', 'zero',' mid'}, default 'left'
@@ -1134,33 +1069,22 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
1134
1069
-------
1135
1070
self : Styler
1136
1071
"""
1137
- subset = _maybe_numeric_slice (self .data , subset )
1138
- subset = _non_reducing_slice (subset )
1139
-
1140
- base = 'width: 10em; height: 80%;'
1072
+ if align not in ('left' , 'zero' , 'mid' ):
1073
+ raise ValueError ("`align` must be one of {'left', 'zero',' mid'}" )
1141
1074
1142
- if not (is_list_like (color )):
1075
+ if not (is_list_like (color )):
1143
1076
color = [color , color ]
1144
1077
elif len (color ) == 1 :
1145
1078
color = [color [0 ], color [0 ]]
1146
1079
elif len (color ) > 2 :
1147
- msg = ("Must pass `color` as string or a list-like"
1148
- " of length 2: [`color_negative`, `color_positive`]\n "
1149
- "(eg: color=['#d65f5f', '#5fba7d'])" )
1150
- raise ValueError (msg )
1080
+ raise ValueError ("`color` must be string or a list-like"
1081
+ " of length 2: [`color_neg`, `color_pos`]"
1082
+ " (eg: color=['#d65f5f', '#5fba7d'])" )
1151
1083
1152
- if align == 'left' :
1153
- self .apply (self ._bar_left , subset = subset , axis = axis , color = color ,
1154
- width = width , base = base )
1155
- elif align == 'zero' :
1156
- self .apply (self ._bar_center_zero , subset = subset , axis = axis ,
1157
- color = color , width = width , base = base )
1158
- elif align == 'mid' :
1159
- self .apply (self ._bar_center_mid , subset = subset , axis = axis ,
1160
- color = color , width = width , base = base )
1161
- else :
1162
- msg = ("`align` must be one of {'left', 'zero',' mid'}" )
1163
- raise ValueError (msg )
1084
+ subset = _maybe_numeric_slice (self .data , subset )
1085
+ subset = _non_reducing_slice (subset )
1086
+ self .apply (self ._bar , subset = subset , axis = axis ,
1087
+ align = align , colors = color , width = width )
1164
1088
1165
1089
return self
1166
1090
0 commit comments