diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 26dac44f0d15f..cb2a59860783f 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -31,7 +31,7 @@ Other enhancements ^^^^^^^^^^^^^^^^^^ - Add support for assigning values to ``by`` argument in :meth:`DataFrame.plot.hist` and :meth:`DataFrame.plot.box` (:issue:`15079`) - :meth:`Series.sample`, :meth:`DataFrame.sample`, and :meth:`.GroupBy.sample` now accept a ``np.random.Generator`` as input to ``random_state``. A generator will be more performant, especially with ``replace=False`` (:issue:`38100`) -- Additional options added to :meth:`.Styler.bar` to control alignment and display (:issue:`26070`) +- Additional options added to :meth:`.Styler.bar` to control alignment and display, with keyword only arguments (:issue:`26070`, :issue:`36419`) - :meth:`Series.ewm`, :meth:`DataFrame.ewm`, now support a ``method`` argument with a ``'table'`` option that performs the windowing operation over an entire :class:`DataFrame`. See :ref:`Window Overview ` for performance and functional benefits (:issue:`42273`) - diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 3465d353cc2ca..0e89f21fe2431 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -2046,8 +2046,10 @@ def bar( self, subset: Subset | None = None, axis: Axis | None = 0, + *, color="#d65f5f", width: float = 100, + height: float = 100, align: str | float | int | Callable = "mid", vmin: float | None = None, vmax: float | None = None, @@ -2056,6 +2058,8 @@ def bar( """ Draw bar chart in the cell backgrounds. + .. versionchanged:: 1.4.0 + Parameters ---------- subset : label, array-like, IndexSlice, optional @@ -2074,6 +2078,10 @@ def bar( width : float, default 100 The percentage of the cell, measured from the left, in which to draw the bars, in [0, 100]. + height : float, default 100 + The percentage height of the bar in the cell, centrally aligned, in [0,100]. + + .. versionadded:: 1.4.0 align : str, int, float, callable, default 'mid' How to align the bars within the cells relative to a width adjusted center. If string must be one of: @@ -2131,6 +2139,7 @@ def bar( align=align, colors=color, width=width / 100, + height=height / 100, vmin=vmin, vmax=vmax, base_css=props, @@ -2791,6 +2800,7 @@ def _bar( align: str | float | int | Callable, colors: list[str], width: float, + height: float, vmin: float | None, vmax: float | None, base_css: str, @@ -2808,6 +2818,9 @@ def _bar( Two listed colors as string in valid CSS. width : float in [0,1] The percentage of the cell, measured from left, where drawn bars will reside. + height : float in [0,1] + The percentage of the cell's height where drawn bars will reside, centrally + aligned. vmin : float, optional Overwrite the minimum value of the window. vmax : float, optional @@ -2873,7 +2886,7 @@ def css_calc(x, left: float, right: float, align: str): Notes ----- - Uses ``colors`` and ``width`` from outer scope. + Uses ``colors``, ``width`` and ``height`` from outer scope. """ if pd.isna(x): return base_css @@ -2911,7 +2924,13 @@ def css_calc(x, left: float, right: float, align: str): else: start, end = z_frac, (x - left) / (right - left) - return css_bar(start * width, end * width, color) + ret = css_bar(start * width, end * width, color) + if height < 1 and "background: linear-gradient(" in ret: + return ( + ret + f" no-repeat center; background-size: 100% {height * 100:.1f}%;" + ) + else: + return ret values = data.to_numpy() left = np.nanmin(values) if vmin is None else vmin diff --git a/pandas/tests/io/formats/style/test_bar.py b/pandas/tests/io/formats/style/test_bar.py index 1f6fefd93cf6e..aa115624ec2f1 100644 --- a/pandas/tests/io/formats/style/test_bar.py +++ b/pandas/tests/io/formats/style/test_bar.py @@ -275,6 +275,22 @@ def test_colors_mixed(align, exp): assert result == {(0, 0): exp[0], (1, 0): exp[1]} +def test_bar_align_height(): + # test when keyword height is used 'no-repeat center' and 'background-size' present + data = DataFrame([[1], [2]]) + result = data.style.bar(align="left", height=50)._compute().ctx + bg_s = "linear-gradient(90deg, #d65f5f 100.0%, transparent 100.0%) no-repeat center" + expected = { + (0, 0): [("width", "10em")], + (1, 0): [ + ("width", "10em"), + ("background", bg_s), + ("background-size", "100% 50.0%"), + ], + } + assert result == expected + + def test_bar_bad_align_raises(): df = DataFrame({"A": [-100, -60, -30, -20]}) msg = "`align` should be in {'left', 'right', 'mid', 'mean', 'zero'} or"