Skip to content

Commit a659c1e

Browse files
committed
Merge pull request #139 from plotly/yet-another-date-formatter
Yet another date formatter
2 parents 7456d1e + ea9326d commit a659c1e

File tree

5 files changed

+76
-18
lines changed

5 files changed

+76
-18
lines changed

optional-requirements.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
### ###
66
###################################################################
77

8+
## numpy (technically, this is covered by matplotlib's deps) ##
9+
numpy
10+
811
## matplotlylib dependencies ##
912
matplotlib==1.3.1
1013

1114
## testing dependencies ##
1215
nose==1.3.3
1316

1417
## ipython dependencies ##
15-
ipython[all]
18+
ipython[all]
19+
20+
## pandas deps for some matplotlib functionality ##
21+
pandas

plotly/matplotlylib/mpltools.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -422,13 +422,17 @@ def prep_ticks(ax, index, ax_type, props):
422422
if ax_type == 'x' and 'DateFormatter' in formatter:
423423
axis_dict['type'] = 'date'
424424
try:
425-
axis_dict['tick0'] = mpl_dates_to_datestrings(axis_dict['tick0'])
425+
axis_dict['tick0'] = mpl_dates_to_datestrings(
426+
axis_dict['tick0'], formatter
427+
)
426428
except KeyError:
427429
pass
428430
finally:
429431
axis_dict.pop('dtick', None)
430432
axis_dict.pop('autotick', None)
431-
axis_dict['range'] = mpl_dates_to_datestrings(props['xlim'])
433+
axis_dict['range'] = mpl_dates_to_datestrings(
434+
props['xlim'], formatter
435+
)
432436

433437
if formatter == 'LogFormatterMathtext':
434438
axis_dict['exponentformat'] = 'e'
@@ -457,22 +461,39 @@ def prep_xy_axis(ax, props, x_bounds, y_bounds):
457461
return xaxis, yaxis
458462

459463

460-
def mpl_dates_to_datestrings(mpl_dates, format_string="%Y-%m-%d %H:%M:%S"):
461-
"""Convert matplotlib dates to formatted datestrings for plotly.
464+
def mpl_dates_to_datestrings(dates, mpl_formatter):
465+
"""Convert matplotlib dates to iso-formatted-like time strings.
466+
467+
Plotly's accepted format: "YYYY-MM-DD HH:MM:SS" (e.g., 2001-01-01 00:00:00)
462468
463469
Info on mpl dates: http://matplotlib.org/api/dates_api.html
464470
465471
"""
466-
try:
467-
date_times = matplotlib.dates.num2date(mpl_dates, tz=pytz.utc)
468-
time_strings = [date_time.strftime(format_string)
469-
for date_time in date_times]
470-
if len(time_strings) > 1:
471-
return time_strings
472-
else:
473-
return time_strings[0]
474-
except TypeError:
475-
return mpl_dates
472+
_dates = dates
473+
474+
# this is a pandas datetime formatter, times show up in floating point days
475+
# since the epoch (1970-01-01T00:00:00+00:00)
476+
if mpl_formatter == "TimeSeries_DateFormatter":
477+
try:
478+
dates = matplotlib.dates.epoch2num(
479+
[date*24*60*60 for date in dates]
480+
)
481+
dates = matplotlib.dates.num2date(dates, tz=pytz.utc)
482+
except:
483+
return _dates
484+
485+
# the rest of mpl dates are in floating point days since
486+
# (0001-01-01T00:00:00+00:00) + 1. I.e., (0001-01-01T00:00:00+00:00) == 1.0
487+
# according to mpl --> try num2date(1)
488+
else:
489+
try:
490+
dates = matplotlib.dates.num2date(dates, tz=pytz.utc)
491+
except:
492+
return _dates
493+
494+
time_stings = [' '.join(date.isoformat().split('+')[0].split('T'))
495+
for date in dates]
496+
return time_stings
476497

477498

478499
DASH_MAP = {

plotly/matplotlylib/renderer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,9 @@ def draw_bar(self, coll):
258258
[bar['x1'] for bar in trace])
259259
if self.x_is_mpl_date:
260260
x = [bar['x0'] for bar in trace]
261-
x = mpltools.mpl_dates_to_datestrings(x)
261+
formatter = (self.current_mpl_ax.get_xaxis()
262+
.get_major_formatter().__class__.__name__)
263+
x = mpltools.mpl_dates_to_datestrings(x, formatter)
262264
else:
263265
self.msg += " Attempting to draw a horizontal bar chart\n"
264266
old_rights = [bar_props['x1'] for bar_props in trace]
@@ -367,8 +369,10 @@ def draw_marked_line(self, **props):
367369
line=line,
368370
marker=marker)
369371
if self.x_is_mpl_date:
372+
formatter = (self.current_mpl_ax.get_xaxis()
373+
.get_major_formatter().__class__.__name__)
370374
marked_line['x'] = mpltools.mpl_dates_to_datestrings(
371-
marked_line['x']
375+
marked_line['x'], formatter
372376
)
373377
self.plotly_fig['data'] += marked_line,
374378
self.msg += " Heck yeah, I drew that line\n"

plotly/tests/test_optional/test_matplotlylib/test_date_times.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from __future__ import absolute_import
2+
3+
import random
24
import matplotlib
35
# Force matplotlib to not use any Xwindows backend.
46
matplotlib.use('Agg')
57
import matplotlib.pyplot as plt
68
import datetime
79
from matplotlib.dates import date2num
10+
import pandas as pd
811
import plotly.tools as tls
912
from unittest import TestCase
1013

@@ -37,4 +40,28 @@ def test_normal_mpl_dates(self):
3740
print date_strings
3841
print pfig['data'][0]['x']
3942
# we use the same format here, so we expect equality here
43+
self.assertEqual(
44+
fig.axes[0].lines[0].get_xydata()[0][0], 7.33776000e+05
45+
)
4046
self.assertEqual(pfig['data'][0]['x'], date_strings)
47+
48+
def test_pandas_time_series_date_formatter(self):
49+
ndays = 3
50+
x = pd.date_range('1/1/2001', periods=ndays, freq='D')
51+
y = [random.randint(0, 10) for i in range(ndays)]
52+
s = pd.DataFrame(y, columns=['a'])
53+
54+
s['Date'] = x
55+
s.plot(x='Date')
56+
57+
fig = plt.gcf()
58+
pfig = tls.mpl_to_plotly(fig)
59+
60+
expected_x = ['2001-01-01 00:00:00',
61+
'2001-01-02 00:00:00',
62+
'2001-01-03 00:00:00']
63+
expected_x0 = 11323.0 # this is floating point days since epoch
64+
65+
x0 = fig.axes[0].lines[0].get_xydata()[0][0]
66+
self.assertEqual(x0, expected_x0)
67+
self.assertListEqual(pfig['data'][0]['x'], expected_x)

plotly/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.4.1'
1+
__version__ = '1.4.2'

0 commit comments

Comments
 (0)