Dynamic selection of both html iframe and dcc.Graph objects in the same area using a dropdown selection

Hi,
I am trying to create a plotly dash dashboard using multi-level dropdown list/ radio button options which will load plotly graph figures as well as html.iframe embedded charts. The problem is I am unable to configure the dropdown option equity to the html.iframe object. I need this MongoDB iFrame plot to be visible only when the dropdown option “Equity” is selected and the iframe should load in the same area where the dcc.Graph figure is shown. That is iFrame plot should move up into the dcc.Graph figure area and the label Chart X-Axis needs to get hidden.


Also, the dcc.Graph object will need to become visible only when the dropdown options Balance and EquityDailyAggregation is selected and remain hidden when dropdown option = Equity

The code for this project is written below:

## Load Libraries
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import plotly.express as px
import plotly.graph_objects as go
import json
import pandas as pd
import numpy as np
######

# Instantiate our App and incorporate BOOTSTRAP theme stylesheet
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

def max_drawdown(df: pd.DataFrame, equity_col_name:str = "equity") -> tuple:
    '''
    Computes the points of maximum drawdown on the equity series data
    and returns the indices and drawdown peak/trough values.

    Parameters
    ----------
    df              : pd.DataFrame, dataframe containing the equity curve series data
    equity_col_name : str, column name of the dataframe containing the equity data.

    Returns
    -------
    max_drawdown, df       : tuple unpacking -> (float, pd.DataFrame)
                             float of max_drawdown, pd.DataFrame containing peak and trough values and indices location.
    '''
    xs = df[[equity_col_name]].values
    # j = np.argmax(np.maximum.accumulate(xs) - xs) # end of the period
    j = np.argmax((np.maximum.accumulate(xs) - xs) / np.maximum.accumulate(xs))
    i = np.argmax(xs[:j]) # start of period
    df_i_max = df.iloc[[i]].to_dict(orient = 'records')
    df_j_min = df.iloc[[j]].to_dict(orient = 'records')
    recorded_peak_balance = df["equity"].iloc[i]
    floating_lowest_equity = df["equity"].iloc[j]
    max_drawdown_percent = (recorded_peak_balance - floating_lowest_equity)/recorded_peak_balance
    print("Recorded Peak Balance:",recorded_peak_balance ,"\nFloating Lowest Equity:",floating_lowest_equity,
          "\nMaximum Drawdown: {} %".format(np.round(max_drawdown_percent*100, 3)))
    output_data =  df_i_max + df_j_min
    return pd.DataFrame(output_data), max_drawdown_percent

## Plotly Figure
def equity_plot(line_plot_df : pd.DataFrame, scatterplot_df : pd.DataFrame, x_axis_label : str = "time", y_axis_label: str = "Floating Equity (US$)") -> go.Figure():
    ''' Returns the Equity plot vs. time or Equity plot vs. nr. of closed positions along with drawdown markers.
    Parameters
    ----------
    line_plot_df  : pd.DataFrame,
                    input dataframe for the lineplot, containing the equity data
    scatterplot_df: pd.DataFrame,
                    input dataframe for the scatterplot, containing the peak and trough of the max. drawdown
    x_axis_label  : str, {"time", "ClosedPositions"}, default = "time"
                    x-axis label of the plot and also the column name in line_plot_df and scatterplot_df
    y_axis_label  : str, default = "Floating Equity (US$)",
                    y-axis label of the plot, no reference to any dataframe column names

    Returns
    -------
    fig_final     : Plotly graph objects figure
    '''
    fig1 = px.line(line_plot_df, x = x_axis_label, y="equity", markers = True, height=1920)
    fig2 = px.scatter(scatterplot_df, x = x_axis_label, y="equity",  height=1920)
    fig2.update_traces(marker={'size': 12, 'color' : ["limegreen","red"], 'symbol' : ['triangle-up','triangle-down']})
    ## Draw Dotted Line connecting maximum drawdown
    fig3 = go.Scatter(x = scatterplot_df[x_axis_label].to_list(),y = scatterplot_df['equity'].to_list(),name = "MaxDrawDown",
                      mode = 'lines',line = dict(color='firebrick', width=1, dash='dot'))
    fig_final = go.Figure(data=fig1.data + fig2.data).add_trace(fig3)
    fig_final.update_layout(title="{} vs. {}".format(y_axis_label,x_axis_label),title_x = 0.5,
                            xaxis_title = x_axis_label, yaxis_title= y_axis_label,
                            titlefont=dict(family="Courier New",size=16,color="RoyalBlue"),
                            legend=dict(yanchor="top",y=0.99,xanchor="left", x=0.01),
                            plot_bgcolor = "beige",
                            font=dict(family="monospace",size=10,color="RebeccaPurple"))
    return fig_final

