5

[Solution has been added to the EDIT sections in this post]

2 animated subplots are stacked vertically.

I would like to show a black vertical line through them according to the mouse position.

Up to now I can only completely mess the figure when moving the mouse...

How to clear the old vertical lines between updates?

(Just out of curiosity: since mouse movement control, my PC fan goes crazy when executing the code even without moving the mouse. Is mouse so "calculation expensive"?!?)

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import sleep

val1 = np.zeros(100)         
val2 = np.zeros(100)      

level1 = 0.2
level2 = 0.5

fig, ax = plt.subplots()

ax1 = plt.subplot2grid((2,1),(0,0))
lineVal1, = ax1.plot(np.zeros(100))
ax1.set_ylim(-0.5, 1.5)    

ax2 = plt.subplot2grid((2,1),(1,0))
lineVal2, = ax2.plot(np.zeros(100), color = "r")
ax2.set_ylim(-0.5, 1.5)    


def onMouseMove(event):
  ax1.axvline(x=event.xdata, color="k")
  ax2.axvline(x=event.xdata, color="k")



def updateData():
  global level1, val1
  global level2, val2

  clamp = lambda n, minn, maxn: max(min(maxn, n), minn)

  level1 = clamp(level1 + (np.random.random()-.5)/20.0, 0.0, 1.0)
  level2 = clamp(level2 + (np.random.random()-.5)/10.0, 0.0, 1.0)

  # values are appended to the respective arrays which keep the last 100 readings
  val1 = np.append(val1, level1)[-100:]
  val2 = np.append(val2, level2)[-100:]

  yield 1     # FuncAnimation expects an iterator

def visualize(i):

  lineVal1.set_ydata(val1)
  lineVal2.set_ydata(val2)

  return lineVal1,lineVal2

fig.canvas.mpl_connect('motion_notify_event', onMouseMove)
ani = animation.FuncAnimation(fig, visualize, updateData, interval=50)
plt.show()

Edit1

As solved by Ophir:

def onMouseMove(event):
    ax1.lines = [ax1.lines[0]]
    ax2.lines = [ax2.lines[0]]
    ax1.axvline(x=event.xdata, color="k")
    ax2.axvline(x=event.xdata, color="k")

Edit2

In case there are more datasets in the same plot such as in:

ax1 = plt.subplot2grid((2,1),(0,0))
lineVal1, = ax1.plot(np.zeros(100))
lineVal2, = ax2.plot(np.zeros(100), color = "r")
ax1.set_ylim(-0.5, 1.5)    

each dataset's line is stored in ax1.lines[]:

  • ax1.lines[0] is lineVal1
  • ax1.lines[1] is lineVal2
  • ax1.lines[2] is the vertical line if you already drew it.

This means onMouseMove has to be changed to:

def onMouseMove(event):
  ax1.lines = ax1.lines[:2] # keep the first two lines
  ax1.axvline(x=event.xdata, color="k") # then draw the vertical line
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Alex Poca
  • 2,406
  • 4
  • 25
  • 47

2 Answers2

4

Instead of adding new axvlines to the plot you simply change the data of the existing one. You only need to store the return value of the axvline call to keep the handle on it. The data format is ([x, x], [0, 1]), which can be changed using set_data. (For axhlines the format is ([0, 1], [y, y]) by the way.)

Add the following global variables:

axvline1 = ax1.axvline(x=0., color="k")
axvline2 = ax2.axvline(x=0., color="k")

and change the conMouseMove handler to:

def onMouseMove(event):
  axvline1.set_data([event.xdata, event.xdata], [0, 1])
  axvline2.set_data([event.xdata, event.xdata], [0, 1])

A minor drawback is that you start with the vlines at x=0 from the start.

Full code:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import sleep

val1 = np.zeros(100)         
val2 = np.zeros(100)      

level1 = 0.2
level2 = 0.5

fig, ax = plt.subplots()

ax1 = plt.subplot2grid((2,1),(0,0))
lineVal1, = ax1.plot(np.zeros(100))
ax1.set_ylim(-0.5, 1.5)    

ax2 = plt.subplot2grid((2,1),(1,0))
lineVal2, = ax2.plot(np.zeros(100), color = "r")
ax2.set_ylim(-0.5, 1.5)    

axvline1 = ax1.axvline(x=0., color="k")
axvline2 = ax2.axvline(x=0., color="k")


def onMouseMove(event):
  axvline1.set_data([event.xdata, event.xdata], [0, 1])
  axvline2.set_data([event.xdata, event.xdata], [0, 1])


def updateData():
  global level1, val1
  global level2, val2

  clamp = lambda n, minn, maxn: max(min(maxn, n), minn)

  level1 = clamp(level1 + (np.random.random()-.5)/20.0, 0.0, 1.0)
  level2 = clamp(level2 + (np.random.random()-.5)/10.0, 0.0, 1.0)

  # values are appended to the respective arrays which keep the last 100 readings
  val1 = np.append(val1, level1)[-100:]
  val2 = np.append(val2, level2)[-100:]

  yield 1     # FuncAnimation expects an iterator

def visualize(i):

  lineVal1.set_ydata(val1)
  lineVal2.set_ydata(val2)

  return lineVal1,lineVal2

fig.canvas.mpl_connect('motion_notify_event', onMouseMove)
ani = animation.FuncAnimation(fig, visualize, updateData, interval=50)
plt.show()
Martin Scharrer
  • 1,424
  • 1
  • 18
  • 35
3

replace your onMouseMove with the following one:

(I used How to remove lines in a Matplotlib plot)

def onMouseMove(event):
  ax1.lines = [ax1.lines[0]]
  ax2.lines = [ax2.lines[0]]
  ax1.axvline(x=event.xdata, color="k")
  ax2.axvline(x=event.xdata, color="k")
Community
  • 1
  • 1
Ophir Carmi
  • 2,701
  • 1
  • 23
  • 42