'In matplotlib, how can I create fast and rich tooltips?

I have an app that uses matplotlib to plot a large number of data points (15,000 or more). Using the commonly-recommended approach of the matplotlib feature annotate, the resulting tooltips are slow to display, often prompting the blue "waiting" circle in Windows. I would also like an easy way to display rich text in these tooltips.

Here is a example program drawing 30,000 data points, and using annotate for tooltips:

# matplotlib_tooltip_slow.py: show a tooltip for plot item using plot annotation
import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QToolTip

fig = None
artist = None

fig = plt.figure("circle_plots", figsize=(12, 6))
ax = fig.add_subplot(1, 1, 1)

count = 30000
x = np.random.rand(count)
y = np.random.rand(count)
area = (4 * np.random.rand(count))**2  

artist = ax.scatter(x, y, s=area, color="blue", alpha=0.5, facecolors='none')

# create tooltip control (annotation)
annot = ax.annotate("", xy=(0,0), xytext=(5,5), textcoords="offset points", bbox=dict(boxstyle="round", fc="w"))
annot.set_visible(False)

def set_tooltip(text):
    if text:
        annot.set_text(text)
        annot.set_visible(True)
    else:
        annot.set_visible(False)

    fig.canvas.draw_idle()

def hover(event):
    cont, ind = artist.contains(event)
    if cont and len(ind):
        indexes = list(ind["ind"])

        # set position of annotation to the first plot item in our list
        annot.xy = artist.get_offsets()[indexes[0]]

        text = "\n".join(["Name: item{}, Area: {:.0f}".format(i, area[i]) for i in indexes])
    else:
        text = ""

    set_tooltip(text)

fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()


Solution 1:[1]

I have found that the underlying GUI framework offers tooltips for the window associated with the matplotlib figure, and they are fast and support HTML for rich text. Here is a new version of the example program, where the annotate creation has been removed, and the set_tooltip() function rewritten using the underlying pyqt5 framework's tooltip. The result is faster displaying tooltips that display rich text:

# matplotlib_tooltip_fast.py: show a tooltip for plot item using pyqt5 tooltip 
import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QToolTip

fig = None
artist = None

fig = plt.figure("circle_plots", figsize=(12, 6))
ax = fig.add_subplot(1, 1, 1)

count = 30000
x = np.random.rand(count)
y = np.random.rand(count)
area = (4 * np.random.rand(count))**2  

artist = ax.scatter(x, y, s=area, color="blue", alpha=0.5, facecolors='none')

def set_tooltip(text):
    win = fig.canvas.window()

    if text:
        win.setToolTip(text)
    else:
        win.setToolTip(text)
        QToolTip.hideText()

def hover(event):
    cont, ind = artist.contains(event)
    if cont and len(ind):
        indexes = list(ind["ind"])
        text = "<br>".join(["<b>Name:</b> item{}, <b>Area</b>: {:.0f}".format(i, area[i]) for i in indexes])
    else:
        text = ""
    set_tooltip(text)

fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()

Here is a screenshot of the resulting plot with a rich tooptip: enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1