Order of rendering when using two y-axis

Hello. I’m fairly new to plotly dash and I’ve set up a graph using two y-axis and a shared x-axis. The graph is mostly as expected, but the bar chart (yaxis2) covers the line/scatter chart (yaxis1). From what I’ve read, the order of the traces in the code should control which of the two is shown on top. I’ve tried reversing it, but no effect. As a work around, I’ve lowered the opacity of the bar chart. The code I’m using is:

def updateForwardGraph(selectedRows):
    if not selectedRows:
        return []

    selectedName = [row['Name'] for row in selectedRows]
    selectedTeam = [row['Team'] for row in selectedRows]
    selectedTeamDisplay = [row['Team Name Full'] for row in selectedRows]

    dffName = dfGameweek[dfGameweek['Name'].isin(selectedName)].copy()
    dffTeam = dfOddsGameweek[dfOddsGameweek['Team'].isin(selectedTeam)].copy()

    dffTeamHomeMatch = dffTeam[dffTeam['HomeAway'] == 'H']
    dffTeamAwayMatch = dffTeam[dffTeam['HomeAway'] == 'A']

    # Create the main graph
    fig = go.Figure()

    # Create the second y-axis
    fig.add_trace(go.Bar(x=dffTeamAwayMatch['Gameweek'], y=dffTeamAwayMatch['Winning Probability'], marker_color= 'lightgrey',
                         name=f'{selectedTeamDisplay[0]} winning probab. away', yaxis='y2', opacity=0.4,
                         text=dffTeamAwayMatch['Opponent name'], textposition='outside'))
    fig.add_trace(go.Bar(x=dffTeamHomeMatch['Gameweek'], y=dffTeamHomeMatch['Winning Probability'], marker_color='lightblue',
                         name=f'{selectedTeamDisplay[0]} winning probab. home', yaxis='y2', opacity=0.4,
                         text=dffTeamHomeMatch['Opponent name'], textposition='outside'))

    # Create the first y-axis
    fig.add_trace(go.Scatter(x=dffName['Gameweek'], y=dffName['xG rolling 5'], mode='lines',
                             name='5 matches rolling average'))
    fig.add_trace(go.Scatter(x=dffName['Gameweek'], y=dffName['xGI non-pen'], mode='lines+markers+text',
                             name='Per match', text=dffName['xGI non-pen'], textposition='top right'))

    # Update the layout for the individual series
    fig.update_traces(line=dict(color='red'), selector=dict(name='5 matches rolling average'))
    fig.update_traces(line=dict(color='black'), selector=dict(name='Per match'))

    # Update the layout for the figure
    fig.update_layout(
        height=600,
        title=f'{selectedName[0]}: xGI non-penalty per game and 5 game rolling average',
        paper_bgcolor='#e7edf1',
        plot_bgcolor='white',
        yaxis=dict(gridcolor='lightgrey', title='xGI non-penalty', range=[0, 2.5]),
        yaxis2=dict(showgrid=False, title='Winning probability', range=[0, 100], dtick=10, overlaying='y', side='right'),
        xaxis=dict(dtick=1, showgrid=False, title='Gameweek'),
        legend_title='',
        legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01, bgcolor='white'),
        font=dict(family='Nova mono', size=16, color='black')
    )

    graph = dcc.Graph(id='xG line', figure=fig)

    return graph

Does anyone have a suggestion to how I can change the code to get the lines to show on top of the bars?

@Andabe welcome to the forums.

Could you add data so that people willing to help you can reproduce your code/graph on their computers?

Thank you for your replies. I’ve tried to look into using update_z, but from what I can understand it only applies to 3d plots and not 2d. Please correct me if I’m wrong.

@AIMPED Below is an example code including some test data for those willing to have a go at reproducing the issue I’m facing. I’ve almost conceded and accepted the opacity work around, but please let me know if you can solve it :slight_smile:

import dash
from dash import dcc, html, Input, Output
import plotly.graph_objs as go
import pandas as pd
import random

# Start the Dash app
app = dash.Dash(__name__)

