2

I'm making a program to quickly analyze testing curves of battery chargers and such. I would like to combine the hoverbox, which snaps to each curve with a vertical line for easy comparison. If I activate both codes, they collide and I get a line while moving the mouse, when I stop it disappear and the hoverbox doesn't snap to the curves.

The hoverbox is made from the mplcursors library, while the line is made fro the cursor widget in matplotlib.

Picture 1, vertical line created with code below

cursor = Cursor(
    ax2, useblit=True, horizOn=False, vertOn=True, color="red", linewidth=0.5
)

Picture 2, hoverbox with information from the graph, code below

mplcursors.cursor(hover=True)

Complete code here:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
import mplcursors

data = np.loadtxt("test.txt")

x = data[:, 0]
y = data[:, 1]
y2 = data[:, 2]
y3 = data[:, 3]

fig = plt.figure(figsize=(13, 5))

ax = fig.add_subplot(111)

ax.plot(x, y, "--", label="Voltage")
ax.plot(x, y2, "-.", label="Current")


ax2 = ax.twinx()
ax2.plot(x, y3, "g:", label="Temperature")
ax2.set_ylabel("Celsius", color=("LightBlue"))
ax2.set_ylim(18, 100)

fig.legend(
    edgecolor=("DarkBlue"),
    facecolor=("LightBlue"),
    loc="upper right",
    bbox_to_anchor=(1, 1),
    bbox_transform=ax.transAxes,
)

ax.set_title("Test Surveillance", color=("Purple"))
ax.set_xlabel("Milliseconds", color=("LightGreen"))
ax.set_ylabel("Volts and Amps", color=("DarkGrey"))


plt.xlim(0)

# cursor = Cursor(
#     ax2, useblit=True, horizOn=False, vertOn=True, color="red", linewidth=0.5
# )
mplcursors.cursor(hover=True)

plt.show()

As an added bonus: The X-value is measured in seconds in the example (I know it says milliseconds). I would like to show 1:45:24 or whatever instaid of x=5.77e+04 like in the picture. Is this possible?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Slangfil
  • 23
  • 6

1 Answers1

2

mplcursors allows to explicitly set a function that will be called every time an annotation is shown. There the displayed text can be changed (as well as many other attributes). Also, extra elements such as lines can be drawn. When such elements are appended to sel.extras, they will be automatically erased and then redrawn as the cursor position changes.

With ax.xaxis.set_major_formatter() a formatting function will be set, both for the tick labels and for the coordinate display in the status bar.

import numpy as np
import matplotlib.pyplot as plt
import mplcursors
from math import floor

def hhmmss_formatter(x, pos=None):
    s = floor(x % 60)
    m = floor((x - s) / 60) % 60
    h = floor(x / 3600)
    return f'{h}:{m:02d}' if s == 0 else f'{h}:{m:02d}:{s:02d}'

def show_annotation(sel):
    xi = sel.target[0]
    vertical_line = ax.axvline(xi, color='red', ls=':', lw=1)
    sel.extras.append(vertical_line)
    # print('label:', sel.artist.get_label())
    y1i = np.interp(xi, x, y)
    y2i = np.interp(xi, x, y2)
    y3i = np.interp(xi, x, y3)
    annotation_str = f'Time: {hhmmss_formatter(xi)}\nVoltage: {y1i:.1f}\nCurrent: {y2i:.1f}\nTemperature: {y3i:.1f}'
    sel.annotation.set_text(annotation_str)

x = np.linspace(0, 85000, 500)
y = np.random.randn(len(x)).cumsum() / 5 + 15
y2 = np.random.randn(len(x)).cumsum() / 5 + 30
y3 = np.random.randn(len(x)).cumsum() / 5 + x / 10000 + 25

fig = plt.figure(figsize=(13, 5))

ax = fig.add_subplot(111)

ax.plot(x, y, "--", label="Voltage")
ax.plot(x, y2, "-.", label="Current")

ax2 = ax.twinx()
ax2.plot(x, y3, "g:", label="Temperature")
ax2.set_ylabel("Celsius", color="LightBlue")
ax2.set_ylim(18, 100)

fig.legend(edgecolor=("DarkBlue"), facecolor=("LightBlue"), loc="upper right", bbox_to_anchor=(1, 1),
           bbox_transform=ax.transAxes, )

ax.set_title("Test Surveillance", color="Purple")
ax.set_xlabel("Seconds", color="LightGreen")
ax.set_ylabel("Volts and Amps", color="DarkGrey")

ax.set_xlim(xmin=0)
ax.xaxis.set_major_formatter(plt.FuncFormatter(hhmmss_formatter))  # show x-axis as hh:mm:ss
ax.xaxis.set_major_locator(plt.MultipleLocator(2 * 60 * 60))  # set ticks every two hours

cursor = mplcursors.cursor(hover=True)
cursor.connect('add', show_annotation)
plt.show()

example plot

JohanC
  • 71,591
  • 8
  • 33
  • 66