plotly.py icon indicating copy to clipboard operation
plotly.py copied to clipboard

plotly 6.1.2 not showing datetime for FigureWidget

Open mgmarino opened this issue 7 months ago • 5 comments

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:

Image

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:

Image

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.

mgmarino avatar May 30 '25 10:05 mgmarino

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"],

miohtama avatar Jun 17 '25 08:06 miohtama

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),

miohtama avatar Jun 17 '25 08:06 miohtama

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

miohtama avatar Jun 17 '25 09:06 miohtama

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 Image

groggyjohn avatar Oct 23 '25 13:10 groggyjohn

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.

mgmarino avatar Dec 05 '25 13:12 mgmarino