Home > Mobile >  How to use SpanSelector in matplotlib with subplots?
How to use SpanSelector in matplotlib with subplots?

Time:03-10

I am plotting data from multiple sources and using SpanSelector to analyze the data during a particular event. I can select the data from each subplot and extract the data. However, when I make a new selection in a new subplot, I want the selection in the previous subplot to disappear.

There is span.set_visible(True/False). But for this to work, I need to know which subplot is being selected. I tried to find that using lambda function, but it is not working at the moment.

I tried first:

for signal in self.signals:
    self.span[k] = SpanSelector(
    self.ax[k],
    onselect = lambda min, max : self.onselect_function(min, max, k),
    # onselect = self.onselect_function,
    direction = 'horizontal',
    minspan = 1,
    useblit = True,
    interactive = True,
    button = 1,
    props = {'facecolor': 'red', 'alpha':0.5},
    drag_from_anywhere = True
    )
    k  = 1

def onselect_function(self, min_value, max_value, selected_graph)
    ...

But it keeps on sending a value of selected_graph = 3 for selected_graph, no matter, which subplot I select. There are 3 subgraphs ( k = 0 - 2).

I thought this was because k was not in the namespace, and I tried:

onselect = lambda min, max, k : self.onselect_function(min, max, k)

But now, I get the following error:

File "C:\Users\Ashu\miniconda3\envs\my_env\lib\site-packages\matplotlib\cbook\__init__.py", line 287, in process
    func(*args, **kwargs)
  File "C:\Users\Ashu\miniconda3\envs\my_env\lib\site-packages\matplotlib\widgets.py", line 1958, in release
    self._release(event)  File "C:\Users\Ashu\miniconda3\envs\my_env\lib\site-packages\matplotlib\widgets.py", line 2359, in _release
    self.onselect(vmin, vmax)
TypeError: <lambda>() missing 1 required positional argument: 'k'

Ideally, I would want the selection to highlight the x-axis range in all subplots (I can still do that manually as I have info on what range is being selected, as shown in the figure with dashed lines). But I need the previous selection to disappear when the new selection begins.

Example of how it currently displays. The last selection was made in the top graph, so each graph shows the textual information for that selection (51-53), but the highlights in the bottom two graphs are in the wrong parts. I tried clearing axes as well but that also doesn't work.

enter image description here

Edit: Adding minimal reproducible example

import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
from random import randrange
import math
plt.style.use('seaborn')

noOfDataToShow = 10
noOfSignals = 3
fig, ax = plt.subplots(nrows = noOfSignals, ncols = 1)
x_pos = []
signal_data_holder = []
for i in range(noOfDataToShow):
    x_pos.append(i)

for i in range(noOfSignals):
    temp = []
    for j in range (noOfDataToShow):
        temp.append(randrange(0,10))
    signal_data_holder.append(temp)

def onselect_function(min_value, max_value, selected_graph = 0):
    k = 0
    for param in signal_data_holder:
        ax[k].cla()
        ax[k].set_ylim([0,10])
        ax[k].plot(x_pos, param, label = str (math.trunc(param[noOfDataToShow - 1])))
        ax[k].legend(loc = "upper left")
        k  = 1

#******Calculating Min_Max (Can be ignored) ******#
    min_value = math.floor(min_value)
    max_value = math.ceil(max_value)
    min_value_data = [None] * noOfSignals
    max_value_data = [None] * noOfSignals
    if (min_value < 0): 
        min_value = 0
    if (max_value >= noOfDataToShow):
        max_value = noOfDataToShow
    for k in range(noOfSignals):
        min_value_data = signal_data_holder[k][min_value]
        max_value_data = signal_data_holder[k][min_value]
        for i in range(min_value, max_value):
            if (signal_data_holder[k][i] < min_value_data):
                min_value_data = signal_data_holder[k][i]
            elif (signal_data_holder[k][i] > max_value_data):
                max_value_data = signal_data_holder[k][i]
        labelText = "Min_Value: "   str (min_value_data)   "\n"
        labelText  = "Max_Value: "   str (max_value_data)   "\n"
        labelText  = "Range: "   str (min_value)   "-"   str (max_value)   "\n"
        ax[k].legend(labels = [labelText], loc = "upper left")
        print(min_value_data, max_value_data)
        ax[k].axvline(min_value, color = 'red', linestyle = 'dashed')
        ax[k].axvline(max_value, color = 'red', linestyle = 'dashed')
    fig.canvas.draw()
#******Calculating Min_Max (Can be ignored) ******#

    return min_value, max_value

span = [None] * noOfSignals
for k in range(noOfSignals):
    span[k] = SpanSelector(
    ax[k],
    # onselect = lambda min, max, k : onselect_function(min, max, k),
    # onselect = lambda min, max : onselect_function(min, max, k),
    onselect = onselect_function,
    direction = 'horizontal',
    minspan = 1,
    useblit = True,
    interactive = True,
    button = 1,
    props = {'facecolor': 'red', 'alpha':0.5},
    drag_from_anywhere = True
    )

k = 0
for param in signal_data_holder:
    ax[k].cla()
    ax[k].set_ylim([0,10])
    ax[k].plot(x_pos, param, label = str (math.trunc(param[noOfDataToShow - 1])))
    ax[k].legend(loc = "upper left")
    k  = 1

plt.show()

CodePudding user response:

I don't think you can directly retrieve the axis object linked to the span selector (or at least, I wouldn't know how). However, we can also collect the axis object of the last mouse click:

import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import numpy as np

#sample data
fig, axis = plt.subplots(3)
for i, ax in enumerate(axis):
    t=np.linspace(-i, i 1 , 100)
    ax.plot(t, np.sin(2 * np.pi * t))

#list to store the axis last used with a mouseclick
curr_ax = []

#detect the currently modified axis
def on_click(event):
    if event.inaxes:
        curr_ax[:] = [event.inaxes]

#modify the current axis objects
def onselect(xmin, xmax):
    #ignore if accidentally clicked into an axis object
    if xmin==xmax:
        return
    #set all span selectors invisible accept the current 
    for ax, span in zip(axis, list_of_spans):
        if ax != curr_ax[0]:
            span.set_visible(False)
    #do something with xmin, xmax
    print(xmin, xmax)
    fig.canvas.draw_idle()


#collect span selectors in a list in the same order as their axes objects
list_of_spans = [SpanSelector(
        ax,
        onselect,
        "horizontal",
        useblit=True,
        props=dict(alpha=0.5, facecolor="tab:blue"),
        interactive=True,
        drag_from_anywhere=True
        )
        for ax in axis]

plt.connect('button_press_event', on_click)

plt.show()
  • Related