6

我有 3 个数据集的图,这些数据集在 x 轴上有日期时间对象。我想要一个光标捕捉数据并显示精确的 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()

这是我的模块“Helper”,其中包含“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,稍后应该将其与鼠标坐标进行比较。而且这些数据类型没有可比性。

4

1 回答 1

5

我知道了!!!

SnaptoCursor 类的init方法中这两行的问题:

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

他们以某种方式弄乱了日期时间 x 轴(例如,序数高达 730,000),因此您只需初始化线条的坐标:

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()
于 2013-01-17T10:36:54.367 回答