@@ -184,6 +184,8 @@ def __init__(
184
184
self .cell_ids = cell_ids
185
185
self .na_rep = na_rep
186
186
187
+ self .tooltips : Optional [_Tooltips ] = None
188
+
187
189
self .cell_context : Dict [str , Any ] = {}
188
190
189
191
# display_funcs maps (row, col) -> formatting function
@@ -207,6 +209,117 @@ def _repr_html_(self) -> str:
207
209
"""
208
210
return self .render ()
209
211
212
+ def _init_tooltips (self ):
213
+ """
214
+ Checks parameters compatible with tooltips and creates instance if necessary
215
+ """
216
+ if not self .cell_ids :
217
+ # tooltips not optimised for individual cell check. requires reasonable
218
+ # redesign and more extensive code for a feature that might be rarely used.
219
+ raise NotImplementedError (
220
+ "Tooltips can only render with 'cell_ids' is True."
221
+ )
222
+ if self .tooltips is None :
223
+ self .tooltips = _Tooltips ()
224
+
225
+ def set_tooltips (self , ttips : DataFrame ) -> "Styler" :
226
+ """
227
+ Add string based tooltips that will appear in the `Styler` HTML result. These
228
+ tooltips are applicable only to`<td>` elements.
229
+
230
+ .. versionadded:: 1.3.0
231
+
232
+ Parameters
233
+ ----------
234
+ ttips : DataFrame
235
+ DataFrame containing strings that will be translated to tooltips, mapped
236
+ by identical column and index values that must exist on the underlying
237
+ `Styler` data. None, NaN values, and empty strings will be ignored and
238
+ not affect the rendered HTML.
239
+
240
+ Returns
241
+ -------
242
+ self : Styler
243
+
244
+ Notes
245
+ -----
246
+ Tooltips are created by adding `<span class="pd-t"></span>` to each data cell
247
+ and then manipulating the table level CSS to attach pseudo hover and pseudo
248
+ after selectors to produce the required the results. For styling control
249
+ see `:meth:Styler.set_tooltips_class`.
250
+ Tooltips are not designed to be efficient, and can add large amounts of
251
+ additional HTML for larger tables, since they also require that `cell_ids`
252
+ is forced to `True`.
253
+
254
+ Examples
255
+ --------
256
+ >>> df = pd.DataFrame(data=[[0, 1], [2, 3]])
257
+ >>> ttips = pd.DataFrame(
258
+ ... data=[["Min", ""], [np.nan, "Max"]], columns=df.columns, index=df.index
259
+ ... )
260
+ >>> s = df.style.set_tooltips(ttips).render()
261
+ """
262
+ self ._init_tooltips ()
263
+ assert self .tooltips is not None # mypy requiremen
264
+ self .tooltips .tt_data = ttips
265
+ return self
266
+
267
+ def set_tooltips_class (
268
+ self ,
269
+ name : Optional [str ] = None ,
270
+ properties : Optional [Sequence [Tuple [str , Union [str , int , float ]]]] = None ,
271
+ ) -> "Styler" :
272
+ """
273
+ Manually configure the name and/or properties of the class for
274
+ creating tooltips on hover.
275
+
276
+ .. versionadded:: 1.3.0
277
+
278
+ Parameters
279
+ ----------
280
+ name : str, default None
281
+ Name of the tooltip class used in CSS, should conform to HTML standards.
282
+ properties : list-like, default None
283
+ List of (attr, value) tuples; see example.
284
+
285
+ Returns
286
+ -------
287
+ self : Styler
288
+
289
+ Notes
290
+ -----
291
+ If arguments are `None` will not make any changes to the underlying ``Tooltips``
292
+ existing values.
293
+
294
+ The default properties for the tooltip CSS class are:
295
+
296
+ - visibility: hidden
297
+ - position: absolute
298
+ - z-index: 1
299
+ - background-color: black
300
+ - color: white
301
+ - transform: translate(-20px, -20px)
302
+
303
+ The property ('visibility', 'hidden') is a key prerequisite to the hover
304
+ functionality, and should always be included in any manual properties
305
+ specification.
306
+
307
+ Examples
308
+ --------
309
+ >>> df = pd.DataFrame(np.random.randn(10, 4))
310
+ >>> df.style.set_tooltips_class(name='tt-add', properties=[
311
+ ... ('visibility', 'hidden'),
312
+ ... ('position', 'absolute'),
313
+ ... ('z-index', 1)])
314
+ """
315
+ self ._init_tooltips ()
316
+ assert self .tooltips is not None # mypy requirement
317
+ if properties :
318
+ self .tooltips .class_properties = properties
319
+ if name :
320
+ self .tooltips .class_name = name
321
+ return self
322
+
210
323
@doc (
211
324
NDFrame .to_excel ,
212
325
klass = "Styler" ,
@@ -436,7 +549,7 @@ def format_attr(pair):
436
549
else :
437
550
table_attr += ' class="tex2jax_ignore"'
438
551
439
- return {
552
+ d = {
440
553
"head" : head ,
441
554
"cellstyle" : cellstyle ,
442
555
"body" : body ,
@@ -446,6 +559,10 @@ def format_attr(pair):
446
559
"caption" : caption ,
447
560
"table_attributes" : table_attr ,
448
561
}
562
+ if self .tooltips :
563
+ d = self .tooltips ._translate (self .data , self .uuid , d )
564
+
565
+ return d
449
566
450
567
def format (self , formatter , subset = None , na_rep : Optional [str ] = None ) -> Styler :
451
568
"""
@@ -691,6 +808,7 @@ def clear(self) -> None:
691
808
Returns None.
692
809
"""
693
810
self .ctx .clear ()
811
+ self .tooltips = None
694
812
self .cell_context = {}
695
813
self ._todo = []
696
814
@@ -1660,6 +1778,179 @@ def pipe(self, func: Callable, *args, **kwargs):
1660
1778
return com .pipe (self , func , * args , ** kwargs )
1661
1779
1662
1780
1781
+ class _Tooltips :
1782
+ """
1783
+ An extension to ``Styler`` that allows for and manipulates tooltips on hover
1784
+ of table data-cells in the HTML result.
1785
+
1786
+ Parameters
1787
+ ----------
1788
+ css_name: str, default "pd-t"
1789
+ Name of the CSS class that controls visualisation of tooltips.
1790
+ css_props: list-like, default; see Notes
1791
+ List of (attr, value) tuples defining properties of the CSS class.
1792
+ tooltips: DataFrame, default empty
1793
+ DataFrame of strings aligned with underlying ``Styler`` data for tooltip
1794
+ display.
1795
+
1796
+ Notes
1797
+ -----
1798
+ The default properties for the tooltip CSS class are:
1799
+
1800
+ - visibility: hidden
1801
+ - position: absolute
1802
+ - z-index: 1
1803
+ - background-color: black
1804
+ - color: white
1805
+ - transform: translate(-20px, -20px)
1806
+
1807
+ Hidden visibility is a key prerequisite to the hover functionality, and should
1808
+ always be included in any manual properties specification.
1809
+ """
1810
+
1811
+ def __init__ (
1812
+ self ,
1813
+ css_props : Sequence [Tuple [str , Union [str , int , float ]]] = [
1814
+ ("visibility" , "hidden" ),
1815
+ ("position" , "absolute" ),
1816
+ ("z-index" , 1 ),
1817
+ ("background-color" , "black" ),
1818
+ ("color" , "white" ),
1819
+ ("transform" , "translate(-20px, -20px)" ),
1820
+ ],
1821
+ css_name : str = "pd-t" ,
1822
+ tooltips : DataFrame = DataFrame (),
1823
+ ):
1824
+ self .class_name = css_name
1825
+ self .class_properties = css_props
1826
+ self .tt_data = tooltips
1827
+ self .table_styles : List [Dict [str , Union [str , List [Tuple [str , str ]]]]] = []
1828
+
1829
+ @property
1830
+ def _class_styles (self ):
1831
+ """
1832
+ Combine the ``_Tooltips`` CSS class name and CSS properties to the format
1833
+ required to extend the underlying ``Styler`` `table_styles` to allow
1834
+ tooltips to render in HTML.
1835
+
1836
+ Returns
1837
+ -------
1838
+ styles : List
1839
+ """
1840
+ return [{"selector" : f".{ self .class_name } " , "props" : self .class_properties }]
1841
+
1842
+ def _pseudo_css (self , uuid : str , name : str , row : int , col : int , text : str ):
1843
+ """
1844
+ For every table data-cell that has a valid tooltip (not None, NaN or
1845
+ empty string) must create two pseudo CSS entries for the specific
1846
+ <td> element id which are added to overall table styles:
1847
+ an on hover visibility change and a content change
1848
+ dependent upon the user's chosen display string.
1849
+
1850
+ For example:
1851
+ [{"selector": "T__row1_col1:hover .pd-t",
1852
+ "props": [("visibility", "visible")]},
1853
+ {"selector": "T__row1_col1 .pd-t::after",
1854
+ "props": [("content", "Some Valid Text String")]}]
1855
+
1856
+ Parameters
1857
+ ----------
1858
+ uuid: str
1859
+ The uuid of the Styler instance
1860
+ name: str
1861
+ The css-name of the class used for styling tooltips
1862
+ row : int
1863
+ The row index of the specified tooltip string data
1864
+ col : int
1865
+ The col index of the specified tooltip string data
1866
+ text : str
1867
+ The textual content of the tooltip to be displayed in HTML.
1868
+
1869
+ Returns
1870
+ -------
1871
+ pseudo_css : List
1872
+ """
1873
+ return [
1874
+ {
1875
+ "selector" : "#T_"
1876
+ + uuid
1877
+ + "row"
1878
+ + str (row )
1879
+ + "_col"
1880
+ + str (col )
1881
+ + f":hover .{ name } " ,
1882
+ "props" : [("visibility" , "visible" )],
1883
+ },
1884
+ {
1885
+ "selector" : "#T_"
1886
+ + uuid
1887
+ + "row"
1888
+ + str (row )
1889
+ + "_col"
1890
+ + str (col )
1891
+ + f" .{ name } ::after" ,
1892
+ "props" : [("content" , f'"{ text } "' )],
1893
+ },
1894
+ ]
1895
+
1896
+ def _translate (self , styler_data : FrameOrSeriesUnion , uuid : str , d : Dict ):
1897
+ """
1898
+ Mutate the render dictionary to allow for tooltips:
1899
+
1900
+ - Add `<span>` HTML element to each data cells `display_value`. Ignores
1901
+ headers.
1902
+ - Add table level CSS styles to control pseudo classes.
1903
+
1904
+ Parameters
1905
+ ----------
1906
+ styler_data : DataFrame
1907
+ Underlying ``Styler`` DataFrame used for reindexing.
1908
+ uuid : str
1909
+ The underlying ``Styler`` uuid for CSS id.
1910
+ d : dict
1911
+ The dictionary prior to final render
1912
+
1913
+ Returns
1914
+ -------
1915
+ render_dict : Dict
1916
+ """
1917
+ self .tt_data = (
1918
+ self .tt_data .reindex_like (styler_data )
1919
+ .dropna (how = "all" , axis = 0 )
1920
+ .dropna (how = "all" , axis = 1 )
1921
+ )
1922
+ if self .tt_data .empty :
1923
+ return d
1924
+
1925
+ name = self .class_name
1926
+
1927
+ mask = (self .tt_data .isna ()) | (self .tt_data .eq ("" )) # empty string = no ttip
1928
+ self .table_styles = [
1929
+ style
1930
+ for sublist in [
1931
+ self ._pseudo_css (uuid , name , i , j , str (self .tt_data .iloc [i , j ]))
1932
+ for i in range (len (self .tt_data .index ))
1933
+ for j in range (len (self .tt_data .columns ))
1934
+ if not mask .iloc [i , j ]
1935
+ ]
1936
+ for style in sublist
1937
+ ]
1938
+
1939
+ if self .table_styles :
1940
+ # add span class to every cell only if at least 1 non-empty tooltip
1941
+ for row in d ["body" ]:
1942
+ for item in row :
1943
+ if item ["type" ] == "td" :
1944
+ item ["display_value" ] = (
1945
+ str (item ["display_value" ])
1946
+ + f'<span class="{ self .class_name } "></span>'
1947
+ )
1948
+ d ["table_styles" ].extend (self ._class_styles )
1949
+ d ["table_styles" ].extend (self .table_styles )
1950
+
1951
+ return d
1952
+
1953
+
1663
1954
def _is_visible (idx_row , idx_col , lengths ) -> bool :
1664
1955
"""
1665
1956
Index -> {(idx_row, idx_col): bool}).
0 commit comments