【问题标题】:Matplotlib: Cursor snap to plotted data with datetime axisMatplotlib:光标捕捉到带有日期时间轴的绘图数据
【发布时间】:2022-01-05 03:23:45
【问题描述】:

我有 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,稍后应该将其与鼠标坐标进行比较。而且这些数据类型没有可比性。

【问题讨论】:

    标签: datetime matplotlib mouse-cursor


    【解决方案1】:

    我明白了!!!

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-09
      • 1970-01-01
      • 1970-01-01
      • 2020-12-07
      • 2018-04-13
      • 1970-01-01
      • 2018-08-10
      相关资源
      最近更新 更多