Place ticklabels of a subplot shared xaxis inside plot to improve readability

I have a subplot showing the time evolution of some variables. As all variables share the same time axis I’m using a shared xaxis so that every drag/zoom action is linked amongst all subplots.

The problem I have is that when the plot becomes too long (especially on mobile) the visibility of the time axis is not optimal. The best solution would be a sticky time xaxis that is placed somewhere on the page and moves with user input. However, I don’t think this is possible.
Placing the ticklabels also at the top or bottom of every subplot is not ideal because the space is reduced.

I had the idea of placing the ticklabels inside the plots. I tried to use ticklabelposition but this doesn’t seem to work, so I ended up placing the labels manually as annotations (as shown in the figure).

This is not optimal because it doesn’t get updated when the plot is zoomed (as the ticklabels are placed at the beginning).

Is there any way to have ticklabels like these inside the plot? Or do you think there’s a better approach to improve redability of the time axis?


Here is the MWE code

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
import pandas as pd

# Create subplots
fig = make_subplots(
    rows=4, cols=1, shared_xaxes=True,
    row_heights=[0.25, 0.25, 0.25, 0.25],
    subplot_titles=("2m Temp", "850hPa Temp", "Rain [mm] | Snow [cm] | Prob. [%]", "Clouds [%]")

# Sample data (replace this with your actual data)
dates = pd.date_range(start="2023-07-10", periods=100, freq='H')
y = np.sin(np.linspace(0, 10, 100)) * 20 + 10
y2 = np.cos(np.linspace(0, 10, 100)) * 10 + 15
rain = np.random.rand(100) * 2
clouds = np.random.rand(10, 100)

# Add traces
fig.add_trace(go.Scatter(x=dates, y=y, mode='lines', name='2m Temp',showlegend=False,), row=1, col=1)
fig.add_trace(go.Scatter(x=dates, y=y2, mode='lines', name='850hPa Temp',showlegend=False,), row=2, col=1)
fig.add_trace(go.Bar(x=dates, y=rain, name='Rain',showlegend=False,), row=3, col=1)
fig.add_trace(go.Heatmap(z=clouds, colorscale='Viridis', x=dates, showscale=False,showlegend=False,), row=4, col=1)

# Manually calculate tick values and labels
tickvals = pd.date_range(start=dates.min().normalize(), end=dates.max().normalize(), freq='D')
ticktext = [date.strftime('%a %d %b') for date in tickvals]

# Add annotations for the first subplot
for i, tick in enumerate(tickvals):
        x=tick, y=0, text=ticktext[i], showarrow=False, textangle=-90,
        xref='x1', yref='y1', yanchor='top', xanchor='center'

# Add annotations for the second subplot
for i, tick in enumerate(tickvals):
        x=tick, y=0, text=ticktext[i], showarrow=False, textangle=-90,
        xref='x2', yref='y2', yanchor='top', xanchor='center'

# Update layout to force Plotly to generate tickvals and ticktext
fig.update_layout(height=800, width=600, title_text="Weather Forecast", margin={"r": 5, "t": 50, "l": 0.1, "b": 0.1},)

# Show the figure