pythonfunctionplotlynicegui

NiceGUI won't update Plotly trace on Scattermap fig


NiceGUI won't update Plotly trace Scattermap when I utilize a function, almost exactly like the documentation update sample here: https://nicegui.io/documentation/plotly#plot_updates Code runs fine without a function:

import plotly.graph_objects as go
import dataretrieval.nwis as nwis
from nicegui import ui

fig = go.Figure(go.Scattermap(
    fill = "toself",
    lon = [-90,-89,-89,-90],
    lat = [45,45,44,44],
    marker = { 'size': 10, 'color': "orange" }, 
    name='BBox'))

fig.update_layout(margin=dict(l=0,r=0,t=0,b=0), width=400, showlegend=False,
                  map = {
                      'style': 'carto-darkmatter',
                      'center': {'lon': -90, 'lat': 44 },
                      'zoom':5})

plot = ui.plotly(fig)

# def update_plt():

siteBBOX, md = nwis.get_info(bBox=[-90,44,-89,45])

select_y = siteBBOX['dec_lat_va']

select_x = siteBBOX['dec_long_va']
names = siteBBOX['station_nm']

fig.add_trace(go.Scattermap(lon=select_x,
                            lat=select_y,
                            fill=None, 
                            mode= 'markers',
                            marker=dict(
                                size=15,  
                                color='blue' 
                                ), text=names, name='sites'))

plot.update()

# ui.button('Update', on_click=update_plt)

ui.run(title='Test')

But when I uncomment and indent to make a function, it does not update to add the new points.

import plotly.graph_objects as go
import dataretrieval.nwis as nwis
from nicegui import ui

fig = go.Figure(go.Scattermap(
    fill = "toself",
    lon = [-90,-89,-89,-90],
    lat = [45,45,44,44],
    marker = { 'size': 10, 'color': "orange" }, 
    name='BBox'))

fig.update_layout(margin=dict(l=0,r=0,t=0,b=0), width=400, showlegend=False,
                  map = {
                      'style': 'carto-darkmatter',
                      'center': {'lon': -90, 'lat': 44 },
                      'zoom':5})

plot = ui.plotly(fig)

def update_plt():

    siteBBOX, md = nwis.get_info(bBox=[-90,44,-89,45])
    
    select_y = siteBBOX['dec_lat_va']
    
    select_x = siteBBOX['dec_long_va']
    names = siteBBOX['station_nm']
    
    fig.add_trace(go.Scattermap(lon=select_x,
                                lat=select_y,
                                fill=None, 
                                mode= 'markers',
                                marker=dict(
                                    size=15,  
                                    color='blue' 
                                    ), text=names, name='sites'))
    
    plot.update()

ui.button('Update', on_click=update_plt)

ui.run(title='Test')

Edited. I'm not sure if I'm understanding what is different about my function and the NiceGUI example function. Thanks.