chart_url = "https://charts.mongodb.com/charts-project-0-nvgoj/embed/charts?id=624eb117-33c6-43f2-879f-a9158b315fb3&maxDataAge=10&theme=dark&autoRefresh=true"

# Build the layout to define what will be displayed on the page
app.layout = dbc.Container(
[
    dbc.Row([
       dbc.Col([html.H1("Charts")], width=10)], justify="center"),

    dbc.Row([
        dbc.Col([
            html.Label('Chart Y-Axis:'),
            dcc.Dropdown(id='chart_y_axis',
                         options=[{'label' : 'Equity', 'value' : 'equity'},
                                  {'label' : 'Equity_DailyAggregation', 'value' : 'equity_agg'},
                                  {'label' : 'Balance', 'value' : 'balance'}],
                         # placeholder="Select chart",
                         value = "balance",
                         optionHeight = 25, style = {'width':'75%'})
            ], width=4),
        dbc.Col([
            html.Label('Chart X-Axis:'),
            dcc.RadioItems(id='chart_x_axis',value='time',inline=True,
            labelStyle  = {'width':'13%'}, inputStyle={"margin-right": "5px"})
            ], width=8)
    ]),
    # <iframe style="background: #21313C; border: none; border-radius: 2px; box-shadow: 0 2px 10px 0 rgba(70, 76, 79, .2);" width="1280" height="720" ></iframe>
    html.Div([dcc.Graph(id='graph')]),
    html.Iframe(id='mongoplot',src=chart_url, style={'background' : '#21313C', 'border': 'none','width': "100%", 'height': "540px"}),
]
)

# callback is used to create app interactivity
#---------------------------------------------------------------
@app.callback(
    Output('chart_x_axis', 'options'),
    Input('chart_y_axis', 'value'))

def filter_radio_options(first_dropdown):
    if first_dropdown == "equity_agg":
        options = [ {'label' : 'Time', 'value' : 'time'} ]
    if first_dropdown == "equity":
        options = []        
    else:
        options = [{'label' : 'Time', 'value' : 'time'},{'label' : 'Nr. of Trades', 'value' : 'ClosedPositions'}]
    return options
#---------------------------------------------------------------
@app.callback(
    Output(component_id='graph', component_property='figure'),
    [Input(component_id='chart_y_axis', component_property='value'),
     Input(component_id='chart_x_axis', component_property='value')])


def update_graph(chart_y_axis, chart_x_axis):
    if chart_y_axis == "balance":
        y_axis_label = "Closed Balance (US$)"
        balance_curve =  pd.read_csv('ClosedTradesBalance.csv')
        balance_curve['ClosedPositions'] = balance_curve.index
        line_plot = balance_curve
        drawdown_df, max_dd = max_drawdown(balance_curve)
    elif chart_y_axis == "equity_agg":
        y_axis_label = "DailyFloatingEquity.Aggregated (US$)"
        df_fedr = pd.read_csv('FloatingEquityDailyReport.csv')
        df1 = df_fedr[["timestamp_min_equity","MinEquity"]].rename(columns={"timestamp_min_equity": "time", "MinEquity": "equity"})
        df2 = df_fedr[["timestamp_max_equity","MaxEquity"]].rename(columns={"timestamp_max_equity": "time", "MaxEquity": "equity"})
        FloatingEquityAgg = pd.concat([df1,df2], ignore_index=True, join = "outer", verify_integrity=True, axis = 0).sort_values(by=['time']).reset_index(drop = True)
        line_plot = FloatingEquityAgg
        drawdown_df, max_dd = max_drawdown(FloatingEquityAgg)
    # elif chart_y_axis == "equity":
        ## How to Load iFrame instead of Plot

    figure = equity_plot(line_plot, drawdown_df, x_axis_label = chart_x_axis,  y_axis_label = y_axis_label)
    return figure

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

