Plotting

Overview

The plotting routines are the very core of the Eye, which interprets the information stored in models and coverts them to images/plots in the Figure. The Figure class creates a Pane instance for each axes created. The Pane oversees the details of applying plot configurations and data to the underlying matplotlib.Axes instance.

All display processes are overseen by the :class`View <sofia_redux.visualization.display.view.View>` class which interprets commands from the Eye class along with interactions from the user and GUI to create instructions for the Figure class. To keep the display current, the View utilizes Signals to respond to instructions (from the Eye and the user) in preparation for the refresh loop to complete.

Diagram

Activity diagram for plotting loop

Fig. 137 Activity diagram for the figure refresh loop. Blue events are handled by the Figure class, red events are handled by the Pane class, green events are handled by the BlitManager class.

Initial Plot Creation

When the Eye initially starts up, the figure is blank, with no axes or Panes. A data set is loaded into the Eye, either through command line arguments, GUI navigation, or API calls. The user can then select the model from the “model_list” to display. The Eye passes the model selected to the View, which then passes it to the Figure, flags the full background as stale (see Atrophy System), and updates the controls panels to reflect the newly visible dataset.

The Figure instance starts the display process by first determining what pane the model should be added to. Since initially there are no panes, one must be created. A singe Pane object is instantiated based on the number of dimensions the data being displayed has, so diplaying spectral data leads to the creation of a one-dimensional pane object (ie OneDimPane).

After the pane is created, the model is added. The pane creates a copy of the model and keeps reference to that. This allows the pane to change and manipulate the model (e.g., by changing units) while keeping the original model tracked by the Eye identical to the FITS file. The pane then sets the extensions to be plotted based on the pane’s fields, assigns a color to the model based on its color cycle, and assigns a marker style. With the model now configured for the pane, an artist object can be created.

In the OneDimPane, an artist represents a single line of data that displays one field against another (e.g., ‘flux’ vs. ‘wavepos’). Each order in the model to be displayed represents an additional line to be plotted. By default, all orders in a model are shown. For grism data, there is only every one model. However echelle data can have several orders, all of which are displayed by default. The data are then converted to the desired units based on the :class`Pane` settings, For each order in the model, the data corresponding to the x- and y-axis are pulled using the retrieve method and used to create matplotlib.lines.Line2D objects. It the y-axis field shows is ‘flux’ then an additional artist (matplotlib.collections.PolyCollection this time) is created to show the error range based on the associated error extension.

All artists created are then returned to the Figure, which then adds them to the Artist instance to be tracked. The Pane only keeps track of the matplotlib.Axes instance and the plot configurations, then uses them to create and return artists and returns. If the artists need to be updated, removed, or redrawn in any manner, the Artists class handles those changes.

Once the artists have been added to the Artist instance everything is prepped to be drawn. The standard refresh process describe in Atrophy System is utilized to finally draw all artists on screen.

Atrophy System

The Eye is an interactive tool, meaning it spends much of its time running an idle looping waiting for user interactions. After the user interacts with the Eye in a manner, be that by adding new models, changing the color, or merely moving the cursor across the plot, the Eye must assess the interaction, determine what actions need to be taken to respond to the interaction, then process those actions.

Whenever the Eye determines an interaction requires the plot to respond, it implements the change in two steps. First the settings and underlying data structures of the plot are changed. This can be changing the units of a model, selecting a different field to plot, or just updating the cursor’s location. Second the figure is marked as “stale” by emitting the “atrophy” signal. During the idle loop, the Eye checks if the figure is stale. If is not stale, then the loop continues on, waiting for its next interaction. However if the figure is stale, the figure is updated to reflect the new settings and data.

By default, matplotlib plots redraw every part of themselves whenever they are updated. Usually this is minor, but if there are many panes, models, or fits plotted it can get wasteful. If the user moved the cursor to a new location on the plot, only the cursor’s location artist needs to be updated. The axis, labels, and other lines do not need to redrawn, but the default implementation of matplotlib will do just that. This issue is avoided by implementing blitting techniques, which are described in Blitting. To use it properly, the Eye needs to keep track of when the background of a pane needs to be updated or if just the artists need to be updated. To differentiate between the type of update that needs to happen, the Eye has three different atrophy signals which correspond to three different stale states.

  • atrophy: The simplest of the atrophy signals, this marks the figure as “stale” which corresponds to the lowest level of changes required. In this state only the animated artists will be updated. The background and axes of the figure are still valid and does not need to be updated.

  • atrophy_background_partial: The second stage of atrophy signals. In this state the background needs to updated but the axis are still good, which keeps the figure from having to redraw the matplotlib.Axes objects. This signal is used when the current Axes is still valid but needs to be altered, such as turning on the grid, changing limits, changing the scale, or enabling dark mode.

  • atrophy_background_full: The third stage of atrophy signals, this marks everything in the figure as out of date. All aspects of the Axes object are redrawn. This is used when new data is added to a pane, or a new pane itself is added to the figure, or other similarly large scale changes.

If any of the atrophy signals are emitted, the refresh loop in the Eye catches it and sets up the figure to be refreshed. Before that, though, the refresh flags are reset. If this did not happen until after the figure refresh, the Eye could miss later atrophy emits that occur while the figure is refreshing.

Updating the figure has two steps. First the panes each have to apply any changes they’ve recorded to their axes. The “correct” value of an axis property, such as its limit or scale, is held by the Pane instance the axis is member of. Any changes made by the user or from other parts of the Eye do not interact with the axis itself. Instead the changes are made to the properties of the Pane instance. This limits the access to the axes to the Pane, preventing any other commands to overwrite the axes, as well as abstracting away the details of how to apply a parameter to the matplotlib.Axes object in favor of the easier to use Pane object. The cost of this protection and abstraction is the additional step of applying the parameters of the pane to the Axes object. This is the first step of the refresh process.

Once all the axes object have been updated with the correct configuration, the second step is to use the BlitManager class to actually implement the changes. If either the “atrophy_background_full” or “atrophy_background_partial” flags were triggered, then the blitter is instructed to update everything. Otherwise, only the animated artists are updated.