|
17 | 17 | "or `pip install Jinja2`"
|
18 | 18 | raise ImportError(msg)
|
19 | 19 |
|
20 |
| -from pandas.types.common import is_float, is_string_like |
| 20 | +from pandas.types.common import is_float, is_string_like, is_list_like |
21 | 21 |
|
22 | 22 | import numpy as np
|
23 | 23 | import pandas as pd
|
@@ -857,39 +857,182 @@ def set_properties(self, subset=None, **kwargs):
|
857 | 857 | return self.applymap(f, subset=subset)
|
858 | 858 |
|
859 | 859 | @staticmethod
|
860 |
| - def _bar(s, color, width): |
861 |
| - normed = width * (s - s.min()) / (s.max() - s.min()) |
| 860 | + def _bar_left(s, color, width, base): |
| 861 | + """ |
| 862 | + The minimum value is aligned at the left of the cell |
862 | 863 |
|
863 |
| - base = 'width: 10em; height: 80%;' |
864 |
| - attrs = (base + 'background: linear-gradient(90deg,{c} {w}%, ' |
| 864 | + Parameters |
| 865 | + ---------- |
| 866 | + color: 2-tuple/list, of [``color_negative``, ``color_positive``] |
| 867 | + width: float |
| 868 | + A number between 0 or 100. The largest value will cover ``width`` |
| 869 | + percent of the cell's width |
| 870 | + base: str |
| 871 | + The base css format of the cell, e.g.: |
| 872 | + ``base = 'width: 10em; height: 80%;'`` |
| 873 | +
|
| 874 | + Returns |
| 875 | + ------- |
| 876 | + self : Styler |
| 877 | + """ |
| 878 | + normed = width * (s - s.min()) / (s.max() - s.min()) |
| 879 | + zero_normed = width * (0 - s.min()) / (s.max() - s.min()) |
| 880 | + attrs = (base + 'background: linear-gradient(90deg,{c} {w:.1f}%, ' |
865 | 881 | 'transparent 0%)')
|
866 |
| - return [attrs.format(c=color, w=x) if x != 0 else base for x in normed] |
867 | 882 |
|
868 |
| - def bar(self, subset=None, axis=0, color='#d65f5f', width=100): |
| 883 | + return [base if x == 0 else attrs.format(c=color[0], w=x) |
| 884 | + if x < zero_normed |
| 885 | + else attrs.format(c=color[1], w=x) if x >= zero_normed |
| 886 | + else base for x in normed] |
| 887 | + |
| 888 | + @staticmethod |
| 889 | + def _bar_center_zero(s, color, width, base): |
| 890 | + """ |
| 891 | + Creates a bar chart where the zero is centered in the cell |
| 892 | +
|
| 893 | + Parameters |
| 894 | + ---------- |
| 895 | + color: 2-tuple/list, of [``color_negative``, ``color_positive``] |
| 896 | + width: float |
| 897 | + A number between 0 or 100. The largest value will cover ``width`` |
| 898 | + percent of the cell's width |
| 899 | + base: str |
| 900 | + The base css format of the cell, e.g.: |
| 901 | + ``base = 'width: 10em; height: 80%;'`` |
| 902 | +
|
| 903 | + Returns |
| 904 | + ------- |
| 905 | + self : Styler |
| 906 | + """ |
| 907 | + |
| 908 | + # Either the min or the max should reach the edge |
| 909 | + # (50%, centered on zero) |
| 910 | + m = max(abs(s.min()), abs(s.max())) |
| 911 | + |
| 912 | + normed = s * 50 * width / (100.0 * m) |
| 913 | + |
| 914 | + attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%' |
| 915 | + ', transparent {w:.1f}%, {c} {w:.1f}%, ' |
| 916 | + '{c} 50%, transparent 50%)') |
| 917 | + |
| 918 | + attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%' |
| 919 | + ', transparent 50%, {c} 50%, {c} {w:.1f}%, ' |
| 920 | + 'transparent {w:.1f}%)') |
| 921 | + |
| 922 | + return [attrs_pos.format(c=color[1], w=(50 + x)) if x >= 0 |
| 923 | + else attrs_neg.format(c=color[0], w=(50 + x)) |
| 924 | + for x in normed] |
| 925 | + |
| 926 | + @staticmethod |
| 927 | + def _bar_center_mid(s, color, width, base): |
| 928 | + """ |
| 929 | + Creates a bar chart where the midpoint is centered in the cell |
| 930 | +
|
| 931 | + Parameters |
| 932 | + ---------- |
| 933 | + color: 2-tuple/list, of [``color_negative``, ``color_positive``] |
| 934 | + width: float |
| 935 | + A number between 0 or 100. The largest value will cover ``width`` |
| 936 | + percent of the cell's width |
| 937 | + base: str |
| 938 | + The base css format of the cell, e.g.: |
| 939 | + ``base = 'width: 10em; height: 80%;'`` |
| 940 | +
|
| 941 | + Returns |
| 942 | + ------- |
| 943 | + self : Styler |
| 944 | + """ |
| 945 | + |
| 946 | + if s.min() >= 0: |
| 947 | + # In this case, we place the zero at the left, and the max() should |
| 948 | + # be at width |
| 949 | + zero = 0.0 |
| 950 | + slope = width / s.max() |
| 951 | + elif s.max() <= 0: |
| 952 | + # In this case, we place the zero at the right, and the min() |
| 953 | + # should be at 100-width |
| 954 | + zero = 100.0 |
| 955 | + slope = width / -s.min() |
| 956 | + else: |
| 957 | + slope = width / (s.max() - s.min()) |
| 958 | + zero = (100.0 + width) / 2.0 - slope * s.max() |
| 959 | + |
| 960 | + normed = zero + slope * s |
| 961 | + |
| 962 | + attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%' |
| 963 | + ', transparent {w:.1f}%, {c} {w:.1f}%, ' |
| 964 | + '{c} {zero:.1f}%, transparent {zero:.1f}%)') |
| 965 | + |
| 966 | + attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%' |
| 967 | + ', transparent {zero:.1f}%, {c} {zero:.1f}%, ' |
| 968 | + '{c} {w:.1f}%, transparent {w:.1f}%)') |
| 969 | + |
| 970 | + return [attrs_pos.format(c=color[1], zero=zero, w=x) if x > zero |
| 971 | + else attrs_neg.format(c=color[0], zero=zero, w=x) |
| 972 | + for x in normed] |
| 973 | + |
| 974 | + def bar(self, subset=None, align='left', axis=0, |
| 975 | + color='#d65f5f', width=100): |
869 | 976 | """
|
870 | 977 | Color the background ``color`` proptional to the values in each column.
|
871 | 978 | Excludes non-numeric data by default.
|
872 |
| -
|
873 | 979 | .. versionadded:: 0.17.1
|
874 | 980 |
|
875 | 981 | Parameters
|
876 | 982 | ----------
|
877 | 983 | subset: IndexSlice, default None
|
878 | 984 | a valid slice for ``data`` to limit the style application to
|
879 | 985 | axis: int
|
880 |
| - color: str |
| 986 | + color: str or 2-tuple/list |
| 987 | + If a str is passed, the color is the same for both |
| 988 | + negative and positive numbers. If 2-tuple/list is used, the |
| 989 | + first element is the color_negative and the second is the |
| 990 | + color_positive (eg: ['#d65f5f', '#5fba7d']) |
881 | 991 | width: float
|
882 | 992 | A number between 0 or 100. The largest value will cover ``width``
|
883 | 993 | percent of the cell's width
|
| 994 | + align : {'left', 'zero',' mid'} |
| 995 | +
|
| 996 | + .. versionadded:: 0.20.0 |
| 997 | +
|
| 998 | + - 'left' : the min value starts at the left of the cell |
| 999 | + - 'zero' : a value of zero is located at the center of the cell |
| 1000 | + - 'mid' : the center of the cell is at (max-min)/2, or |
| 1001 | + if values are all negative (positive) the zero is aligned |
| 1002 | + at the right (left) of the cell |
884 | 1003 |
|
885 | 1004 | Returns
|
886 | 1005 | -------
|
887 | 1006 | self : Styler
|
888 | 1007 | """
|
889 | 1008 | subset = _maybe_numeric_slice(self.data, subset)
|
890 | 1009 | subset = _non_reducing_slice(subset)
|
891 |
| - self.apply(self._bar, subset=subset, axis=axis, color=color, |
892 |
| - width=width) |
| 1010 | + |
| 1011 | + base = 'width: 10em; height: 80%;' |
| 1012 | + |
| 1013 | + if not(is_list_like(color)): |
| 1014 | + color = [color, color] |
| 1015 | + elif len(color) == 1: |
| 1016 | + color = [color[0], color[0]] |
| 1017 | + elif len(color) > 2: |
| 1018 | + msg = ("Must pass `color` as string or a list-like" |
| 1019 | + " of length 2: [`color_negative`, `color_positive`]\n" |
| 1020 | + "(eg: color=['#d65f5f', '#5fba7d'])") |
| 1021 | + raise ValueError(msg) |
| 1022 | + |
| 1023 | + if align == 'left': |
| 1024 | + self.apply(self._bar_left, subset=subset, axis=axis, color=color, |
| 1025 | + width=width, base=base) |
| 1026 | + elif align == 'zero': |
| 1027 | + self.apply(self._bar_center_zero, subset=subset, axis=axis, |
| 1028 | + color=color, width=width, base=base) |
| 1029 | + elif align == 'mid': |
| 1030 | + self.apply(self._bar_center_mid, subset=subset, axis=axis, |
| 1031 | + color=color, width=width, base=base) |
| 1032 | + else: |
| 1033 | + msg = ("`align` must be one of {'left', 'zero',' mid'}") |
| 1034 | + raise ValueError(msg) |
| 1035 | + |
893 | 1036 | return self
|
894 | 1037 |
|
895 | 1038 | def highlight_max(self, subset=None, color='yellow', axis=0):
|
|
0 commit comments