Solution

  • On my computer problem makes nwis.get_info() which runs longer time.

    First version works correctly because it gets data before it runs code in browser - and browser knows that there are new data to display.

    But in second version it runs code when browser tries to keep connection with server, but long running nwis.get_info() blocks all code on server and server can't response for browser's requests and finally browser shows me:

    connectin lost

    and after reconnecting it doesn't know that there are new data to display
    (probably it may run all code from the beginning).

    Partial solution is to change reconnection timeout:

    ui.run(title='Test', reconnect_timeout=60)
    

    and browser waits longer time before it reconnects.
    And nwis.get_info() has time to update data before before tries to reconnect.

    But if code would need even longer time to get data then it would need to run it in background - as thread, subprocess, async function or something else.

    At this moment I didn't find nice method to run it in background.


    import plotly.graph_objects as go
    import dataretrieval.nwis as nwis
    from nicegui import ui
    
    # --- functions ---
    
    def update_plt():
        print('clicked')
    
        print('searching ...')
        siteBBOX, md = nwis.get_info(bBox=[-90,44,-89,45])
    
        print('updating ...')
        #print(siteBBOX)
        #print(md)
    
        select_y = siteBBOX['dec_lat_va']
        select_x = siteBBOX['dec_long_va']
        names = siteBBOX['station_nm']
    
        #print(select_y)
        #print(select_x)
        #print(names)
    
        fig.data = []  # I remove previous map (because it keeps it in memory even if it doesn't show it in browser)
        print(f"{fig.data = }")  # if I don't remove previous data then it shows 2 (and more) Scattermaps on list 
    
        fig.add_trace(go.Scattermap(
                                lon=select_x,
                                lat=select_y,
                                fill=None,
                                mode='markers',
                                marker=dict(
                                    size=15,
                                    color='blue'
                                ),
                                text=names,
                                name='sites'
        ))
    
        plot.update()
    
    # --- main ---
    
    fig = go.Figure(go.Scattermap(
        fill = "toself",
        lon = [-90,-89,-89,-90],
        lat = [45,45,44,44],
        marker = { 'size': 10, 'color': "orange" }, 
        name='BBox'))
    
    fig.update_layout(margin=dict(l=0,r=0,t=0,b=0), width=400, showlegend=False,
                      map = {
                          'style': 'carto-darkmatter',
                          'center': {'lon': -90, 'lat': 44 },
                          'zoom':5})
    
    plot = ui.plotly(fig)
    
    ui.button('Update', on_click=update_plt)
    
    ui.run(title='Test', reconnect_timeout=60)
    

    EDIT:

    I was searching method to run long-running code in plotly but solution is in nicegui.

    It has

    It gets function's name and positional and/or named parameters.

    I had problem to send named parameters directly to nwis.get_info so I created new function with nwis.get_info and I used it with cpu_bound().

    It creates async function so it has to run with await, and update_plt has to use async

    def get_data(**kwargs):
        return nwis.get_info(**kwargs)
    
    async def update_plt():
        #siteBBOX, md = nwis.get_info(bBox=[-90,44,-89,45])
    
        #siteBBOX, md = await run.cpu_bound(nwis.get_info, bBox=[-90,44,-89,45])  # it sends `bBox` to web service incorrectly formatted in url.
    
        siteBBOX, md = await run.cpu_bound(get_data, bBox=[-90,44,-89,45])  
        
        # ... rest ...
    

    And it seems it works withou extra reconnect_timeout.


    import plotly.graph_objects as go
    import dataretrieval.nwis as nwis
    from nicegui import ui, run
    
    # --- functions ---
    
    def get_data(**kwargs):
        return nwis.get_info(**kwargs)
    
    async def update_plt():
        print('clicked')
    
        print('searching ...')
        #siteBBOX, md = nwis.get_info(bBox=[-90,44,-89,45])
    
        #siteBBOX, md = await run.cpu_bound(nwis.get_info, bBox=[-90,44,-89,45])  # it sends `bBox` to web service incorrectly formatted in url.
    
        siteBBOX, md = await run.cpu_bound(get_data, bBox=[-90,44,-89,45])
    
        print('updating ...')
        #print(siteBBOX)
        #print(md)
    
        select_y = siteBBOX['dec_lat_va']
        select_x = siteBBOX['dec_long_va']
        names = siteBBOX['station_nm']
    
        #print(select_y)
        #print(select_x)
        #print(names)
    
        fig.data = []  # I remove previous map (because it keeps it in memory even if it doesn't show it in browser)
        print(f"{fig.data = }")  # if I don't remove previous data then it shows 2 (and more) Scattermaps on list 
    
        fig.add_trace(go.Scattermap(
                                lon=select_x,
                                lat=select_y,
                                fill=None,
                                mode='markers',
                                marker=dict(
                                    size=15,
                                    color='blue'
                                ),
                                text=names,
                                name='sites'
        ))
    
        plot.update()
    
    # --- main ---
    
    fig = go.Figure(go.Scattermap(
        fill = "toself",
        lon = [-90,-89,-89,-90],
        lat = [45,45,44,44],
        marker = { 'size': 10, 'color': "orange" }, 
        name='BBox'))
    
    fig.update_layout(margin=dict(l=0,r=0,t=0,b=0), width=400, showlegend=False,
                      map = {
                          'style': 'carto-darkmatter',
                          'center': {'lon': -90, 'lat': 44 },
                          'zoom':5})
    
    plot = ui.plotly(fig)
    
    ui.button('Update', on_click=update_plt)
    
    ui.run(title='Test')
    

    Doc: Why is my long running function blocking UI updates?