Fill area between lines with colour that depends on condition

I have created a Python dashboard for a school project. The code generates two graphs (the second of which is unimportant for the purpose of this question). The first one plots a line that represents Wealth ($). This fluctuates above and below that horizontal line that represent’s the individual starting wealth. I would like to shade in green and red the area between the Wealth ($) like and the horizontal dashed line based on whether the former is above or below the latter. The image below shows what I have managed to achieve thus far.

To do so, I have followed the steps suggested in this post:

Unfortunately, you can see that the colour does not switch at the moment the horizontal axis is crossed, and there are some regions where the shading overlaps. Could anyone help me solve this problem? I have really tried my best and I can’t tackle it.

This is the code I am using:

import dash
from dash import dcc, html, Input, Output
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objs as go

# Load the data
file_path = 'poker.xlsx'  
df = pd.read_excel(file_path, sheet_name='Online')

# Filter out unnecessary columns if there are any
columns_to_keep = ['Day', 'Wealth ($)', 'Daily P&L ($)', 'Cumul P&L ($)', 'Cumul P&L (%)']
df = df[columns_to_keep]

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

# Define the color for each bar based on 'Daily P&L ($)' value
df['color'] = df['Daily P&L ($)'].apply(lambda x: 'green' if x >= 0 else 'red')

# Get the starting value of Wealth ($)
starting_wealth = df['Wealth ($)'].iloc[0]

# Create the initial figures for the graphs (they will be updated by callbacks)
wealth_over_time_fig = go.Figure()
daily_pl_fig = go.Figure()

# Define the layout of the app
app.layout = html.Div([
    html.H1('Online Poker Performance Dashboard', style={'textAlign': 'center'}),

    dcc.Graph(id='wealth-over-time', figure=wealth_over_time_fig),

    html.Div([
        dcc.RangeSlider(
            id='wealth-time-range-slider',
            min=df['Day'].min(),
            max=df['Day'].max(),
            value=[df['Day'].min(), df['Day'].max()],
            marks={str(day): str(day) for day in df['Day'].unique()},
            step=None
        )], style={'padding': '0px 60px 0px 60px'}),

    dcc.Graph(id='daily-pl', figure=daily_pl_fig),

    html.Div([
        dcc.RangeSlider(
            id='daily-pl-range-slider',
            min=df['Day'].min(),
            max=df['Day'].max(),
            value=[df['Day'].min(), df['Day'].max()],
            marks={str(day): str(day) for day in df['Day'].unique()},
            step=None
        )
    ], style={'padding': '0px 60px 0px 60px'}),

    html.Div([
        html.H3(f"Sharpe Ratio: {df['Cumul P&L (%)'].mean() / df['Cumul P&L (%)'].std():.4f}")
    ], style={'textAlign': 'left', 'padding-left': '75px'}),
])

# Callback for updating the Wealth Over Time graph based on the selected range from the RangeSlider
@app.callback(
    Output('wealth-over-time', 'figure'),
    Input('wealth-time-range-slider', 'value'))
def update_wealth_over_time(selected_range):
    
    filtered_df = df[df['Day'].between(selected_range[0], selected_range[1])]
    
    # Calculate the 'boundary' line
    filtered_df['boundary'] = np.where(filtered_df['Wealth ($)'] < starting_wealth, 
                                       filtered_df['Wealth ($)'], 
                                       starting_wealth)
    print(filtered_df)
    
    # Create the figure
    fig = go.Figure()

    # Add the traces for 'Wealth ($)' line and 'boundary' line
    fig.add_trace(go.Scatter(
        x=filtered_df['Day'], 
        y=filtered_df['boundary'],
        line=dict(color='rgba(255,255,255,0)'),
        showlegend=False
    ))
    
    # Fill between 'boundary' and starting wealth (red area)
    fig.add_trace(go.Scatter(
        x=filtered_df['Day'], 
        y=[starting_wealth] * len(filtered_df),
        fill='tonexty',
        fillcolor='rgba(255, 0, 0, 0.3)',
        line=dict(color='rgba(255,0,0,0.3)'),
        showlegend=False
    ))

    # Fill between 'Wealth ($)' and 'boundary' (green area)
    fig.add_trace(go.Scatter(
        x=filtered_df['Day'], 
        y=filtered_df['boundary'],
        line=dict(color='rgba(255,255,255,0)'),
        showlegend=False
    ))

    # Add the Wealth ($) line on top
    fig.add_trace(go.Scatter(
        x=filtered_df['Day'],
        y=filtered_df['Wealth ($)'],
        fill='tonexty',
        fillcolor = 'rgba(0, 255, 0, 0.3)',
        name='Wealth ($)',
        line=dict(color='black')
    ))

    # Add the starting wealth line (dashed)
    fig.add_trace(go.Scatter(
        x=[filtered_df['Day'].min(), filtered_df['Day'].max()],
        y=[starting_wealth, starting_wealth],
        mode='lines',
        name='Starting Wealth',
        line=dict(color='black', width=2, dash='dash')
    ))

    # Update layout
    fig.update_layout(
        title='Wealth Over Time',
        xaxis=dict(title='Day'),
        yaxis=dict(title='Wealth ($)'),
        plot_bgcolor='white'
    )

    return fig

