I have a simple idea that I would like to execute. From some data on a 1D plot, extract different segments by selecting the starting and ending points. Moreover, for each segment selected, specify a corresponding (string) name for this line. These three values (name, starting index, ending index) are then stored for later use.
I have included a minimum working example. Now, this works, but what I prefer is to have the "name" input done on the figure instead of the terminal. In other words, use something like TextBox
, rather that input
. The problem is that I cannot replicate the same behavior of input
from the TextBox
example. Mainly, I cannot have the program "wait for the user to input a string name, before continuing onwards with the program". Instead, it keeps taking input.
Anyway, below is a MWE. Can you please help me find a way to replicate the input
function in a graphical way? Thank you and I hope this proves useful for future readers in their endeavors :)
import numpy as np
from matplotlib import pyplot as plt
def main():
# Predefine a fixed number of segments, for demonstration.
nSegment = 3
# Counter for the number of points selected.
nPointPressed = 0
# Default initial starting point index.
I0 = 0
# Initialize segment variable.
segment = []
def onpick(event):
# Non-local and global variables.
nonlocal nPointPressed
nonlocal I0
# Get the index of the point chosen.
ind = event.ind
# Points selected must be one.
if len(ind) != 1:
return
# Illustrate graphically the selected segment.
ax.plot(x[ind], y[ind], 'o', markerfacecolor='none', color='red', linewidth=2, markersize=5)
plt.draw()
# Increment the number of points pressed.
nPointPressed += 1
# Distinguish between starting/ending indices. Odd values are starting indices, even ones are ending indices.
if nPointPressed % 2 == 0:
# An overkill, but for demonstration, explicitly define the interval.
ij = np.linspace( I0, ind, dtype=int )
# Plot the connecting line between the two indices.
plt.plot(x[ij], y[ij], color='green')
# Ask the user for an explicit segment name.
name = input("Enter a name for current line segment: ")
# Book-keep the current segment information: ["name", iStart, iEnd].
segment.append([name, ij[0], ij[-1]])
# Extract the plots from the figure and remove the starting and ending points.
pts = ax.get_lines()
pts[-3].remove() # start point
pts[-2].remove() # end point
# Change the color of the most recent connecting line.
pts[-1].set_color("green")
else:
# Save the starting index.
I0 = ind
# Expected segments are complete, close the figure.
if len(segment) == nSegment:
plt.close()
# Some data.
x = np.linspace( 0, 2*np.pi, 20 , dtype=float )
y = np.sin( x )
# Plot original data and enable interaction.
fig, ax = plt.subplots()
ax.plot(x, y, '-^', picker=True)
fig.canvas.mpl_connect("pick_event", onpick)
plt.show()
# For demonstration, print the resulting segment information.
for i in range( len(segment) ):
print( segment[i] )
if __name__ == '__main__':
main()
EDIT: Here's a snippet of what I considered via the TextBox widget, but didn't work. The below replaces the name = input...
line above (after importing the TextBox widgeet, ofc).
name = []
def submit(expression):
nonlocal name
name = expression
axbox = fig.add_axes([0.1, 0.05, 0.8, 0.075])
text_box = TextBox(axbox, "Segment name: ", textalignment="center")
text_box.on_submit(submit)
text_box.set_val("Unknown")
Reproducing your code, I have experience two main issues:
set_val()
actually triggers on_submit()
, sending an unintended Unknown
instead of waiting for user input. What you are (likely) looking for, i.e. a default value for the text in your textbox, can be achieved with the initial
argument when creating the Textbox
or by turning on and off the eventson
flag so that using set_val()
does not trigger on_submit()
.on_submit
is triggered.Find here the snippet implementing what I have described:
name = []
done = False # flag to stop updating the plot when the user hits Enter
def on_submit(expression):
nonlocal name, done
name = expression
done = True
axbox = fig.add_axes([0.1, 0.05, 0.8, 0.075])
initial_string = 'Unknown' # the default string in the TextBox
text_box = TextBox(axbox, "Segment name: ",
textalignment="center",
initial=initial_string)
text_box.on_submit(on_submit)
# The plot has to be re-drawn at each keystroke for the typing process to be seen
while not done:
plt.draw()
plt.pause(0.01)
# Reset the box to the initial value without triggering `on_pick`
text_box.eventson = False
text_box.set_val(initial_string)
text_box.eventson = True
Hope it helps you!