Issue
I am trying to get a scatter plot of 2 different variables, one set of scatter points is height values for a projectile under the effect of air resistant, and one is a projectile with no air resistance. I can get this working with 1 set of scatterpoints, but not both of them. I'm using the mplcursors library to show annotation labels when hovering over a point. This is the relevant code:
import numpy as np
from matplotlib import pyplot as plt
import mplcursors
# ...
fig, ax = plt.subplots()
nair_scatter = ax.scatter(nairRangeValues, nairHeightValues, c="blue", label="No air resistance", s=3)
air_scatter = ax.scatter(airRangeValues, airHeightValues, c="red", label="Air resistance", s=3)
ax.legend()
plt.xlabel("Range", size=10)
plt.ylabel("Height", size=10)
crs = mplcursors.cursor(ax,hover=True)
crs.connect("add", lambda sel: sel.annotation.set_text(
'Range {} m\nHeight {} m\nVelocity {} m/s at angle {} degrees\nDisplacement {} m\nTime of flight {} s' .format(
(sel.target[0]), (sel.target[1]),
(airVelocityValues[get_index(airRangeValues, sel.target[0])]),
(airAngleValues[get_index(airRangeValues, sel.target[0])]),
(airDisplacementValues[get_index(airRangeValues, sel.target[0])]),
(airTimeValues[get_index(airRangeValues, sel.target[0])]) ) ) )
crs2 = mplcursors.cursor(ax,hover=True)
crs2.connect("add", lambda ok: ok.annotation.set_text(
'Range {} m\nHeight {} m' .format(ok.target[0], ok.target[1])))
plt.show()
There are a few problems with this. Firstly, it gives me a massive error and says StopIteration
at the end. The other one is that it shows the correct label on 1 set of scatter points, but also shows the crs2 values for the same scatterplot, not the other one. I have no idea how to allow them to be able to be unique to each scatter point set, if anyone can help id appreciate it.
Solution
Instead of connecting the cursors to the ax, try connecting them only to the scatter plot they belong to. The first parameter of mplcursors.cursor
is meant for just that.
You'll notice that when you move to a point from the other set, that the annotation of the previous one is not removed. Therefore, I added some code to remove the other annotation when a new one is opened.
Note that you can directly access the index via sel.target.index
. Calling get_index(airRangeValues, ...)
with a point of the wrong set could be a cause of the error you encountered.
Here's some code to demonstrate the principles. The backgroundcolor of the annotations is set differently to better illustrate which cursor is being displayed. Also, the alpha is changed to ease reading the text.
import numpy as np
from matplotlib import pyplot as plt
import mplcursors
def cursor1_annotations(sel):
sel.annotation.set_text(
'Cursor One:\nRange {:.2f} m\nHeight {:.2f} m\nindex: {}'.format(sel.target[0], sel.target[1], sel.target.index))
sel.annotation.get_bbox_patch().set(fc="powderblue", alpha=0.9)
for s in crs2.selections:
crs2.remove_selection(s)
def cursor2_annotations(sel):
sel.annotation.set_text(
'Cursor Two:\nRange {:.2f} m\nHeight {:.2f} m\nindex: {}'.format(sel.target[0], sel.target[1], sel.target.index))
sel.annotation.get_bbox_patch().set(fc="lightsalmon", alpha=0.9)
for s in crs1.selections:
crs1.remove_selection(s)
N = 100
nairRangeValues = np.random.normal(30, 10, N)
nairHeightValues = np.random.uniform(40, 100, N)
airRangeValues = np.random.normal(40, 10, N)
airHeightValues = np.random.uniform(50, 120, N)
fig, ax = plt.subplots()
nair_scatter = ax.scatter(nairRangeValues, nairHeightValues, c="blue", label="No air resistance", s=3)
air_scatter = ax.scatter(airRangeValues, airHeightValues, c="red", label="Air resistance", s=3)
ax.legend()
plt.xlabel("Range", size=10)
plt.ylabel("Height", size=10)
crs1 = mplcursors.cursor(nair_scatter, hover=True)
crs1.connect("add", cursor1_annotations)
crs2 = mplcursors.cursor(air_scatter, hover=True)
crs2.connect("add", cursor2_annotations)
plt.show()
PS: Something similar can also be achieved using only one cursor and adding a test. In that case there is no need to manually remove the other cursor:
def cursor_annotations(sel):
if sel.artist == nair_scatter:
sel.annotation.set_text(
'Cursor One:\nRange {:.2f} m\nHeight {:.2f} m\nindex: {}'.format(sel.target[0], sel.target[1], sel.target.index))
sel.annotation.get_bbox_patch().set(fc="powderblue", alpha=0.9)
else:
sel.annotation.set_text(
'Cursor Two:\nRange {:.2f} m\nHeight {:.2f} m\nindex: {}'.format(sel.target[0], sel.target[1], sel.target.index))
sel.annotation.get_bbox_patch().set(fc="lightsalmon", alpha=0.9)
crs = mplcursors.cursor([nair_scatter, air_scatter], hover=True)
crs.connect("add", cursor_annotations)
Answered By - JohanC
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.