Issue
I am doing an application in pyqt and there is a plot build with matplotlib. I use mplcursors to show coordinates, but it does not show x coordinate:
class Canvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=5, dpi=120):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
x = ['22-02 11:16:15', '22-02 15:31:54', '22-02 15:32:30',
'22-02 15:32:45', '22-02 15:33:57', '22-02 15:34:13',
'22-02 15:34:46']
y = [1, 4, 3, 4, 8, 9, 2]
self.figure.tight_layout()
self.figure.autofmt_xdate()
#mplcursors.Cursor()
ax = self.figure.add_subplot(111)
dt = ax.plot(x, y)
cursor = mplcursors.cursor(dt, hover = True)
Solution
Note that in the example no numerical timestamps are given. Matplotlib interprets them as text labels and numbers them 0,1,2,...,N-1. Also note that the times are not spaced equally, but matplotlib shows the exact x-labels evenly spaced on the x-axis.
To display the x-axis, an explicit annotation function can interpret the numerical x-coordinate (in the range 0 to N-1), round it and use it as an index into the list of strings. In that case the x-coordinate will show the nearest x-label, while the y-value will be nicely interpolated.
Here is some example code:
from matplotlib import pyplot as plt
import mplcursors
def show_annotation(sel):
xi, yi = sel.target
xi = int(round(xi))
sel.annotation.set_text(f'{x[xi]}\nvalue:{yi:.3f}')
x = ['22-02 11:16:15', '22-02 15:31:54', '22-02 15:32:30',
'22-02 15:32:45', '22-02 15:33:57', '22-02 15:34:13',
'22-02 15:34:46']
y = [1, 4, 3, 4, 8, 9, 2]
figure, ax = plt.subplots()
dt = ax.plot(x, y)
cursor = mplcursors.cursor(dt, hover=True)
cursor.connect('add', show_annotation)
figure.tight_layout()
figure.autofmt_xdate() # has no effect, because matplotlib only encountered texts for the x-axis
plt.show()
If you need also fully interpolated time stamps for the x, you should convert the x
to a numerical timestamp. Be careful to also provide the year, because the default year would be 1901, which can cause conflicts during a leap year.
In the example code below, the first timestamp is modified to go together with the rest. The plot now uses the distances proportional to the time.
from matplotlib import pyplot as plt
from matplotlib import dates as mdates
import mplcursors
from datetime import datetime
def show_annotation(sel):
xi, yi = sel.target
sel.annotation.set_text(f"{mdates.DateFormatter('%d %b %H:%M:%S')(xi)}\nvalue:{yi:.3f}")
x = ['22-02 15:31:15', '22-02 15:31:54', '22-02 15:32:30',
'22-02 15:32:45', '22-02 15:33:57', '22-02 15:34:13',
'22-02 15:34:46']
# first, convert the strings to datetime objects, and then convert to a numerical time
# as the day is put before the month, a specific format conversion needs to be supplied
# the year needs to be prepended to get the timestamps in the correct year
x = [mdates.date2num(datetime.strptime('2020-'+xi, '%Y-%d-%m %H:%M:%S')) for xi in x]
y = [1, 4, 3, 4, 8, 9, 2]
figure, ax = plt.subplots()
dt = ax.plot(x, y)
ax.xaxis_date()
# display the time on two lines: the day and the shortened month name, and then HH:MM:SS
ax.xaxis.set_major_formatter(mdates.DateFormatter('%d %b\n%H:%M:%S'))
# ax.set_xticks(x) # to set the input time stamps as xticks
figure.tight_layout()
cursor = mplcursors.cursor(dt, hover=True)
cursor.connect('add', show_annotation)
plt.show()
Answered By - JohanC
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.