2D timeline date x hour: Heatmap/Scatter?

I am interested in creating information about signal detections over time, much like what was done in this figure from Simonis et al. 2018 (Inter Research » MEPS » v577 » p221-235):

This figure with time on the x-axis and dates on the y-axis shows daily patterns in detections of feeding dolphins (the blue rectangles) with information about day/night (light gray shading indicates nighttime), lunar illumination during the night (the orange bands), and periods where there was no effort made to detect the animals (dark gray polygons).
I am interested in producing similar figures in dash/plotly. If I understand correctly, the Timeline class does not seem appropriate for this, and I was hoping that I could use the Heatmap class. I’ve written some preliminary code to do this and include a paired down version with hardcoded values at the end of this post. In it, I have a couple of sample periods of signals (e.g., dolphin detections), and a period that shows when we were not looking for the signal.

I used Heatmap with columns of dates and times where time was discretized to a duration (30 min). When I have only a few entries as in the example code, it becomes apparent that heatmap is creating boxes that are expanding to multiple days, even when I specify the xperiod and yperiod parameters. The 30 bins between midnight and 4 AM on Nov 16 are split on opposite sides of the x-axis which for some reason has gone beyond the 24 h, wrapping around to 4 AM.

I also tried to add a trace to this plot to show a region without analysis effort. The x-axis becomes furhter corrupted with times that are incongruent with the existing time axis. I am a novice dash/plotly user, but I am wondering if the two methods that I am using Heatmap and Scatter have different time representations. The figure below is the result of my Heatmap and Scatter:

I could manually draw each of the detection blocks as rectangles as I did for the gray region. However, I am hoping that there is something within plotly that might be more suitable. Any suggestions are welcome.

Thank you - Marie

# Demonstration of incongruent time axes when plotting with
# plotly graph_objects Scatter and Heatmap as well as Heatmap box interpolation

import plotly.graph_objects as go
import analysis.time_series
import pandas as pd
import datetime

# Data for showing presence of a signal over fixed intervals
# Will be discretized into bins of a given duration
bin = pd.Timedelta(minutes=30)  # discretization duration
intervals = [  # UTC timestamps
    "2017-11-04T00:00:15Z", "2017-11-04T01:45:00Z",
    "2017-11-04T06:00:30Z", "2017-11-04T09:50:00Z",
    "2017-11-15T19:25:58Z", "2017-11-16T03:50:00Z",
]
intervals = [pd.Timestamp(t) for t in intervals]  # str -> Timestamp
# Create dataframe of times and values discretized by bin size
data = []
for idx in range(0, len(intervals), 2):
    # Find range of bins that cover the interval
    start = intervals[idx].floor(bin)
    end = intervals[idx+1].ceil(bin)
    current = start
    while current <= end:
        # Record presence (1) by date and time
        data.append((current.date(), current.time(), 1))
        # Also tried to use a duration instead of time without success:
        # data.append((current.normalize(), current - current.normalize(), 1))
        current = current + bin
# Merge into a dataframe for plotting
df = pd.DataFrame(data, columns=('Date', 'Time', 'Presence'))

"""
Contents of df :
          Date      Time  Presence
0   2017-11-04  00:00:00         1   # Span between 11/4 00:00 - 02:00
1   2017-11-04  00:30:00         1
2   2017-11-04  01:00:00         1
3   2017-11-04  01:30:00         1
4   2017-11-04  02:00:00         1
5   2017-11-04  06:00:00         1   # Span between 11/4 06:00 - 10:00
6   2017-11-04  06:30:00         1
7   2017-11-04  07:00:00         1
8   2017-11-04  07:30:00         1
9   2017-11-04  08:00:00         1
10  2017-11-04  08:30:00         1
11  2017-11-04  09:00:00         1
12  2017-11-04  09:30:00         1
13  2017-11-04  10:00:00         1
14  2017-11-15  19:00:00         1   # Span between 11/15 19:00 - 11/16 04:00
15  2017-11-15  19:30:00         1
16  2017-11-15  20:00:00         1
17  2017-11-15  20:30:00         1
18  2017-11-15  21:00:00         1
19  2017-11-15  21:30:00         1
20  2017-11-15  22:00:00         1
21  2017-11-15  22:30:00         1
22  2017-11-15  23:00:00         1
23  2017-11-15  23:30:00         1
24  2017-11-16  00:00:00         1
25  2017-11-16  00:30:00         1
26  2017-11-16  01:00:00         1
27  2017-11-16  01:30:00         1
28  2017-11-16  02:00:00         1
29  2017-11-16  02:30:00         1
30  2017-11-16  03:00:00         1
31  2017-11-16  03:30:00         1
32  2017-11-16  04:00:00         1
"""
# Plot using a heatmap
fig = go.Figure(
    # Plot bricks for the time and date
    # Goal:  color bricks by day to show which days and times have signal
    # presence.  In addition to below, tried zsmooth=None and connectgaps=False
    go.Heatmap(x=df.Time, y=df.Date, z=df.Presence,
               xperiod=bin.total_seconds()*1000.0,
               yperiod=pd.Timedelta(days=1).total_seconds()*1000.0,
               )
)
fig.update_xaxes(ticklabelmode="period")
fig.update_yaxes(ticklabelmode="period")
fig.show()

# These data are used to draw a polygonal region over areas where
# there was no effort to detect the signal
noeffort = [
    "2017-11-01 04:30:19Z",
    "2017-11-01 23:30:00Z",
    "2017-11-02 23:30:00Z",
    "2017-11-02 22:26:35Z",
    "2017-11-03 22:26:35Z",
    "2017-11-03 00:00:00Z",
    "2017-11-02 00:00:00Z",
    "2017-11-02 04:30:19Z",
]
noeffort = [pd.Timestamp(t) for t in noeffort]
fig.add_trace(
    go.Scatter(x=[t.time() for t in noeffort],
               y=[t.date() for t in noeffort],
               line={'width': 0, 'color': 'grey'},
               hovertext=f"No effort from {noeffort[0]} to {noeffort[-1]}",
               hoverlabel_bgcolor="gray",
               hoverlabel_font_color='white',
               fill="toself",
               name='no effort',
               ),
    )

    #xaxis={'range':[day_start, day_end]})
fig.show()