Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib animation: vertical cursor line through subplots

[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
like image 453
Alex Poca Avatar asked Aug 22 '16 12:08

Alex Poca


People also ask

What does PLT subplot return?

Plt.subplots(nrows, ncols) The two integer arguments to this function specify the number of rows and columns of the subplot grid. The function returns a figure object and a tuple containing axes objects equal to nrows*ncols.


2 Answers

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()
like image 90
Martin Scharrer Avatar answered Oct 01 '22 14:10

Martin Scharrer


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")
like image 41
Ophir Carmi Avatar answered Oct 01 '22 13:10

Ophir Carmi