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