datetime with slider widget in matplotlib

2020-07-18 12:16发布

问题:

I want to be able to scroll across x (horizontal) line which represents date/time. For this purpose I'm using slider widget. The problem is that when I scroll too far (where no y values exists) I am no more able to use scrolling feature provided by slider widget and the whole plotted lines disappear and remains just empty figure. The error I get on terminal is following:

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1535, in __call__
    return self.func(*args)
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 586, in callit
    func(*args)
  File "/usr/lib/python2.7/dist-packages/matplotlib/backends/backend_tkagg.py", line 365, in idle_draw
    self.draw()
  File "/usr/lib/python2.7/dist-packages/matplotlib/backends/backend_tkagg.py", line 349, in draw
    FigureCanvasAgg.draw(self)
  File "/usr/lib/python2.7/dist-packages/matplotlib/backends/backend_agg.py", line 469, in draw
    self.figure.draw(self.renderer)
  File "/usr/lib/python2.7/dist-packages/matplotlib/artist.py", line 59, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/matplotlib/figure.py", line 1079, in draw
    func(*args)
  File "/usr/lib/python2.7/dist-packages/matplotlib/artist.py", line 59, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/matplotlib/axes/_base.py", line 2092, in draw
    a.draw(renderer)
  File "/usr/lib/python2.7/dist-packages/matplotlib/artist.py", line 59, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/matplotlib/axis.py", line 1114, in draw
    ticks_to_draw = self._update_ticks(renderer)
  File "/usr/lib/python2.7/dist-packages/matplotlib/axis.py", line 957, in _update_ticks
    tick_tups = [t for t in self.iter_ticks()]
  File "/usr/lib/python2.7/dist-packages/matplotlib/axis.py", line 901, in iter_ticks
    majorLocs = self.major.locator()
  File "/usr/lib/python2.7/dist-packages/matplotlib/dates.py", line 867, in __call__
    return self._locator()
  File "/usr/lib/python2.7/dist-packages/matplotlib/dates.py", line 676, in __call__
    start = dmin - delta
  File "/usr/local/lib/python2.7/dist-packages/dateutil/relativedelta.py", line 309, in __rsub__
    return self.__neg__().__radd__(other)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/relativedelta.py", line 306, in __radd__
    return self.__add__(other)
  File "/usr/local/lib/python2.7/dist-packages/dateutil/relativedelta.py", line 293, in __add__
    microseconds=self.microseconds))
OverflowError: date value out of range
Exception in Tkinter callback

Code:

#!/usr/bin/python

import csv
import sys
import datetime
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

if len(sys.argv[1:]) != 1:
  print "Pass csv file(s) to read from"
  sys.exit(1)

x  = []
y1 = []
y2 = []

# read csv
with open(sys.argv[1], 'rt') as inputFile:
  csvReader = csv.reader(inputFile)

  # skip header row
  csvReader.next()

  for row in csvReader:
    timestamp = datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S")
    x.append(timestamp)
    y1.append(row[1])
    y2.append(row[2])


# plot
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)

# I've also tried convert the dates to matplotlib format but did not help
# import matplotlib
# x = matplotlib.dates.date2num(x)
# matplotlib.pyplot.plot_date(x, y1)

consumption, = plt.plot(x, y1, "b-", label="kw_energy_consumption")
prediction,  = plt.plot(x, y2, "r-", label="prediction")

axcolor = 'lightgoldenrodyellow'
axpos = plt.axes([0.2, 0.1, 0.65, 0.03], axisbg=axcolor)
spos = Slider(axpos, 'Pos', 0.1, 90.0)

def update(val):
    pos = spos.val
    ax.axis([pos,pos+10,-1,1])
    fig.canvas.draw_idle()

spos.on_changed(update)

plt.gcf().autofmt_xdate()
plt.show()

Input (short version) Need to say that this short version also causes described errors:

timestamp,kw_energy_consumption,prediction
2010-07-02 00:00:00,21.2,21.2
2010-07-02 01:00:00,16.4,16.4
2010-07-02 02:00:00,4.7,16.4
2010-07-02 03:00:00,4.7,4.7
2010-07-02 04:00:00,4.6,4.7
2010-07-02 05:00:00,23.5,4.67
2010-07-02 06:00:00,47.5,47.5
2010-07-02 07:00:00,45.4,47.5
2010-07-02 08:00:00,46.1,45.4
2010-07-02 09:00:00,41.5,45.61
2010-07-02 10:00:00,43.4,45.61
2010-07-02 11:00:00,43.8,45.61
2010-07-02 12:00:00,37.8,43.519999999999996
2010-07-02 13:00:00,36.6,43.519999999999996
2010-07-02 14:00:00,35.7,37.44
2010-07-02 15:00:00,38.9,37.44
2010-07-02 16:00:00,36.2,38.9
2010-07-02 17:00:00,36.6,38.9
2010-07-02 18:00:00,37.2,37.188

I suspect that this has something to do with maximum size of Slider or some of it's limits.

Edit: Here is working solution, in case anyone interested (csv reader is replaced by random generator for shorter code)

#!/usr/bin/python

import sys
import numpy as np
import datetime
import random
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)

x = [datetime.datetime(2015,6,25) + datetime.timedelta(hours=i) for i in range(25)]
y = [i+random.gauss(0,1) for i,_ in enumerate(x)]

l, = plt.plot(x,y)

x_min_index = 0
x_max_index = 5

x_min = x[x_min_index]
x_max = x[x_max_index]

# timedelta
x_dt = x_max - x_min

# plt.axis(x_min, x_max, y_min, y_max)
y_min = plt.axis()[2]
y_max = plt.axis()[3]

plt.axis([x_min, x_max, y_min, y_max])


axcolor = 'lightgoldenrodyellow'
axpos = plt.axes([0.2, 0.1, 0.65, 0.03], axisbg=axcolor)

slider_max = len(x) - x_max_index - 1

# Slider(axes, name, min, max)
spos = Slider(axpos, 'Pos', matplotlib.dates.date2num(x_min), matplotlib.dates.date2num(x[slider_max]))

# pretty date names
plt.gcf().autofmt_xdate()

def update(val):
    pos = spos.val
    xmin_time = matplotlib.dates.num2date(pos)
    xmax_time = matplotlib.dates.num2date(pos) + x_dt
    # print "x_min: %s, x_max: %s" % (xmin_time.strftime("%H:%M:%S.%f"), xmax_time.strftime("%H:%M:%S.%f"))

    ########################################################
    # RETURNS THE SAME RESULT:

    # xmin_time is datetime.datetime
    # print type(xmin_time)
    # ax.axis([xmin_time, xmax_time, y_min, y_max])

    # xmin_time is numpy.float64
    xmin_time = pos
    print type(xmin_time)
    ax.axis([xmin_time, xmax_time, y_min, y_max])
    ########################################################
    fig.canvas.draw_idle()

spos.on_changed(update)

plt.show()

回答1:

Your problem is with the definition of your axes

If you add

print plt.axis()

Just after your first plots, you'll get

(733955.0, 733955.75, 0.0, 50.0)

From the documentation of the slider widget you can see that your slider is creating values from 0.1 to 90

And then the slider update function is creating new axes to your graph at

ax.axis([pos,pos+10,-1,1])

Where pos is the value of your slider. So the new axis does not match nor the shape nor the position of your plotted data

So you should update the range and values of your slider and the shape of the updated axis.

Also the errors are because you are trying to parse a very small number as a date, but that is irrelevant in this case