This article explains how to insert a cursor to your plot, how to customize it and how to store the values that you selected on the plot window. In lots of situations we may want to select and store the coordinates of specific points in our graph; is it just for assessing their value or because we may want to use some specific values for successive processing of the data. As you will see, this is not a difficult task, but it will add a lot of value to your plots. We will also see how to pop in a small frame containing the coordinates of the selected point, every time we click on it.
Here’s our end-goal—an interactive plot that annotates the point you click on:
And here’s the code that we’ll discuss in this article that leads to this output:
import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Cursor #x and y arrays for definining an initial function x = np.linspace(0, 10, 100) y = np.exp(x**0.5) * np.sin(5*x) # Plotting fig = plt.figure() ax = fig.subplots() ax.plot(x,y, color = 'b') ax.grid() # Defining the cursor cursor = Cursor(ax, horizOn=True, vertOn=True, useblit=True, color = 'r', linewidth = 1) # Creating an annotating box annot = ax.annotate("", xy=(0,0), xytext=(-40,40),textcoords="offset points", bbox=dict(boxstyle='round4', fc='linen',ec='k',lw=1), arrowprops=dict(arrowstyle='-|>')) annot.set_visible(False) # Function for storing and showing the clicked values coord = [] def onclick(event): global coord coord.append((event.xdata, event.ydata)) x = event.xdata y = event.ydata # printing the values of the selected point print([x,y]) annot.xy = (x,y) text = "({:.2g}, {:.2g})".format(x,y) annot.set_text(text) annot.set_visible(True) fig.canvas.draw() #redraw the figure fig.canvas.mpl_connect('button_press_event', onclick) plt.show() # Unzipping the coord list in two different arrays x1, y1 = zip(*coord) print(x1, y1)
Importing Libraries
As to begin, we import the libraries and the packages that will be used in this example. We will use NumPy for defining an initial function that will be then displayed using matplotlib.pyplot
. Finally, from the matplotlib.widget
package, we import the function Cursor, which will be used for the creation of the interactive cursor.
import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Cursor
Defining an Initial Function to Plot
In order to use our cursor on a real plot, we introduce an initial function by defining two NumPy arrays, โxโ and โyโ. The โxโ array is defined by exploiting the NumPy function .linspace()
, which will generate an array of 100 equally spaced numbers from 0 to 10. The โyโ array is defined by the following function:
Both the sin()
and the exponential function are introduced using NumPy. Of course, this is only one possible example, any function is good for the final goal of this article. All these procedures are described in the following code-lines.
#x and y arrays x = np.linspace(0, 10, 100) y = np.exp(x**0.5) * np.sin(5*x)
Plotting the function
In the next step we define the plotting window and plot our function. To this purpose, we entirely rely on the matplotlib.pyplot
package.
#Plotting fig = plt.figure() ax = fig.subplots() ax.plot(x,y, color = 'red') ax.grid()
Defining the Cursor
Cursor | ||
Syntax: | Cursor() | |
Parameters: | ax (variable) | Axes defining the space in which the button will be located |
horizOn (bool) | Drawing the horizontal line | |
vertOn (bool) | Drawing the vertical line | |
useblit (bool) | Use blitting for improving the performance | |
color (str or float) | The color of the lines | |
linewidth (float) | Width of the cursor lines | |
Return Value | None |
.Cursor()
function and all the input parameters used in the present example.To introduce a cursor in our plot, we first have to define all its properties; to do that, we exploit the function Cursor, from the matplotlib.widget
package.
The function takes as input the axes in which we want to display the cursor (โaxโ in this case) and other properties of the cursor itself; namely horizOn
and vertOn
, which generate an horizontal and a vertical line that univocally identify the cursor while it is hovering on the plot; their value can be set to True
or False
, depending on how we want to identify the cursor.
It is also possible to specify some properties of the line, like the color and the thickness (using linewidth
).
The last input parameter is useblit
, we set it to True
since it generally improves the performance of interactive figures by โnot re-doing work we do not have toโ (if you are interested in the process of Blitting, please visit: https://matplotlib.org/3.3.1/tutorials/advanced/blitting.html ).
All the input parameters of the function Cursor
are summarized in Table 1 and additional documentation can be found at: https://matplotlib.org/3.3.3/api/widgets_api.html.
All the properties defined within the function Cursor, are assigned to the variable โcursorโ.
#defining the cursor cursor = Cursor(ax, horizOn = True, vertOn=True, color='red', linewidth=1, useblit=True)
At this point, we completed the definition of our cursor, if we were to show the plot, we would get the result displayed in Figure 1.
In the next steps, we will see how to define the framework, containing the coordinates of the selected point, that will pop in at each mouse click. If you are not interested in this feature, you can skip to the next section in which we will see how to store and print the values selected by the cursor.
Creating the Annotating Frameworks
Annotate | ||
Syntax: | annotate() | |
Parameters: | text (str) | The text of the annotation |
xy (float, float) | The point to annotate | |
xytext (float, float) | The position to place the text at | |
textcoords | The coordinate system that xytext is given in | |
bbox | Instancing a frame | |
arrowprops | Instancing an arrow | |
Return Value | None |
Table 2: The .annotate()
function and all the input parameters used in the present example.
As anticipated in the introduction, we want to improve the graphical outcome and the efficiency of our cursor by popping in a small framework, containing the coordinates of the selected point, at each mouse click.
To this purpose, we exploit the matplotlib function .annotate()
, which provides lots of different features for customizing the annotations within a plot (additional documentation can be found here: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.axes.Axes.annotate.html).
The first input parameter of the .annotate()
function is the text that will appear in the annotation; we enter a blank string, since we will add the text later on (it will change at each mouse click).
We then specify the properties โxy
โ, โxytext
โ and โtextcoords
โ with which we define a reference point, the distance of the text from this point and how the distance gets calculated (in our case counting the numerical values in points, pixel is also available), respectively.
To better highlight the annotation in the plot, we also add an external framework, using bbox
and passing all the properties of the framework (like filling color, edgecolor and linewidth) as keys and values of a dictionary.
Finally, we generate an arrow, going from โxy
โ to โxytext
โ in a similar way (all the properties for the arrows can be found in the .
annotate
documentation). The annotation properties just defined are then assigned to the variable โannot
โ; by exploiting the method .set_visible()
, the visibility of the annotation framework is initially set to False
(it will appear only after the mouse click).
All the parameters used in the definition of the .annotate()
function are summarized in Table 2.
#Creating the annotation framework annot = ax.annotate("", xy=(0,0), xytext=(-40,40),textcoords="offset points", bbox=dict(boxstyle="round4", fc="grey", ec="k", lw=2), arrowprops=dict(arrowstyle="-|>")) annot.set_visible(False)
Storing and Displaying the Coordinates of the Selected Point
The cursor is now working but nothing still happens when we click on the plot. In this section, we define a function that will print and store the coordinates of the point clicked on the plot; it will also display the previously defined annotating box.
Storing the values outside the function
We define an empty list, called โcoordโ, in which will be stored the coordinates of all the clicked points.
After that, we start defining the new function, it will be called โonclickโ. The input of the function is set to event, in order to access to the indicator position on the plot.
Within the function, a global variable called โcoordโ is defined, this is done in order to store the values generated within the function and to have them available also in the โcoordโ variable outside the function. To store the coordinates of the selected point, we append the variables event.xdata
and event.ydata
, as a tuple, to the list coord; in this way, the values will be accessible even outside the function. For the sake of simplicity, we then assign them to two different local variables โxโ and โyโ.
Printing the coordinates values
At this point we can also print their value by just typing the print()
command.
Displaying the point coordinates in the annotating box
The next feature that we can add to the function is to display the annotating box, containing the โxโ and โyโ values. For this task, the โxโ and โyโ values are firstly used to define the position of the annotating box, changing the xy property of the โannotโ variable and then to define the variable โtextโ, a string that contains the annotation text. To change the text of the โannotโ variable, we use the method .set_text(), entering, as the only input parameter, the variable โtextโ.
We conclude by changing the visibility of the โannotโ function to True and by redrawing the figure. The following code lines display the entire function definition, following the same order used in the above description.
#Function for storing and showing the clicked values coord = [] def onclick(event): global coord coord.append((event.xdata, event.ydata)) x = event.xdata y = event.ydata print([x,y]) annot.xy = (x,y) text = "({:.2g},{:.2g})".format( x,y ) annot.set_text(text) annot.set_visible(True) fig.canvas.draw() #redraw the figure
In order to connect the clicking event with the execution of the โonclickโ function, we exploit the matplotlib method .mpl_connect(), linking it with the event โbutton_press_eventโ. We finally plot the figure. Figure 2 displays the ending result.
Accessing the Stored Values Outside the Function
Since the coordinates of the selected points have all been stored in the list โcoordโ, it is now possible to have access to their values by just processing the list with standard functions. One example is to use the function .zip(*)
, in which we enter the name of the list after the asterisk, in order to unzip all the tuples into two different arrays โx1โ and โy1โ.
#unzipping the x and y values of the selected points x1, y1 = zip(*coord)
Conclusion
In this article, we have seen how to introduce a cursor into a matplotlib window, how to customize its properties and appearance. We also explored the possibility of creating an annotating box and how to display it at every mouse click.
All these features will provide additional value to your plots from both an aesthetic and functional point of view, making them more enjoyable and understandable, two fundamental aspects that every data science report should always possess.