# Writing a custom viewer for glue with Qt and Matplotlib¶

If you are a user trying to build a very simple viewer using Matplotlib, you may want to check out Writing a simple custom data viewer – the present tutorial is intended for people who wish to write and distribute a viewer using Matplotlib with full control over layout and behavior. This tutorial assumes that you have already gone over the Writing a custom viewer for glue and Writing a custom viewer for glue with Qt tutorials.

Glue provides a set of base classes for the state classes, layer artist, and data viewer which already take care of a number of aspects common to all Matplotlib-based viewers. We describe each of these in turn in the following sections, then simplify the example from Writing a custom viewer for glue with Qt using this infrastructure.

## State classes¶

The MatplotlibDataViewerState class provides a subclass of ViewerState which adds properties related to:

• the appearance of the plot (font and tick sizes)
• the limits of the current view (this currently assumes 2D plots)
• the aspect ratio of the axes
• whether the axes are log or linear

Note that it does not add e.g. x_att and y_att since not all Matplotlib- based viewers will require the same number of attributes, and since some viewers may define attributes that aren’t specific to the x or y axis (e.g. in the case of networks).

The MatplotlibLayerState class provides a subclass of LayerState which adds the color and alpha property (and keeps them in sync with layer.style.color and layer.style.alpha).

## Layer artist¶

The MatplotlibLayerArtist class implements the redraw(), remove(), and clear() methods assuming that all the contents of the layer use Matplotlib artists. In the __init__ of your MatplotlibLayerArtist sub-class, you should make sure you add all artist references to the mpl_artists property for this to work.

## Data viewer¶

The MatplotlibDataViewer class adds functionality on top of the base DataViewer class:

• It automatically sets up the Matplotlib axes
• It keeps the x/y limits of the plot, the scale (linear/log), the font/tick parameters, and the aspect ratio in sync with the MatplotlibDataViewerState
• It adds tools for saving, zooming, panning, and resetting the view
• It recognizes the global glue preferences for foreground/background color

## Functional example¶

Let’s now take the take full example from Writing a custom viewer for glue with Qt and update/improve it to use the infrastructure described above. As before if you want to try this out, you can copy the code below into a file called config.py in the directory from where you are starting glue. In addition you will also need the viewer_state.ui file.

import os

import numpy as np

from qtpy.QtWidgets import QWidget, QVBoxLayout, QCheckBox

from glue.config import qt_client
from glue.core.data_combo_helper import ComponentIDComboHelper

from glue.external.echo import CallbackProperty, SelectionCallbackProperty
from glue.external.echo.qt import (connect_checkable_button,
autoconnect_callbacks_to_qt)

from glue.viewers.matplotlib.layer_artist import MatplotlibLayerArtist
from glue.viewers.matplotlib.state import MatplotlibDataViewerState, MatplotlibLayerState
from glue.viewers.matplotlib.qt.data_viewer import MatplotlibDataViewer

class TutorialViewerState(MatplotlibDataViewerState):

x_att = SelectionCallbackProperty(docstring='The attribute to use on the x-axis')
y_att = SelectionCallbackProperty(docstring='The attribute to use on the y-axis')

def __init__(self, *args, **kwargs):
super(TutorialViewerState, self).__init__(*args, **kwargs)
self._x_att_helper = ComponentIDComboHelper(self, 'x_att')
self._y_att_helper = ComponentIDComboHelper(self, 'y_att')

def _on_layers_change(self, value):
self._x_att_helper.set_multiple_data(self.layers_data)
self._y_att_helper.set_multiple_data(self.layers_data)

def _on_attribute_change(self, value):
if self.x_att is not None:
self.x_axislabel = self.x_att.label
if self.y_att is not None:
self.y_axislabel = self.y_att.label

class TutorialLayerState(MatplotlibLayerState):
fill = CallbackProperty(False, docstring='Whether to show the markers as filled or not')

class TutorialLayerArtist(MatplotlibLayerArtist):

_layer_state_cls = TutorialLayerState

def __init__(self, axes, *args, **kwargs):

super(TutorialLayerArtist, self).__init__(axes, *args, **kwargs)

self.artist = self.axes.plot([], [], 'o', mec='none')[0]
self.mpl_artists.append(self.artist)

def _on_visual_change(self, value=None):

self.artist.set_visible(self.state.visible)
self.artist.set_zorder(self.state.zorder)
self.artist.set_markeredgecolor(self.state.color)
if self.state.fill:
self.artist.set_markerfacecolor(self.state.color)
else:
self.artist.set_markerfacecolor('white')
self.artist.set_alpha(self.state.alpha)

self.redraw()

def _on_attribute_change(self, value=None):

if self._viewer_state.x_att is None or self._viewer_state.y_att is None:
return

x = self.state.layer[self._viewer_state.x_att]
y = self.state.layer[self._viewer_state.y_att]

self.artist.set_data(x, y)

self.axes.set_xlim(np.nanmin(x), np.nanmax(x))
self.axes.set_ylim(np.nanmin(y), np.nanmax(y))

self.redraw()

def update(self):
self._on_attribute_change()
self._on_visual_change()

class TutorialViewerStateWidget(QWidget):

def __init__(self, viewer_state=None, session=None):

super(TutorialViewerStateWidget, self).__init__()

directory=os.path.dirname(__file__))

self.viewer_state = viewer_state
autoconnect_callbacks_to_qt(self.viewer_state, self.ui)

class TutorialLayerStateWidget(QWidget):

def __init__(self, layer_artist):

super(TutorialLayerStateWidget, self).__init__()

self.checkbox = QCheckBox('Fill markers')
layout = QVBoxLayout()
self.setLayout(layout)

self.layer_state = layer_artist.state
connect_checkable_button(self.layer_state, 'fill', self.checkbox)

class TutorialDataViewer(MatplotlibDataViewer):

LABEL = 'Tutorial viewer'
_state_cls = TutorialViewerState
_options_cls = TutorialViewerStateWidget
_layer_style_widget_cls = TutorialLayerStateWidget
_data_artist_cls = TutorialLayerArtist
_subset_artist_cls = TutorialLayerArtist


In addition, the layer artist has been improved to take into account the color and transparency given by the layer state (via the _on_visual_change method), and the axis labels are now set in the viewer state class.