The datasets for this app are uploaded in dropbox:
1.) ClosedTradesBalance.csv
2.) FloatingEquityDailyReport.csv

Would greatly appreciate if a plotly expert here could provide any solution to the above issue of loading html iframe object along with dcc.graph objects using a dropdown list and also tell me how to center the label “Charts” and the dropdown list “Chart Y-Axis” and RadioButtons “Chart X-axis” to the center of the web page?

Thanks and Regards,
Dilip

Hi,

The simplest approach in this situation is to create a div block in the initial layout and populate it with a Graph or Iframe dynamically using a callback.

Assuming that you have a html.Div(id="graph-block") in the layout, you can modify your graph callback to something like:

@app.callback(
    Output(component_id='graph-block', component_property='children'),
    [Input(component_id='chart_y_axis', component_property='value'),
     Input(component_id='chart_x_axis', component_property='value')])

def update_graph(chart_y_axis, chart_x_axis):
    if chart_y_axis == "equity":
        ## How to Load iFrame instead of Plot
        return html.Iframe(id='mongoplot',src=chart_url, style={'background' : '#21313C', 'border': 'none','width': "100%", 'height': 

    if chart_y_axis == "balance":
        y_axis_label = "Closed Balance (US$)"
        balance_curve =  pd.read_csv('ClosedTradesBalance.csv')
        balance_curve['ClosedPositions'] = balance_curve.index
        line_plot = balance_curve
        drawdown_df, max_dd = max_drawdown(balance_curve)
    elif chart_y_axis == "equity_agg":
        y_axis_label = "DailyFloatingEquity.Aggregated (US$)"
        df_fedr = pd.read_csv('FloatingEquityDailyReport.csv')
        df1 = df_fedr[["timestamp_min_equity","MinEquity"]].rename(columns={"timestamp_min_equity": "time", "MinEquity": "equity"})
        df2 = df_fedr[["timestamp_max_equity","MaxEquity"]].rename(columns={"timestamp_max_equity": "time", "MaxEquity": "equity"})
        FloatingEquityAgg = pd.concat([df1,df2], ignore_index=True, join = "outer", verify_integrity=True, axis = 0).sort_values(by=['time']).reset_index(drop = True)
        line_plot = FloatingEquityAgg
        drawdown_df, max_dd = max_drawdown(FloatingEquityAgg)
"540px"})

    figure = equity_plot(line_plot, drawdown_df, x_axis_label = chart_x_axis,  y_axis_label = y_axis_label)
    return dcc.Graph(id='graph', figure=figure)

As for the Chart X-Axis and RadioItems, you could simply set style={"display": "hidden"} for the entire block (dbc.Col) holding this elements in the same callback where you change the options.

Hope this helps!

1 Like

Thank you very much. I was able to fix all issues. Some screenshots below:


Below is the updated code:

## Load Libraries
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import plotly.express as px
import plotly.graph_objects as go
import json
import pandas as pd
import numpy as np
######

# Instantiate our App and incorporate BOOTSTRAP theme stylesheet
app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])

