How to save current zoom and position after filtering?

Hi,
I have a Mapbox section with markers plotted on it. When I zoom on the markers after filtering the markers data with a slider, the map refreshes itself to its initial zoom and position.
Is it possible to save these 2 parameters after filtering ?
Thank you.

Good question. You can pass in the current figure or relayoutData as state and use the range values from that figure in the new figure that you create. Here’s an example: https://plot.ly/dash/gallery/new-york-oil-and-gas/ (see the “Lock camera” checkbox) and here’s the relevant section in the code: https://github.com/plotly/dash-oil-and-gas-demo/blob/d937ebbbf9da244d9832e25c42b46319148d9ef5/app.py#L437-L448

Here’s a simple example that shows the two types of modes with a cartesian plot:

import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html

import copy

app = dash.Dash()

FIGURE = {
    'data': [{
        'x': [1, 2, 3],
        'y': [4, 3, 6],
        'mode': 'markers',
        'marker': {
            'size': 8
        }
    }],
    'layout': {
        'xaxis': {},
        'yaxis': {},
    }
}

app.layout = html.Div([
    html.H3('Persistent Zoom on Updates'),
    html.Div('''
        Try zooming into the graph (clicking and dragging on the graph),
        then updating the text box. Notice how the graph does not zoom out
        when the graph is updated. Switching to "Refresh View" will
        redraw the graph with auto-range enabled.
    '''),
    dcc.Input(id='my-input'),
    dcc.RadioItems(
        id='lock-zoom',
        options=[{'label': i, 'value': i} for i in ['Lock View', 'Refresh View']],
        value='Lock View'
    ),
    dcc.Graph(
        id='my-graph',
        figure=FIGURE
    )
])

@app.callback(
    Output('my-graph', 'figure'),
    [Input('my-input', 'value'),
     Input('lock-zoom', 'value')],
    [State('my-graph', 'relayoutData')])
def update_graph(value, lock_zoom, relayout_data):
    new_figure = copy.deepcopy(FIGURE)
    new_figure['layout']['title'] = value

    # relayout_data contains data on the zoom and range actions
    print(relayout_data)
    if relayout_data and lock_zoom == 'Lock View':
        if 'xaxis.range[0]' in relayout_data:
            new_figure['layout']['xaxis']['range'] = [
                relayout_data['xaxis.range[0]'],
                relayout_data['xaxis.range[1]']
            ]
        if 'yaxis.range[0]' in relayout_data:
            new_figure['layout']['yaxis']['range'] = [
                relayout_data['yaxis.range[0]'],
                relayout_data['yaxis.range[1]']
            ]

    return new_figure

app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

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

5 Likes

Thanks. But, is it possible to integrate a styled lock button inside the map ? like below

You’ll have to style the checkbox yourself. You could also remove the checkbox and have this as the default behaviour.

I tried to implement the relayoutdata feature in my code but I was unable to do the same. Can anyone help me regarding this?

#call back for All Plating Lines

@app.callback(
    dash.dependencies.Output('table', 'children'),
    [dash.dependencies.Input('lineSelect', 'value'),
    dash.dependencies.Input('refresh-table', 'n_intervals'),])

