plotly 6.1.2 not showing datetime for FigureWidget
The FigureWidget seems to be broken in plotly 6.0 when showing datetime types.
Reproduced using the example here
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv').assign(Date=lambda x: pd.to_datetime(x.Date))
fig = go.Figure([go.Scatter(x=df['Date'], y=df['AAPL.High'])])
fig.show()
displays correctly with datetime:
With FigureWidget:
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv').assign(Date=lambda x: pd.to_datetime(x.Date))
fig = go.FigureWidget([go.Scatter(x=df['Date'], y=df['AAPL.High'])])
fig.show()
shows the nanosecond ints:
This was run in VSCode (1.100.2) and also jupyterlab/server (lab == 4.4.3, jupyter-server == 2.16.0)
Seems similar to #5101.
As a workaround you can do .index.to_pydatetime().tolist():
index = df.index.to_pydatetime().tolist()
# Price chart (top subplot)
fig.add_trace({
"x": index,
"y": df["mark_price"],
I debugged difference of Trace object in Plotly 5.x and 6.x. Apparently DateTimeIndex does not get converted from np.datetime64 to Python datetime.datetime.
Plotly 6.x:
ipdb> pprint(trace)
Scatter({
'fillcolor': '#aaa',
'fillpattern': {'shape': '-', 'size': 5, 'solidity': 0.8},
'hovertemplate': 'asset=USDC<br>timestamp=%{x}<br>% of portfolio=%{y}<extra></extra>',
'legendgroup': 'USDC',
'line': {'color': '#FD3216', 'width': 0},
'marker': {'symbol': 'circle'},
'mode': 'lines',
'name': 'USDC',
'orientation': 'v',
'showlegend': True,
'stackgroup': '1',
'x': array(['2021-06-01T00:00:00.000000000', '2021-06-02T00:00:00.000000000',
'2021-06-03T00:00:00.000000000', '2021-06-04T00:00:00.000000000',
'2021-06-05T00:00:00.000000000', '2021-06-06T00:00:00.000000000',
'2021-06-07T00:00:00.000000000', '2021-06-08T00:00:00.000000000',
Plotly 5.x:
ipdb> pprint(trace)
Scatter({
'fillcolor': '#aaa',
'fillpattern': {'shape': '-', 'size': 5, 'solidity': 0.8},
'hovertemplate': 'asset=USDC<br>timestamp=%{x}<br>% of portfolio=%{y}<extra></extra>',
'legendgroup': 'USDC',
'line': {'color': '#FD3216', 'width': 0},
'marker': {'symbol': 'circle'},
'mode': 'lines',
'name': 'USDC',
'orientation': 'v',
'showlegend': True,
'stackgroup': '1',
'x': array([datetime.datetime(2021, 6, 1, 0, 0),
datetime.datetime(2021, 6, 2, 0, 0),
datetime.datetime(2021, 6, 3, 0, 0),
datetime.datetime(2021, 6, 4, 0, 0),
datetime.datetime(2021, 6, 5, 0, 0),
datetime.datetime(2021, 6, 6, 0, 0),
Here is a monkey-patch to fix the issue:
"""Monkey-patch Plotly 6.x bug: FigureWidget showing nanoseconds instead of dates.
- Workaround bug https://github.com/plotly/plotly.py/issues/5210
"""
from importlib.metadata import version, PackageNotFoundError
import datetime
from packaging.version import Version
try:
pkg_version = version("plotly")
except PackageNotFoundError:
pkg_version = None
if (pkg_version is not None) and Version(pkg_version) <= Version("6.1.2"):
import numpy
import pandas
from plotly.graph_objs import Figure
def fix_trace_x_axis_dates(self: Figure):
for trace in self.data:
item = trace.x[0]
# Detect datetime64 and convert it to native Python datetime that show() can handle
if isinstance(trace.x, numpy.ndarray):
if isinstance(item, numpy.datetime64):
trace.x = pandas.Series(trace.x).dt.to_pydatetime().tolist()
# Run in the monkey patch,
# so that traces are fixed when fig.show() is called
_old_show = Figure.show
def _new_show(self: Figure, *args, **kwargs):
fix_trace_x_axis_dates(self)
_old_show(self, *args, **kwargs)
Figure.show = _new_show
I came across this problem in plotly 6.3.1. To stay with 6.3.1, a workaround seems to be to reduce the resolution of the datetime64 to something other than nanosecs. Eg, below changes to millisecs, but microsecs also work. Looking at other similar issues raised, use of datetime64 with nanosecond resolution seems to be a common factor.
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv').assign(Date=lambda x: pd.to_datetime(x.Date))
df['Date'] = df['Date'].astype('datetime64[ms]')
fig = go.FigureWidget([go.Scatter(x=df['Date'], y=df['AAPL.High'])])
fig.show()
Resulting in
I am a bit disappointed that this hasn't been addressed. However, based upon the feedback here (thanks @groggyjohn), I came up with a workaround that, I believe, actually gets close to fixing the underlying issue and doesn't rely upon monkeypatching Figure which seems, to me, to be unreliable given that the data can be changed/shown in many different ways:
import plotly.serializers as pls
_old_py_to_js = pls._py_to_js
def _new_py_to_js(v, widget_manager):
if isinstance(v, np.ndarray) and v.dtype == "datetime64[ns]":
v = v.astype("datetime64[ms]")
return _old_py_to_js(v, widget_manager)
pls._py_to_js = _new_py_to_js
Of course, this code will need to be executed before anything is serialized.