def max_drawdown(df: pd.DataFrame, equity_col_name:str = "equity") -> tuple:
    '''
    Computes the points of maximum drawdown on the equity series data
    and returns the indices and drawdown peak/trough values.

    Parameters
    ----------
    df              : pd.DataFrame, dataframe containing the equity curve series data
    equity_col_name : str, column name of the dataframe containing the equity data.

    Returns
    -------
    max_drawdown, df       : tuple unpacking -> (float, pd.DataFrame)
                             float of max_drawdown, pd.DataFrame containing peak and trough values and indices location.
    '''
    xs = df[[equity_col_name]].values
    # j = np.argmax(np.maximum.accumulate(xs) - xs) # end of the period
    j = np.argmax((np.maximum.accumulate(xs) - xs) / np.maximum.accumulate(xs))
    i = np.argmax(xs[:j]) # start of period
    df_i_max = df.iloc[[i]].to_dict(orient = 'records')
    df_j_min = df.iloc[[j]].to_dict(orient = 'records')
    recorded_peak_balance = df["equity"].iloc[i]
    floating_lowest_equity = df["equity"].iloc[j]
    max_drawdown_percent = (recorded_peak_balance - floating_lowest_equity)/recorded_peak_balance
    print("Recorded Peak Balance:",recorded_peak_balance ,"\nFloating Lowest Equity:",floating_lowest_equity,
          "\nMaximum Drawdown: {} %".format(np.round(max_drawdown_percent*100, 3)))
    output_data =  df_i_max + df_j_min
    return pd.DataFrame(output_data), max_drawdown_percent

## Plotly Figure
def equity_plot(line_plot_df : pd.DataFrame, scatterplot_df : pd.DataFrame, x_axis_label : str = "time", y_axis_label: str = "Floating Equity (US$)") -> go.Figure():
    ''' Returns the Equity plot vs. time or Equity plot vs. nr. of closed positions along with drawdown markers.
    Parameters
    ----------
    line_plot_df  : pd.DataFrame,
                    input dataframe for the lineplot, containing the equity data
    scatterplot_df: pd.DataFrame,
                    input dataframe for the scatterplot, containing the peak and trough of the max. drawdown
    x_axis_label  : str, {"time", "ClosedPositions"}, default = "time"
                    x-axis label of the plot and also the column name in line_plot_df and scatterplot_df
    y_axis_label  : str, default = "Floating Equity (US$)",
                    y-axis label of the plot, no reference to any dataframe column names

    Returns
    -------
    fig_final     : Plotly graph objects figure
    '''
    fig1 = px.line(line_plot_df, x = x_axis_label, y="equity", markers = True, width=540)
    fig1.update_traces(line_color='cornflowerblue', line_width=2)
    fig2 = px.scatter(scatterplot_df, x = x_axis_label, y="equity",  width = 540)
    fig2.update_traces(marker={'size': 12, 'color' : ["limegreen","red"], 'symbol' : ['triangle-up','triangle-down']})
    ## Draw Dotted Line connecting maximum drawdown
    fig3 = go.Scatter(x = scatterplot_df[x_axis_label].to_list(),y = scatterplot_df['equity'].to_list(),name = "MaxDrawDown",
                      mode = 'lines',line = dict(color='firebrick', width=1, dash='dot'))
    fig_final = go.Figure(data=fig1.data + fig2.data).add_trace(fig3)
    fig_final.update_layout(title="{} vs. {}".format(y_axis_label,x_axis_label),title_x = 0.5,title_y = 0.85,
                            xaxis_title = x_axis_label, yaxis_title= y_axis_label,
                            titlefont=dict(family="Courier New",size=16,color="deepskyblue"),
                            legend=dict(yanchor="top",y=0.99,xanchor="left", x=0.01),
                            template='plotly_dark',
                            # plot_bgcolor = "beige",
                            font=dict(family="monospace",size=10,color="aqua"))
    return fig_final

##--------------------------------------------------------------------------------------------------------------------
## DASH APP

