2013-01-15 16 views
6

Mam wykres 3 zestawy danych, które mają datetime objetcs na osi X. Chcę mieć kursor, który przyciąga dane i pokazuje dokładną wartość xiy.Matplotlib: Przyciąganie kursora do danych wykreślanych z datetime oś czasu

Mam już "przyciąganie do kursora", ale działa to tylko dla skalarnych osi x. Czy ktoś może mi pomóc zmodyfikować przystawkę do kursora, aby działała również dla datetime x osi?

Oto moje wykresy danych: enter image description here

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

A to mój moduł „Helper”, który zawiera „SnaptoCursor” Klasa: (mam podstawową klasę SnaptoCursor gdzieś indziej i modyfikowane to trochę)

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

oczywiście to nie zadziała, ponieważ jestem wywołanie SnaptoCursor z tablicy datetime „dayArray”, który ma być porównywany do położenia myszy później. A te typy danych nie są porównywalne.

Odpowiedz

5

Mam to !!!

Problemy gdzie te dwie linie w Init metody klasy SnaptoCursor:

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

Byli jakoś brudząc się oś datetime X (który ma porządkowych górę do 730.000 EG), więc po prostu trzeba zainicjować współrzędne linii:

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

To działa dobrze!

Będę ogłaszał moją pełną klasę SnaptoCursor, którą teraz zmodyfikowałem, aby akceptowała poszczególne ciągi formatowania, i może zająć do 3 wykresów danych wejściowych - które zostaną przyciągnięte zgodnie z pozycją myszy.

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() 
Powiązane problemy