Search code examples
pythonmatplotlibmplcursors

mplcursors shows only y coordinate


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:

picture
See my code of the canvas:

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()
    

    example plot with annotation

    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()
    

    example with interpolated time