@@ -184,6 +184,8 @@ def __init__(
184184 self .cell_ids = cell_ids
185185 self .na_rep = na_rep
186186
187+ self .tooltips : Optional [_Tooltips ] = None
188+
187189 self .cell_context : Dict [str , Any ] = {}
188190
189191 # display_funcs maps (row, col) -> formatting function
@@ -207,6 +209,117 @@ def _repr_html_(self) -> str:
207209 """
208210 return self .render ()
209211
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+
210323 @doc (
211324 NDFrame .to_excel ,
212325 klass = "Styler" ,
@@ -436,7 +549,7 @@ def format_attr(pair):
436549 else :
437550 table_attr += ' class="tex2jax_ignore"'
438551
439- return {
552+ d = {
440553 "head" : head ,
441554 "cellstyle" : cellstyle ,
442555 "body" : body ,
@@ -446,6 +559,10 @@ def format_attr(pair):
446559 "caption" : caption ,
447560 "table_attributes" : table_attr ,
448561 }
562+ if self .tooltips :
563+ d = self .tooltips ._translate (self .data , self .uuid , d )
564+
565+ return d
449566
450567 def format (self , formatter , subset = None , na_rep : Optional [str ] = None ) -> Styler :
451568 """
@@ -691,6 +808,7 @@ def clear(self) -> None:
691808 Returns None.
692809 """
693810 self .ctx .clear ()
811+ self .tooltips = None
694812 self .cell_context = {}
695813 self ._todo = []
696814
@@ -1660,6 +1778,179 @@ def pipe(self, func: Callable, *args, **kwargs):
16601778 return com .pipe (self , func , * args , ** kwargs )
16611779
16621780
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+
16631954def _is_visible (idx_row , idx_col , lengths ) -> bool :
16641955 """
16651956 Index -> {(idx_row, idx_col): bool}).
0 commit comments