8000 Fix support for datetime axes in 2d plots. · matplotlib/matplotlib@f8344a1 · GitHub
[go: up one dir, main page]

Skip to content

Commit f8344a1

Browse files
committed
Fix support for datetime axes in 2d plots.
Fixes the error raisesd when 2d datetime coordinates are passed to contour/contourf and the errors when 1d or 2d datetime coordinates are passed to pcolor/pcolormesh.
1 parent 954b752 commit f8344a1

File tree

10 files changed

+160
-3
lines changed

10 files changed

+160
-3
lines changed

CHANGELOG

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
2013-07-12 Added support for datetime axes to 2d plots. Axis values are passed
2+
through Axes.convert_xunits/Axes.convert_yunits before being used by
3+
contour/contourf, pcolormesh and pcolor.
4+
5+
2013-07-12 Allowed matplotlib.dates.date2num, matplotlib.dates.num2date,
6+
and matplotlib.dates.datestr2num to accept n-d inputs. Also
7+
factored in support for n-d arrays to matplotlib.dates.DateConverter
8+
and matplotlib.units.Registry.
9+
110
2013-06-26 Refactored the axes module: the axes module is now a folder,
211
containing the following submodule:
312
- _subplots.py, containing all the subplots helper methods

doc/users/whats_new.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@ revision, see the :ref:`github-stats`.
1818
.. contents:: Table of Contents
1919
:depth: 3
2020