## Load Chart Variables and specify UI elements
chart_url = "https://charts.mongodb.com/charts-project-0-nvgoj/embed/charts?id=624eb117-33c6-43f2-879f-a9158b315fb3&maxDataAge=10&theme=dark&autoRefresh=true"
chart_y_axis_dropdown_list = html.Div([
    html.P('Chart Y-axis:',style={"font-weight":"bold"}),
    dcc.Dropdown(id='chart_y_axis',
                 options=[{'label' : 'Equity', 'value' : 'equity'},
                          {'label' : 'Equity_DailyAggregation', 'value' : 'equity_agg'},
                          {'label' : 'Balance', 'value' : 'balance'},
                          {'label' : 'PerformanceFactor', 'value' : 'PerformanceFactor', 'disabled':True}],
                 # placeholder="Select chart",
                 value = "balance",
                 optionHeight = 25, style = {'width':'75%','text-align': 'left'})
])

chart_x_axis_radioitems = html.Div([
    html.P('Chart X-axis:',style={"font-weight":"bold"}),
    dcc.RadioItems(id='chart_x_axis',value='time',inline=True,labelStyle  = {'width':'20%'})
], id = "radio_options_block_col")

# Build the layout to define what will be displayed on the page
app.layout = dbc.Container(
[
    dbc.Row([dbc.Col([html.H3("Charts")], width=12)], justify="center", style = {'text-align': 'center'}),
    dbc.Row([dbc.Col([chart_y_axis_dropdown_list]), dbc.Col([chart_x_axis_radioitems])]),
    html.Div(id='graph_block')
]
)

# callback is used to create app interactivity
#---------------------------------------------------------------
@app.callback(
    [Output('chart_x_axis', 'options'),Output('radio_options_block_col', 'style')],
    Input('chart_y_axis', 'value'))

def filter_radio_options(first_dropdown):
    if first_dropdown == "equity_agg":
        options = [ {'label' : 'Time', 'value' : 'time'} ]
        style = {}
    if first_dropdown == "equity":
        options = []
        style = {"display": "none"}
    elif first_dropdown == "balance":
        options = [{'label' : 'Time', 'value' : 'time'},{'label' : 'Nr. of Trades', 'value' : 'ClosedPositions'}]
        style = {}
    return options, style
#---------------------------------------------------------------
@app.callback(
    Output(component_id='graph_block', component_property='children'),
    [Input(component_id='chart_y_axis', component_property='value'),
     Input(component_id='chart_x_axis', component_property='value')])


def update_graph(chart_y_axis, chart_x_axis):
    if chart_y_axis == "equity":
        # <iframe style="background: #21313C; border: none; border-radius: 2px; box-shadow: 0 2px 10px 0 rgba(70, 76, 79, .2);" width="1280" height="720" ></iframe>
        return html.Iframe(id='mongoplot',src=chart_url, style={'background' : 'black', 'border': 'none','width': "100%",
                           'height': "540px", "box-shadow": "0 2px 10px 0 rgba(70, 76, 79, .2)"}),
    if chart_y_axis == "balance":
        y_axis_label = "Closed Balance (US$)"
        balance_curve =  pd.read_csv('ClosedTradesBalance.csv')
        balance_curve['ClosedPositions'] = balance_curve.index
        line_plot = balance_curve
        drawdown_df, max_dd = max_drawdown(balance_curve)
    elif chart_y_axis == "equity_agg":
        y_axis_label = "DailyFloatingEquity.Aggregated (US$)"
        df_fedr = pd.read_csv('FloatingEquityDailyReport.csv')
        df1 = df_fedr[["timestamp_min_equity","MinEquity"]].rename(columns={"timestamp_min_equity": "time", "MinEquity": "equity"})
        df2 = df_fedr[["timestamp_max_equity","MaxEquity"]].rename(columns={"timestamp_max_equity": "time", "MaxEquity": "equity"})
        FloatingEquityAgg = pd.concat([df1,df2], ignore_index=True, join = "outer", verify_integrity=True, axis = 0).sort_values(by=['time']).reset_index(drop = True)
        line_plot = FloatingEquityAgg
        drawdown_df, max_dd = max_drawdown(FloatingEquityAgg)

    figure = equity_plot(line_plot, drawdown_df, x_axis_label = chart_x_axis,  y_axis_label = y_axis_label)
    return dcc.Graph(id='graph', figure=figure)

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