def filter_table(input,num):
    if num == 99999999999:
        raise PreventUpdate
    else:
        print ("The code is inside the function")
        df2 = pd.read_sql_query(query_3, engine)
        df2['location'] = df2['location'].replace(regex='_I_Act',value='')
        
        df2['line'] = np.where(df2.location.str[0] == 'A' ,df2.line+' A',df2.line)
        df2['line'] = np.where(df2.location.str[0] == 'B' ,df2.line+' B',df2.line)

        tableData = df2.pivot_table('alarmflag',['line','linespeed'],'location',aggfunc = 'first')
        tableData = pd.DataFrame(tableData.to_records())
        tableData.columns = [hdr.replace("('alarmflag',","").replace(")","") for hdr in tableData.columns]

        td1 = tableData
        td1['linespeed'] = td1['linespeed'].round(1)
        td1['linespeed'] = td1['linespeed'].astype(str)


        for j in td1.columns:
            if j not in ['line','linespeed']:
                td1[j] = j + td1[j]

        sort0_cols = td1.columns[td1.columns.str.contains(pat="EC")  ]
        sort1_cols = td1.columns[td1.columns.str.contains(pat="Ni")  ]
        sort2_cols = td1.columns[td1.columns.str.contains(pat="Ag")  ]
        sort3_cols = td1.columns[td1.columns.str.contains(pat="Sn")  ]
        sort4_cols = td1.columns[(~td1.columns.str.contains(pat="EC") & ~td1.columns.str.contains(pat="Ni") &
                                  ~td1.columns.str.contains(pat="Ag") & ~td1.columns.str.contains(pat="Sn") &
                                  ~td1.columns.str.contains(pat="line") ) ]
        td1 = pd.concat([td1['line'],td1['linespeed'],td1[sort0_cols],td1[sort1_cols],td1[sort2_cols],td1[sort3_cols],td1[sort4_cols]],axis=1)
        td1['new'] = td1.apply(lambda x: ','.join(x.dropna()),axis=1)
        td2 = td1['new'].apply(lambda x:pd.Series(x.split(',')))
        td2.rename(columns = {0:'line'},inplace=True)

        df = pd.DataFrame(data = td2)
        dfFiltered = pd.DataFrame()
        temp = input

        dfFiltered = df[df.line.isin(temp)]

        return html.Div([
            dash_table.DataTable(
                id='tab',
                columns = [{"name": i, "id": i} for i in dfFiltered.columns],
                data = dfFiltered.to_dict('records'),

                style_cell={'maxWidth': 0,
                            'overflow': 'hidden',
                            'whiteSpace': 'normal',
                            'height': 'auto',
                            #'textOverflow': 'ellipsis',
                            'textAlign': 'center',
                            'font-family': 'Verdana',
                            'margin':'40px',
                            'font-weight': 'bold',
                            },
                style_header = {'display': 'none'},
                style_data_conditional = ([{'if':{'filter_query': '{{{col}}} is blank'.format(col=col),
                                                  'column_id' : col},
                                            'backgroundColor': 'white',
                                            'color': 'white' } for col in dfFiltered.columns] +
                                          [{'if':{'filter_query': '{{{col}}} contains "."'.format(col=col),
                                                  'column_id' : col},
                                            'backgroundColor': '#CC0000',
                                            'color': 'white' } for col in dfFiltered.columns] +
                                          [{'if':{'filter_query': '{{{col}}} contains "`"'.format(col=col),
                                                  'column_id' : col},
                                            'backgroundColor': 'white',
                                            'color': 'green' } for col in dfFiltered.columns] +
                                          [{'if':{'filter_query': '{{{col}}} contains " "'.format(col=col),
                                                  'column_id' : col},
                                            'backgroundColor': '#D3D3D3',
                                            'color': 'black' } for col in dfFiltered.columns]
                                           +  [{'if':{
                                                   'column_id' : 1},
                                             'backgroundColor': '#D3D3D3',
                                             'color': 'black' }]
                                          ),
                )
            ,   dcc.Graph(id="graph3", style={"width": "50%", "display": "inline-block"}),
            dcc.Graph(id="graph4", style={"width": "50%", "display": "inline-block"}),])


#call back for selection on All Plating Lines

@app.callback(
    [Output('graph3', 'figure'),Output('graph4', 'figure')],
    [Input('graph3', 'relayoutData'),Input('graph4', 'relayoutData')],
    [State('graph3','figure'), State('graph4','figure')])


