I am trying to turn off the spanselector after using it. When i go to the matplotlib documents, it says about this:
Set the visible attribute to False if you want to turn off the functionality of the span selector
But I do not know how to turn off the functionality of span selector like the documentation states.
Here is the code that I have.
def disconnect_span(self):
if self.selection_mode == None: #This is a variable that i use to call this method
self.SpanSelector(self.axes, self.onselect, "horizontal", useblit = True,
rectprops = dict(alpha= 0.5, facecolor = "blue"))
self.figure_canvas.draw_idle()
else:
#Here is where i want to put the disconnect condition
In order to toggle the visibility of the SpanSelector you will need to use the set_visible() method.
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
ax = plt.subplot(111)
ax.plot([1,2], [3,4])
def onselect(vmin, vmax):
print vmin, vmax
span = SpanSelector(ax, onselect, 'horizontal')
span.set_visible(False)
Here I have created it right after object creation, but you can call the set_visible() from anywhere to disable the selector as long you have the SpanSelector object.
Related
As the title suggests, I am trying to put multiple SpanSelector widgets on the same axis. In the test code below (adapted from this example), I am using a multi-plot figure with the qt backend, to zoom in on the two regions selected by the span selectors. I want to make sure that the two widgets can function independently. I have set non-overlapping initial spans for them both when the figure is generated. I also need to make sure that ignore_event_outside is set to True for both span selectors, so that trying to move/resize one does not re-draw the other span selector at that location. I thought this is would be enough to make sure the two widgets function independently and there is no ambiguity in which object should respond to a given event.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
# Fixing random state for reproducibility
np.random.seed(19680801)
mosaic = """AA
BC"""
fig = plt.figure(figsize=(8, 6))
ax_dict = fig.subplot_mosaic(mosaic)
(ax0,ax1,ax2) = list(ax_dict.values())
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2 * np.pi * x) + 0.5 * np.random.randn(len(x))
ax0.plot(x, y, color='black')
ax0.set_ylim(-2, 2)
ax0.set_title('Press left mouse button and drag '
'to select a region in the top graph')
line1, = ax1.plot([], [], color='dodgerblue')
line2, = ax2.plot([], [], color='coral')
def onselect1(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x) - 1, indmax)
region_x = x[indmin:indmax]
region_y = y[indmin:indmax]
if len(region_x) >= 2:
line1.set_data(region_x, region_y)
ax1.set_xlim(region_x[0], region_x[-1])
ax1.set_ylim(region_y.min(), region_y.max())
fig.canvas.draw_idle()
def onselect2(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x) - 1, indmax)
region_x = x[indmin:indmax]
region_y = y[indmin:indmax]
if len(region_x) >= 2:
line2.set_data(region_x, region_y)
ax2.set_xlim(region_x[0], region_x[-1])
ax2.set_ylim(region_y.min(), region_y.max())
fig.canvas.draw_idle()
span1 = SpanSelector(
ax0,
onselect1,
"horizontal",
useblit=True,
props=dict(alpha=0.5, facecolor="dodgerblue"),
interactive=True,
drag_from_anywhere=True,
ignore_event_outside=True
)
span2 = SpanSelector(
ax0,
onselect2,
"horizontal",
useblit=True,
props=dict(alpha=0.5, facecolor="coral"),
interactive=True,
drag_from_anywhere=True,
ignore_event_outside=True
)
# Set useblit=True on most backends for enhanced performance.
# Set default values
xmin1, xmax1 = 1.4, 2.3
span1.extents = (xmin1, xmax1)
span1.onselect(xmin1, xmax1)
xmin2, xmax2 = 2.5, 3.1
span2.extents = (xmin2, xmax2)
span2.onselect(xmin2, xmax2)
plt.show()
However, whenever I try to interact with the two widgets, things don't go as planned. As seen in the GIF below, when I first click on one of the span selectors (red in this example) after the figure is generated, and move/resize it, the other widget (blue in this example) responds to it, even though ignore_event_outside is set to True. Subsequent interactions seem to work almost fine but still look strange. In this example, moving/resizing the blue span selector makes the red span selector "blink" off until the mouse button is released. Please note that the opposite does not happen. Also note that the cursor does not change (to ↔) when I hover over the handles for the blue span selector - I have click on the handle before the cursor shape changes. Again, the red span selector is not affected in the same way.
I know I can try to assign different mouse buttons to the two span selectors in this case, but I eventually plan to generate 3-4 span selectors on the same figure and I would simply run out of mouse buttons.
Additional info:
OS: Windows 10
Python version: 3.9.13
Matplotlib version: 3.5.3
Jupyter Notebook server version: 6.4.12
IPython version: 8.4.0
N.B.- Matplotlib community forum post for this issue.
I'm trying to embed the "home" function (i.e., "reset original review") to a widget button combined with other operations. Below is an example code I have.
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.widgets import Button
from random import randrange
matplotlib.use('QtAgg')
plt.rcParams['toolbar'] = 'toolmanager' # switch this row to see the difference.
fig, ax = plt.subplots(nrows=1, figsize=(4,6))
ax.imshow([[1, 2], [5, 6]])
def change_color(event):
ax.imshow([[randrange(10), randrange(10)], [randrange(10), randrange(10)]])
plt.get_current_fig_manager().toolbar.home()
axprev0 = plt.axes([0.35, 0.05, 0.3, 0.0375])
bprev0 = Button(axprev0, 'Change Color')
bprev0.on_clicked(change_color)
plt.show()
My problem is that when plt.rcParams['toolbar'] = 'toolmanager' is on, it shows error message toolbar.home() won't work. The reason I need plt.rcParams['toolbar'] = 'toolmanager' is because it allows me to scroll the mouse wheel for zooming when I'm on the zoom or drag tool.
It seems that when we specify plt.rcParams['toolbar'] = 'toolmanager', it is using ToolbarQt. The ToolbarQt supports mouse wheel scrolling for zooming. While the name of the home function of ToolbarQt is different.
If we do not specify plt.rcParams['toolbar'] = 'toolmanager', plt is using NavigationToolbar2QT. However, it does not support mouse wheel scrolling for zooming.
plt.get_current_fig_manager().toolmanager.tools['home'].trigger(fig, event) # use this function when toolmanager
I want to update subplots based on a SpanSelector which works as expected. But another requirement is that I need to initialize the selected span to some given values. I have tried to call the _press, _release functions but they just return False and nothing happens. How can I set the span programmatically such that the selected span is visible in the SpanSelector as well?
from matplotlib.widgets import SpanSelector
from collections import namedtuple
widgets.append(
SpanSelector(range_ax, onselect=self._plot_subplots, direction='horizontal', rectprops=dict(alpha=0.5, facecolor='red'), span_stays=True)
)
# fake some events
Event = namedtuple('Event', ['xdata', 'ydata'])
widgets[0]._press(Event(0, 119))
widgets[0]._release(Event(500, 120))
Set the selection rectangle stay_rect and call the onselect handler with the initial xmin and xmax values.
Example: initial view of the example from the docs with the following inserted before plt.show():
xmin, xmax = 3, 4
span.stay_rect.set_bounds(xmin, 0, xmax-xmin, 1)
span.stay_rect.set_visible(True)
span.onselect(xmin, xmax)
I think that using extents is cleaner. Based on the example from the docs, you can do the following:
span = SpanSelector(
ax1,
onselect,
"horizontal",
useblit=True,
props=dict(alpha=0.5, facecolor="tab:blue"),
interactive=True,
drag_from_anywhere=True,
span_stays=True, # Add this to make SpanSelector persistent
)
# Set default values
xmin, xmax = 1, 4
span.extents = (xmin, xmax)
span.onselect(xmin, xmax)
I'm woring on a simple interface where I have a text box and an interactive plot.
The idea of the text box in to used for some logs.
I'm able to get the plot and update the text from the same function, but my issue is that the text box is to large, I need to change its width.
Below is my current code and a screenshot of the result:
from IPython.display import display
from ipywidgets import Button, Layout, Textarea, HBox, VBox
import numpy as np
from ipywidgets import interact, interactive, fixed, interact_manual
import plotly.graph_objects as go
def f(x):
x = np.arange(x)
t1.value = "".join([str(c) for c in x.tolist()] )
fig = go.Figure(data=go.Scatter(x=x, y=x**2))
fig.show()
x = np.linspace(10,100,1)
interactive_plot = interactive(f, x=(1, 20))
l = Layout(flex='0 1 auto', height='400px', min_height='40px', width='10')
t1 = Textarea(value='TA: height=40px', layout=l)
The layout attribute exposes CSS properties like height, width, border, margin of the ipywidgets. This allows one to control how the ipywidgets are laid out. It is possible to control the width and height of the textbox by tweaking the values in the Layout snippet below as required. The parameters accept values in pixels(px) or percentage of available space(%) or even 'auto' to allow these values to be set automatically. You can find an example in the docs. I've set the height as 30px and width as 'auto' to automatically set a width:
l = Layout(flex='0 1 auto', height='30px', min_height='40px', width='auto')
Output:
I am trying to visualise sensor output in relation to its path.
I plot path as scatter in one figure and some range of signal amplitude in the second figure. I need to visualise (highlight) a path point at which the particular reading was taken.
I started using bokeh as a backend and in general, got very good results with visualisations I need. But I am stuck on this particular interaction.
I would like to have some marker like a vertical line anchored in the middle of the figure. When I move/scroll the amplitude plot (the bottom one), I would like to highlight the point on the path plot where the reading closest to the marker line was taken.
The example code:
(I would like to anchor the marker line and add interaction between the red dot and the vertical line taking an index of the signal, which is not implemented.)
import numpy as np
import pandas as pd
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, HoverTool, Span
from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
output_file('interactive_path_sig.html', title="interactive path")
class InteractivePath():
def __init__(self):
x = np.arange(0, 1000, 0.5)
self.df = pd.DataFrame({"x": x,
"y": np.sin(x),
"z": np.cos(x)})
self.source = ColumnDataSource(self.df)
def plot_path(self):
plt = figure(title = "Sensor Path")
plt.scatter(x="x", y="y",source=self.source,
line_color=None, size = 6)
# TODO implement interaction instead of hard coded index
index=500 # this is where I think I need to create working callback
print("x={}, y={}".format(self.df['x'][index], self.df['y'][index]))
plt.circle(x=self.df['x'][index], y=self.df['y'][index],
fill_color="red", size=15)
hover = HoverTool()
hover.tooltips=[("index", "#index"), ("senosr","#z")]
plt.add_tools(hover)
return plt
def plot_signal(self):
plt = figure(x_range=(450, 550), title="Signal Amplitude")
plt.line(x="index", y="z", source=self.source, line_color="black", line_width=2)
# TODO implement interaction instead of hard coded index
index = 500 # I think this needs emit some singal to other plot
vline = Span(location=index, dimension='height', line_color='red', line_width=3)
plt.renderers.extend([vline])
return plt
def get_grid(self):
""" place visualisation in a grid and display"""
grid = gridplot([[self.plot_path()], [self.plot_signal()]],
sizing_mode='stretch_both',)
return grid
def vis_main(self):
""" use all visualisations"""
show(self.get_grid())
if __name__=="__main__":
vis = InteractivePath()
vis.vis_main()
So a few pointers:
I think you'll want both of those plots in the same method because the columndatasource is common between them, and you can set CustomJS behaviors between them if they're in the same scope.
The index that you're using already exists within your self.df which will be easier to interact with once it's on your plot, since you can handle it with JS plot behavior instead of going back to a python variable and reloading data.
Instead of drawing a new glyph for your 'highlighted' point, consider using the 'hover' or 'selected' functionality built in. hover_color='red' for example could replace drawing and moving another class of glyph. If you want to leave statically selected so you can generate a nice report without a mouse in a screenshot, defining a callback using the built-in selected property of ColumnDataSource
I can post some actual code blocks with more specific examples, but if any of these points is a hard stop for your actual use case, it'll drive solution.
Edit:
So I got pretty close using one class method - the issue is being able to edit the second plot from the first method, not the actual change to the ColumnDataSource itself.
def plot_it(self):
self.plot_signal = figure(x_range=(450, 550), y_range=(-1, 1), title='signal')
self.plot_signal.line(x='index', y='z', source=self.source)
self.plot_signal.segment(x0=500, y0=-2, x1=500, y1=2, source=self.source)
self.plot_path = figure(title='sensor')
self.plot_path.scatter(x='x', y='y', source=self.source, hover_color='red')
jscode='''
var data = source.data;
var plot_signal = plot_signal;
var index = cb_data.index['1d'].indices;
var xmin = 0;
var xmax = 0;
if (index > 0) {
xmin = index[0] - 50;
xmax = index[0] + 50;
plot_signal.x_range.end = xmax;
plot_signal.x_range.start = xmin;
plot_signal.change.emit();
}
hover_callback = CustomJS(args=dict(source=self.source, plot_signal=self.plot_signal), code=jscode)
hover.tooltips = [('index', '#index'), ('sensor', '#z')]
self.plot_path.add_tools(hover)
def get_grid(self):
self.plot_it()
grid = gridplot([[self.plot_path], [self.plot_signal]])
return grid
That should do everything but move the line segment. I couldn't find the segment naming convention to add plot_signal.SOMEOBJECT.x0 and .x1 but it would just get added to the if (index > 0) block just like using index[0]. I took some of the style options out because I'm transcribing from another computer.
This question on moving a line segment might give you the syntax on the segment JSON object.