"""ABCs.""" # Authors: The MNE-Python contributors. # License: BSD-3-Clause # Copyright the MNE-Python contributors. import warnings from abc import ABC, abstractclassmethod, abstractmethod from ..ui_events import TimeChange, publish class Figure3D(ABC): """Class that refers to a 3D figure. .. note:: This class should not be instantiated directly via ``mne.viz.Figure3D(...)``. Instead, use :func:`mne.viz.create_3d_figure`. See Also -------- mne.viz.create_3d_figure """ # Here we use _init rather than __init__ so that users are less tempted to # instantiate the class directly. It also helps us # document the class more easily, as we don't have to say what all the # params are in public docs. @abstractclassmethod def _init( self, fig=None, size=(600, 600), bgcolor=(0.0, 0.0, 0.0), name=None, show=False, shape=(1, 1), splash=False, ): pass @property def plotter(self): """The native 3D plotting widget. Returns ------- plotter : instance of pyvista.Plotter The plotter. Useful for interacting with the native 3D library. """ return self._plotter class _AbstractRenderer(ABC): @abstractclassmethod def __init__( self, fig=None, size=(600, 600), bgcolor=(0.0, 0.0, 0.0), name=None, show=False, shape=(1, 1), splash=False, ): """Set up the scene.""" pass @property @abstractmethod def _kind(self): pass @abstractclassmethod def subplot(self, x, y): """Set the active subplot.""" pass @abstractclassmethod def scene(self): """Return scene handle.""" pass @abstractclassmethod def set_interaction(self, interaction): """Set interaction mode.""" pass @abstractclassmethod def legend(self, labels, border=False, size=0.1, face="triangle", loc="upper left"): """Add a legend to the scene. Parameters ---------- labels : list of tuples Each entry must contain two strings, (label, color), where ``label`` is the name of the item to add, and ``color`` is the color of the label to add. border : bool Controls if there will be a border around the legend. The default is False. size : float The size of the entire figure window. loc : str The location of the legend. face : str Face shape of legend face. One of the following: * None: ``None`` * Line: ``"-"`` or ``"line"`` * Triangle: ``"^"`` or ``'triangle'`` * Circle: ``"o"`` or ``'circle'`` * Rectangle: ``"r"`` or ``'rectangle'`` """ pass @abstractclassmethod def mesh( self, x, y, z, triangles, color, opacity=1.0, backface_culling=False, scalars=None, colormap=None, vmin=None, vmax=None, interpolate_before_map=True, representation="surface", line_width=1.0, normals=None, polygon_offset=None, **kwargs, ): """Add a mesh in the scene. Parameters ---------- x : array, shape (n_vertices,) The array containing the X component of the vertices. y : array, shape (n_vertices,) The array containing the Y component of the vertices. z : array, shape (n_vertices,) The array containing the Z component of the vertices. triangles : array, shape (n_polygons, 3) The array containing the indices of the polygons. color : tuple | str The color of the mesh as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). opacity : float The opacity of the mesh. shading : bool If True, enable the mesh shading. backface_culling : bool If True, enable backface culling on the mesh. scalars : ndarray, shape (n_vertices,) The scalar valued associated to the vertices. vmin : float | None vmin is used to scale the colormap. If None, the min of the data will be used. vmax : float | None vmax is used to scale the colormap. If None, the max of the data will be used. colormap : str | np.ndarray | matplotlib.colors.Colormap | None The colormap to use. interpolate_before_map : Enabling makes for a smoother scalars display. Default is True. When False, OpenGL will interpolate the mapped colors which can result is showing colors that are not present in the color map. representation : str The representation of the mesh: either 'surface' or 'wireframe'. line_width : int The width of the lines when representation='wireframe'. normals : array, shape (n_vertices, 3) The array containing the normal of each vertex. polygon_offset : float If not None, the factor used to resolve coincident topology. kwargs : args The arguments to pass to triangular_mesh Returns ------- surface : Handle of the mesh in the scene. """ pass @abstractclassmethod def contour( self, surface, scalars, contours, width=1.0, opacity=1.0, vmin=None, vmax=None, colormap=None, normalized_colormap=False, kind="line", color=None, ): """Add a contour in the scene. Parameters ---------- surface : surface object The mesh to use as support for contour. scalars : ndarray, shape (n_vertices,) The scalar valued associated to the vertices. contours : int | list Specifying a list of values will only give the requested contours. width : float The width of the lines or radius of the tubes. opacity : float The opacity of the contour. vmin : float | None vmin is used to scale the colormap. If None, the min of the data will be used. vmax : float | None vmax is used to scale the colormap. If None, the max of the data will be used. colormap : str | np.ndarray | matplotlib.colors.Colormap | None The colormap to use. normalized_colormap : bool Specify if the values of the colormap are between 0 and 1. kind : 'line' | 'tube' The type of the primitives to use to display the contours. color : tuple | str The color of the mesh as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). """ pass @abstractclassmethod def surface( self, surface, color=None, opacity=1.0, vmin=None, vmax=None, colormap=None, normalized_colormap=False, scalars=None, backface_culling=False, polygon_offset=None, ): """Add a surface in the scene. Parameters ---------- surface : surface object The information describing the surface. color : tuple | str The color of the surface as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). opacity : float The opacity of the surface. vmin : float | None vmin is used to scale the colormap. If None, the min of the data will be used. vmax : float | None vmax is used to scale the colormap. If None, the max of the data will be used. colormap : str | np.ndarray | matplotlib.colors.Colormap | None The colormap to use. scalars : ndarray, shape (n_vertices,) The scalar valued associated to the vertices. backface_culling : bool If True, enable backface culling on the surface. polygon_offset : float If not None, the factor used to resolve coincident topology. """ pass @abstractclassmethod def sphere( self, center, color, scale, opacity=1.0, resolution=8, backface_culling=False, radius=None, ): """Add sphere in the scene. Parameters ---------- center : ndarray, shape(n_center, 3) The list of centers to use for the sphere(s). color : tuple | str The color of the sphere as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). scale : float The scaling applied to the spheres. The given value specifies the maximum size in drawing units. opacity : float The opacity of the sphere(s). resolution : int The resolution of the sphere created. This is the number of divisions along theta and phi. backface_culling : bool If True, enable backface culling on the sphere(s). radius : float | None Replace the glyph scaling by a fixed radius value for each sphere. """ pass @abstractclassmethod def tube( self, origin, destination, radius=0.001, color="white", scalars=None, vmin=None, vmax=None, colormap="RdBu", normalized_colormap=False, reverse_lut=False, ): """Add tube in the scene. Parameters ---------- origin : array, shape(n_lines, 3) The coordinates of the first end of the tube(s). destination : array, shape(n_lines, 3) The coordinates of the other end of the tube(s). radius : float The radius of the tube(s). color : tuple | str The color of the tube as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). scalars : array, shape (n_quivers,) | None The optional scalar data to use. vmin : float | None vmin is used to scale the colormap. If None, the min of the data will be used. vmax : float | None vmax is used to scale the colormap. If None, the max of the data will be used. colormap : str | np.ndarray | matplotlib.colors.Colormap | None The colormap to use. opacity : float The opacity of the tube(s). backface_culling : bool If True, enable backface culling on the tube(s). reverse_lut : bool If True, reverse the lookup table. Returns ------- actor : The actor in the scene. surface : Handle of the tube in the scene. """ pass @abstractclassmethod def quiver3d( self, x, y, z, u, v, w, color, scale, mode, resolution=8, glyph_height=None, glyph_center=None, glyph_resolution=None, opacity=1.0, scale_mode="none", scalars=None, backface_culling=False, colormap=None, vmin=None, vmax=None, line_width=2.0, name=None, ): """Add quiver3d in the scene. Parameters ---------- x : array, shape (n_quivers,) The X component of the position of the quiver. y : array, shape (n_quivers,) The Y component of the position of the quiver. z : array, shape (n_quivers,) The Z component of the position of the quiver. u : array, shape (n_quivers,) The last X component of the quiver. v : array, shape (n_quivers,) The last Y component of the quiver. w : array, shape (n_quivers,) The last Z component of the quiver. color : tuple | str The color of the quiver as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). scale : float The scaling applied to the glyphs. The size of the glyph is by default calculated from the inter-glyph spacing. The given value specifies the maximum glyph size in drawing units. mode : 'arrow', 'cone' or 'cylinder' The type of the quiver. resolution : int The resolution of the glyph created. Depending on the type of glyph, it represents the number of divisions in its geometric representation. glyph_height : float The height of the glyph used with the quiver. glyph_center : tuple The center of the glyph used with the quiver: (x, y, z). glyph_resolution : float The resolution of the glyph used with the quiver. opacity : float The opacity of the quiver. scale_mode : 'vector', 'scalar' or 'none' The scaling mode for the glyph. scalars : array, shape (n_quivers,) | None The optional scalar data to use. backface_culling : bool If True, enable backface culling on the quiver. colormap : str | np.ndarray | matplotlib.colors.Colormap | None The colormap to use. vmin : float | None vmin is used to scale the colormap. If None, the min of the data will be used vmax : float | None vmax is used to scale the colormap. If None, the max of the data will be used line_width : float The width of the 2d arrows. Returns ------- actor : The actor in the scene. surface : Handle of the quiver in the scene. """ pass @abstractclassmethod def text2d(self, x_window, y_window, text, size=14, color="white"): """Add 2d text in the scene. Parameters ---------- x : float The X component to use as position of the text in the window coordinates system (window_width, window_height). y : float The Y component to use as position of the text in the window coordinates system (window_width, window_height). text : str The content of the text. size : int The size of the font. color : tuple | str The color of the text as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). """ pass @abstractclassmethod def text3d(self, x, y, z, text, width, color="white"): """Add 2d text in the scene. Parameters ---------- x : float The X component to use as position of the text. y : float The Y component to use as position of the text. z : float The Z component to use as position of the text. text : str The content of the text. width : float The width of the text. color : tuple | str The color of the text as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). """ pass @abstractclassmethod def scalarbar(self, source, color="white", title=None, n_labels=4, bgcolor=None): """Add a scalar bar in the scene. Parameters ---------- source The object of the scene used for the colormap. color : tuple | str The color of the label text. title : str | None The title of the scalar bar. n_labels : int | None The number of labels to display on the scalar bar. bgcolor : tuple | str The color of the background when there is transparency. """ pass @abstractclassmethod def show(self): """Render the scene.""" pass @abstractclassmethod def close(self): """Close the scene.""" pass @abstractclassmethod def set_camera( self, azimuth=None, elevation=None, distance=None, focalpoint=None, roll=None, ): """Configure the camera of the scene. Parameters ---------- azimuth : float The azimuthal angle of the camera. elevation : float The zenith angle of the camera. distance : float The distance to the focal point. focalpoint : tuple The focal point of the camera: (x, y, z). roll : float The rotation of the camera along its axis. """ pass @abstractclassmethod def screenshot(self, mode="rgb", filename=None): """Take a screenshot of the scene. Parameters ---------- mode : str Either 'rgb' or 'rgba' for values to return. Default is 'rgb'. filename : str | None If not None, save the figure to the disk. """ pass @abstractclassmethod def project(self, xyz, ch_names): """Convert 3d points to a 2d perspective. Parameters ---------- xyz : array, shape(n_points, 3) The points to project. ch_names : array, shape(_n_points,) Names of the channels. """ pass @abstractclassmethod def remove_mesh(self, mesh_data): """Remove the given mesh from the scene. Parameters ---------- mesh_data : tuple | Surface The mesh to remove. """ pass # ------------------- # Widget Abstractions # ------------------- class _AbstractWidget(ABC): @abstractclassmethod def __init__(self): pass @abstractmethod def _show(self): pass @abstractmethod def _hide(self): pass @abstractmethod def _set_enabled(self, state): pass @abstractmethod def _is_enabled(self): pass @abstractmethod def _update(self, repaint=True): pass @abstractmethod def _set_style(self, style): pass @abstractmethod def _get_tooltip(self): pass @abstractmethod def _set_tooltip(self, tooltip: str): pass @abstractmethod def _add_keypress(self, callback): pass @abstractmethod def _trigger_keypress(self, key): pass @abstractmethod def _set_focus(self): pass @abstractmethod def _set_layout(self, layout): pass @abstractmethod def _set_theme(self, theme): pass @abstractmethod def _set_size(self, width=None, height=None): pass class _AbstractLabel(_AbstractWidget): @abstractclassmethod def __init__(self, value, center=False, selectable=False): pass class _AbstractText(_AbstractWidget): @abstractclassmethod def __init__(self, value=None, placeholder=None, callback=None): pass @abstractmethod def _set_value(self, value): pass class _AbstractButton(_AbstractWidget): @abstractclassmethod def __init__(self, value, callback, icon=None): pass @abstractmethod def _click(self): pass @abstractmethod def _set_icon(self, icon): pass class _AbstractSlider(_AbstractWidget): @abstractclassmethod def __init__(self, value, rng, callback, horizontal=True): pass @abstractmethod def _set_value(self, value): pass @abstractmethod def _get_value(self): pass @abstractmethod def _set_range(self, rng): pass class _AbstractProgressBar(_AbstractWidget): @abstractclassmethod def __init__(self, count): pass @abstractmethod def _increment(self): pass class _AbstractCheckBox(_AbstractWidget): @abstractclassmethod def __init__(self, value, callback): pass @abstractmethod def _set_checked(self, checked): pass @abstractmethod def _get_checked(self): pass class _AbstractSpinBox(_AbstractWidget): @abstractclassmethod def __init__(self, value, rng, callback, step=None): pass @abstractmethod def _set_value(self, value): pass @abstractmethod def _get_value(self): pass class _AbstractComboBox(_AbstractWidget): @abstractclassmethod def __init__(self, value, items, callback): pass @abstractmethod def _set_value(self, value): pass @abstractmethod def _get_value(self): pass class _AbstractRadioButtons(_AbstractWidget): @abstractclassmethod def __init__(self, value, items, callback): pass @abstractmethod def _set_value(self, value): pass @abstractmethod def _get_value(self): pass class _AbstractGroupBox(_AbstractWidget): @abstractclassmethod def __init__(self, name, items): pass class _AbstractFileButton(_AbstractWidget): @abstractclassmethod def __init__( self, callback, content_filter=None, initial_directory=None, save=False, is_directory=False, icon="folder", window=None, ): pass class _AbstractPlayMenu(_AbstractWidget): @abstractclassmethod def __init__(self, value, rng, callback): pass @abstractmethod def _play(self): pass @abstractmethod def _pause(self): pass @abstractmethod def _reset(self): pass @abstractmethod def _loop(self): pass @abstractmethod def _set_value(self, value): pass class _AbstractPopup(_AbstractWidget): _supported_button_names = ["Ok"] # TODO: Add back support for below, file browser takes care of most # so no big need currently """ # from QMessageBox.StandardButtons ['Ok', 'Open', 'Save', 'Cancel', 'Close', 'Discard', 'Apply', 'Reset', 'RestoreDefaults', 'Help', 'SaveAll', 'Yes', 'YesToAll', 'No', 'NoToAll', 'Abort', 'Retry', 'Ignore'] """ _supported_icon_names = ["question", "information", "warning", "critical"] @abstractmethod def __init__( self, title, text, info_text=None, callback=None, icon="Warning", buttons=None, window=None, ): pass @abstractmethod def _click(self, value): pass # ------- # Layouts # ------- class _AbstractBoxLayout(ABC): @abstractmethod def _add_widget(self, widget): pass @abstractmethod def _add_stretch(self, amount=1): pass class _AbstractHBoxLayout(_AbstractBoxLayout): @abstractmethod def __init__(self, height=None, scroll=None): pass class _AbstractVBoxLayout(_AbstractBoxLayout): @abstractmethod def __init__(self, width=None, scroll=None): pass class _AbstractGridLayout(ABC): @abstractmethod def __init__(self, height=None, width=None, scroll=None): pass @abstractmethod def _add_widget(self, widget, row=None, col=None): pass class _AbstractAppWindow(ABC): def __init__(self, size=None, fullscreen=False): pass @abstractmethod def _set_central_layout(self, central_layout): pass @abstractmethod def _get_dpi(self): pass @abstractmethod def _get_size(self): pass @abstractmethod def _get_cursor(self): pass @abstractmethod def _set_cursor(self, cursor): pass @abstractmethod def _new_cursor(self, name): pass @abstractmethod def _close_connect(self, func, *, after=True): pass @abstractmethod def _close_disconnect(self, after=True): pass @abstractmethod def _clean(self): pass @abstractmethod def _show(self, block=False): pass @abstractmethod def _close(self): pass # ------------------- # Matplotlib Canvases # ------------------- class _AbstractCanvas(ABC): def __init__(self, width=None, height=None, dpi=None): """Initialize the matplotlib Canvas.""" pass def show(self): """Show the canvas.""" if self.manager is None: self.show() else: self.manager.show() def close(self): """Close the canvas.""" self.close() def update(self): """Update the canvas.""" self.fig.canvas.draw() self.fig.canvas.flush_events() def clear(self): """Clear internal variables.""" self.close() self.ax.clear() self.fig.clear() self.manager = None @abstractmethod def _set_size(self, width=None, height=None): pass # ------------------------------------ # Non-object-based Widget Abstractions # ------------------------------------ # These are planned to be deprecated in favor of the simpler, object- # oriented abstractions above when time allows. class _AbstractToolBar(ABC): @abstractmethod def _tool_bar_initialize(self, name="default", window=None): pass @abstractmethod def _tool_bar_add_button(self, name, desc, func, *, icon_name=None, shortcut=None): pass @abstractmethod def _tool_bar_update_button_icon(self, name, icon_name): pass @abstractmethod def _tool_bar_add_text(self, name, value, placeholder): pass @abstractmethod def _tool_bar_add_spacer(self): pass @abstractmethod def _tool_bar_add_file_button(self, name, desc, func, *, shortcut=None): pass @abstractmethod def _tool_bar_add_play_button(self, name, desc, func, *, shortcut=None): pass class _AbstractDock(ABC): @abstractmethod def _dock_initialize( self, window=None, name="Controls", area="left", max_width=None ): pass @abstractmethod def _dock_finalize(self): pass @abstractmethod def _dock_show(self): pass @abstractmethod def _dock_hide(self): pass @abstractmethod def _dock_add_stretch(self, layout=None): pass @abstractmethod def _dock_add_layout(self, vertical=True): pass @abstractmethod def _dock_add_label(self, value, *, align=False, layout=None, selectable=False): pass @abstractmethod def _dock_add_button( self, name, callback, *, style="pushbutton", icon=None, tooltip=None, layout=None, ): pass @abstractmethod def _dock_named_layout(self, name, *, layout=None, compact=True): pass @abstractmethod def _dock_add_slider( self, name, value, rng, callback, *, compact=True, double=False, tooltip=None, layout=None, ): pass @abstractmethod def _dock_add_check_box(self, name, value, callback, *, tooltip=None, layout=None): pass @abstractmethod def _dock_add_spin_box( self, name, value, rng, callback, *, compact=True, double=True, step=None, tooltip=None, layout=None, ): pass @abstractmethod def _dock_add_combo_box( self, name, value, rng, callback, *, compact=True, tooltip=None, layout=None ): pass @abstractmethod def _dock_add_radio_buttons( self, value, rng, callback, *, vertical=True, layout=None ): pass @abstractmethod def _dock_add_group_box(self, name, *, collapse=None, layout=None): pass @abstractmethod def _dock_add_text(self, name, value, placeholder, *, callback=None, layout=None): pass @abstractmethod def _dock_add_file_button( self, name, desc, func, *, filter_=None, initial_directory=None, save=False, is_directory=False, icon=False, tooltip=None, layout=None, ): pass class _AbstractMenuBar(ABC): @abstractmethod def _menu_initialize(self, window=None): pass @abstractmethod def _menu_add_submenu(self, name, desc): pass @abstractmethod def _menu_add_button(self, menu_name, name, desc, func): pass class _AbstractStatusBar(ABC): @abstractmethod def _status_bar_initialize(self, window=None): pass @abstractmethod def _status_bar_add_label(self, value, *, stretch=0): pass @abstractmethod def _status_bar_add_progress_bar(self, stretch=0): pass @abstractmethod def _status_bar_update(self): pass class _AbstractPlayback(ABC): @abstractmethod def _playback_initialize(self, func, timeout, value, rng, time_widget, play_widget): pass class _AbstractKeyPress(ABC): @abstractmethod def _keypress_initialize(self, widget=None): pass @abstractmethod def _keypress_add(self, shortcut, callback): pass @abstractmethod def _keypress_trigger(self, shortcut): pass class _AbstractDialog(ABC): @abstractmethod def _dialog_create( self, title, text, info_text, callback, *, icon="Warning", buttons=(), modal=True, window=None, ): pass class _AbstractLayout(ABC): @abstractmethod def _layout_initialize(self, max_width): pass @abstractmethod def _layout_add_widget(self, layout, widget, stretch=0, *, row=None, col=None): pass @abstractmethod def _layout_create(self, orientation="vertical"): pass class _AbstractWidgetList(ABC): @abstractmethod def set_enabled(self, state): pass @abstractmethod def get_value(self, idx): pass @abstractmethod def set_value(self, idx, value): pass class _AbstractWdgt(ABC): def __init__(self, widget): self._widget = widget @property def widget(self): return self._widget @abstractmethod def set_value(self, value): pass @abstractmethod def get_value(self): pass @abstractmethod def set_range(self, rng): pass @abstractmethod def show(self): pass @abstractmethod def hide(self): pass @abstractmethod def set_enabled(self, state): pass @abstractmethod def is_enabled(self): pass @abstractmethod def update(self, repaint=True): pass @abstractmethod def get_tooltip(self): pass @abstractmethod def set_tooltip(self, tooltip: str): pass @abstractmethod def set_style(self, style): pass class _AbstractAction(ABC): def __init__(self, action): self._action = action @abstractmethod def trigger(self): pass @abstractmethod def set_icon(self): pass @abstractmethod def set_shortcut(self): pass class _AbstractMplInterface(ABC): @abstractmethod def _mpl_initialize(): pass class _AbstractMplCanvas(ABC): def __init__(self, width, height, dpi): """Initialize the MplCanvas.""" from matplotlib.figure import Figure self._extra_events = ("resize",) self.fig = Figure(figsize=(width, height), dpi=dpi, layout="constrained") self.axes = self.fig.add_subplot(111) self.axes.set(xlabel="Time (s)", ylabel="Activation (AU)") self.manager = None def _connect(self): for event in ("button_press", "motion_notify") + self._extra_events: self.canvas.mpl_connect(event + "_event", getattr(self, "on_" + event)) def plot(self, x, y, label, update=True, **kwargs): """Plot a curve.""" (line,) = self.axes.plot(x, y, label=label, **kwargs) if update: self.update_plot() return line def plot_time_line(self, x, label, update=True, **kwargs): """Plot the vertical line.""" line = self.axes.axvline(x, label=label, **kwargs) if update: self.update_plot() return line def update_plot(self): """Update the plot.""" with warnings.catch_warnings(record=True): warnings.filterwarnings("ignore", "constrained_layout") self.canvas.draw() def set_color(self, bg_color, fg_color): """Set the widget colors.""" self.axes.set_facecolor(bg_color) self.axes.xaxis.label.set_color(fg_color) self.axes.yaxis.label.set_color(fg_color) self.axes.spines["top"].set_color(fg_color) self.axes.spines["bottom"].set_color(fg_color) self.axes.spines["left"].set_color(fg_color) self.axes.spines["right"].set_color(fg_color) self.axes.tick_params(axis="x", colors=fg_color) self.axes.tick_params(axis="y", colors=fg_color) self.fig.patch.set_facecolor(bg_color) def show(self): """Show the canvas.""" if self.manager is None: self.canvas.show() else: self.manager.show() def close(self): """Close the canvas.""" self.canvas.close() def clear(self): """Clear internal variables.""" self.close() self.axes.clear() self.fig.clear() self.canvas = None self.manager = None def on_resize(self, event): """Handle resize events.""" pass class _AbstractBrainMplCanvas(_AbstractMplCanvas): def __init__(self, brain, width, height, dpi): """Initialize the MplCanvas.""" super().__init__(width, height, dpi) self.brain = brain def update_plot(self): """Update the plot.""" leg = self.axes.legend( prop={"family": "monospace", "size": "small"}, framealpha=0.5, handlelength=1.0, facecolor=self.brain._bg_color, ) for text in leg.get_texts(): text.set_color(self.brain._fg_color) super().update_plot() def on_button_press(self, event): """Handle button presses.""" # left click (and maybe drag) in progress in axes if event.inaxes != self.axes or event.button != 1: return publish(self.brain, TimeChange(time=event.xdata)) on_motion_notify = on_button_press # for now they can be the same def clear(self): """Clear internal variables.""" super().clear() self.brain = None class _AbstractWindow(ABC): def _window_initialize(self, *, window=None, central_layout=None, fullscreen=False): self._icons = dict() self._window = None self._interactor = None self._mplcanvas = None self._show_traces = None self._separate_canvas = None self._interactor_fraction = None @abstractmethod def _window_load_icons(self): pass @abstractmethod def _window_close_connect(self, func, *, after=True): pass @abstractmethod def _window_close_disconnect(self, after=True): pass @abstractmethod def _window_get_dpi(self): pass @abstractmethod def _window_get_size(self): pass def _window_get_mplcanvas_size(self, fraction): ratio = (1 - fraction) / fraction dpi = self._window_get_dpi() w, h = self._window_get_size() h /= ratio return (w / dpi, h / dpi) @abstractmethod def _window_get_simple_canvas(self, width, height, dpi): pass @abstractmethod def _window_get_mplcanvas( self, brain, interactor_fraction, show_traces, separate_canvas ): pass @abstractmethod def _window_adjust_mplcanvas_layout(self): pass @abstractmethod def _window_get_cursor(self): pass @abstractmethod def _window_set_cursor(self, cursor): pass @abstractmethod def _window_new_cursor(self, name): pass @abstractmethod def _window_ensure_minimum_sizes(self): pass @abstractmethod def _window_set_theme(self, theme): pass @abstractmethod def _window_create(self): pass