1
+ import warnings
1
2
from datetime import datetime , timedelta
2
3
import datetime as pydt
3
4
import numpy as np
45
46
46
47
MUSEC_PER_DAY = 1e6 * SEC_PER_DAY
47
48
49
+ _WARN = True # Global for whether pandas has registered the units explicitly
50
+ _mpl_units = {} # Cache for units overwritten by us
48
51
49
- def register ():
50
- units .registry [lib .Timestamp ] = DatetimeConverter ()
51
- units .registry [Period ] = PeriodConverter ()
52
- units .registry [pydt .datetime ] = DatetimeConverter ()
53
- units .registry [pydt .date ] = DatetimeConverter ()
54
- units .registry [pydt .time ] = TimeConverter ()
55
- units .registry [np .datetime64 ] = DatetimeConverter ()
52
+
53
+ def get_pairs ():
54
+ pairs = [
55
+ (lib .Timestamp , DatetimeConverter ),
56
+ (Period , PeriodConverter ),
57
+ (pydt .datetime , DatetimeConverter ),
58
+ (pydt .date , DatetimeConverter ),
59
+ (pydt .time , TimeConverter ),
60
+ (np .datetime64 , DatetimeConverter ),
61
+ ]
62
+ return pairs
63
+
64
+
65
+ def register (explicit = True ):
66
+ """Register Pandas Formatters and Converters with matplotlib
67
+
68
+ This function modifies the global ``matplotlib.units.registry``
69
+ dictionary. Pandas adds custom converters for
70
+
71
+ * pd.Timestamp
72
+ * pd.Period
73
+ * np.datetime64
74
+ * datetime.datetime
75
+ * datetime.date
76
+ * datetime.time
77
+
78
+ See Also
79
+ --------
80
+ deregister_matplotlib_converter
81
+ """
82
+ # Renamed in pandas.plotting.__init__
83
+ global _WARN
84
+
85
+ if explicit :
86
+ _WARN = False
87
+
88
+ pairs = get_pairs ()
89
+ for type_ , cls in pairs :
90
+ converter = cls ()
91
+ if type_ in units .registry :
92
+ previous = units .registry [type_ ]
93
+ _mpl_units [type_ ] = previous
94
+ units .registry [type_ ] = converter
95
+
96
+
97
+ def deregister ():
98
+ """Remove pandas' formatters and converters
99
+
100
+ Removes the custom converters added by :func:`register`. This
101
+ attempts to set the state of the registry back to the state before
102
+ pandas registered its own units. Converters for pandas' own types like
103
+ Timestamp and Period are removed completely. Converters for types
104
+ pandas overwrites, like ``datetime.datetime``, are restored to their
105
+ original value.
106
+
107
+ See Also
108
+ --------
109
+ deregister_matplotlib_converters
110
+ """
111
+ # Renamed in pandas.plotting.__init__
112
+ for type_ , cls in get_pairs ():
113
+ # We use type to catch our classes directly, no inheritance
114
+ if type (units .registry .get (type_ )) is cls :
115
+ units .registry .pop (type_ )
116
+
117
+ # restore the old keys
118
+ for unit , formatter in _mpl_units .items ():
119
+ if type (formatter ) not in {DatetimeConverter , PeriodConverter ,
120
+ TimeConverter }:
121
+ # make it idempotent by excluding ours.
122
+ units .registry [unit ] = formatter
123
+
124
+
125
+ def _check_implicitly_registered ():
126
+ global _WARN
127
+
128
+ if _WARN :
129
+ msg = ("Using an implicitly registered datetime converter for a "
130
+ "matplotlib plotting method. The converter was registered "
131
+ "by pandas on import. Future versions of pandas will require "
132
+ "you to explicitly register matplotlib converters.\n \n "
133
+ "To register the converters:\n \t "
134
+ ">>> from pandas.plotting import register_matplotlib_converters"
135
+ "\n \t "
136
+ ">>> register_matplotlib_converters()" )
137
+ warnings .warn (msg , FutureWarning )
138
+ _WARN = False
56
139
57
140
58
141
def _to_ordinalf (tm ):
@@ -190,6 +273,7 @@ class DatetimeConverter(dates.DateConverter):
190
273
@staticmethod
191
274
def convert (values , unit , axis ):
192
275
# values might be a 1-d array, or a list-like of arrays.
276
+ _check_implicitly_registered ()
193
277
if is_nested_list_like (values ):
194
278
values = [DatetimeConverter ._convert_1d (v , unit , axis )
195
279
for v in values ]
@@ -274,6 +358,7 @@ class PandasAutoDateLocator(dates.AutoDateLocator):
274
358
275
359
def get_locator (self , dmin , dmax ):
276
360
'Pick the best locator based on a distance.'
361
+ _check_implicitly_registered ()
277
362
delta = relativedelta (dmax , dmin )
278
363
279
364
num_days = (delta .years * 12.0 + delta .months ) * 31.0 + delta .days
@@ -315,6 +400,7 @@ def get_unit_generic(freq):
315
400
316
401
def __call__ (self ):
317
402
# if no data have been set, this will tank with a ValueError
403
+ _check_implicitly_registered ()
318
404
try :
319
405
dmin , dmax = self .viewlim_to_dt ()
320
406
except ValueError :
@@ -918,6 +1004,8 @@ def _get_default_locs(self, vmin, vmax):
918
1004
def __call__ (self ):
919
1005
'Return the locations of the ticks.'
920
1006
# axis calls Locator.set_axis inside set_m<xxxx>_formatter
1007
+ _check_implicitly_registered ()
1008
+
921
1009
vi = tuple (self .axis .get_view_interval ())
922
1010
if vi != self .plot_obj .view_interval :
923
1011
self .plot_obj .date_axis_info = None
@@ -1002,6 +1090,8 @@ def set_locs(self, locs):
1002
1090
'Sets the locations of the ticks'
1003
1091
# don't actually use the locs. This is just needed to work with
1004
1092
# matplotlib. Force to use vmin, vmax
1093
+ _check_implicitly_registered ()
1094
+
1005
1095
self .locs = locs
1006
1096
1007
1097
(vmin , vmax ) = vi = tuple (self .axis .get_view_interval ())
@@ -1013,6 +1103,8 @@ def set_locs(self, locs):
1013
1103
self ._set_default_format (vmin , vmax )
1014
1104
1015
1105
def __call__ (self , x , pos = 0 ):
1106
+ _check_implicitly_registered ()
1107
+
1016
1108
if self .formatdict is None :
1017
1109
return ''
1018
1110
else :
@@ -1043,6 +1135,7 @@ def format_timedelta_ticks(x, pos, n_decimals):
1043
1135
return s
1044
1136
1045
1137
def __call__ (self , x , pos = 0 ):
1138
+ _check_implicitly_registered ()
1046
1139
(vmin , vmax ) = tuple (self .axis .get_view_interval ())
1047
1140
n_decimals = int (np .ceil (np .log10 (100 * 1e9 / (vmax - vmin ))))
1048
1141
if n_decimals > 9 :
0 commit comments