def show_graph(relayoutData, *figures):

    dff = pd.DataFrame(rows)

    if selection is None:
        n_intervals = 0
        return {}, {}
    else:
        n_intervals = 99999999999
        loc = dff.iloc[selection['row']][selection['column']-1]
        loc = loc.replace('.','').replace('`','').replace(' ','').replace('_I_Act','')
        input1 = loc + '_I_Act'
        t1 = dff.iloc[selection['row']]['line']
        input2 = t2 = t1[:-2]
        t1 = 'CNMO_CDUP_' + t2

        cnxn_1 = {AWS connection details}
        cursor = cnxn_1.cursor()

        query_4 = """Select COLUMN_NAME from information_schema.columns where TABLE_NAME = '{}'
        and TABLE_SCHEMA = 'ppqa' and
        (COLUMN_NAME like '{}%' or COLUMN_NAME = 'DateTime' )
        order by ORDINAL_POSITION"""

        table_columns = pd.read_sql_query(query_4.format(t1,loc), cnxn_1)


        col_list = [i for i in table_columns['COLUMN_NAME']]
        col_string = ', '.join([str(i) for i in col_list])

        query_5 = """SELECT {},  '{}' as line, NOW() as lastrefreshdate
                FROM ppqa.{}
                where DateTime >= now() - interval 24 hour
                order by DateTime"""

        df = pd.read_sql_query(query_5.format(col_string,t2,t1), cnxn_1)

        if len(df) == 0:
            return {}, {}
        else:
            df['data point i'] = df.index + 1
            filename = t2
            volt_col = input1.replace('_I_Act','_U_Act')

            tolerance_inputs = pd.read_excel("path"+input2 +".xlsx", sheet_name = 'Sheet1')

            current_tolerance = int(tolerance_inputs["Current Tolerance for " + input1].loc[0])
            voltage_tolerance = int(tolerance_inputs["Voltage Tolerance for Standard Deviation for " + volt_col].loc[0])

            standard_dev_15_rows = df[df['data point i'] <= 15][volt_col].std()

            df['rolling_average']  = df[volt_col].rolling(15).mean()

            def func_voltage_mu_o(df):
                if df['data point i'] <= 15:
                    return (df[volt_col])
                else:
                    return (df['rolling_average'])

            df['voltage_mu_o'] = df.apply(func_voltage_mu_o, axis = 1)
            Voltage_SD = 0.045 

            df['voltage_sigma'] =  Voltage_SD
            
            
            output = []
            
            for fig in figures:
                
                fig=go.Figure()
                fig.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'_I_Set'],name = 'Current Set Point',line=dict(color='black',width=4,dash='dash')))
                fig.add_trace(go.Scatter(x=df['DateTime'],y=df[input1],name = 'Actual Current',line=dict(color='black',width=4)))
                fig.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'_I_Set']+df[loc+'_I_Set']*current_tolerance/100,name = 'Upper Limit',line=dict(color='red',width=4)))
                fig.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'_I_Set']-df[loc+'_I_Set']*current_tolerance/100,name = 'Lower Limit',line=dict(color='red',width=4)))
                fig.update_layout(title= input2+' '+input1[:-6]+ ' : Current Vs Time',xaxis_title = 'Time',yaxis_title = 'Current (Amp)')
                
                
                fig2=go.Figure()
                fig2.add_trace(go.Scatter(x=df['DateTime'],y=df[loc+'_U_Set'],name = 'Actual Voltage',line=dict(color='black',width=4)))
                fig2.add_trace(go.Scatter(x=df['DateTime'],y=df['voltage_mu_o'] + df['voltage_sigma'] * voltage_tolerance,name = 'Voltage Red UCL',line=dict(color='red',width=4)))
                fig2.add_trace(go.Scatter(x=df['DateTime'],y=df['voltage_mu_o'] - df['voltage_sigma'] * voltage_tolerance,name = 'Voltage Red LCL',line=dict(color='red',width=4)))
                fig2.update_layout(title= input2+' '+input1[:-6]+ ' : Voltage Vs Time',xaxis_title = 'Time',yaxis_title = 'Voltage (Volt)')
                
                return fig,fig2


@app.callback(
    Output('refresh-table','n_intervals'),
    Input('tab', 'active_cell'))

def stop( selection):
    if selection is None:
        n_intervals = 0
        return n_intervals
    else:
        n_intervals = 99999999999
        return n_intervals

@chriddyp Any suggestions?