I assume this is a simple fix that I cannot seem to wrap my head around. I've defined the default values in four NiceGUI ui.inputs and bound them to a dict. I know these values update on_change, but the Plotly plot doesn't. I can't seem to understand what function to create that takes these updated values from the input to the dict to the new plot. Ultimately I want the user to be able to draw and redraw a bounding box on the map by changing the float values in the input.
As is, code simply brings up a map and bounding box that can't be changed. Uncomment the fig.data = [] line and it will start as an empty graph. What is up with that behavior? Why would that function run before the button is clicked?
from nicegui import ui
import plotly.graph_objects as go
bbox_dict = {
'lat_n': '',
'lat_s': '',
'lon_w': '',
'lon_e': '',
}
ui.input(label='North Latitude', value=45, placeholder='eg 45.0').bind_value_to(bbox_dict, 'lat_n')
ui.input(label='South Latitude', value=40, placeholder='eg 40.0').bind_value_to(bbox_dict, 'lat_s')
ui.input(label='West Longitude', value=-90, placeholder='eg -90.0').bind_value_to(bbox_dict, 'lon_w')
ui.input(label='East Longitude', value=-88, placeholder='eg -88.0').bind_value_to(bbox_dict, 'lon_e')
lon = [bbox_dict['lon_w'], bbox_dict['lon_e'], bbox_dict['lon_e'], bbox_dict['lon_w']]
lat = [bbox_dict['lat_n'], bbox_dict['lat_n'],bbox_dict['lat_s'], bbox_dict['lat_s']]
lon_mid = (bbox_dict['lon_w']+bbox_dict['lon_e'])/2
lat_mid = (bbox_dict['lat_n']+bbox_dict['lat_s'])/2
fig = go.Figure(go.Scattermap(
fill = "toself",
lon = lon, lat = lat,
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': lon_mid, 'lat': lat_mid },
'zoom':5})
plot = ui.plotly(fig)
def update_plt():
# fig.data = []
plot.update()
ui.button('Update', on_click=update_plt())
ui.run()
on_click
needs function's name without ()
- on_click=update_plt
- and when you will press button then it will use ()
to execute this function.
You can see this probably in all GUI's (i.e. tkinter
, PyQt
), and in many languages (ie. in JavaScript
), and often this function's name is called "callback"
.
Your code with on_click=update_plt()
runs like
result = update_plt()
ui.button('Update', on_click=result)
So it runs update_plt()
when you start program, and it doesn't wait for click.
And this runs fig.data = []
at start, and it creates empty graph at start.
You need:
ui.button('Update', on_click=update_plt)
As for updating values I would do it in update_plt
without fig.data = []
fig.data[0]
should gives access to existing go.Scattermap()
def update_plt():
#global lon # to assign new list to external variable `lon` instead of creating local `lon`
#global lat # to assign new list to external variable `lat` instead of creating local `lat`
lon = [bbox_dict['lon_w'], bbox_dict['lon_e'], bbox_dict['lon_e'], bbox_dict['lon_w']]
lat = [bbox_dict['lat_n'], bbox_dict['lat_n'], bbox_dict['lat_s'], bbox_dict['lat_s']]
fig.data[0].lon = lon
fig.data[0].lat = lat
plot.update()
But I expect that ui.input()
gives value as string
and it may need to convert to float()
def update_plt():
#global lon # to assign new list to external variable `lon` instead of creating local `lon`
#global lat # to assign new list to external variable `lat` instead of creating local `lat`
#global lon_mid
#global lat_mid
lon = [bbox_dict['lon_w'], bbox_dict['lon_e'], bbox_dict['lon_e'], bbox_dict['lon_w']]
lat = [bbox_dict['lat_n'], bbox_dict['lat_n'], bbox_dict['lat_s'], bbox_dict['lat_s']]
lon = [float(i) for i in lon]
lat = [float(i) for i in lat]
fig.data[0].lon = lon
fig.data[0].lat = lat
# ---
lon_mid = (float(bbox_dict['lon_w']) + float(bbox_dict['lon_e'])) / 2
lat_mid = (float(bbox_dict['lat_n']) + float(bbox_dict['lat_s'])) / 2
fig.update_layout(map={'center': {'lon': lon_mid, 'lat': lat_mid }))
# ---
plot.update()
EDIT:
It seems bind_value_to()
allows to use forward=callback
to assign function which will be executed before sending data to binded variable. And it can convert string
to float
.
.bind_value_to(bbox_dict, 'lat_n', forward=float)
And again it has to be function's name without ()
This way it doesn't need float()
in update_plt()
def update_plt():
#global lon # to assign new list to external variable `lon` instead of creating local `lon`
#global lat # to assign new list to external variable `lat` instead of creating local `lat`
#global lon_mid
#global lat_mid
lon = [bbox_dict['lon_w'], bbox_dict['lon_e'], bbox_dict['lon_e'], bbox_dict['lon_w']]
lat = [bbox_dict['lat_n'], bbox_dict['lat_n'], bbox_dict['lat_s'], bbox_dict['lat_s']]
fig.data[0].lon = lon
fig.data[0].lat = lat
# ---
lon_mid = (bbox_dict['lon_w'] + bbox_dict['lon_e']) / 2
lat_mid = (bbox_dict['lat_n'] + bbox_dict['lat_s']) / 2
fig.update_layout(map={'center': {'lon': lon_mid, 'lat': lat_mid}})
# ---
plot.update()
Doc: ui.input() (there is .bind_valuet_to(..., forward=...)
Full working code:
from nicegui import ui
import plotly.graph_objects as go
bbox_dict = {
'lat_n': '',
'lat_s': '',
'lon_w': '',
'lon_e': '',
}
ui.input(label='North Latitude', value=45, placeholder='eg 45.0' ).bind_value_to(bbox_dict, 'lat_n', forward=float)
ui.input(label='South Latitude', value=40, placeholder='eg 40.0' ).bind_value_to(bbox_dict, 'lat_s', forward=float)
ui.input(label='West Longitude', value=-90, placeholder='eg -90.0').bind_value_to(bbox_dict, 'lon_w', forward=float)
ui.input(label='East Longitude', value=-88, placeholder='eg -88.0').bind_value_to(bbox_dict, 'lon_e', forward=float)
lon = [bbox_dict['lon_w'], bbox_dict['lon_e'], bbox_dict['lon_e'], bbox_dict['lon_w']]
lat = [bbox_dict['lat_n'], bbox_dict['lat_n'], bbox_dict['lat_s'], bbox_dict['lat_s']]
lon_mid = (bbox_dict['lon_w'] + bbox_dict['lon_e']) / 2
lat_mid = (bbox_dict['lat_n'] + bbox_dict['lat_s']) / 2
fig = go.Figure(
go.Scattermap(
fill="toself",
lon=lon, lat=lat,
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': lon_mid, 'lat': lat_mid},
'zoom': 5
}
)
plot = ui.plotly(fig)
def update_plt():
#global lon # to assign new list to external variable `lon` instead of creating local `lon`
#global lat # to assign new list to external variable `lat` instead of creating local `lat`
#global lon_mid
#global lat_mid
lon = [bbox_dict['lon_w'], bbox_dict['lon_e'], bbox_dict['lon_e'], bbox_dict['lon_w']]
lat = [bbox_dict['lat_n'], bbox_dict['lat_n'], bbox_dict['lat_s'], bbox_dict['lat_s']]
fig.data[0].lon = lon
fig.data[0].lat = lat
# ---
lon_mid = (bbox_dict['lon_w'] + bbox_dict['lon_e']) / 2
lat_mid = (bbox_dict['lat_n'] + bbox_dict['lat_s']) / 2
fig.update_layout(map={'center': {'lon': lon_mid, 'lat': lat_mid}})
# ---
plot.update()
ui.button('Update', on_click=update_plt)
ui.run()