Matplotlib:光标捕捉到用datetime轴绘制的数据 [英] Matplotlib: Cursor snap to plotted data with datetime axis

查看:449
本文介绍了Matplotlib:光标捕捉到用datetime轴绘制的数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个3个数据集在x轴上有datetime对象的情节。
我想有一个光标捕捉到数据并显示精确的x和y值。

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.

我已经有一个对齐光标,但是只适用于标量x轴。
任何人都可以帮助我修改捕捉光标,以便它也适用于datetime x轴。

I already have a "snap to cursor", but that only works for scalar x axes. Can anyone help me to modify the snap to cursor so that it works for datetime x axes as well?

这是我的数据图:

Here are my data plots:

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类的模块Helper:
(我从其他地方获得了基本的SnaptoCursor类并对其进行了一些修改)

And this is my module "Helper" that contains the "SnaptoCursor" class: (I got the basic SnaptoCursor class from somewhere else and modified it a little bit)

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

当然这不起作用,因为我打电话SnaptoCursor与datetime数组dayArray,这应该与鼠标坐标稍后进行比较。这些数据类型是不可比的。

Of course this does not work since I am calling the SnaptoCursor with the datetime array "dayArray", which is supposed to be compared to the mouse coordinates later on. And these data types are not comparable.

推荐答案

我得到了!

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

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

他们不知何故弄乱了datetime x轴(有序号高达730,000 eg),所以你只需要初始化线的坐标: / p>

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


b $ b

然后就可以了!

Then it works just fine!

我将发布我完整的SnaptoCursor类,现在我已经修改,因此它接受单独的格式化字符串,最多可以占用3个输入数据图 - 根据您的鼠标位置捕捉。

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

这篇关于Matplotlib:光标捕捉到用datetime轴绘制的数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