Artists and Blitting¶
Background¶
To generate plots in an efficient manner the Eye employs an animation technique called blitting. Usually when a matplotlib figure is refreshed, like when a new like is plotted for example, the entire figure is redrawn. Axis limits are recomputed, all data points are redrawn, labels generated again, etc. Most of the time, however, few of these objects actually change, meaning a lot of resources were effectively wasted. Blitting addresses that by only drawing what changes. It requires the code to handle the objects created by plotting with matplotlib more directly, since a lot of the automatic features are turned off.
Updating a plot using blitting consists of two steps. Step one is to refresh
the background. This includes the splines, axis labels, things that do not
change. Nothing is regenerated. A copy of the background is stored in the
Blitter
class and is merely re-applied. Step two is draw all the animated
artists. These include all data lines, cursor indicators, pane borders, etc.
Basically anything that ever changes. The artist objects behind each artist,
such as the Line2D
object behind a plotted line, is not regenerated. If the
line has changed, such as the data points changed due to a units change, then
the data contained in the Line2D
object is altered. A new Line2D
object
is not created. These “animated” artists are merely redrawn, the bare minimum
that needs to be done to add the artist to the screen.
Matplotlib Artists¶
The bulk of the work in implementing blitting comes from handling the artists. They need to be kept track of, and altered when necessary. Matplotlib does not make this easy as the different plot types seemingly have divergent APIs. The main artists contained in the Eye are:
Line2D: These objects are generated when any data is plotted with the
plot
method. They have the most basic API calls. Updating the x-data, for instance, is done with:line.set_xdata(x_data)PathCollection: A PathCollection is a combination of list of points that describe how to draw a shape (the “Path”) and a list of places to draw that shape (the “Collection”). For the Eye, these come up for any kind of scatte plot, most notably the cursor location (a scatter plot with only one point). When updating the artist to change the data points, the “Path” stays the same and the “Collection” is updated. Since the data underlying a PathCollection is not a series of points that have to be connected in a particular order but instead a set of x,y coordinates, there are no methods like
set_xdata
. Instead the coordinates, or “offsets” as matplotlib likes to call them, have to be all changed at once with:data = numpy.vstack((x_data, y_data)) line.set_offsets(data)PolyCollection: A PolyCollection describes areas to be drawn, as opposed to the points drawn by Line2D and PathCollections. The area is defined bo a set of vertices that are connected to create a polygon. In the Eye, this artist is used to define the shaded region above and below a line to denote the error range and is generated by
fill_between
. Unlike a PathCollection or a Line2D object, the PolyCollection does not have a simple method for updating its data. The vertices would be the property to update, and there are no simple API calls to update the vertices. As such, when a PollyCollection needs to be updated, it is simpler to remake the artist.Patch: A Patch is generally a geometric shape used to highlight or block an area of the figure. Currently the only use for patches in the Eye are the highlights around the current pane. The highlight ia a Rectangle patch whose bounds are set to the very edges of the axis. Since its shape is defined by the axis itself, there is no need to reshape it. The only alteration that must be done to the highlight is to turn it on or off, which is achieved through the
set_visible
method:patch.set_visible(True)The artist handling and how they are draw in the Eye are handled by three classes discussed below:
Drawing
,Gallery
, and theBlitManager
class.
Drawing¶
The Drawing
class, found in
sofia_redux.visualization.display.drawing
, is the base artist object
in the Eye. Each artist object created by matplotlib gets its own Drawing
object. The Drawing
class also keeps track of various parameters that
describe what the artist is, such as the model name, the kind of artist it
is, what axes it belongs to, and what pane it belongs to. This makes it
easier for the Eye to filter the artists, such as finding all artists that
were made with a certain model. Additionally the Drawing
class handles
all updates to the artist, and provides shortcuts for accessing various
attributes of the artist such as color and marker.
Drawing
objects are only made by panes. When a new Drawing
object is
created it parses its artist object to determine what kind of artist it is.
Available kinds are:
line: Describes an artist created by plotting a dataset
error: The error range around a data line
cursor: The marker plotted to denote the cursor’s location
border: A pane’s border that denotes it is the current pane
crosshair: A horizontal and vertical line used when performing a box zoom
guide: A vertical line used when selecting a data range to zoom in on or perform a fit on
fit_line: The line of a model fit overplotted on the data
fit_center: A vertical line at the center of a model fit
reference: A vertical line at the wavelength of a reference transistion
text: A label attached to the reference line denoting the atomic or molecular species responsible for the transition
patch: Any shapes that might end up being drawn
Line¶
The line drawings holds all artists that describe the data itself. The root matplotlib artists is a Line2D object. To simulate a scatter plot the Line2D object applies markers to the data points and makes the line segments invisible. This keeps the base matplotlib artist class the same regardless of the plot type selected by the user, as the scatter plot method implemented in matplotlib generates a PathCollection object, not a Line2D object.
Error Range¶
The error range drawings holds PolyCollection artists, generated by
fill_between
. When the data in a line or cursor subtype changes, the
corresponding artist not re-created, merely updated with the correct data
points. This is not the case for the error range subtypes. Due to the
complexities of how a PolyCollection artist is defined, it is more efficient
to recreate the PolyCollection artist with the new data and replace the
artist stored in the error range subtype.
Cursor¶
The cursor drawings only holds PathCollection artists as it shows information about the data at the cursor. All the artists in this subtype only have a single data point attached. The data point has the same x-value as the cursor and the y-value corresponding to the data value at the x-value. The cursor’s y-value is not considered. Each artist in the line subtype has a corresponding artist in the cursor subtype. The cursor artists are generated when the line artist is generated regardless of if the cursor’s location is to be shown. If the cursor’s location is not to be shown, the cursor artist is kept invisible and not updated. It is more efficient to keep an invisible artist in memory than to recreate the artist every time it is needed.
Border¶
The border drawings hold Rectangle objects. Each Pane has one associated border drawing that describes the border around it. The border is only visible when the associated pane is currently selected. The only updates to border drawings are visibility when the current pane changes and color when the color cycle changes.
Crosshair¶
The crosshair kind is a unique drawing, as there are only exactly two instances. The crosshair plots a dashed vertical line and a dashed horizontal line across the span of the current axis, crossing at the cursor’s location. The underlying matplotlib artists are Line2D objects.
Guide¶
The guide drawings are similar to the crosshair drawings except there can be multiple of them. Guides are used to help denote regions of interest for actions such as zooming or fitting curves to the data. The guides take the form of either vertical or horizontal dotted lines, depending of if the task requires selecting x values or y values respectively. The artist is created when the user clicks on a location while a selection mode is active. The location of the mouse is recorded and used to create the guide artist. Once the range selection ends the guides are removed. Guide artists are created and destroyed at a higher rate than any other artist. Their ephemeral status means guide artists do not require updating.
Fit Line¶
The fit line drawings are created when a model, such as a Gaussian, is fit to a selection of data. The resulting fit is evaluated at all wavelength points then plotted over the data. The fit lines can be turned on and off through the fitting results window. If a fit fails then a fit line drawing is not created.
Fit Center¶
When a fit line drawing is created an associated fit center drawing is created. The fit center drawing is a vertical line centered at the peak of the model feature, ie the mean of the Gaussian. If the model used to fit the data doesn’t have a feature, such as a linear fit, then the fit center is placed at the midpoint of the wavelength range.
Reference¶
Reference drawings are created when reference data is loaded into the Eye. Reference data are usually atmospheric absorption lines and are discussed in more detail in NumPy reference . The associated artists are vertical, grey, dashed lines at each reference wavelength. They can be turned on and off in the Reference Data window. These drawings are unique in that they are not considered when autoscaling the data. As such all lines that lie outside the data range are not visible until the x-range is extended. To handle this, when autoscaling the data all reference lines are initially made invisible. Then the axis is autoscaled based only on visible artists, after which the reference lines are made visible again.
Text¶
Text drawings handle all artists that are blocks of text on a plot. They accompany reference drawings and serve to label what molecular or atomic species is responsible for a given reference transition. Their visibility can be controlled independantly of the reference line, however the labels cannot be made visible with the reference lines invisbile.
Labels are place to the left of their reference line, rotated 90 degrees, at the bottom of the axes. If two reference lines are close together in wavelength space the labels could overlap rendering them illegible. To counter this any time the x-axis limits are changed the text boxes are scraped and regenerated fresh. Upon creation their initial bounding boxes are compared and if two boxes overlap (meaning the labels would overlap) then they are combined. The right most label is deleted and its text appended to the end of the left most label. This process is repeated until no more bounding boxes are overlapping.
Patch¶
The patch subtype is used for artists describing basic shapes, such as rectangles or circles, and are independent from any data being plotted. The most common patch used is the border around the current pane, or the “pane highlight”. These artists are created when a Pane is assigned an axis and are not changed. The only change to the patches are whether they are visible based on if their pane is the current pane.
Gallery¶
The primary purpose of the Gallery
class (found at
sofia_redux.visualization.display.gallery
) is to manage all
Drawing
objects created by the Eye. There is only one instance of the
Gallery
class, kept track of by the View
class. Gallery
only has
one attribute, a dictionary that stores the various Drawings
created by
panes. The dictionary keys are the different kinds of drawings:
line
line_alt
cursor
cursor_alt
error_range
crosshair
guide
patch
fit
text
ref_line
ref_label
Line and cursor drawings are split based on which axis they are on for easy access. Other drawings like error range are not split based on their axes because they only exist on the primary axes. The values of the dictionary are lists. New drawings are appended to the appropriate list.
In addition to keeping track of all drawings, the Gallery
also handles
drawing updates. Whenever a pane is altered, such as a change in color cycle,
axis scale changed, markers changed, the drawings need to be updated to
reflect the change. Updates are processed by generating a new Drawing
object with the same identifiers as the target Drawing
but with the
updates
attribute populated with a dictionary describing the updates. For
example, to update the color to blue, the updates dictionary would look like
updates = {'color': 'blue'}
The updates are then passed to Gallery
which compares the update
Drawing
to all Drawing
objects of the same kind looking for a match.
When it finds the match the original Drawing
object is given the updates
dictionary and the Drawing
class applies the update to the actual
matplotlib artist. In order for two Drawing
objects to be considered a
match they must have the same:
kind
High model (the model’s UUID)
Mid model (order/aperture number)
Data ID (used to identify reference lines and labels)
Pane
Fields (such as “wavepos”, “spectral_flux”)
Axes (such as “primary”, “alt”)
The Gallery
class also handles things like clearing drawings (such as
when a datafile is removed from a plot), determining when reference labels
are overlapping and need to be condensed, determining which drawings are
involved in a mouse event, and updating artist visibilities.
Blitting¶
Blitting is the method used to draw all relevant artists in the Gallery
class and is managed by the BlitManager
class. Its main task is to draw
the plot backgrounds and artists, rendering them to screen. It keeps track of
a copy of the background of the canvas.
The background is defined as any artist in the plot that does not represent
data. When data are plotted in the Pane
class, the artists are all
created with their animated
flag set to True
, which exempts them from
being drawn anytime the standard draw
function is called. Instead, they
will only be drawn when the specifically called with the draw_artist
function.
If the artists are updated but the background is not, then the background is restored and the animated artists are drawn upon it. If the background has changed, then the background is cleared and redrawn before the animated artists are drawn. By keeping the background unchanged whenever possible, the processing load of refreshing the plots is greatly reduced, improving performance.