Matplotlib:光标捕捉到与日期时间轴绘制的数据(Matplotlib: Cursor sna

2019-08-06 22:49发布

我有具有在x轴上的日期时间objetcs 3数据集的曲线图。 我想有捕捉到的数据,并示出了精确的x和y值的光标。

我已经有一个“捕捉光标”,但只适用于标X轴。 谁能帮我修改捕捉到光标,它适用于日期时间X轴呢?

这里是我的数据图:

import numpy as np
import matplotlib.pyplot as plot
import matplotlib.ticker as mticker
import matplotlib.dates as dates
import datetime
import Helpers

fig = plot.figure(1)
DAU = (  2,  20,  25,  60, 190, 210,  18, 196, 212)
WAU = ( 50, 160, 412, 403, 308, 379, 345, 299, 258)
MAU = (760, 620, 487, 751, 612, 601, 546, 409, 457)

firstDay = datetime.datetime(2012,1,15)

#create an array with len(DAU) entries from given starting day
dayArray = [firstDay + datetime.timedelta(days = i) for i in xrange(len(DAU))]

line1 = plot.plot(dayArray, DAU, 'o-', color = '#336699')
line2 = plot.plot(dayArray, WAU, 'o-', color = '#993333')
line3 = plot.plot(dayArray, MAU, 'o-', color = '#89a54e')

ax = plot.subplot(111)
dateLocator   = mticker.MultipleLocator(2)
dateFormatter = dates.DateFormatter('%d.%m.%Y')
ax.xaxis.set_major_locator(dateLocator)
ax.xaxis.set_major_formatter(dateFormatter)
fig.autofmt_xdate(rotation = 90, ha = 'center')

yMax = max(np.max(DAU), np.max(WAU), np.max(MAU))
yLimit = 100 - (yMax % 100) + yMax
plot.yticks(np.arange(0, yLimit + 1, 100))

plot.title('Active users', weight = 'bold')
plot.grid(True, axis = 'both')
plot.subplots_adjust(bottom = 0.2)
plot.subplots_adjust(right = 0.82)

legend = plot.legend((line1[0], line2[0], line3[0]),
                 ('DAU',
                 'WAU',
                 'MAU'),
                 'upper left',
                 bbox_to_anchor = [1, 1],
                 shadow = True)

frame = legend.get_frame()
frame.set_facecolor('0.80')
for t in legend.get_texts():
    t.set_fontsize('small')

#THIS DOES NOT WORK
cursor = Helpers.SnaptoCursor(ax, dayArray, DAU, 'euro daily')
plot.connect('motion_notify_event', cursor.mouse_move)

plot.show()

这是我的模块,它包含了“SnaptoCursor”类“助手”:(我从别的地方得到了基本SnaptoCursor类,并修改了它一点点)

from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plot

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, z = None):
        """
        ax: plot axis
        x: plot spacing
        y: plot data
        formatting: string flag for desired formatting
        z: optional second plot data
        """
        self.ax = ax
        self.lx = ax.axhline(color = 'k')  #the horiz line
        self.ly = ax.axvline(color = 'k')  #the vert line
        self.x = x
        self.y = y
        self.z = z
        # 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)

        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

        #searchsorted: returns an index or indices that suggest where x should be inserted
        #so that the order of the list self.x would be preserved
        indx = np.searchsorted(self.x, [mouseX])[0]

        mouseX = self.x[indx]
        #if z wasn't defined
        if self.z == None:
            mouseY = self.y[indx]
        #if z was defined: compare the distance between mouse and the two plots y and z
        #and use the nearest one
        elif abs(mouseY - self.y[indx]) < abs(mouseY - self.z[indx]):
            mouseY = self.y[indx]
        else:
            mouseY = self.z[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()

当然,因为我有日期时间排列“dayArray”,这应该是比鼠标坐标以后调用SnaptoCursor这不起作用。 而这些数据类型没有可比性。

Answer 1:

我知道了!!!

的问题,其中在SnaptoCursor类的init方法以下两行:

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

他们在某种程度上搞乱了日期时间x轴(即具有序号多达73万例),所以你只需要初始化线坐标:

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

然后,它工作得很好!

我将现在公布我的完整SnaptoCursor类,我已经修改,因此接受个别格式化字符串,它可能需要长达3个输入数据图 - 即得到啪根据您的鼠标位置。

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


文章来源: Matplotlib: Cursor snap to plotted data with datetime axis