21+
.. _whats-new-1-4:
22+
23+
new in matplotlib-1.4
24+
=====================
25+
26+
New plotting features
27+
---------------------
28+
29+
Support for datetime axes in 2d plots
30+
`````````````````````````````````````
31+
Andrew Dawson added support for datetime axes to
32+
:func:`~matplotlib.pyplot.contour`, :func:`~matplotlib.pyplot.contourf`,
33+
:func:`~matplotlib.pyplot.pcolormesh` and :func:`~matplotlib.pyplot.pcolor`.
34+
35+
36+
Date handling
37+
-------------
38+
39+
n-d array support for date conversion
40+
``````````````````````````````````````
41+
Andrew Dawson added support for n-d array handling to
42+
:func:`matplotlib.dates.num2date`, :func:`matplotlib.dates.date2num`
43+
and :func:`matplotlib.dates.datestr2num`. Support is also added to the unit
44+
conversion interfaces :class:`matplotlib.dates.DateConverter` and
45+
:class:`matplotlib.units.Registry`.
46+
2147
.. _whats-new-1-3:
2248

2349
new in matplotlib-1.3

lib/matplotlib/axes/_axes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4472,10 +4472,16 @@ def pcolor(self, *args, **kwargs):
44724472
X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False)
44734473
Ny, Nx = X.shape
44744474

4475+
# unit conversion allows e.g. datetime objects as axis values
4476+
self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
4477+
X = self.convert_xunits(X)
4478+
Y = self.convert_yunits(Y)
4479+
44754480
# convert to MA, if necessary.
44764481
C = ma.asarray(C)
44774482
X = ma.asarray(X)
44784483
Y = ma.asarray(Y)
4484+
44794485
mask = ma.getmaskarray(X) + ma.getmaskarray(Y)
44804486
xymask = (mask[0:-1, 0:-1] + mask[1:, 1:] +
44814487
mask[0:-1, 1:] + mask[1:, 0:-1])
@@ -4668,6 +4674,11 @@ def pcolormesh(self, *args, **kwargs):
46684674
X = X.ravel()
46694675
Y = Y.ravel()
46704676

4677+
# unit conversion allows e.g. datetime objects as axis values
4678+
self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
4679+
X = self.convert_xunits(X)
4680+
Y = self.convert_yunits(Y)
4681+
46714682
coords = np.zeros(((Nx * Ny), 2), dtype=float)
46724683
coords[:, 0] = X
46734684
coords[:, 1] = Y

lib/matplotlib/dates.py

Lines changed: 27 additions & 3 deletions
< 8000 td data-grid-cell-id="diff-31219318ec1480e3b32a4dc999aeb48c78f14cc19a24b2c709c00ae1cbd508b2-261-276-2" data-line-anchor="diff-31219318ec1480e3b32a4dc999aeb48c78f14cc19a24b2c709c00ae1cbd508b2R276" data-selected="false" role="gridcell" style="background-color:var(--bgColor-default);padding-right:24px" tabindex="-1" valign="top" class="focusable-grid-cell diff-text-cell right-side-diff-cell left-side">

Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ def _to_ordinalf(dt):
203203
return base
204204

205205

206+
# a version of _to_ordinalf that can operate on numpy arrays
207+
_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)
208+
209+
206210
def _from_ordinalf(x, tz=None):
207211
"""
208212
Convert Gregorian float of the date, preserving hours, minutes,
@@ -229,6 +233,10 @@ def _from_ordinalf(x, tz=None):
229233
return dt
230234

231235

236+
# a version of _from_ordinalf that can operate on numpy arrays
237+
_from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf)
238+
239+
232240
class strpdate2num:
233241
"""
234242
Use this class to parse date strings to matplotlib datenums when
@@ -246,6 +254,10 @@ def __call__(self, s):
246254
return date2num(datetime.datetime(*time.strptime(s, self.fmt)[:6]))
247255

248256

257+
# a version of dateutil.parser.parse that can operate on nump0y arrays
258+
_dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse)
259+
260+
249261
def datestr2num(d):
250262
"""
251263
Convert a date string to a datenum using
@@ -256,7 +268,10 @@ def datestr2num(d):
256268
dt = dateutil.parser.parse(d)
257269
return date2num(dt)
258270
else:
259-
return date2num([dateutil.parser.parse(s) for s in d])
271+
d = np.asarray(d)
272+
if not d.size:
273+
return d
274+
return date2num(_dateutil_parser_parse_np_vectorized(d))
260275

261276
262277
def date2num(d):
@@ -273,7 +288,10 @@ def date2num(d):
273288
if not cbook.iterable(d):
274289
return _to_ordinalf(d)
275290
else:
276-
return np.asarray([_to_ordinalf(val) for val in d])
291+
d = np.asarray(d)
292+
if not d.size:
293+
return d
294+
return _to_ordinalf_np_vectorized(d)
277295

278296

279297
def julian2num(j):
@@ -310,7 +328,10 @@ def num2date(x, tz=None):
310328
if not cbook.iterable(x):
311329
return _from_ordinalf(x, tz)
312330
else:
313-
return [_from_ordinalf(val, tz) for val in x]
331+
x = np.asarray(x)
332+
if not x.size:
333+
return x
334+
return _from_ordinalf_np_vectorized(x, tz).tolist()
314335

315336

316337
def drange(dstart, dend, delta):
@@ -1292,6 +1313,9 @@ def convert(value, unit, axis):
12921313
@staticmethod
12931314
def default_units(x, axis):
12941315
'Return the tzinfo instance of *x* or of its first element, or None'
1316+
if isinstance(x, np.ndarray):
1317+
x = x.ravel()
1318+
12951319
try:
12961320
x = x[0]
12971321
except (TypeError, IndexError):
Loading
Loading
Loading

lib/matplotlib/tests/test_axes.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from nose.tools import assert_equal
22
from nose.tools import assert_raises
3+
import datetime
34

45
import numpy as np
56
from numpy import ma
@@ -663,6 +664,59 @@ def test_pcolormesh():
663664
ax = fig.add_subplot(133)
664665
ax.pcolormesh(Qx,Qz,Z, shading="gouraud")
665666

667+
668+
@image_comparison(baseline_images=['pcolormesh_datetime_axis'],
669+
extensions=['png'], remove_text=False)
670+
def test_pcolormesh_datetime_axis():
671+
fig = plt.figure()
672+
fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
673+
base = datetime.datetime(2013, 1, 1)
674+
x = np.array([base + datetime.timedelta(days=d) for d in range(21)])
675+
y = np.arange(21)
676+
z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
677+
z = z1 * z2
678+
plt.subplot(221)
679+
plt.pcolormesh(x[:-1], y[:-1], z)
680+
plt.subplot(222)
681+
plt.pcolormesh(x, y, z)
682+
x = np.repeat(x[np.newaxis], 21, axis=0)
683+
y = np.repeat(y[:, np.newaxis], 21, axis=1)
684+
plt.subplot(223)
685+
plt.pcolormesh(x[:-1, :-1], y[:-1, :-1], z)
686+
plt.subplot(224)
687+
plt.pcolormesh(x, y, z)
688+
for ax in fig.get_axes():
689+
for label in ax.get_xticklabels():
690+
label.set_ha('right')
691+
label.set_rotation(30)
692+
693+
694+
@image_comparison(baseline_images=['pcolor_datetime_axis'],
695+
extensions=['png'], remove_text=False)
696+
def test_pcolor_datetime_axis():
697+
fig = plt.figure()
698+
fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
699+
base = datetime.datetime(2013, 1, 1)
700+
x = np.array([base + datetime.timedelta(days=d) for d in range(21)])
701+
y = np.arange(21)
702+
z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
703+
z = z1 * z2
704+
plt.subplot(221)
705+
plt.pcolor(x[:-1], y[:-1], z)
706+
plt.subplot(222)
707+
plt.pcolor(x, y, z)
708+
x = np.repeat(x[np.newaxis], 21, axis=0)
709+
y = np.repeat(y[:, np.newaxis], 21, axis=1)
710+
plt.subplot(223)
711+
plt.pcolor(x[:-1, :-1], y[:-1, :-1], z)
712+
plt.subplot(224)
713+
plt.pcolor(x, y, z)
714+
for ax in fig.get_axes():
715+
for label in ax.get_xticklabels():
716+
label.set_ha('right')
717+
label.set_rotation(30)
718+
719+
666720
def test_pcolorargs():
667721
n = 12
668722
x = np.linspace(-1.5, 1.5, n)

lib/matplotlib/tests/test_contour.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import datetime
2+
13
import numpy as np
24

35
from matplotlib.testing.decorators import cleanup, image_comparison
@@ -177,6 +179,32 @@ def test_given_colors_levels_and_extends():
177179
plt.colorbar()
178180

179181

182+
@image_comparison(baseline_images=['contour_datetime_axis'],
183+
extensions=['png'], remove_text=False)
184+
def test_contour_datetime_axis():
185+
fig = plt.figure()
186+
fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
187+
base = datetime.datetime(2013, 1, 1)
188+
x = np.array([base + datetime.timedelta(days=d) for d in range(20)])
189+
y = np.arange(20)
190+
z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
191+
z = z1 * z2
192+
plt.subplot(221)
193+
plt.contour(x, y, z)
194+
plt.subplot(222)
195+
plt.contourf(x, y, z)
196+
x = np.repeat(x[np.newaxis], 20, axis=0)
197+
y = np.repeat(y[:, np.newaxis], 20, axis=1)
198+
plt.subplot(223)
199+
plt.contour(x, y, z)
200+
plt.subplot(224)
201+
plt.contourf(x, y, z)
202+
for ax in fig.get_axes():
203+
for label in ax.get_xticklabels():
204+
label.set_ha('right')
205+
label.set_rotation(30)
206+
207+
180208
if __name__ == '__main__':
181209
import nose
182210
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

lib/matplotlib/units.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def default_units(x, axis):
4444
"""
4545
from __future__ import print_function
4646
from matplotlib.cbook import iterable, is_numlike
47+
import numpy as np
4748

4849

4950
class AxisInfo:
@@ -132,6 +133,10 @@ def get_converter(self, x):
132133
if classx is not None:
133134
converter = self.get(classx)
134135

136+
if isinstance(x, np.ndarray) and x.size:
137+
converter = self.get_converter(x.ravel()[0])
138+
return converter
139+
135140
if converter is None and iterable(x):
136141
for thisx in x:
137142
# Make sure that recursing might actually lead to a solution,

0 commit comments

Comments
 (0)
0