@@ -2042,75 +2042,16 @@ def set_properties(self, subset: Subset | None = None, **kwargs) -> Styler:
2042
2042
values = "" .join (f"{ p } : { v } ;" for p , v in kwargs .items ())
2043
2043
return self .applymap (lambda x : values , subset = subset )
2044
2044
2045
- @staticmethod
2046
- def _bar (
2047
- s ,
2048
- align : str ,
2049
- colors : list [str ],
2050
- width : float = 100 ,
2051
- vmin : float | None = None ,
2052
- vmax : float | None = None ,
2053
- ):
2054
- """
2055
- Draw bar chart in dataframe cells.
2056
- """
2057
- # Get input value range.
2058
- smin = np .nanmin (s .to_numpy ()) if vmin is None else vmin
2059
- smax = np .nanmax (s .to_numpy ()) if vmax is None else vmax
2060
- if align == "mid" :
2061
- smin = min (0 , smin )
2062
- smax = max (0 , smax )
2063
- elif align == "zero" :
2064
- # For "zero" mode, we want the range to be symmetrical around zero.
2065
- smax = max (abs (smin ), abs (smax ))
2066
- smin = - smax
2067
- # Transform to percent-range of linear-gradient
2068
- normed = width * (s .to_numpy (dtype = float ) - smin ) / (smax - smin + 1e-12 )
2069
- zero = - width * smin / (smax - smin + 1e-12 )
2070
-
2071
- def css_bar (start : float , end : float , color : str ) -> str :
2072
- """
2073
- Generate CSS code to draw a bar from start to end.
2074
- """
2075
- css = "width: 10em; height: 80%;"
2076
- if end > start :
2077
- css += "background: linear-gradient(90deg,"
2078
- if start > 0 :
2079
- css += f" transparent { start :.1f} %, { color } { start :.1f} %, "
2080
- e = min (end , width )
2081
- css += f"{ color } { e :.1f} %, transparent { e :.1f} %)"
2082
- return css
2083
-
2084
- def css (x ):
2085
- if pd .isna (x ):
2086
- return ""
2087
-
2088
- # avoid deprecated indexing `colors[x > zero]`
2089
- color = colors [1 ] if x > zero else colors [0 ]
2090
-
2091
- if align == "left" :
2092
- return css_bar (0 , x , color )
2093
- else :
2094
- return css_bar (min (x , zero ), max (x , zero ), color )
2095
-
2096
- if s .ndim == 1 :
2097
- return [css (x ) for x in normed ]
2098
- else :
2099
- return DataFrame (
2100
- [[css (x ) for x in row ] for row in normed ],
2101
- index = s .index ,
2102
- columns = s .columns ,
2103
- )
2104
-
2105
2045
def bar (
2106
2046
self ,
2107
2047
subset : Subset | None = None ,
2108
2048
axis : Axis | None = 0 ,
2109
2049
color = "#d65f5f" ,
2110
2050
width : float = 100 ,
2111
- align : str = "left " ,
2051
+ align : str | float | int | Callable = "mid " ,
2112
2052
vmin : float | None = None ,
2113
2053
vmax : float | None = None ,
2054
+ props : str = "width: 10em;" ,
2114
2055
) -> Styler :
2115
2056
"""
2116
2057
Draw bar chart in the cell backgrounds.
@@ -2131,16 +2072,26 @@ def bar(
2131
2072
first element is the color_negative and the second is the
2132
2073
color_positive (eg: ['#d65f5f', '#5fba7d']).
2133
2074
width : float, default 100
2134
- A number between 0 or 100. The largest value will cover `width`
2135
- percent of the cell's width.
2136
- align : {'left', 'zero',' mid'}, default 'left'
2137
- How to align the bars with the cells.
2138
-
2139
- - 'left' : the min value starts at the left of the cell.
2075
+ The percentage of the cell, measured from the left, in which to draw the
2076
+ bars, in [0, 100].
2077
+ align : str, int, float, callable, default 'mid'
2078
+ How to align the bars within the cells relative to a width adjusted center.
2079
+ If string must be one of:
2080
+
2081
+ - 'left' : bars are drawn rightwards from the minimum data value.
2082
+ - 'right' : bars are drawn leftwards from the maximum data value.
2140
2083
- 'zero' : a value of zero is located at the center of the cell.
2141
- - 'mid' : the center of the cell is at (max-min)/2, or
2142
- if values are all negative (positive) the zero is aligned
2143
- at the right (left) of the cell.
2084
+ - 'mid' : a value of (max-min)/2 is located at the center of the cell,
2085
+ or if all values are negative (positive) the zero is
2086
+ aligned at the right (left) of the cell.
2087
+ - 'mean' : the mean value of the data is located at the center of the cell.
2088
+
2089
+ If a float or integer is given this will indicate the center of the cell.
2090
+
2091
+ If a callable should take a 1d or 2d array and return a scalar.
2092
+
2093
+ .. versionchanged:: 1.4.0
2094
+
2144
2095
vmin : float, optional
2145
2096
Minimum bar value, defining the left hand limit
2146
2097
of the bar drawing range, lower values are clipped to `vmin`.
@@ -2149,14 +2100,16 @@ def bar(
2149
2100
Maximum bar value, defining the right hand limit
2150
2101
of the bar drawing range, higher values are clipped to `vmax`.
2151
2102
When None (default): the maximum value of the data will be used.
2103
+ props : str, optional
2104
+ The base CSS of the cell that is extended to add the bar chart. Defaults to
2105
+ `"width: 10em;"`
2106
+
2107
+ .. versionadded:: 1.4.0
2152
2108
2153
2109
Returns
2154
2110
-------
2155
2111
self : Styler
2156
2112
"""
2157
- if align not in ("left" , "zero" , "mid" ):
2158
- raise ValueError ("`align` must be one of {'left', 'zero',' mid'}" )
2159
-
2160
2113
if not (is_list_like (color )):
2161
2114
color = [color , color ]
2162
2115
elif len (color ) == 1 :
@@ -2172,14 +2125,15 @@ def bar(
2172
2125
subset = self .data .select_dtypes (include = np .number ).columns
2173
2126
2174
2127
self .apply (
2175
- self . _bar ,
2128
+ _bar ,
2176
2129
subset = subset ,
2177
2130
axis = axis ,
2178
2131
align = align ,
2179
2132
colors = color ,
2180
- width = width ,
2133
+ width = width / 100 ,
2181
2134
vmin = vmin ,
2182
2135
vmax = vmax ,
2136
+ base_css = props ,
2183
2137
)
2184
2138
2185
2139
return self
@@ -2830,3 +2784,166 @@ def _highlight_between(
2830
2784
else np .full (data .shape , True , dtype = bool )
2831
2785
)
2832
2786
return np .where (g_left & l_right , props , "" )
2787
+
2788
+
2789
+ def _bar (
2790
+ data : FrameOrSeries ,
2791
+ align : str | float | int | Callable ,
2792
+ colors : list [str ],
2793
+ width : float ,
2794
+ vmin : float | None ,
2795
+ vmax : float | None ,
2796
+ base_css : str ,
2797
+ ):
2798
+ """
2799
+ Draw bar chart in data cells using HTML CSS linear gradient.
2800
+
2801
+ Parameters
2802
+ ----------
2803
+ data : Series or DataFrame
2804
+ Underling subset of Styler data on which operations are performed.
2805
+ align : str in {"left", "right", "mid", "zero", "mean"}, int, float, callable
2806
+ Method for how bars are structured or scalar value of centre point.
2807
+ colors : list-like of str
2808
+ Two listed colors as string in valid CSS.
2809
+ width : float in [0,1]
2810
+ The percentage of the cell, measured from left, where drawn bars will reside.
2811
+ vmin : float, optional
2812
+ Overwrite the minimum value of the window.
2813
+ vmax : float, optional
2814
+ Overwrite the maximum value of the window.
2815
+ base_css : str
2816
+ Additional CSS that is included in the cell before bars are drawn.
2817
+ """
2818
+
2819
+ def css_bar (start : float , end : float , color : str ) -> str :
2820
+ """
2821
+ Generate CSS code to draw a bar from start to end in a table cell.
2822
+
2823
+ Uses linear-gradient.
2824
+
2825
+ Parameters
2826
+ ----------
2827
+ start : float
2828
+ Relative positional start of bar coloring in [0,1]
2829
+ end : float
2830
+ Relative positional end of the bar coloring in [0,1]
2831
+ color : str
2832
+ CSS valid color to apply.
2833
+
2834
+ Returns
2835
+ -------
2836
+ str : The CSS applicable to the cell.
2837
+
2838
+ Notes
2839
+ -----
2840
+ Uses ``base_css`` from outer scope.
2841
+ """
2842
+ cell_css = base_css
2843
+ if end > start :
2844
+ cell_css += "background: linear-gradient(90deg,"
2845
+ if start > 0 :
2846
+ cell_css += f" transparent { start * 100 :.1f} %, { color } { start * 100 :.1f} %,"
2847
+ cell_css += f" { color } { end * 100 :.1f} %, transparent { end * 100 :.1f} %)"
2848
+ return cell_css
2849
+
2850
+ def css_calc (x , left : float , right : float , align : str ):
2851
+ """
2852
+ Return the correct CSS for bar placement based on calculated values.
2853
+
2854
+ Parameters
2855
+ ----------
2856
+ x : float
2857
+ Value which determines the bar placement.
2858
+ left : float
2859
+ Value marking the left side of calculation, usually minimum of data.
2860
+ right : float
2861
+ Value marking the right side of the calculation, usually maximum of data
2862
+ (left < right).
2863
+ align : {"left", "right", "zero", "mid"}
2864
+ How the bars will be positioned.
2865
+ "left", "right", "zero" can be used with any values for ``left``, ``right``.
2866
+ "mid" can only be used where ``left <= 0`` and ``right >= 0``.
2867
+ "zero" is used to specify a center when all values ``x``, ``left``,
2868
+ ``right`` are translated, e.g. by say a mean or median.
2869
+
2870
+ Returns
2871
+ -------
2872
+ str : Resultant CSS with linear gradient.
2873
+
2874
+ Notes
2875
+ -----
2876
+ Uses ``colors`` and ``width`` from outer scope.
2877
+ """
2878
+ if pd .isna (x ):
2879
+ return base_css
2880
+
2881
+ color = colors [0 ] if x < 0 else colors [1 ]
2882
+ x = left if x < left else x
2883
+ x = right if x > right else x # trim data if outside of the window
2884
+
2885
+ start : float = 0
2886
+ end : float = 1
2887
+
2888
+ if align == "left" :
2889
+ # all proportions are measured from the left side between left and right
2890
+ end = (x - left ) / (right - left )
2891
+
2892
+ elif align == "right" :
2893
+ # all proportions are measured from the right side between left and right
2894
+ start = (x - left ) / (right - left )
2895
+
2896
+ else :
2897
+ z_frac : float = 0.5 # location of zero based on the left-right range
2898
+ if align == "zero" :
2899
+ # all proportions are measured from the center at zero
2900
+ limit : float = max (abs (left ), abs (right ))
2901
+ left , right = - limit , limit
2902
+ elif align == "mid" :
2903
+ # bars drawn from zero either leftwards or rightwards with center at mid
2904
+ mid : float = (left + right ) / 2
2905
+ z_frac = (
2906
+ - mid / (right - left ) + 0.5 if mid < 0 else - left / (right - left )
2907
+ )
2908
+
2909
+ if x < 0 :
2910
+ start , end = (x - left ) / (right - left ), z_frac
2911
+ else :
2912
+ start , end = z_frac , (x - left ) / (right - left )
2913
+
2914
+ return css_bar (start * width , end * width , color )
2915
+
2916
+ values = data .to_numpy ()
2917
+ left = np .nanmin (values ) if vmin is None else vmin
2918
+ right = np .nanmax (values ) if vmax is None else vmax
2919
+ z : float = 0 # adjustment to translate data
2920
+
2921
+ if align == "mid" :
2922
+ if left >= 0 : # "mid" is documented to act as "left" if all values positive
2923
+ align , left = "left" , 0 if vmin is None else vmin
2924
+ elif right <= 0 : # "mid" is documented to act as "right" if all values negative
2925
+ align , right = "right" , 0 if vmax is None else vmax
2926
+ elif align == "mean" :
2927
+ z , align = np .nanmean (values ), "zero"
2928
+ elif callable (align ):
2929
+ z , align = align (values ), "zero"
2930
+ elif isinstance (align , (float , int )):
2931
+ z , align = float (align ), "zero"
2932
+ elif not (align == "left" or align == "right" or align == "zero" ):
2933
+ raise ValueError (
2934
+ "`align` should be in {'left', 'right', 'mid', 'mean', 'zero'} or be a "
2935
+ "value defining the center line or a callable that returns a float"
2936
+ )
2937
+
2938
+ assert isinstance (align , str ) # mypy: should now be in [left, right, mid, zero]
2939
+ if data .ndim == 1 :
2940
+ return [css_calc (x - z , left - z , right - z , align ) for x in values ]
2941
+ else :
2942
+ return DataFrame (
2943
+ [
2944
+ [css_calc (x - z , left - z , right - z , align ) for x in row ]
2945
+ for row in values
2946
+ ],
2947
+ index = data .index ,
2948
+ columns = data .columns ,
2949
+ )
0 commit comments