Search code examples
pythonplotlyplotly-python

Python Plotly: X-Axis Glitches When Attempting to Draw Unified Spike Across Multiple Subplots


Background

I am currently attempting to draw a unified spike across multiple subplots when hovering. I want to do this to improve the visualization of the x-axis. I am currently following advice from this thread on GitHub.

My Problem

However, when I try to do it, the x-axis immediately glitches out and shoves all the data to the far right of the chart. Could anyone more familiar with Plotly explain why this is happening? I have included a reproducible piece of code below.

Note: In order to see the visual bug, you have to uncomment the problematic lines

Reproducible Code

import plotly.graph_objects as go
from plotly.subplots import make_subplots
from datetime import date

trace1 = go.Scatter(x=[date(2023, 1, 15), date(2023, 2, 15), date(2023, 3, 15), date(2023, 4, 15),
                       date(2023, 5, 15), date(2023, 6, 15), date(2023, 7, 15), date(2023, 8, 15),
                       date(2023, 9, 15), date(2023, 10, 15), date(2023, 11, 15), date(2023, 12, 15)],
                    y=[1000, 2000, 3000, 4000,
                       5000, 6000, 7000, 8000,
                       9000, 10000, 11000, 12000],
                    name='trace1')

trace2 = go.Scatter(x=[date(2023, 1, 15), date(2023, 2, 15), date(2023, 3, 15), date(2023, 4, 15),
                       date(2023, 5, 15), date(2023, 6, 15), date(2023, 7, 15), date(2023, 8, 15),
                       date(2023, 9, 15), date(2023, 10, 15), date(2023, 11, 15), date(2023, 12, 15)],
                    y=[10_000, 20_000, 30_000, 40_000,
                       50_000, 60_000, 70_000, 80_000,
                       90_000, 100_000, 110_000, 120_000],
                    name='trace2')

fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.02)

fig.add_trace(trace1, row=1, col=1)
fig.add_trace(trace2, row=2, col=1)

fig.add_annotation(
    xref='x domain',
    yref='y domain',
    x=0,
    y=1,
    text='FIRST ANNOTATION',
    showarrow=False,
    font=dict(size=14, color='blue'),
    row=1, col=1
)

fig.add_annotation(
    xref='x domain',
    yref='y domain',
    x=0,
    y=1,
    text='SECOND ANNOTATION',
    showarrow=False,
    font=dict(size=14, color='red'),
    row=2, col=1
)

# fig.update_traces(xaxis='x')
# fig.update_xaxes(spikemode='across+marker')
# fig.update_shapes(selector=dict(type="line"), xref="x domain")
# fig.update_shapes(selector=dict(type="rect"), xref="x")

fig.update_layout(height=600, width=600, title_text='Example Chart')
fig.show()

Chart Outputs

Before Uncommenting After Uncommenting
enter image description here enter image description here

Solution

  • I don't know really why this is happening but it turns out that the x axes (auto)ranges are not computed correctly when we make both traces refer to the same xaxis with fig.update_traces(xaxis='x'). To fix that, we need to set the proper range manually. Also instead of 'x', we need to refer to 'x2', that is, the x axis of the 2nd row, otherwise we loose the date ticks.

    Doing the above will make the first annotation disappear, but again we can fix that by setting its xref to 'x2 domain' (instead of 'x'). The weird thing is doing that via fig.add_annotation() has no effect (I guess because references to x and/or y are overridden automatically to match the axis id of the subplot corresponding to the specified row and col), so we have to assign the value manually.

    Replacing the commented lines with what follows works correctly :

    # The main xaxis is 'x2'
    fig.update_traces(xaxis='x2')
    fig['layout']['annotations'][0]['xref'] = 'x2 domain'
    
    # Compute xaxes date range 
    d0, d1 = min(trace1.x), max(trace1.x)
    room = (d1 - d0) * 0.07
    d0 -= room
    d1 += room
    
    fig.update_xaxes(spikemode='across+marker', type='date', range=[d0, d1])