Skip to content

Bug: make_subplots() doesn't use shared x-axes for traces on different subplots, even when shared_xaxes is set to True #5427

Description

@videni

Description

When using make_subplots() with shared_xaxes=True, spike lines only appear on the subplot where the cursor is hovering, rather than showing across all subplots simultaneously.

Expected Behavior

When hovering over any subplot, a vertical spike line should appear at the same x-position across all subplots, similar to the behavior in TradingView or other financial charting tools.

Current Behavior

The spike line only appears on the subplot currently under the cursor. When moving to a different subplot, the spike line moves with the cursor instead of showing on all subplots.

Code Example

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

# Create sample data
df = pd.DataFrame({
    'x': pd.date_range('2024-01-01', periods=100),
    'y1': range(100),
    'y2': [x**2 for x in range(100)]
})

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

fig.add_trace(go.Scatter(x=df['x'], y=df['y1'], name='Line 1'), row=1, col=1)
fig.add_trace(go.Scatter(x=df['x'], y=df['y2'], name='Line 2'), row=2, col=1)

fig.update_xaxes(
    showspikes=True,
    spikemode='across',
    spikesnap='cursor',
    spikecolor='black',
    spikethickness=1
)

fig.update_layout(hovermode='x unified')
fig.show()

What I've Tried

  1. hovermode='x unified' - Only unifies hover labels, not spike lines
  2. fig.update_traces(xaxis='x') - Shows spikes across all subplots but breaks subplot layout (all data gets squeezed into first subplot's x-axis range)
  3. Various combinations of spikemode, spikesnap, and matches='x' - No effect

Use Case

This feature is critical for financial/trading dashboards where users need to compare multiple indicators (price, volume, RSI, MACD, etc.) at the same point in time across multiple subplots.

Activity

  1. robertclaus commented on Nov 20, 2025

    @robertclaus

    Assigning to @emilykl to take a look and triage.

  2. emilykl commented on Nov 24, 2025

    @emilykl
    Contributor

    Thanks for the ticket @videni!

    I'm able to reproduce the issue. Shared spikelines across multiple subplots should be supported, and it seems like the root cause is a bug in make_subplots() which is assigning different x-axes to the two traces even though the subplots were created with the setting shared_xaxes=True.

    As a workaround, I'm able to get the shared spikeline behavior by manually setting both data traces to use the same x axis by adding this line after the add_trace() calls:

    fig.update_traces(xaxis='x')
    Image

    I would consider this a bug; I'll update the issue description to match and we'll do our best to take a look when we have the chance.

  3. changed the title [-]Spike lines don't show across all subplots simultaneously with shared x-axis[/-] [+]`make_subplots()` doesn't use shared x-axes even when `shared_xaxes` is set to True[/+] on Nov 24, 2025
  4. emilykl commented on Nov 24, 2025

    @emilykl
    Contributor

    Small example demonstrating the shared x-axis behavior with make_subplots(shared_xaxes=True). The code snippet below produces the following plot JSON, where the two subplots have separate x-axes (x and x2), but use the axis.matched parameter to share the same range. Traces added to the figure then automatically use the x-axis of whichever subplot they are added to.

    This means that the x-axes of the two plots remain synced when zooming and panning, BUT it means that features such as cross-subplot unified hover and spikelines don't actually work across subplots.

    I think it's probably OK to have different x-axes for the individual subplots (because this allows us to do things like hide the axis tick labels on the top subplot), but I think that traces added via add_trace() should then always refer to the main x-axis (x), as this will allow the cross-subplot hover behavior to work as expected.

    Note: I suspect the same behavior also applies to the y-axis but I haven't tested it.

    import plotly.graph_objects as go
    from plotly.subplots import make_subplots
    
    # Create two vertically-stacked subplots with shared x-axis
    fig = make_subplots(rows=2, cols=1, shared_xaxes=True)
    
    # Add one trace to each subplot
    fig.add_trace(go.Scatter(x=[1,2,3], y=[1,2,3]), row=1, col=1)
    fig.add_trace(go.Scatter(x=[1,2,3], y=[1,3,2]), row=2, col=1)
    
    print(fig.to_json())

    Plot JSON:

    Details
    {
        "data": [
            {
                "x": [1, 2, 3],
                "y": [1, 2, 3],
                "type": "scatter",
                "xaxis": "x",
                "yaxis": "y"
            },
            {
                "x": [1, 2, 3],
                "y": [1, 3, 2],
                "type": "scatter",
                "xaxis": "x2",
                "yaxis": "y2"
            }
        ],
        "layout": {
            "template": {...},
            "xaxis": {
                "anchor": "y",
                "domain": [0.0, 1.0],
                "matches": "x2",
                "showticklabels": false
            },
            "yaxis": {
                "anchor": "x",
                "domain": [0.575, 1.0]
            },
            "xaxis2": {
                "anchor": "y2",
                "domain": [0.0, 1.0]
            },
            "yaxis2": {
                "anchor": "x2",
                "domain": [0.0, 0.425]
            }
        }
    }
  5. changed the title [-]`make_subplots()` doesn't use shared x-axes even when `shared_xaxes` is set to True[/-] [+]Bug: `make_subplots()` doesn't use shared x-axes for traces on different subplots, even when `shared_xaxes` is set to True[/+] on Nov 24, 2025
  6. removed their assignment
    on Nov 24, 2025
  7. AnneEstoppey commented on Apr 10, 2026

    @AnneEstoppey

    Hello, do we have any updates about this bug?

  8. emilykl commented on Apr 10, 2026

    @emilykl
    Contributor

    @AnneEstoppey Realistically we're not likely to address this in the near future, but a workaround is to add

    fig.update_traces(xaxis='x')

    after adding traces to a plot.

    If you are interested in working on a PR for this issue we'd also be happy to assist.

  9. richmanpoorman commented on Apr 14, 2026

    @richmanpoorman

    Hi @emilykl

    Can I try taking this bug?

  10. emilykl commented on Apr 15, 2026

    @emilykl
    Contributor

    @richmanpoorman Absolutely, go ahead.

    As I mentioned in my comment above, I think the solution is to modify the add_trace() method such that traces added via add_trace() to a plot with matched x-axes reference the main x-axis (x), rather than the x-axis of the plot they are added to. Likewise for matched y-axes.

    However I haven't yet tried implementing this myself so not sure if there are additional complications.

  11. richmanpoorman commented on Apr 17, 2026

    @richmanpoorman

    Hi,
    I just wanted to follow up because I think that I found the bug, but there needs to be a note on the workaround in the mean time. The fig.update_traces(xaxis='x') actually causes a bug where the scale at the bottom disappears.

    This is because it should be fig.update_traces(xaxis='x2') to make the scale come back, as the code internally makes each of the figures a unique value, and only shows the scale on the bottom/last one (so if would be xn for n=number of figures)

  12. richmanpoorman commented on May 8, 2026

    @richmanpoorman

    @emilykl Hi, I tried to use that method, but it seems that there are tests that explicitly expect the plots to be the x-axis they are added to; should I change these tests to match the expectation you have listed above, or should I revert the changes I have? (Note that the change has to be made in make_subplots, as that is where the x-axis references are defined)

    In particular, I am failing a test in tests/test_optional/test_figure_factory/test_figure_factory.py

  13. Juice-de-Orange commented on Oct 2, 2026

    @Juice-de-Orange

    @richmanpoorman are you still planning to post the proposal emilykl asked for when she closed #5581? If not, I'd like to take this over and write up an approach here first.

  14. richmanpoorman commented on Oct 2, 2026

    @richmanpoorman

    Hi, I can summarize my findings here.

    Basically, I think the bug actually stems from a deeper issue in the JS repository with how things are drawn, where the spike line only looks at the current plot and doesn't consider that there are other plots that may match with axis (aka the line getting drawn checks by plot rather than by axis). Since each plot has its own unique set of x and y axis labels (ie "x", "x2", ...) it doesn't draw the spiked line even if the plot says its supposed to match. If you were to fix this, it would going to where the spiked line drawing is in the code, and adding a check to also draw the spikeline on plots that match (aka if drawing a y-axis spikeline, you need to also check the other plots on that y-axis to see if they match that y-axis and vice versa).

    However, we can have a work around where reassign the axis to have the same label as the one you want to match instead. However, the problem with the work around that was listed above was that you need to choose the right axis to assign all of them to, depending on where the labels are. You need to do this, otherwise the tick values will disappear or are on the wrong plot (not at the bottom or top) as the ticks should have the option to be "turned off" for the intermediate graphs (ie if they share the x-axis, only the bottom or top [if it is above] needs to have the ticks shown, you can hide it for the others). You also need to make sure to consider secondary_y labels too, as this method will cause a bug where the secondary_y axis will not be "synced up", as the secondary_y technically creates a distinct y_axis alongside the current y_axis. In addition, there can be an error with the "all" mode if you don't do it properly, where all the graphs can be put into one subplot.

    In retrospect, there is a much cleaner way to solve this problem, as I realize I could just make the changes at the end, rather than trying to make the changes during construction, but the edge cases are why the code change was a bit messy. I might go back and clean it up and refactor what I had done.

    There is also conflict with some tests which expect the axes to be unique between subplots, but in this case, we are purposefully making them the same.

    Please let me know if there are any questions or concerns, and I will try to clarify the best that I can

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3backlogbugsomething broken

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions