Issue
import pandas as pd
import matplotlib.pyplot as plt
import mplcursors
df = pd.DataFrame(
{'Universe': ['Darvel', 'MC', 'MC', 'Darvel', 'MC', 'Other', 'Darvel'],
'Value': [10, 11, 13, 12, 9, 7, 10],
'Upper': [12.5, 11.3, 15.4, 12.2, 13.1, 8.8, 11.5],
'Lower': [4.5, 9.6, 11.8, 6, 6.5, 5, 8]})
df['UpperError'] = df['Upper'] - df['Value']
df['LowerError'] = df['Value'] - df['Lower']
colors = ['r', 'g', 'b']
fig, ax = plt.subplots()
for i, universe in enumerate(df['Universe'].unique()):
to_plot = df[df['Universe'] == universe]
ax.scatter(to_plot.index, to_plot['Value'], s=16, c=colors[i])
error = to_plot[['LowerError', 'UpperError']].transpose().to_numpy()
ax.errorbar(to_plot.index, to_plot['Value'], yerr=error, fmt='o',
markersize=0, capsize=6, color=colors[i])
ax.scatter(to_plot.index, to_plot['Upper'], c='w', zorder=-1)
ax.scatter(to_plot.index, to_plot['Lower'], c='w', zorder=-1)
mplcursors.cursor(hover=True)
plt.show()
This does most of what I want, but I want the following changes.
I do not want the mplcursors cursor to interact with the errorbars, but just the scatter plots, including the invisible scatterplots on top and bottom of the errorbars.
I just want the y value to show. For example, the first bar should say "12.5" on the top, "10.0" in the middle, and "4.5" on the bottom.
Solution
To have mplcursors only interact with some elements, a list of those elements can be given as the first parameter to mplcursors.cursor()
. The list could be built from the return values of the calls to ax.scatter
.
To modify the annotation text shown, a custom function can be connected. In the example below, the label and the y-position are extracted from the selected element and put into the annotation text. Such label can be added via ax.scatter(..., label=...)
.
(Choosing 'none'
as the color for the "invisible" elements makes them really invisible. To make the code more "Pythonic" explicit indices can be avoided, working with zip
instead of with enumerate
.)
import matplotlib.pyplot as plt
import mplcursors
import pandas as pd
def show_annotation(sel):
text = f'{sel.artist.get_label()}\n y={sel.target[1]:.1f}'
sel.annotation.set_text(text)
df = pd.DataFrame(
{'Universe': ['Darvel', 'MC', 'MC', 'Darvel', 'MC', 'Other', 'Darvel'],
'Value': [10, 11, 13, 12, 9, 7, 10],
'Upper': [12.5, 11.3, 15.4, 12.2, 13.1, 8.8, 11.5],
'Lower': [4.5, 9.6, 11.8, 6, 6.5, 5, 8]})
df['UpperError'] = df['Upper'] - df['Value']
df['LowerError'] = df['Value'] - df['Lower']
colors = ['r', 'g', 'b']
fig, ax = plt.subplots()
all_scatters = []
for universe, color in zip(df['Universe'].unique(), colors):
to_plot = df[df['Universe'] == universe]
all_scatters.append(ax.scatter(to_plot.index, to_plot['Value'], s=16, c=color, label=universe))
error = to_plot[['LowerError', 'UpperError']].transpose().to_numpy()
ax.errorbar(to_plot.index, to_plot['Value'], yerr=error, fmt='o',
markersize=0, capsize=6, color=color)
all_scatters.append(ax.scatter(to_plot.index, to_plot['Upper'], c='none', zorder=-1, label=universe))
all_scatters.append(ax.scatter(to_plot.index, to_plot['Lower'], c='none', zorder=-1, label=universe))
cursor = mplcursors.cursor(all_scatters, hover=True)
cursor.connect('add', show_annotation)
plt.show()
PS: You can also show the 'Universe'
via the x ticks:
ax.set_xticks(df.index)
ax.set_xticklabels(df['Universe'])
If you want to, for short functions you could use the lambda notation instead of writing a separate function:
cursor.connect('add',
lambda sel: sel.annotation.set_text(f'{sel.artist.get_label()}\n y={sel.target[1]:.1f}'))
Answered By - JohanC
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.