Matplotlib: Cursor snap to plotted data with datetime axis

前端 未结 1 1335
星月不相逢
星月不相逢 2021-02-09 21:53

I have a plot of 3 data sets that have datetime objetcs on the x axis. I want to have a cursor that snaps to the data and shows the precise x and y value.

I already have

相关标签:
1条回答
  • 2021-02-09 22:08

    I got it!!!

    The problems where these two lines in the init method of the SnaptoCursor class:

    self.lx = ax.axhline(color = 'k')  #the horiz line
    self.ly = ax.axvline(color = 'k')  #the vert line
    

    They were somehow messing up the datetime x axis (that has ordinals up to 730,000 e.g.), so you just have to initialize the lines' coordinates:

    self.lx = ax.axhline(y = min(y), color = 'k')  #the horiz line
    self.ly = ax.axvline(x = min(x), color = 'k')  #the vert line
    

    Then it works just fine!

    I'll be posting my complete SnaptoCursor class now that I have modified so it accepts individual formatting strings, and it can take up to 3 input data plots - that get snapped to according to your mouse position.

    def percent(x, unused):
        """
        Returns a string of the float number x formatted as %.
        """
        return '{0:1.2f}%'.format(x * 100)
    
    def minsec(sec, unused):
        """
        Returns a string of the input seconds formatted as mm'ss''.
        """
        minutes = sec // 60
        sec = sec - minutes * 60
        return '{0:02d}\'{1:02d}\'\''.format(int(minutes), int(sec))
    
    class SnaptoCursor():
        """
        A cursor with crosshair snaps to the nearest x point.
        For simplicity, I'm assuming x is sorted.
        """
        def __init__(self, ax, x, y, formatting, y2 = None, y3 = None):
            """
            ax: plot axis
            x: plot spacing
            y: plot data
            formatting: string flag for desired formatting
            y2: optional second plot data
            y3: optional third plot data
            """
            self.ax = ax
            self.lx = ax.axhline(y = min(y), color = 'k')  #the horiz line
            self.ly = ax.axvline(x = min(x), color = 'k')  #the vert line
            self.x = x
            self.y = y
            self.y2 = y2
            self.y3 = y3
            # text location in axes coords
            self.txt = ax.text(0.6, 0.9, '', transform = ax.transAxes)
            self.formatting = formatting
    
        def format(self, x, y):
            if self.formatting == 'minsec':
                return 'x={0:d}, y='.format(x) + minsec(y, 0)
    
            if self.formatting == 'decimal':
                return 'x={0:d}, y={1:d}'.format(x, int(y))
    
            elif self.formatting == 'date decimal':
                return 'x={0:%d.%m.%Y}, y={1:d}'.format(x, int(y))
    
            elif self.formatting == 'decimal percent':
                return 'x={0:d}, y={1:d}%'.format(x, int(y * 100))
    
            elif self.formatting == 'float':
                return 'x={0:d}, y={1:.2f}'.format(x, y)
    
            elif self.formatting == 'float percent':
                return 'x={0:d}, y='.format(x) + percent(y, 0)
    
            elif self.formatting == 'daily euro':
                return u'day {0:d}: {1:.2f}€'.format(x, y)
    
        def mouse_move(self, event):
            if not event.inaxes:
                return
    
            mouseX, mouseY = event.xdata, event.ydata
            if type(self.x[0]) == datetime.datetime:
                mouseX = dates.num2date(int(mouseX)).replace(tzinfo = None)
    
            #searchsorted: returns an index or indices that suggest where mouseX should be inserted
            #so that the order of the list self.x would be preserved
            indx = np.searchsorted(self.x, [mouseX])[0]
    
            #if indx is out of bounds
            if indx >= len(self.x):
                indx = len(self.x) - 1
    
            #if y2 wasn't defined
            if self.y2 == None:
                mouseY = self.y[indx]
    
            #if y2 was defined AND y3 wasn't defined
            elif self.y3 == None: 
                if abs(mouseY - self.y[indx]) < abs(mouseY - self.y2[indx]):
                    mouseY = self.y[indx]
                else:
                    mouseY = self.y2[indx]
    
            #if y2 AND y3 were defined
            elif abs(mouseY - self.y2[indx]) < abs(mouseY - self.y[indx]):
                if abs(mouseY - self.y2[indx]) < abs(mouseY - self.y3[indx]):
                    mouseY = self.y2[indx]
                else:
                    mouseY = self.y3[indx]
            #lastly, compare y with y3
            elif abs(mouseY - self.y[indx]) < abs(mouseY - self.y3[indx]):
                mouseY = self.y[indx]
            else:
                mouseY = self.y3[indx]
    
            #update the line positions
            self.lx.set_ydata(mouseY)
            self.ly.set_xdata(mouseX)
    
            self.txt.set_text(self.format(mouseX, mouseY))
            plot.draw()
    
    0 讨论(0)
提交回复
热议问题