# Callback for updating the Daily P&L graph based on the selected range from the RangeSlider
@app.callback(
    Output('daily-pl', 'figure'),
    Input('daily-pl-range-slider', 'value'))
def update_daily_pl(selected_range):
    filtered_df = df[df['Day'].between(selected_range[0], selected_range[1])]
    fig = go.Figure(
        data=[
            go.Bar(
                x=filtered_df['Day'], 
                y=filtered_df['Daily P&L ($)'], 
                name='Daily P&L ($)',
                marker=dict(
                    color=filtered_df['color'],
                    line=dict(
                        color='black',
                        width=1,
                    ),
                )
            )
        ]
    )
    fig.update_layout(
        title='Daily P&L',
        plot_bgcolor='white',
        xaxis=dict(
            title='Day',
            showgrid=True,
            gridcolor='lightgrey',
            gridwidth=1,
            showline=True,
            linewidth=2,
            linecolor='black',
            mirror=True,
        ),
        yaxis=dict(
            title='Daily P&L ($)',
            showgrid=True,
            gridcolor='lightgrey',
            gridwidth=1,
            showline=True,
            linewidth=2,
            linecolor='black',
            mirror=True,
        )
    )
    return fig

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

The data is not very important, as I have set it randomly, but here is the one I am using:

Day Date Wealth ($) Daily P&L ($) Daily Wealth P&L (%) Daily Buy-In P&L (%) Cumul P&L ($) Cumul P&L (%)
1 44966 11.69 0 0 0 0 0
2 44967 11.91 0.22 0.018819504 0.11 0.22 0.018819504
3 44968 12.12 0.21 0.017632242 0.105 0.43 0.036783576
4 44980 13.74 1.62 0.133663366 0.81 2.05 0.175363559
5 45254 11.74 -2 -0.145560408 -1 0.05 0.00427716
6 45255 10.44 -1.3 -0.110732538 -0.65 -1.25 -0.106928999
7 45256 13.84 3.4 0.325670498 1.7 2.15 0.183917879
8 45257 14.01 0.17 0.012283237 0.085 2.32 0.198460222
9 45258 14.74 0.73 0.052105639 0.365 3.05 0.260906758
10 45262 14.88 0.14 0.009497965 0.07 3.19 0.272882806
11 45263 15.03 0.15 0.010080645 0.075 3.34 0.285714286
12 45264 15.44 0.41 0.027278776 0.205 3.75 0.320786997
13 45265 15.65 0.21 0.013601036 0.105 3.96 0.338751069
14 45266 13.46 -2.19 -0.139936102 -1.095 1.77 0.151411463
15 45267 13.81 0.35 0.026002972 0.175 2.12 0.181351583
16 45269 13.92 0.11 0.007965243 0.055 2.23 0.190761334
17 45270 14.77 0.85 0.061063218 0.425 3.08 0.263473054
18 45271 14.87 0.1 0.006770481 0.05 3.18 0.272027374
19 45272 15.27 0.4 0.026899798 0.2 3.58 0.306244654
20 45273 16.35 1.08 0.070726916 0.54 4.66 0.398631309
21 45293 17.31 0.96 0.058715596 0.48 5.62 0.48075278
22 45294 8.55 -8.76 -0.506065858 -4.38 -3.14 -0.268605646
23 45299 8.75 0.2 0.023391813 0.1 -2.94 -0.251497006
24 45300 8.81 0.06 0.006857143 0.03 -2.88 -0.246364414
25 45301 9.26 0.45 0.05107832 0.225 -2.43 -0.207869974
26 45304 10.42 1.16 0.125269978 0.58 -1.27 -0.108639863
27 45305 11.07 0.65 0.062380038 0.325 -0.62 -0.053036784
28 45306 11.9 0.83 0.074977416 0.415 0.21 0.017964072
29 45309 11.96 0.06 0.005042017 0.03 0.27 0.023096664
30 45310 8.78 -3.18 -0.265886288 -1.59 -2.91 -0.24893071
31 45311 7.33 -1.45 -0.165148064 -0.725 -4.36 -0.372968349
32 45328 7.58 0.25 0.034106412 0.125 -4.11 -0.351582549
33 45329 8.41 0.83 0.109498681 0.415 -3.28 -0.280581694
34 45330 10.12 1.71 0.20332937 0.855 -1.57 -0.134302823

HI @nohedge welcome to the forums.

You could try adding a new data point at Y=0 every time your values switch from positive to negative (or the other way around). This leads to lines having both endpoints on the same side of the y- axis.

I understand, but this will change the number of data points in the graph. And this in turn will impact the slider at the bottom of the graph, which will show 34+(number of intersections) points. Do you agree?

Yes, this correct.