# Create dummy data for players and teams
data = {
    'Gameweek': list(range(1, 11)),
    'xG rolling 5': [random.uniform(0, 2) for _ in range(10)],
    'xGI non-pen': [random.uniform(0, 2) for _ in range(10)],
    'Name': ['Player 1'] * 10,
    'Team': ['Team A'] * 10
}

dfGameweek = pd.DataFrame(data)

odds_data = {
    'Gameweek': list(range(1, 11)),
    'Winning Probability': [random.uniform(40, 90) for _ in range(10)],
    'Team': ['Team A'] * 5 + ['Team A'] * 5,
    'HomeAway': ['H'] * 5 + ['A'] * 5,
    'Opponent name': ['Opponent ' + str(i) for i in range(1, 11)]
}

dfOddsGameweek = pd.DataFrame(odds_data)

# App layout
app.layout = html.Div([
    html.H1("Player Performance Dashboard"),
    dcc.Dropdown(
        id='player-dropdown',
        options=[{'label': 'Player 1', 'value': 'Player 1'}],
        value='Player 1',
        clearable=False
    ),
    html.Div(id='forward-graph')
])

# Callback to update the graph based on selected player
@app.callback(
    Output('forward-graph', 'children'),
    [Input('player-dropdown', 'value')]
)
def updateForwardGraph(selectedPlayer):
    if not selectedPlayer:
        return []

    selectedRows = [{'Name': 'Player 1', 'Team': 'Team A', 'Team Name Full': 'Team Alpha'}]

    selectedName = [row['Name'] for row in selectedRows]
    selectedTeam = [row['Team'] for row in selectedRows]
    selectedTeamDisplay = [row['Team Name Full'] for row in selectedRows]

    dffName = dfGameweek[dfGameweek['Name'].isin(selectedName)].copy()
    dffTeam = dfOddsGameweek[dfOddsGameweek['Team'].isin(selectedTeam)].copy()

    dffTeamHomeMatch = dffTeam[dffTeam['HomeAway'] == 'H']
    dffTeamAwayMatch = dffTeam[dffTeam['HomeAway'] == 'A']

    # Create the main graph
    fig = go.Figure()

    # Add bars first
    fig.add_trace(go.Bar(x=dffTeamHomeMatch['Gameweek'], y=dffTeamHomeMatch['Winning Probability'], marker_color='lightblue',
                         name=f'{selectedTeamDisplay[0]} winning prob. home', yaxis='y2', opacity=1,
                         text=dffTeamHomeMatch['Opponent name'], textposition='outside'))
    fig.add_trace(go.Bar(x=dffTeamAwayMatch['Gameweek'], y=dffTeamAwayMatch['Winning Probability'], marker_color='lightgrey',
                         name=f'{selectedTeamDisplay[0]} winning prob. away', yaxis='y2', opacity=1,
                         text=dffTeamAwayMatch['Opponent name'], textposition='outside'))

    # Then add the lines (scatter traces)
    fig.add_trace(go.Scatter(x=dffName['Gameweek'], y=dffName['xG rolling 5'], mode='lines',
                             name='5 matches rolling average'))
    fig.add_trace(go.Scatter(x=dffName['Gameweek'], y=dffName['xGI non-pen'], mode='lines+markers+text',
                             name='Per match', text=dffName['xGI non-pen'], textposition='top right'))

    # Update the layout for the individual series
    fig.update_traces(line=dict(color='red'), selector=dict(name='5 matches rolling average'))
    fig.update_traces(line=dict(color='black'), selector=dict(name='Per match'))

    # Update the layout for the figure
    fig.update_layout(
        height=600,
        title=f'{selectedName[0]}: xGI non-penalty',
        paper_bgcolor='#e7edf1',
        plot_bgcolor='white',
        yaxis=dict(gridcolor='lightgrey', title='xGI non-penalty', range=[0, 2.5]),
        yaxis2=dict(showgrid=False, title='Winning probability', range=[0, 100], dtick=10, overlaying='y', side='right'),
        xaxis=dict(dtick=1, showgrid=False, title='Gameweek'),
        legend_title='',
        legend=dict(yanchor="middle", y=1.2, xanchor="right", x=1, bgcolor='white'),
        font=dict(family='Nova mono', size=16, color='black')
    )

    graph = dcc.Graph(id='xG line', figure=fig)

    return graph

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)