Skip to content

Commit

Permalink
Bug: Missing plot refresh of tasks sharing the Matplotlib figure with…
Browse files Browse the repository at this point in the history
… the prior task #1076
  • Loading branch information
detlefarend committed Oct 30, 2024
1 parent ab21307 commit 78e2d8d
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 41 deletions.
133 changes: 100 additions & 33 deletions src/mlpro/bf/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@
## -- - New property Plottable.color
## -- - Class PlotSettings: removed parameter p_plot_depth
## -- 2024-07-08 2.16.1 SY Add MinVal for undefined range in DataPlotting
## -- 2024-10-30 2.17.0 DA Extensions on classes PlotSettings, Plottable:
## -- - Class PlotSettings: new methods register(), unregister(),
## -- is_last_registered()
## -- - Class Plottable: extensions on init_plot(), update_plot()
## -------------------------------------------------------------------------------------------------

"""
Ver. 2.16.1 (2024-07-08)
Ver. 2.17.0 (2024-10-30)
This module provides various classes related to data plotting.
Expand Down Expand Up @@ -168,6 +172,7 @@ def __init__( self,
self.id = p_id
self.view_autoselect = p_view_autoselect
self.kwargs = p_kwargs.copy()
self._registered_obj = []

if ( p_plot_horizon > 0 ) and ( p_data_horizon > 0 ):
self.plot_horizon = min(p_plot_horizon, p_data_horizon)
Expand All @@ -176,23 +181,80 @@ def __init__( self,
self.plot_horizon = p_plot_horizon
self.data_horizon = p_data_horizon


## -------------------------------------------------------------------------------------------------
def register( self, p_plot_obj : type):
"""
Registers the specified plotting object. Internally used in class Plottable.
Parameters
----------
p_plot_obj : type
Plotting object to be registered
"""

self._registered_obj.append(p_plot_obj)


## -------------------------------------------------------------------------------------------------
def unregister( self, p_plot_obj : type):
"""
Unregisters the specified plotting object. Internally used in class Plottable.
Parameters
----------
p_plot_obj : type
Plotting object to be registered
"""

try:
self._registered_obj.remove(p_plot_obj)
except:
pass


## -------------------------------------------------------------------------------------------------
def is_last_registered( self, p_plot_obj : type ) -> bool:
"""
Checks whether the specified plot object was the last one registered. Internally used in
class Plottable.
Parameters
----------
p_plot_obj : type
Plotting object to be registered
Returns
-------
bool
True, if the specified plotting object was the last one registering. False otherwise.
"""

try:
return p_plot_obj == self._registered_obj[-1]
except:
return False


## -------------------------------------------------------------------------------------------------
def copy(self):
return self.__class__( p_view = self.view,
p_axes = self.axes,
p_pos_x = self.pos_x,
p_pos_y = self.pos_y,
p_size_x = self.size_x,
p_size_y = self.size_y,
p_step_rate = self.step_rate,
p_plot_horizon = self.plot_horizon,
p_data_horizon = self.data_horizon,
p_detail_level = self.detail_level,
p_force_fg = self.force_fg,
p_id = self.id,
p_view_autoselect = self.view_autoselect,
p_kwargs = self.kwargs )
duplicate = self.__class__( p_view = self.view,
p_axes = self.axes,
p_pos_x = self.pos_x,
p_pos_y = self.pos_y,
p_size_x = self.size_x,
p_size_y = self.size_y,
p_step_rate = self.step_rate,
p_plot_horizon = self.plot_horizon,
p_data_horizon = self.data_horizon,
p_detail_level = self.detail_level,
p_force_fg = self.force_fg,
p_id = self.id,
p_view_autoselect = self.view_autoselect,
p_kwargs = self.kwargs )

#duplicate._registered_obj = self._registered_obj.copy()
return duplicate



Expand Down Expand Up @@ -322,26 +384,27 @@ def init_plot( self,
plt.ioff()


# 2 Prepare internal data structures

# 2.1 Plot settings per view
self.set_plot_settings( p_plot_settings=p_plot_settings )

# 2.2 Dictionary with methods for initialization and update of a plot per view
self._plot_methods = { PlotSettings.C_VIEW_2D : [ self._init_plot_2d, self._update_plot_2d, self._remove_plot_2d ],
PlotSettings.C_VIEW_3D : [ self._init_plot_3d, self._update_plot_3d, self._remove_plot_3d ],
PlotSettings.C_VIEW_ND : [ self._init_plot_nd, self._update_plot_nd, self._remove_plot_nd ] }
# 2 Prepare internal data structures

# 2.1 Dictionary with methods for initialization and update of a plot per view
self._plot_methods = { PlotSettings.C_VIEW_2D : ( self._init_plot_2d, self._update_plot_2d, self._remove_plot_2d ),
PlotSettings.C_VIEW_3D : ( self._init_plot_3d, self._update_plot_3d, self._remove_plot_3d ),
PlotSettings.C_VIEW_ND : ( self._init_plot_nd, self._update_plot_nd, self._remove_plot_nd ) }

# 3 Setup the Matplotlib host figure if no one is provided as parameter
# 2.2 Plot settings per view
self.set_plot_settings( p_plot_settings=p_plot_settings )

# 2.3 Setup the Matplotlib host figure if no one is provided as parameter
if p_figure is None:
self._figure : Figure = self._init_figure()
self._plot_own_figure = True
else:
self._figure : Figure = p_figure

self._plot_settings.register( p_plot_obj = self )


# 4 Call of all initialization methods of the required views
# 3 Call of all initialization methods of the required views
view = self._plot_settings.view
try:
plot_method = self._plot_methods[view][0]
Expand All @@ -354,13 +417,13 @@ def init_plot( self,
raise ImplementationError('Please set attribute "axes" in your custom _init_plot_' + view + ' method')


# # 5 In standalone mode: refresh figure
# 4 In standalone mode: refresh figure
if self._plot_own_figure:
self._figure.canvas.draw()
self._figure.canvas.flush_events()


# 6 Marker to ensure that initialization runs only once
# 5 Marker to ensure that initialization runs only once
self._plot_initialized = True
self._plot_first_time = True

Expand Down Expand Up @@ -471,10 +534,10 @@ def refresh_plot(self, p_force:bool=False):


# 1 Object has own figure or refresh is forced by caller?
if not self._plot_own_figure and not p_force: return
# if not self._plot_own_figure and not p_force: return

if self._plot_own_figure:
self._plot_step_counter = mod(self._plot_step_counter+1, self._plot_settings.step_rate)
# if self._plot_own_figure:
self._plot_step_counter = mod(self._plot_step_counter+1, self._plot_settings.step_rate)


# 2 Refresh plot
Expand Down Expand Up @@ -589,8 +652,9 @@ def update_plot(self, **p_kwargs):
self._plot_methods[view][1](p_settings=self._plot_settings, **p_kwargs)


# 4 Update content of own(!) figure after self._plot_step_rate calls
self.refresh_plot(p_force=False)
# 4 The last plotting object for the figure refreshs the plot
if self._plot_settings.is_last_registered( p_plot_obj = self ):
self.refresh_plot(p_force=True)


## -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -665,6 +729,8 @@ def remove_plot(self, p_refresh:bool = True):
# 3 Optionally refresh
if p_refresh: self.refresh_plot(p_force=False)

# 4 Clear internal plot parameters
self._plot_settings.unregister( p_plot_obj = self )
self._plot_first_time = True


Expand Down Expand Up @@ -695,6 +761,7 @@ def _remove_plot_nd(self):
pass


## -------------------------------------------------------------------------------------------------
color = property( fget = get_plot_color, fset = set_plot_color )
plot_detail_level = property( fget = get_plot_detail_level, fset = assign_plot_detail_level )

Expand Down
12 changes: 5 additions & 7 deletions src/mlpro/bf/streams/basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@
## -- visualization 2D,3D,ND
## -- 2024-09-11 2.1.0 DA Class Instance: new parent KWArgs
## -- 2024-10-29 2.2.0 DA Changed definiton of InstType, InstTypeNew, InstTypeDel
## -- 2024-10-30 2.3.0 DA Refactoring of StreamTask.update_plot()
## -------------------------------------------------------------------------------------------------

"""
Ver. 2.2.0 (2024-10-29)
Ver. 2.3.0 (2024-10-30)
This module provides classes for standardized data stream processing.
Expand Down Expand Up @@ -1096,21 +1097,18 @@ def update_plot( self,
else:
inst = p_inst

if len(inst) == 0: return

try:
self._plot_view_finalized
except:
if self._plot_settings.view_autoselect:
if self._plot_settings.view_autoselect and ( len(inst) > 0 ):
self._finalize_plot_view(p_inst_ref=next(iter(inst.values()))[1])

self._plot_view_finalized = True
self._plot_view_finalized = True

Task.update_plot(self, p_inst=inst, **p_kwargs)

self._plot_num_inst += len(inst)


## -------------------------------------------------------------------------------------------------
def _update_plot_2d( self,
p_settings : PlotSettings,
Expand Down
2 changes: 1 addition & 1 deletion src/mlpro/bf/streams/tasks/windows/ringbuffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def _run(self, p_inst : InstDict ):


# 1 Main processing loop
for inst_id, (inst_type, inst) in sorted(inst.items()):
for inst_id, (inst_type, inst) in sorted(inst.items()):

if inst_type != InstTypeNew:
# Obsolete instances need to be removed from the buffer (not yet implemented)
Expand Down

0 comments on commit 78e2d8d

Please sign in to comment.