I have employed a traitsui.api.Handler
to catch and handle events for a traitsui.api.View
, that view includes a button, whose behavior is to remove a plot from a container containing multiple plots. The container's components list is accessed when the remove
button is used, the pop()
method is called, and the plot is removed. However, the view does not redraw, and so that plot appears to remain in place. Resizing with window through dragging a corner will force the redraw, confirming the pop()
The question is: How can I force the redraw programmatically ?
It seems to me that the right place to do so would be in the Handler's setattr
method, just after pop()
-ing the plot.
# Major library imports
from numpy import linspace
from scipy.special import jn
# Enthought library imports
from enable.api import Container, ComponentEditor
from traits.api import HasTraits, Instance, Button, Int, Str
from traitsui.api import Item, HGroup, View, VSplit, UItem, InstanceEditor, Handler
# Chaco imports
from chaco.api import ArrayPlotData, GridContainer, Plot
# ===============================================================================
# Attributes to use for the plot view.
size = (1000, 800)
COLOR_PALETTE = [
(0.65098039, 0.80784314, 0.89019608, 1.0),
(0.12156863, 0.47058824, 0.70588235, 1.0),
(0.69803922, 0.8745098, 0.54117647, 1.0),
(0.2, 0.62745098, 0.17254902, 1.0),
(0.98431373, 0.60392157, 0.6, 1.0),
(0.89019608, 0.10196078, 0.10980392, 1.0),
(0.99215686, 0.74901961, 0.43529412, 1.0),
(1., 0.49803922, 0., 1.0),
(0.79215686, 0.69803922, 0.83921569, 1.0),
]
class InstanceUItem(UItem):
"""Convenience class for including an Instance in a View"""
style = Str('custom')
editor = Instance(InstanceEditor, ())
# ===============================================================================
# # ManagerHandler will be the View's handler
#===============================================================================
class ManagerHandler(Handler):
def setattr(self, info, object, name, value):
Handler.setattr(self, info, object, name, value)
info.ui.context['pgrid'].plots_container.components.pop()
#At this point, the container does not redraw, and so, while it no longer
#contains the last plot in its components collection, that plot is still
# visible
# ===============================================================================
# # PlotsGrid class that is used by the demo
#===============================================================================
class PlotsGrid(HasTraits):
plots_container = Instance(Container)
rows = Int(3)
cols = Int(3)
#===============================================================================
# # Create the plots, this is adapted from the chaco GridContainer demo
#===============================================================================
def _plots_container_default(self):
# Create a GridContainer to hold all of our plots
container = GridContainer(padding=20, fill_padding=True,
bgcolor="lightgray", use_backbuffer=True,
shape=(self.rows, self.cols), spacing=(20, 20))
# Create the initial series of data
x = linspace(-5, 15.0, 100)
pd = ArrayPlotData(index=x)
# Plot some bessel functions and add the plots to our container
for i in range(self.rows * self.cols):
pd.set_data("y" + str(i), jn(i, x))
plot = Plot(pd)
plot.plot(("index", "y" + str(i)),
color=tuple(COLOR_PALETTE[i]), line_width=2.0,
bgcolor="white", border_visible=True)
container.add(plot)
return container
# ===============================================================================
# # Controls HasTraits provides a button used to wire in the desired behavior
#===============================================================================
class Controls(HasTraits):
rem_plot = Button("remove ...")
def _rem_plot_changed(self):
print "rem plot changed"
# ===============================================================================
# # manager_view provides the View and defines its layout
#===============================================================================
manager_view = View(
VSplit(
HGroup(
Item('controls.rem_plot', height=32)
),
Item('pgrid.plots_container', editor=ComponentEditor(size=size), show_label=False),
show_border=True
),
handler=ManagerHandler(),
resizable=True
)
grid = PlotsGrid()
ctrl = Controls()
if __name__ == "__main__":
ctrl.configure_traits(view=manager_view, context={'pgrid': grid, 'controls': ctrl})
The simplest way to get this working is to call
invalidate_and_redraw
on your plot container after popping the plot. In that case, you could modify your call topop
to look something like:Longer discussion:
Ideally, this would be handled by Chaco. Part of the problem is that the container wasn't designed to have the
components
list modified directly. Instead, (I'm guessing) the intention was to have the user callplots_container.remove
on an item in thecomponents
list.That said, that doesn't seem to work either. It turns out that
remove
invalidates the current state, but doesn't request a redraw. (My guess is that chaco/enable doesn't automatically redraw since there may be many cache-invalidating operations in a row; e.g. if you were to remove all plots from this container, you'd only want to redraw after all the calls toremove
, not after each one.)So to actually use this alternative method, you would write something like: