"
This article is part of in the series

How To Create Python Interactive Plots with Matplotlib

Static plots can tell a story, but interactive plots allow users to explore the story of the represented data on their own. Not only does this make the data more interesting to investigate, but it also makes drawing insights from the data easier. 

The Python community is rich with tools that make creating interactive plots easy. In this brief guide, we will walk you through creating interactive plots with matplotlib. 

How To Create Python Interactive Plots with Matplotlib

There are dozens of development environments and software tools that can help you visualize data. But the two mainstream solutions are Matplotlib and Ipywidgets. 

If you've used Matplotlib before, you might think it can only create static plots. But the library can also enable basic interactivity, including but not limited to panning and zooming. 

Don't forget, Matplotlib is a subset of the Seaborn library, so these interactivity features are also available in the parent library.

Here's more good news:

We can use the Jupyter Widgets library (Ipywidgets) to make a plot more interactive. More specifically, the Ipywidgets library boasts a set of user interface widgets that work flawlessly in Jupyter Notebook and Jupyter Lab.

The library will allow you to pick the features you want to display, change the displayed data, and more.

This is not to say there aren't other libraries you could choose. For example, Plotly and Vega-Altair are two other excellent data visualization libraries you can use to make an interactive plot.

Step #1: Installing the Libraries

Here's a requirements.txt file you can use to install all the libraries necessary to create an interactive plot:

# pip install -r requirements.txt
numpy==1.23.2
matplotlib==3.5.3
jupyterlab==3.4.6
ipywidgets==8.0.2
ipympl==0.9.2

You're not through yet – you must enable the Jupyter Lab extension before you can use the Ipywidgets library. 

To do this, you can run the following command:

jupyter labextension install @jupyter-widgets/jupyterlab-manager

You can now launch Jupyter with this command:

jupyter lab

Step #2: Building the Plot

We must first define a plot with two vectors in the "standard position" – meaning the vector tail must begin at the origin (0,0) of the Cartesian X and Y plane. 

This must be done so we can recognize the X and Y coordinates of two-element vectors like vector_1 = [6,7].

Now, let's begin building the plot by enabling some Matplotlib features and declaring two vectors as lists:

%matplotlib widget

import numpy as np
from math import acos, pi
import matplotlib.pyplot as plt    

vector_1 = [3,4]
vector_2 = [1,3]

You are now ready to draw the plot and also the declared vectors. Here's how it's done:

%matplotlib widget

import numpy as np
from math import acos, pi
import matplotlib.pyplot as plt
  
vector_1 = [3,4]
vector_2 = [1,3]

def get_label(which_vector, vector):
    """Labels the vector with its name and coordinates """
    return f"{which_vector} {vector}"

def plot_line(vector, label):    
    """Plots the vector in standard position"""
    line = plt.plot([0,vector[0]], [0, vector[1]], label=label)[0]
  
    # The following code draws the vector as an arrow 
    line.axes.annotate('',
        xytext=(0, 0),
        xy=(vector[0], vector[1]),       
        arrowprops=dict(color=line.get_color(), arrowstyle="simple"),
        size=10
    )

def configure_graph_grid():
    """Setting up a square grid axis"""   

    # Size
    grid_min = -6
    grid_max = 6    

    # Defining a square and its limits
    plt.axis('square')
    plt.axis(( grid_min, grid_max, grid_min, grid_max))
   
    # Plotting x and y axes
    plt.plot([grid_min, grid_max], [0,0], 'g')
    plt.plot([0,0],[grid_min, grid_max], 'g')
    plt.grid()    

# Displaying the plot and its vectors
figure = plt.figure()
configure_graph_grid()
line_1 = plot_line(vector_1, get_label("Vector 1", vector_1))
line_2 = plot_line(vector_2, get_label("Vector 2", vector_2))
plt.legend()
plt.title(get_title(vector_1, vector_2))
plt.show()

The get_label function simply concatenates the vector and its name. Then, the program plots the vector in the standard position in line 14 of the program.

Then, the Matplotlib function applies an optional marker to the end of the line. However, it doesn't support arrows in an arbitrary direction.

