Use callback calculated value to update slider range

I have a Slider that ranges from IETsr to 0, IETsr is a value calculated from the default setting in two dropdown menus on start-up.
What I’d like to happen is when the values for the DropDown are changed by the user and a new IETsr is calculated, the Slider range changes to us the new value.
All the other parts of this code work correctly and IETsr is being calculated every time I change the DropDown but I don’t know how to feed it back into the Slider.
Ignore the # items at the top, this was for when they were hard coded in.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objects as go


Dep=363                 #Water depth (m)
#Speed = 2.2            #Ship's speed (m/s)
ASV=1.5                 #Speed of sound in water (m/s)
#SPI=6.25                #Distance between sample stations (m)
SB=0-((Dep*2)/ASV)      #Sound travel time to seabed (milliseconds) - negative to denote below time zero
#IET=(SPI/Speed)         #Inter Event Time (s) time to travel SPI
#IETs=IET*1000           #IET in milliseconds
#IETsr=0-round(IETs)           


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    html.H6("Calculate available record lengths"),    
    html.Label('Shot Point Interval'),
    dcc.Dropdown(id='SPIDD',
        options=[
            {'label': '3.125', 'value': 3.125},
            {'label': u'6.25', 'value': 6.25},
            {'label': '12.5', 'value': 12.5},
            {'label': '25.0', 'value': 25.0},
        ],
        value='6.25'
    ),
    html.Label('Vessel Speed m/s / Knots'),
    dcc.Dropdown(id='SpeedDD',
        options=[
            {'label': '1.95/3.8', 'value': 1.955},
            {'label': '2.05/4.0', 'value': 2.058},
            {'label': '2.16/4.2', 'value': 2.161},
            {'label': '2.26/4.4', 'value': 2.264},            
            {'label': '2.37/4.6', 'value': 2.366},
            {'label': '2.47/4.8', 'value': 2.469},            
        ],
        value='2.16/4.2'
    ),
    
    dcc.Graph(id='graph-with-slider'),
    dcc.Slider(
        id='3DHR Slider',
        min=IETsr,
        max=0,
        value=(IETsr-0)/2,
        marks={i: f"{i}" for i in range(round(IETsr,-2),0,100)},        
        step=50
    ),
    html.Div(id='display1'),
    html.Div(id='display2'),
    html.Div(id='display3')     
])


@app.callback(
    Output('graph-with-slider', 'figure'),
    Output('display1', 'children'),
    Output('display2', 'children'), 
    Output('display3', 'children'),
    Output('3DHR Slider', 'value'),    #This is where I thought it might do the feedback   
    Input('3DHR Slider', 'value'),     
    Input('SPIDD','value'),
    Input('SpeedDD','value'))


def update_figure(REC1,SPIDD,SpeedDD):
    SPIDD=float(SPIDD)
    SpeedDD=float(SpeedDD)
    global IET
    global IETs
    global IETsr
    IET=SPIDD/SpeedDD
    IETs=IET*1000
    IETsr=0-round(IETs)
    return IET,IETs,IETsr
    
    REC2= round((0-IETs)-REC1)  #UHRS record length - Function of above

    fig = go.Figure()

    #Add seabeds    
    fig.add_trace(go.Scatter(x=[0,1,None,1.1,1.6],y=[SB,SB,None,SB,SB],name="Seabed",line=dict(color='green',dash='dash'))) 
    fig.add_trace(go.Scatter(x=[1.1,1.6],y=[REC1+SB,REC1+SB],name="Seabed-UHRS",line=dict(color='blue',dash='dash'))) ##Stacked Rec2    
 

    #Add record length windows
    fig.add_trace(go.Scatter(
        x=[0, 0.5,0.5, 0,0,None,1.1, 1.6,1.6, 1.1,1.1],
        y=[0, 0, REC1, REC1,0,None,0, 0, REC1, REC1,0],
        fill='toself', 
        mode='lines', line_color='#bd3786',name="3DHR " +str(REC1)))    
 
    fig.add_trace(go.Scatter(
        x=[0.5, 1,1, 0.5,0.5,None,1.1, 1.6,1.6, 1.1,1.1],
        y=[0, 0, REC2, REC2,0,None,REC1,REC1,REC1+REC2, REC1+REC2,REC1],
        fill='toself', 
        mode='lines', line_color='#0d0887',name="UHRS " +str(REC2)))   
       
    
    fig.update_xaxes(showticklabels=False,showgrid=False)
    fig.update_layout(template='none')
    fig.update_yaxes(range=[IETsr,0])
    return fig


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

Hi @WillH

You are trying to use the same property “value” as Input and Output of the same callback, this could be a problem. Try using State instead Input.

Also I don’t understand your callback, because it has five Outputs and the return has only 3 elements and after the return sentence there are additional code. :thinking:

Hi @Eduardo.
The first Output is the Figure being put into the dcc.Graph element. The next three of the outputs are the calculation results going to the ‘display’ id items. It’s the fifth one I think I have wrong where I’m trying to send IETsr back to the Slider. So if it understand my code correctly (I’m very new to Dash), it returns the three calculated values and then returns the Figure, all under one def. Maybe I should split it into a def for the graph and a def for the slider??
I will see what I can do with State instead of Input.

The sentence return must have all Outputs separated by comas and following the same order as the list of Outputs, then the first element after the return must be "fig"

Related to the State issue, take into account that you need to use Input if you want trigger the callback everytime the component change and you can lisen to other component using State, but using State the callback is not triggered averytime that this comonent change. That means if you need to execute the callback everytime the “value” of the Slider is changed, then the solution with State won’t work.

In this case you can try dividing the function into two different callbacks or use different method to use the same component as Input and Output in the same callback (see solutions in the search button).

@Eduardo.
Using your reply as inspiration, I have reverted to two callbacks and looked at the Slider input/output options.
If I feed back ‘min’ instead of ‘value’, it sends the IETsr value to the min on the slider range. This now does what I want i.e. the Slider range now reacts to changing IET.

@app.callback(
    Output('display1', 'children'),
    Output('3DHR Slider', 'min'),    
    Input('SPIDD','value'),
    Input('SpeedDD','value'))

The only part that now isn’t still working as wanted is updating the ‘marks’ to react to the new IETsr.
marks={i: f"{i}" for i in range(round(IETsr,-2),0,100)},
I cant feed the revised IETsr into this line, I have tried replacing it with min, but as it’s a Python function, it won’t let me do it.

Nice to know you find a solution.

Related to your last question, I do not understand the issue, perhaps will be better if you start a new issue with this problem to receive feed back from more advanced programmers.

1 Like