Announcing Dash Bio 1.0.0 🎉 : a one-stop-shop for bioinformatics and drug development visualizations.

Weird interaction of plotly and widgets: switching plot types from 'date' to 'linear'

I’m trying to show a Gantt-style timeline chart in plotly and to control the items in the chart with checkboxes. I’m using ipywidgets to link the checkbox to the plot (this is in jupyter notebook). There’s something weird going on with the xaxis type.

When I first show the plot everything is fine: the lines show up and the xaxes is set to ‘date’, so the dates are displayed along the xaxis properly. But when I click the checkbox to add or remove lines, something seems to be switching the xaxis type from ‘date’ to ‘linear’. I tried to set the type directly to ‘date’ in the code multiple ways but nothing seems to work.

Here’s a minimal snippet of code that demonstrates this issue:

import datetime
import pandas as pd
import plotly.graph_objects as go
import ipywidgets as widgets

# simple dataframe for example
df = pd.DataFrame([
                    [datetime.datetime(2021, 12, 1, 0), datetime.datetime(2021, 12, 5)],
                    [datetime.datetime(2021, 12, 3, 0), datetime.datetime(2021, 12, 8)],
                ], columns=['start', 'end'])

# need the end time as seconds after the start time
df = df.assign(end_delta = (0.000001 * (df.end - df.start).values).astype(float))

# start with everything (mostly) blank and then fill in later
layout = go.Layout(dict(barmode='overlay', xaxis=dict(type='date')))
bar = go.Bar(dict(orientation='h'))
fw = go.FigureWidget([bar], layout=layout)

# The function that gets called when you click the checkbox
def switch_show(change):
    global show_both_cb  # read from the checkbox widget directly
    if show_both_cb.value:
        x = df
    else:
        x = df.tail(1)
        
    t = fw.data[0]  # dip into the trace to change the data
    with fw.batch_update():
        t.base = x['start']
        t.x = x['end_delta']
        t.y = x.index
        fw.update_layout(dict(xaxis=dict(type='date'))) # attempt to set it here

# Create a checkbox widget and link it to the function
show_both_cb = widgets.Checkbox(description='Show Both', value=True)
show_both_cb.observe(switch_show)

# Call the switching function once at the start to set everything up
switch_show(None)

widgets.VBox([show_both_cb, fw])

When you first run this in a cell, you get the following nice graph:

Note how the labels on the x-axis are proper timestamps. If you print the dictionary of the figure widget at this point you’ll see that the xaxis type in layout is set to ‘date’:

FigureWidget({
    'data': [{'base': array([datetime.datetime(2021, 12, 1, 0, 0),
                             datetime.datetime(2021, 12, 3, 0, 0)], dtype=object),
              'orientation': 'h',
              'type': 'bar',
              'uid': '4ae4a052-7f90-4894-9eb3-dc5e4e9f58d2',
              'x': array([3.456e+08, 4.320e+08]),
              'y': array([0, 1])}],
    'layout': {'autosize': True, 'barmode': 'overlay', 'template': '...', 'xaxis': {'type': 'date'}}
})

But when you check the checkbox to call the function switch_show() to change it to a single line, the resulting image looks like this:

So it’s correctly showing the single line, but notice how the x-axis is no longer a date. Now if you print the contents of the figure widget you’ll see that the layout xaxis type is blank (which defaults to linear):

FigureWidget({
    'data': [{'base': array([datetime.datetime(2021, 12, 3, 0, 0)], dtype=object),
              'orientation': 'h',
              'type': 'bar',
              'uid': '4ae4a052-7f90-4894-9eb3-dc5e4e9f58d2',
              'x': array([4.32e+08]),
              'y': array([1])}],
    'layout': {'autosize': True, 'barmode': 'overlay', 'template': '...'}
})

If you recheck the box you can get the second line back, but the type stays the same (it doesn’t switch back to ‘date’). I tried setting the type inside the update function (which is what the code does above) and that doesn’t work.

I see in the documentation that plotly attempts to guess at the axis type by looking at the data associated with the trace. When (or where) is it attempting to do that? How do I get around that and make it stick with ‘date’?

Thanks.