So, we instead use the "annotate" method on line.axes to create an arrowhead for the vector. Note how we are passing line.get_color() to the arrowprops argument. By doing this, we ensure that the color of the label and that of the line are the same. 

The configure_graph_grid method begins on line 28 and plots the grid and the axes. This part of the program has a plt.axis('square') call. Without this call, the plot would be rendered as a wide rectangle, making it look like the axes don't have the same scale.

Starting from line 44, the program calls the functions we defined to plot the grid and axes and show the legend. The plt.show() function actually displays the graph.

Run the code, and you will see a plot to pan and zoom on. You can also resize it with the triangle and the bottom and save the plot as an image. 

These features appear because we included "%matplotlib widget" in the code. It enables the ipympl package that allows you to access interactive features in Jupyter.

Step #3: Setting a Title

To set a title for your plot, you can pass the title to the matplotlib.title function as a parameter. 

Let's go the extra mile and display the angle between the vectors and their dot product. You can add the following code to the vectors_to_dataframe function:

def vector_angle(vector1, vector2):
    """Calculates the angle between the two vectors"""
    norm1 = np.linalg.norm(vector1)
    norm2 = np.linalg.norm(vector2)
    
    # Prevents the divide by zero error
    if norm1 == 0 or norm2 == 0:
        return 0   

    # Angle between vectors formula
    cos_of_angle = np.dot(vector1, vector2) / (norm1 * norm2)    

    # We must ensure the cosine is in range -1 <= cos(angle) <= 1 
    if cos_of_angle > 1:
        cos_of_angle = 1
    elif cos_of_angle < -1:
        cos_of_angle = -1    

    radians = acos(cos_of_angle)
    return round(radians * 180/pi, 1)

You must also add the following code above the plt.show() command:

plt.title(get_title(vector_1, vector_2))

Run the code, and you will see that the title shows information about the vectors.

Step #4: Adding User Controls

One of the best things about the Ipywidgets library is that it boasts several interactive controls. With it, you can add dropdowns, text fields, and sliders, among other things. You can find the complete list of widgets that this library offers here.

If you have some experience with UI programming, you might already be familiar with the steps that we now need to take:

  1. Display the UI elements on the screen, which is the Jupyter Notebook.
  2. Add at least one event handler to update the state of the added elements.
  3. Redraw the UI elements according to the updated state of the UI. We need to redraw the plot in the example we have discussed so far.

Now that the objectives are clear, here is the code:

from IPython.display import display
from ipywidgets import interactive
import ipywidgets as widgets

def do_update(v1, v2):
    figure.clf()
    configure_graph_grid()
    line_1 = plot_line(v1, get_label("Vector 1", v1))
    line_2 = plot_line(v2, get_label("Vector 2", v2))
    plt.legend()
    plt.title(get_title(v1, v2))
    plt.draw()
    plt.legend()    

def handle_event(v1_x, v1_y, v2_x, v2_y):
    # Here, we are repacking the coordinates into two vectors and processing the change with do_update.
    vector1 = [v1_x, v1_y]
    vector2 = [v2_x, v2_y]

    do_update(vector1, vector2)    
    
def slider_config(value):
    return widgets.IntSlider(min=-6, max=6, step=1, value=value)

slider_controls = interactive(handle_event, 
                 v1_x=slider_config(vector_1[0]), 
                 v1_y=slider_config(vector_1[1]),
                 v2_x=slider_config(vector_2[0]), 
                 v2_y=slider_config(vector_2[1]))
display(slider_controls)

Though the sliders we have defined aren't fancy, they get the job done. 

Conclusion

In conclusion, creating interactive plots with Matplotlib can significantly enhance the understanding of data visualizations by enabling users to explore data stories independently. 

While Matplotlib is often associated with static plots, the library can be used to enable basic interactivity, such as panning and zooming. 

The Jupyter Widgets library can also be used to create more advanced interactive plots with Matplotlib. Other excellent data visualization libraries that can be used to make an interactive plot include Plotly and Vega-Altair. 

Following this guide's steps, you can create interactive plots that provide a more engaging and informative user experience.