Interactive plotting basics in matplotlib

The main goals of this post are:

  • Provide bullet points about  Matplotlib’s architecture and provide documentation for more in depth  exploration.
  • Build two examples plotting multidimensional data with basic interactive capabilities.

1. Matplotlib architecture bullets

In order to enable Matplotlib’s interactive capabilities, it doesn’t hurt to understand how it is structured.  The current matplotlib architecture is comprised of three layers:  the scripting layer, the artist layer and backend layer, which interact in the following way:

  • The data is either created or loaded in the scripting layer, this layer basically supports the programmatic interaction and provides users with the ability to manipulate figures with a syntax that is somewhat intuitive.
  • The data is transformed into various objects in the artist layer; it is adjusted as scripted.  This layer is responsible for the abstraction of each visual component that you see in a figure.
  • These objects are then rendered by the backend. This last layer enables the users to create, render, and update the figure objects. Figures can be displayed and interacted with via common user interface events such as the keyboard and mouse inputs.

Matplotlib has extensive documentation; however, the best way to learn about backends is exploring the sourcecode.  Some of the documents that I also found helpful, aside from the wonderful stackoverflow, are:

Raman, Kirthi. Mastering Python Data Visualization. Packt Publishing Limited, 2015.

Root, Benjamin V. “Interactive Applications Using Matplotlib.” (2015).

McGreggor, Duncan M. “Mastering matplotlib.” (2015).

2. Example: Fun click

Here’s a simple example that connects a 5D scatter plot to a pick and a mouse click event.  The goal is to show information about the point being clicked.  In this case, the row index of the data matrix and the corresponding values are shown in the python terminal as shown in the following figure:Slide1.PNGThis could be useful when plotting a multidimensional Pareto Front.  If you see a solution of interest in your scatter plot,  you can directly access its exact values and its index by a simple mouse click.  This index may be useful to track the corresponding decision vector in your decision matrix.  Also, note that even if I am only plotting a 5D scatter plot, I used a 6 D data matrix, hence, I can see what the sixth value is on the terminal.  The following code was used to generate the previous figure. Parts 2.1.  through 2.5 were adapted from the Visualization strategies for multidimensional data post, refer to this link for a detailed explanation of these fragments.

2.1. Required Libraries

import pylab as plt
mpl_toolkits.mplot3d.axes3d as p3
import numpy as np
import seaborn # not required

 

2.2. Loading or generating data

#data= np.loadtxt('your_data.txt’)
data= X =np.random.random((50,6)) #  setting a random matrix

 

2.3. Accessing object oriented Matplotlib’s objective oriented plotting

fig, ax = plt.subplots(1, 1)
ax = fig.add_subplot(111, projection = '3d')
im= ax.scatter(data[:,0], data[:,1],data[:,2], c=data[:,3], s= data[:,4]*scale, alpha=1, cmap=plt.cm.spectral, picker=True)

 

 2.4. Setting the  main axis labels

ax.set_xlabel('OBJECTIVE 1')
ax.set_ylabel('OBJECTIVE 2')
ax.set_zlabel('OBJECTIVE 3')

 

2.5. Setting colorbar and its label vertically

cbar= fig.colorbar(im)
cbar.ax.set_ylabel('OBJECTIVE 4')

 

2.6. Setting size legend

objs=data[:,4]
max_size=np.amax(objs)*scale
min_size=np.amin(objs)*scale
handles, labels = ax.get_legend_handles_labels()
display = (0,1,2)
size_max = plt.Line2D((0,1),(0,0), color='k', markersize=max_size,linestyle='')
size_min = plt.Line2D((0,1),(0,0), color='k', markersize=min_size,linestyle='')
legend1= ax.legend([handle for i,handle in enumerate(handles) if i in display]+[size_max,size_min],
[label for i,label in enumerate(labels) if i in display]+["%.2f"%(np.amax(objs)), "%.2f"%(np.amin(objs))], labelspacing=1.5, title='OBJECTIVE 5', loc=1, frameon=True, numpoints=1, markerscale=1)

 

2.7. Setting the picker function

This part defines and connects the events.  The  mpl_connect() method connects the event to the figure.  It accepts two arguments,  the name of the event and the callable object (such as a function).

def onpick(event):ind = event.ind
print ('index: %d\nobjective 1: %0.2f\nobjective 2: %0.2f\nobjective 3: %0.2f\nobjective 4: %0.2f\nobjective 5: %0.2f\nobjective 6: %0.2f' % (event.ind[0],data[ind,0],data[ind,1],data[ind,2],data[ind,3],data[ind,4],data[ind,5]))
fig.canvas.mpl_connect('pick_event', onpick)
plt.show

 

For a list of events that Matplotlib supports, please refer to: Matplotlib event options.  To download the previous code go to the following link:  https://github.com/JazminZatarain/Basic-interactive-plotting/blob/master/fun_click.py

3. Example: Fun Annotation

