How do I show text from a third dataframe column when hovering over a line chart made from 2 other columns?

Question:

So I have a dataframe with 3 columns: date, price, text

import pandas as pd
from datetime import datetime
import random

columns = ('dates','prices','text')
datelist = pd.date_range(datetime.today(), periods=5).tolist()
prices = []
for i in range(0, 5):
    prices.append(random.randint(50, 60))
text =['AAA','BBB','CCC','DDD','EEE']

df = pd.DataFrame({'dates': datelist, 'price':prices, 'text':text})
    dates   price   text
0   2022-11-23 14:11:51.142574  51  AAA
1   2022-11-24 14:11:51.142574  57  BBB
2   2022-11-25 14:11:51.142574  52  CCC
3   2022-11-26 14:11:51.142574  51  DDD
4   2022-11-27 14:11:51.142574  59  EEE

I want to plot date and price on a line chart, but when I hover over the line I want it to show the text from the row corresponding to that date.

eg when I hover over the point corresponding to 2022-11-27 I want the text to show ‘EEE’

ive tried a few things in matplotlib etc but can only get data from the x and y axis to show but I cant figure out how to show data from a different column.

Any help would be greatly appreciated.

Thanks

Asked By: DankyKang

||

Answers:

You could use Plotly.

import plotly.graph_objects as go

fig = go.Figure(data=go.Scatter(x=df['dates'], y=df['price'], mode='lines+markers', text=df['text']))
fig.show()
Answered By: ZachW

You should be aware that cursor & dataframe indexing will probably work well with points on a scatter plot, but it is a little bit trickier to handle a lineplot.

With a lineplot, matplotlib draws the line between 2 data points (basically, it’s linear interpolation), so a specific logic must be taken care of to:

  1. specify the intended behavior
  2. implement the corresponding mouseover behavior when the cursor lands "between" 2 data points.

The lib/links below may provide tools to handle scatter plots and lineplots, but I am not expert enough to point you to this specific part in either the SO link nor the mplcursors link.

(besides, the exact intended behavioor was not clearly stated in your initial question; consider editing/clarifying)


So, alternatively to DankyKang’s answer, have a look at this SO question and answers that cover a large panel of possibilities for mouseover: How to add hovering annotations to a plot

A library worth noting is this one: https://mplcursors.readthedocs.io/en/stable/

Quoting:

mplcursors provides interactive data selection cursors for Matplotlib. It is inspired from mpldatacursor, with a much simplified API.
mplcursors requires Python 3, and Matplotlib≥3.1.

Specifically this example based on dataframes: https://mplcursors.readthedocs.io/en/stable/examples/dataframe.html

Quoting:

DataFrames can be used similarly to any other kind of input. Here, we generate a scatter plot using two columns and label the points using all columns.
This example also applies a shadow effect to the hover panel.

copy-pasta of code example, should this answer be considered not complete enough :

from matplotlib import pyplot as plt

from matplotlib.patheffects import withSimplePatchShadow
import mplcursors
from pandas import DataFrame


df = DataFrame(
    dict(
        Suburb=["Ames", "Somerset", "Sawyer"],
        Area=[1023, 2093, 723],
        SalePrice=[507500, 647000, 546999],
    )
)

df.plot.scatter(x="Area", y="SalePrice", s=100)


def show_hover_panel(get_text_func=None):
    cursor = mplcursors.cursor(
        hover=2,  # Transient
        annotation_kwargs=dict(
            bbox=dict(
                boxstyle="square,pad=0.5",
                facecolor="white",
                edgecolor="#ddd",
                linewidth=0.5,
                path_effects=[withSimplePatchShadow(offset=(1.5, -1.5))],
            ),
            linespacing=1.5,
            arrowprops=None,
        ),
        highlight=True,
        highlight_kwargs=dict(linewidth=2),
    )

    if get_text_func:
        cursor.connect(
            event="add",
            func=lambda sel: sel.annotation.set_text(get_text_func(sel.index)),
        )

    return cursor


def on_add(index):
    item = df.iloc[index]
    parts = [
        f"Suburb: {item.Suburb}",
        f"Area: {item.Area:,.0f}m²",
        f"Sale price: ${item.SalePrice:,.0f}",
    ]

    return "n".join(parts)


show_hover_panel(on_add)

plt.show()
Answered By: LoneWanderer