I have a NumPy array which contains data from several samples. Some of the samples are outliers and need to be removed via visual inspection. Is there a way to make an interactive line plot in a jupyter notebook where a user can select a line on the plot by clicking it and have that line disappear/be highlighted and the data be marked for removal?
So far the best I have come up with is using Plotly:
import plotly.graph_objects as go
x = np.linspace(0,100)
y = np.random.randint(5, size=(5, 100))
fig = go.Figure()
for line in range(5):
fig.add_trace(go.Line(x=x, y=y[:,line],mode='lines'))
f = go.FigureWidget(fig)
f
Plotly output line graph
Using this code I can get a line graph with lines that are selectable by selecting the corresponding label in the figure legend, but this quickly becomes unfeasible with more samples. Is there a way to do this without plotting a legend and having the lines be selectable directly in the graph?
Thanks
You can use click events which allow you to define a callback that is bound to each trace. Here is an example of a callback function called update_trace that will remove a trace when it's clicked on (the #out.capture decorator isn't necessary, but can be useful for debugging using print statements):
import numpy as np
import plotly.graph_objects as go
from ipywidgets import Output, VBox
np.random.seed(42)
x = np.linspace(0,100)
y = np.random.randint(5, size=(5, 50))
fig = go.Figure()
for line in range(5):
fig.add_trace(go.Scatter(x=x, y=y[line,:],mode='lines',visible=True,name=f'trace_{line+1}'))
f = go.FigureWidget(fig)
out = Output()
#out.capture(clear_output=False)
def update_trace(trace, points, selector):
## determine whether trace was clicked on
if points.point_inds == []:
pass
else:
selected_trace_name = trace.name
for f_trace in f.data:
if (selected_trace_name == f_trace.name) & (f_trace.visible == True):
f_trace.visible = False
print(f"removing {selected_trace_name}")
traces = f.data
for trace in traces:
trace.on_click(update_trace)
VBox([f, out])
Note: only the last code chunk brings an error. The earlier chunks are to give context to the animation that I want. This is all in Jupyter for Windows.
I have a matplotlib pyplot with two line segments, one is "wrong" and the other is "right." I want to animate the graph to start with both lines where the blue "wrong" line is,and have the red one pivot and move to be in the right place. The "wrong" line goes from (x,y) = (-1.25,9.1) to (0.75,8.0). The "right" line goes from (-1.25,9.7) to (0.75,7.5)
Here is the code for the static comparison:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
boring_fig = plt.figure()
blue = plt.plot([-1.25,.75], [9.1,8], color = 'b', label = 'wrong')
red = plt.plot([-1.25,.75], [9.7,7.5], color = 'r', label = 'right')
plt.show()
Now I want to start them both where the blue line is, and then have the red line incrementally move to the correct position. I made these two arrays for y coordinates to incrementally change between lines. Then I graph everything but the red line, in hopes of adding it as an animation after this.
y_left = np.array([9.1, 9.16, 9.22, 9.28, 9.34, 9.4, 9.46, 9.52, 9.58, 9.64, 9.7])
y_right = np.array([8.0, 7.95, 7.9, 7.85, 7.8, 7.75, 7.7, 7.65, 7.6, 7.55, 7.5])
fig = plt.figure()
blue = plt.plot([-1.25,.75], [9.1,8], color = 'b', label = 'wrong')
plt.show()
And then I try to animate the red line segment shifting along those incremented y values. I get the error somewhere in here:
def animate_1(i):
return plt.plot([-1.25,.75], [y_left[i],y_right[i]], color = 'r'),
anim = FuncAnimation(fig = fig, func = animate_1, interval = 100, frames = 10)
plt.show(anim)
And I get this message: "UserWarning: Animation was deleted without rendering anything. This is most likely unintended. To prevent deletion, assign the Animation to a variable that exists for as long as you need the Animation."
I spent hours trying to figure this out, but I am too much of a noob. Please help.
It turns out I had to install ffmpeg for windows:
https://www.wikihow.com/Install-FFmpeg-on-Windows
Now the graph shows up but I can't see it animating. Oh well. That is an entirely different question.
here is the end graph I made (with a couple of extra details)
Add this line to the beginning of your code in order to see the animation work:
%matplotlib notebook
In Jupyter Notebook (at least on Windows) specifying the GUI toolkit will allow you to see the animation updating, not just a static graph. You can read more about toolkits and user interfaces here.
If there is a message that says:
Warning: Cannot change to a different GUI toolkit: ...
then restart the Jupyter kernel and run the cell again to update the GUI. For troubleshooting the GUI, see this answer.
In regards to the original question, if you set blit = False in FuncAnimation then the animation should display. Otherwise, you can add a return blue, statement to the animate function. See my code below and feel free to ask questions!
Complete Code
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def move_line(num_frames):
x_vals = [-1.25,.75]
y_left_array = np.linspace(9.1, 9.7, num=num_frames, endpoint=True)
y_right_array = np.linspace(8.0, 7.5, num=num_frames, endpoint=True)
fig = plt.figure()
red = plt.plot(x_vals, [9.7,7.5], color = 'r', label = 'right')
blue, = plt.plot([], [], color = 'b', label = 'wrong')
def animate(I):
y_vals = [y_left_array[i],y_right_array[I]]
blue.set_data(x_vals,y_vals)
return blue,
anim = FuncAnimation(
fig,
animate,
frames = num_frames,
interval = 1000/30,
repeat = False,
blit = True
)
plt.show()
return anim
animate_lines = move_line(100)
I searched and found I can create a colormap, but I feel that kinda too much for what I want to do.
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
A=[1,1,1,1,1]
B=[0,0,0,0,0]
C=[0,0,3,0,0]
D=[0,0,0,0,0]
E=[2,2,2,2,2]
plateau=np.array([E,D,C,B,A])
def aff_graph():
ax=np.arange(-0.5,5,1)
plt.matshow(plateau)
for z in ax:
plt.plot((z,z),(ax[0],ax[5]),'k-')
plt.plot((ax[0],ax[5]),(z,z),'k-')
plt.axis('off')
plt.show()
It gives me this : graph
But actually I'd like to set it like this :
0→white
1→green
2→red
3→black
I only need this, and not a color map for values in a certain interval. How could I do the easiest way ?
Bonus question : is there any way to make all the squares white, with a colored circle inside ? Since it's a board for a game, I think it would better convey.
Thanks!
That can be achieved with the following:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
A=[1,1,1,1,1]
B=[0,0,0,0,0]
C=[0,0,3,0,0]
D=[0,0,0,0,0]
E=[2,2,2,2,2]
plateau=np.array([E,D,C,B,A])
c = mpl.colors.ListedColormap(['white', 'green', 'red', 'black'])
n = mpl.colors.Normalize(vmin=0,vmax=3)
def aff_graph():
ax=np.arange(-0.5,5,1)
plt.matshow(plateau,cmap=c,norm=n)
for z in ax:
plt.plot((z,z),(ax[0],ax[5]),'k-')
plt.plot((ax[0],ax[5]),(z,z),'k-')
plt.axis('off')
plt.show()
aff_graph()
I added 2 lines of code: the first one creates the desired colormap, using the colors you asked. With that, your example already works fine.
However, let's say that a given plateau does not contain the number 3, but only 0s, 1s and 2s. That would lead to the following undesired color assignment:
0→white 1→red 2→black
The second line that I added takes care of that. It defines the normalization to be used, making sure that the color scheme is solid. You might not even need that if all four colors are always there (don't know how the game works).
Of course, make sure that you set the cmap and norm options when calling matshow. Now, even if not all colors are present, you still get the result you want:
I go for the bonus question only:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
A=[1,1,1,1,1]
B=[0,0,0,0,0]
C=[0,0,3,0,0]
D=[0,0,0,0,0]
E=[2,2,2,2,2]
plateau=np.array([E,D,C,B,A])
plt.figure()
plt.axis('off')
plt.gca().set(aspect='equal')
for i in range(6):
plt.plot([i,i],[0,5],'k')
plt.plot([0,5],[i,i],'k')
for i1 in range(5):
for i2 in range(5):
color_idx = plateau[i1, i2]
if color_idx == 1:
plt.gca().add_patch(mpatches.Circle((i2+0.5,i1+0.5), 0.3, fc='green'))
if color_idx == 2:
plt.gca().add_patch(mpatches.Circle((i2+0.5,i1+0.5), 0.3, fc='red'))
if color_idx == 3:
plt.gca().add_patch(mpatches.Circle((i2+0.5,i1+0.5), 0.3, fc='black'))
plt.show()
which gives
Edit:
In order to be able to update the figure, you can put the plotting of the circles into a function. Each time you call the function, it should first delete all old circles before drawing the new, like this:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from time import sleep
# initialize figure and grid
fig = plt.figure()
plt.axis('off')
plt.gca().set(aspect='equal')
for i in range(6):
plt.plot([i,i], [0,5], 'k')
plt.plot([0,5], [i,i], 'k')
def draw_circles(plateau):
# remove all circles
for child in plt.gca().get_children():
if type(child).__name__ == 'Circle':
child.remove()
# draw new circles
for i1 in range(5):
for i2 in range(5):
color_idx = plateau[i1, i2]
if color_idx == 1:
circle = mpatches.Circle((i2+0.5, i1+0.5), 0.3, fc='green')
if color_idx == 2:
circle = mpatches.Circle((i2+0.5, i1+0.5), 0.3, fc='red')
if color_idx == 3:
circle = mpatches.Circle((i2+0.5, i1+0.5), 0.3, fc='black')
plt.gca().add_patch(circle)
plt.show(block=False)
# draw for first time
A = [1,1,1,1,1]
B = [0,0,0,0,0]
C = [0,0,3,0,0]
D = [0,0,0,0,0]
E = [2,2,2,2,2]
draw_circles(plateau=np.array([E, D, C, B, A]))
# update after some time
sleep(2)
A = [1,1,1,1,1]
B = [0,0,3,0,0]
C = [0,0,0,0,0]
D = [0,0,0,0,0]
E = [2,2,2,2,2]
draw_circles(plateau=np.array([E, D, C, B, A]))
plt.show()
You can call this function every n seconds, or have it triggered by some kind of event, like a callback of some button click or so. However I need to add that I don't really understand the behavior of the plt.show(block=False) command, and in fact I was not able to redraw the figure more than two times before the figure freezes ... if you have trouble with that, too, it might be worth asking a separate question.
I'm trying to monitor real-time data with matplotlib.
I found that I can update plot dynamically with interactive mode in Pyplot.
And it worked well, but one problem is 'I cannot manipulate the figure window at all'. For example, move or re-size the figure window.
Here is my code.
This is cons of interactive mode? or I'm using it incorrectly?
import matplotlib.pyplot as plt
import time
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# interactive mode
plt.ion() # identical plt.interactive(True)
fig, ax = plt.subplots()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
for i in range(len(x)):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw
plt.draw()
time.sleep(0.01)
Thank you.
As shown in this answer to another question, replace plt.draw() with plt.pause(0.05). This solved the problem for me.
Although I still think you should use bokeh, I'll tell you how to do it with matplotlib.
The problem why it won't work ist that matplotlib's event loop is not active and therefore it cannot digest window events (like close or resize). Unfortunately it is not possible to trigger this digestion from the outside. What you have to do is to use matplotlib's animation system.
Your code is actually quite well prepared for it so you can use FuncAnimation.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# don't need ion, we're using block=True (see end of code)
fig, ax = plt.subplots()
fig.show()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
def showdata(i):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw will be called by the animation system
# instead of time.sleep(0.01) we use an update interval of 10ms
# which has the same effect
anim = FuncAnimation(fig, showdata, range(len(x)), interval=10, repeat=False)
# start eventloop
plt.show(block=True)
I have much the same problem as this guy: Dynamically updating plot in matplotlib. I want to update a graph with data from a serial port and I have been trying to implement this answer, but I can't get a MWE to work. The graph simply doesn't appear, but everything else seems to work. I have read about problems with the installation of Matplotlib causing similar symptoms.
Here is my Minimum Not Working Example (MNWE):
import numpy as np
import matplotlib.pyplot as plt
fig1 = plt.figure() #Create figure
l, = plt.plot([], [], 'r-') #What is the comma for? Is l some kind of struct/class?
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
k = 5
xdata=[0.5 for i in range(k+1)] # Generate a list to hold data
ydata=[j for j in range(k+1)]
while True:
y = float(raw_input("y val :"))
xdata.append(y) # Append new data to list
k = k + 1 # inc x value
ydata.append(k)
l.set_data(xdata,ydata) # update data
print xdata # Print for debug perposes
print ydata
plt.draw # These seem to do nothing
plt.show # !?
Could someone please point me in the right direction / provide a link / tell me what to google? I'm lost. Thanks
As suggested by user #fhdrsdg I was missing brackets. Getting the rescale to work requires code from: set_data and autoscale_view matplotlib
A working MWE is provided below:
import numpy as np
import matplotlib.pyplot as plt
plt.ion() # Enable interactive mode
fig = plt.figure() # Create figure
axes = fig.add_subplot(111) # Add subplot (dont worry only one plot appears)
axes.set_autoscale_on(True) # enable autoscale
axes.autoscale_view(True,True,True)
l, = plt.plot([], [], 'r-') # Plot blank data
plt.xlabel('x') # Set up axes
plt.title('test')
k = 5
xdata=[0.5 for i in range(k+1)] # Generate a list to hold data
ydata=[j for j in range(k+1)]
while True:
y = float(raw_input("y val :")) #Get new data
xdata.append(y) # Append new data to list
k = k + 1 # inc x value
ydata.append(k)
l.set_data(ydata,xdata) # update data
print xdata # Print for debug perposes
print ydata
axes.relim() # Recalculate limits
axes.autoscale_view(True,True,True) #Autoscale
plt.draw() # Redraw