Transitioning to a slightly more sophisticated interaction, we use annotations to link the event to a figure.  That is, the information is shown directly in the figure as opposed to the terminal as in the previous example.   In the following snippet,  the row index of the data matrix  is shown directly in the figure canvas by simply clicking on the point of interest.

Slide1.PNG

The following code was adapted from Matplotlib’s  documentation and this stackoverflow post to execute the previous figure.  Sections 3.1. and 3.2. define our scatter plot with its corresponding labels as we saw in the previous example.

3.1. Required Libraries

import matplotlib.pyplot as plt, numpy as np
from mpl_toolkits.mplot3d import proj3d

 

3.2. Visualizing data in 3d plot with popover next to mouse position

def visualize3DData (X,scale,cmap):
fig = plt.figure(figsize = (16,10))
ax = fig.add_subplot(111, projection = '3d')
im= ax.scatter(X[:, 0], X[:, 1], X[:, 2], c= X[:, 3], s= X[:, 4]*scale, cmap=cmap,     alpha=1, picker = True)
ax.set_xlabel('OBJECTIVE 1')
ax.set_ylabel('OBJECTIVE 2')
ax.set_zlabel('OBJECTIVE 3')
cbar= fig.colorbar(im)
cbar.ax.set_ylabel('OBJECTIVE 4')
objs=X[:,4]
max_size=np.amax(objs)*scale
min_size=np.amin(objs)*scale
handles, labels = ax.get_legend_handles_labels()
display = (0,1,2)
size_max = plt.Line2D((0,1),(0,0), color='k', marker='o', markersize=max_size,linestyle='')
size_min = plt.Line2D((0,1),(0,0), color='k', marker='o', markersize=min_size,linestyle='')
legend1= ax.legend([handle for i,handle in enumerate(handles) if i in display]+[size_max,size_min],
[label for i,label in enumerate(labels) if i in display]+["%.2f"%(np.amax(objs)), "%.2f"%(np.amin(objs))], labelspacing=1.5, title='OBJECTIVE 5', loc=1, frameon=True, numpoints=1, markerscale=1)

 

3.3. Return distance between mouse position and given data point

def distance(point, event):
assert point.shape == (3,), "distance: point.shape is wrong: %s, must be (3,)" % point.shape

 

3.4.  Project 3d data space to 2d data space

 
x2, y2, _ = proj3d.proj_transform(point[0], point[1], point[2], plt.gca().get_proj())
# Convert 2d data space to 2d screen space
x3, y3 = ax.transData.transform((x2, y2))
return np.sqrt ((x3 - event.x)**2 + (y3 - event.y)**2)

 

3.5. Calculate which data point is closest to the mouse position

def calcClosestDatapoint(X, event):
distances = [distance (X[i, 0:3], event) for i in range(X.shape[0])]
return np.argmin(distances)
def annotatePlot(X, index):
# If we have previously displayed another label, remove it first
if hasattr(annotatePlot, 'label'):
annotatePlot.label.remove()
Get data point from array of points X, at position index:
x2, y2,_ = proj3d.proj_transform(X[index, 0], X[index, 1], X[index, 2], ax.get_proj())

 

3.6. Specify the information to be plotted in the annotation label

annotatePlot.label = plt.annotate("index: %d" % index,
xy = (x2, y2), xytext = (-20,20), textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
fig.canvas.draw()

 

3.7.  Defining the event

This last part defines the event that is triggered when the mouse is moved. It also shows the text annotation over the data point closest to the mouse.

def onMouseMotion(event):
closestIndex = calcClosestDatapoint(X, event)
annotatePlot (X, closestIndex)
fig.canvas.mpl_connect('motion_notify_event', onMouseMotion)  # on mouse motion
plt.show()

 

3.8. You can forget the previous code and simply insert your data here

if __name__ == '__main__':
import seaborn
#X=np.loadtxt('your_data.txt’) # your data goes here
X = np.random.random((50,6)) #this is the randomly generated data for this example
scale=1000 # scale of the size objective
cmap=plt.cm.spectral # choose your colormap
visualize3DData (X,scale,cmap)

 

To download the previous code go to the following link: https://github.com/JazminZatarain/Basic-interactive-plotting/blob/master/annotacious.py

Advertisements

2 thoughts on “Interactive plotting basics in matplotlib

  1. Hello, I have looked at your example, and it is great. I have got it working, but don’t understand it entirely. I was wondering do you know how to adapt this for the use of a 2d plot.

    When i am doing just a 2d plot, i get the following error.

    x2, y2, _ = proj3d.proj_transform(point[0], point[1], point[2], plt.gca().get_proj())
    AttributeError: ‘AxesSubplot’ object has no attribute ‘get_proj’

    I know this is probably obvious, but I am not sure how to write this.

    thanks

  2. Pingback: Water Programming Blog Guide (Part I) – Water Programming: A Collaborative Research Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s