Operating System:
Windows
Kit Version:
110 (Kit App Template)
Kit Template:
USD Composer
GPU Hardware:
50 series
GPU Driver:
Latest
Work Flow:
I am trying to plot multiple lines with data provided via events using the ui.Plot widgets in a ZStack. When using static data, this works well, but does not work with dynamic data.
Main Issue:
When trying to update the plot data by using ui.Plot.set_xy_data on a LINE2D plot from an event handler, the plot does not update properly and gets stuck on a constant value. If I call set_xy_data synchronously it works and sets the plot data properly.
I tried many different setups to get this to work but no luck. Even to get to the point where I can plot the static data, I had to find this post on the forum (link) - ui.Plot seems quite poorly documented and not intuitive.
Correct behavior of set_xy_data:
Incorrect behavior when I try to use events:
Reproduction Steps:
Try the following code (issue also happened with Kit 108).
In the console you can clearly see that the data for sin functions is present in the lists, yet the constant value is shown.
Code:
Here is a bare-bones extension code which showcases the issue (some indentation might be incorrect because I cannot get it to paste into this editor properly):
import omni.ext
import omni.ui as ui
from omni.ui import color as cl
from carb.eventdispatcher import ObserverGuard, get_eventdispatcher, Event
import time
import numpy as np
import omni.kit.app
NCOLS = 5
class MyExtension(omni.ext.IExt):
def on_startup(self, _ext_id):
print("[test.uiplot] Extension startup")
self._window = ui.Window(
"UI Plot Test", width=400, height=400
)
self.sub_ui_update = self.create_data_stream(NCOLS)
self.sub_event_stream: ObserverGuard = None
with self._window.frame:
with ui.VStack(width=0, height=0):
ui.Button("Create stream Plot", clicked_fn=self._create_stream_plot)
ui.Button("Create basic Plot", clicked_fn=self._create_basic_plot)
self.plot_frame = ui.Frame(width=300, height=300)
def create_data_stream(self, ncols):
def on_update(_event: Event):
get_eventdispatcher().dispatch_event(
f"data_visualiser.update_data_stream_N_{ncols}",
{"data": [np.sin(time.time() + i) for i in range(ncols)]}
)
sub_update_event: ObserverGuard = get_eventdispatcher().observe_event(
observer_name=f"Main thread update data stream publisher N={ncols}",
event_name=omni.kit.app.GLOBAL_EVENT_UPDATE,
on_event=on_update
)
return sub_update_event
def clear_plots(self):
self.plot_frame.clear()
self.column_plots: list[ui.Plot] = []
self.column_data = [[] for _ in range(NCOLS)]
def _create_basic_plot(self):
self._destroy_sub_event()
self.clear_plots()
with self.plot_frame:
with ui.VStack(width=0, height=0):
ui.Label(f"Observing data stream with {NCOLS} columns", alignment=ui.Alignment.CENTER)
with ui.ZStack(width=0, height=0):
for col in range(NCOLS):
col_plot = ui.Plot(ui.Type.LINE2D, title=f"Column {col + 1}", width=800, height=500, style={"color": cl.red, "background_color": cl(0,0,0,0)})
self.column_plots.append(col_plot)
for col in range(NCOLS):
col_plot = self.column_plots[col]
col_xy_data = [(float(i)/1000, 10*np.sin(float(i + 10*col)/100)) for i in range(1000)]
col_plot.set_xy_data(col_xy_data)
print(f"Finished ui plot setup")
def _create_stream_plot(self):
self._destroy_sub_event()
self.clear_plots()
with self.plot_frame:
with ui.VStack(width=0, height=0):
ui.Label(f"Observing data stream with {NCOLS} columns", alignment=ui.Alignment.CENTER)
with ui.ZStack(width=0, height=0):
for col in range(NCOLS):
col_plot = ui.Plot(ui.Type.LINE2D, title=f"Column {col + 1}", width=800, height=500, style={"color": cl.red, "background_color": cl(0,0,0,0)})
self.column_plots.append(col_plot)
print(f"Finished ui plot setup")
def on_data(event: Event):
col_vals = event.get("data")
if len(col_vals) != NCOLS:
print(f"Received data length {len(col_vals)} does not match expected number of columns {NCOLS}.")
return
data_length = len(self.column_data[0]) + 1
for col in range(NCOLS):
self.column_data[col].append(col_vals[col])
col_xy_data = [(float(i)/data_length, 10*float(self.column_data[col][i])) for i in range(data_length)]
print(f"Updating plot for column {col + 1} with data: {col_xy_data}")
self.column_plots[col].set_xy_data(col_xy_data)
print(f"Creating observer for data stream with {NCOLS} columns")
event_name = f"data_visualiser.update_data_stream_N_{NCOLS}"
self.sub_event_stream: ObserverGuard = get_eventdispatcher().observe_event(
observer_name=f"Data stream observer N={NCOLS}",
event_name=event_name,
on_event=lambda event: on_data(event)
)
print(f"Started observing data stream with {NCOLS} columns. Listening for events named '{event_name}'.")
def _destroy_sub_event(self):
if self.sub_event_stream is not None:
self.sub_event_stream.reset()
self.sub_event_stream = None
def _destroy_sub_update(self):
if self.sub_ui_update is not None:
self.sub_ui_update.reset()
self.sub_ui_update = None
def _destroy_subs(self):
self._destroy_sub_event()
self._destroy_sub_update()
def on_shutdown(self):
print("[test.uiplot] Extension shutdown")
self._destroy_subs()

