I was adding a label as an annotation to a figure. I can set the font size of the label upfront. However, when the size of the browser is adjusted, only the size of the figure is reponsive, the font size of the label is un-responsive.
fig = figure(x_axis_type='datetime', y_axis_label=labels[i],
toolbar_location=None, active_drag=None,
active_scroll=None)
fig.line(x='time', y='data', source=source, line_color='red')
annotation = Label(x=10, y=10, text='text', text_font_size='60px', text_color='white', x_units='screen', y_units='screen', background_fill_color=None))
I tried to adjust the font size using the height of the figure, but this does not work. Is there a way to achieve this purpose? Thank you for any hint/help.
annotation.text_font_size = str(fig.plot_height * 0.1)+'px'
This is an issue more about css rather than bokeh itself. From here, one has a broad range of selections for the unit of the font size. For my situation, 'vh' will do the trick and the font size is now responsive to the dimension of the browser. For example:
annotation = Label(x=10, y=10, text='text', text_font_size='10vh', text_color='white', x_units='screen', y_units='screen', background_fill_color=None))
Standalone example:
from bokeh.server.server import Server
from bokeh.models import ColumnDataSource, Label
from bokeh.plotting import figure
from bokeh.layouts import column
import numpy as np
import datetime as dt
from functools import partial
import time
def f_emitter(p=0.1):
v = np.random.rand()
return (dt.datetime.now(), 0. if v>p else v)
def make_document(doc, functions, labels):
def update():
for index, func in enumerate(functions):
data = func()
sources[index].stream(new_data=dict(time=[data[0]], data=[data[1]]), rollover=1000)
annotations[index].text = f'{data[1]: .3f}'
# print(figs[index].height)
sources = [ColumnDataSource(dict(time=[], data=[])) for _ in range(len(functions))]
figs = []
annotations = []
for i in range(len(functions)):
figs.append(figure(x_axis_type='datetime',
y_axis_label=labels[i], toolbar_location=None,
active_drag=None, active_scroll=None))
figs[i].line(x='time', y='data', source=sources[i])
annotations.append(Label(x=10, y=10, text='', text_font_size='10vh', text_color='black',
x_units='screen', y_units='screen', background_fill_color=None))
figs[i].add_layout(annotations[i])
doc.add_root(column([fig for fig in figs], sizing_mode='stretch_both'))
doc.add_periodic_callback(callback=update, period_milliseconds=100)
if __name__ == '__main__':
# list of functions and labels to feed into the scope
functions = [f_emitter]
labels = ['emitter']
server = Server({'/': partial(make_document, functions=functions, labels=labels)})
server.start()
server.io_loop.add_callback(server.show, "/")
try:
server.io_loop.start()
except KeyboardInterrupt:
print('keyboard interruption')
Related
I have a Bokeh plot in which I have a slider. I want to change the coordinates of the line drawn with the slider, as shown in the screenshot of the figure. When I change the slider, the line changes its coordinates.
I tried using a slider widget with columndatasource. But, as I am new to Python, I cannot get to move the location and text of the label with the slider. Is there a way to do that?
My code is given below:
import math
import numpy as np
from bokeh.io import output_file
from bokeh.plotting import figure, show
from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider, Label, LabelSet
from bokeh.plotting import ColumnDataSource, figure, show
from bokeh.models import Arrow, OpenHead, NormalHead, VeeHead
theta = 0 #input the value here
theta = np.radians(-theta)
#Inputs to be made text boxes
sig_x = 10
# line
x=[1,1]
y=[-1,1]
x1=[1,1]
y1=[1,1]
I want to introduce a variable which will change with the slider also, which, for now is 10 here.
sig_1 = 10*sig_x
then i introduced dictionaries, and along with x=x, y=y the x1=x1, y1=y1.
source = ColumnDataSource(data=dict(x=x, y=y))
fig = figure(title = 'Test of Text Rotation',
plot_height = 300, plot_width = 300,
x_range = (-3,3), y_range=(-3,3),
toolbar_location = None)
I could not find a way to add label to the line, so I added layout (from tutorial example). However, unlike fig.line command, the 'x' and 'y' cannot be added as variables (pardon me if i do not use the right jargon).
citation = Label(x=1, y=1, text = str(sig_1))
fig.line('x', 'y',source=source, line_width = 2) # Main Stress block
fig.add_layout(citation)
amp_slider = Slider(start=0, end=360, value=theta, step=1, title="theta")
# Adding callback code,
callback = CustomJS(args=dict(source=source ,val=amp_slider),
code="""
const data = source.data;
var x = data['x'];
var y = data['y'];
var pi = Math.PI;
var theta = -1*(val.value) * (pi/180);
x[0]=(1*Math.cos(theta))-(1*Math.sin(theta)); // addition
x[1]=(1*Math.cos(theta))+(1*Math.sin(theta)); // addition
y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta)); // addition
y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta)); // addition
source.change.emit();
""")
amp_slider.js_on_change('value', callback)
layout = row(fig, column(amp_slider),)
show(layout)
I added the lines of x1[0]=(1*Math.cos(theta))-(1*Math.sin(theta)), x1[1]=(1*Math.cos(theta))+(1*Math.sin(theta));, y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta)); and y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta));
This code, as anticipated does not move the label along with the line. Any explanation of what i am doing wrong, and the possibility of doing it will be very helpful.
You can pass the Lable to the CustomJS-callback as well and modify the values of this model like you do with the ColumnDataSource. Don't forget to call lable.change.emit();.
See the complete example below.
import numpy as np
from bokeh.plotting import figure, show, output_notebook
from bokeh.layouts import row
from bokeh.models import CustomJS, Slider, Label, ColumnDataSource
output_notebook()
theta = 0 #input the value here
theta = np.radians(-theta)
#Inputs to be made text boxes
sig_x = 10
source = ColumnDataSource(data=dict(x=[1,1], y=[-1,1]))
fig = figure(
title = 'Test of Text Rotation',
plot_height = 300,
plot_width = 300,
x_range = (-3,3),
y_range=(-3,3),
toolbar_location = None
)
fig.line('x', 'y',source=source, line_width = 2)
citation = Label(x=1, y=1, text = str(10*sig_x))
fig.add_layout(citation)
amp_slider = Slider(start=0, end=360, value=theta, step=1, title="theta")
# Adding callback code
callback = CustomJS(args=dict(source=source ,val=amp_slider, lable=citation),
code="""
const data = source.data;
var x = data['x'];
var y = data['y'];
var pi = Math.PI;
var theta = -1*(val.value) * (pi/180);
x[0]=(1*Math.cos(theta))-(1*Math.sin(theta));
x[1]=(1*Math.cos(theta))+(1*Math.sin(theta));
y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta));
y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta));
source.change.emit();
lable['x'] = x[1]
lable['y'] = y[1]
lable.change.emit();
"""
)
amp_slider.js_on_change('value', callback)
layout = row(fig, amp_slider)
show(layout)
Result
If you want to modify the text of the lable, you can use a similar approach.
I am trying to improve my plotting function. I want to plot data using my plotGraph function coming from an EEG board in real-time, pulling samples from an LSL # 250Hz. Previously, I had a functional version using the regular self.ax.plot(x,y), clearing the data with self.ax.clear() every time the plot needed to refresh. Nonetheless, some profiling showed that my code was taking way too much time to plot in comparison to the rest of it.
One of the suggestions I got was to use set_data instead of plot and clear. I have multiple lines of data that I want to plot simultaneously, so I tried following Matplotlib multiple animate multiple lines, which you can see below (adapted code). Also, I was told to use self.figure.canvas.draw_idle(), which I tried, but I'm not sure if I did it correctly.
Unfortunately, it didn't work, the graph is not updating and I can't seem to find why. I'm aware that the source I just mentioned uses animation.FuncAnimation but I'm not sure that would be the problem. Is it?
Any ideas of why none of my lines are showing in my canvas' graph?
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class AppWindow:
def plotGraph(self, x, y):
for lnum,line in enumerate(self.lines):
line.set_data(x[:], y[:, lnum])
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def __init__(self):
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg =
bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=2, color=tx_color)[0]
self.lines.append(lobj)
for line in self.lines:
line.set_data([],[])
def start(self):
self.root.mainloop()
You chart is empty because you are plotting empty arrays:
line.set_data([],[])
If you fill in the line arrays, the chart plots correctly.
Try this code. It updates the chart with new random data every second.
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import random
bg_color='grey'
tx_color='green'
sc_color='linen'
numchan = 8
chlen = 100
xvals=[(x-40)/20 for x in range(chlen)] # X coordinates
chcolors= ['gold','blue','green','maroon','red','brown','purple','cyan']
class AppWindow:
def plotGraph(self):
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def UpdateChannelData(self): # callback with new data
# fake random data
for i,ch in enumerate(self.chdata):
for p in range(len(ch)):
ch[p] += (random.random()-.5)/100
self.lines[i].set_data(xvals, ch)
self.plotGraph()
self.root.after(100, self.UpdateChannelData) # simulate next call
def __init__(self):
global chzero
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg = bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
#numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=1, color=chcolors[index])[0]
self.lines.append(lobj)
# set flat data
self.chdata = [[0 for x in range(chlen)] for ch in range(numchan)]
self.root.after(1000, self.UpdateChannelData) # start data read
def start(self):
self.root.mainloop()
AppWindow().start()
Output:
I am trying to use bokeh to plot the iris data and modify the fill color of the circles interactively but I am running into a problem. I call the plot and the circle with the following:
plot = figure(plot_height=600, plot_width=1000, title="Iris Data",
x_axis_label = 'Sepal length (cm)',
y_axis_label = 'Sepal width (cm)',
tools = "crosshair, pan, reset, save, wheel_zoom")
plot_circle = plot.circle(x='sepal_length', y='sepal_width', source=source,
line_color=None, fill_color={'field':'petal_width','transform':color_mapper},
size='size', fill_alpha = 0.2)
which works but when I try to add the interactivity in the call back it is not clear to me how to modify the 'field' parameter in the fill_color argument to circle. I have tried this:
def update_bubble_color(attrname, old, new):
if new=='petal_width':
color_mapper.low = min(flowers['petal_width'])
color_mapper.high = max(flowers['petal_width'])
fill_color.field='petal_width'
return
if new=='petal_length':
color_mapper.low = min(flowers['petal_length'])
color_mapper.high = max(flowers['petal_length'])
fill_color.field='petal_length'
return
select_bubble_color.on_change('value', update_bubble_color)
the color mapper limits are handled correctly but the colors are not scaled according to the new choice. When I attempt to change it to petal_length with fill_color.field='petal_length' I get an "'name 'fill_color' is not defined" error.
Any help greatly appreciated!
Full code below for reference
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource, LinearColorMapper
from bokeh.models.widgets import Select
from bokeh.plotting import figure
# Load Data
from bokeh.sampledata.iris import flowers
# Global constants (even if python dies not like it)
min_bubble_size = 10
max_bubble_size = 90
def get_scaled_size(vector):
min_vector = min(vector)
max_vector = max(vector)
scaling = (max_bubble_size-min_bubble_size)/(max_vector-min_vector)
scaled_size = [ scaling*(item-min_vector) + min_bubble_size for item in vector]
return scaled_size
# Color Mapper
color_mapper = LinearColorMapper(palette='Inferno256',
low = min(flowers['petal_width']),
high = max(flowers['petal_width']) )
# Define source
flowers['size'] = get_scaled_size(flowers['petal_length'])
source = ColumnDataSource(flowers)
# Set up plot
plot = figure(plot_height=600, plot_width=1000, title="Iris Data",
x_axis_label = 'Sepal length (cm)',
y_axis_label = 'Sepal width (cm)',
tools = "crosshair, pan, reset, save, wheel_zoom")
plot_circle = plot.circle(x='sepal_length', y='sepal_width', source=source,
line_color=None, fill_color={'field':'petal_width','transform':color_mapper},
size='size', fill_alpha = 0.2)
# Set up widgets
select_bubble_size = Select(title ='Bubble size by', value='petal_width',
options = ['petal_width','petal_length'],
width = 200)
select_bubble_color = Select(title ='Bubble color by', value='petal_width',
options = ['petal_width', 'petal_length'],
width = 200)
# Colorbar
from bokeh.models import ColorBar
bar = ColorBar(color_mapper=color_mapper,location=(0,0))
plot.add_layout(bar, 'left')
# Set up callbacks=
# Bubble size call back
def update_bubble_size(attrname, old, new):
if new=='petal_width':
source.data['size'] = get_scaled_size(flowers['petal_width'])
return
if new=='petal_length':
source.data['size'] = get_scaled_size(flowers['petal_length'])
return
select_bubble_size.on_change('value', update_bubble_size)
# bubble color call back
def update_bubble_color(attrname, old, new):
if new=='petal_width':
color_mapper.low = min(flowers['petal_width'])
color_mapper.high = max(flowers['petal_width'])
fill_color.field='petal_width'
return
if new=='petal_length':
color_mapper.low = min(flowers['petal_length'])
color_mapper.high = max(flowers['petal_length'])
fill_color.field='petal_length'
return
select_bubble_color.on_change('value', update_bubble_color)
# Set up layouts and add to document
curdoc().add_root(column(plot, row(select_bubble_size,select_bubble_color), width=800))
curdoc().title = "Iris Data"
fill_color is a property of the glyph, you will need to access it through the glyph:
plot_circle.glyph.fill_color
In your script there is not free variable fill_color anywhere, which is the source of the NameError.
fairly new to python/bokeh so apologies. I'm trying to use the pointdrawtool to add arrows to my figure on a bokeh server generated plot. I can add it by making it add invisible circles which share the same columndatasource and therefore arrows are drawn but I then want to adjust the arrow start points via a callback so that they are arrows rather than just triangles.
I've tried various things I've seen here and elsewhere but I've so far failed. I don't have a good understanding of what can and cannot produce a callback. If there's a better simpler way of doing it then that would be fine too.
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.layouts import layout, row, column
from bokeh.plotting import figure, output_file, show, save, reset_output
from bokeh.models import Label, BoxAnnotation, CustomJS, Slider, Button, ColumnDataSource, BoxEditTool, FreehandDrawTool,PointDrawTool, Arrow, NormalHead
from bokeh.models.widgets import Select
import pandas as pd
import numpy as np
import webbrowser as wb
def make_document(doc):
try:
#set the dimensions for the plot
x_start=1600
x_end=2530
y_start=1800
y_end=5300
#### set up figure
p = figure(plot_width=1000, plot_height=600, x_range=(x_start,x_end),
y_range=(y_end,y_start), tools="pan, wheel_zoom,box_zoom,reset, undo,
redo")
#### set up annotation color and thickness:
thick_ann=10.0
col_ann="yellow"
alpha_ann=0.7
### source dataset and associated code for for any added arrows
#source_ar=ColumnDataSource( {"xs":[0,0],"ys":[0,3],"xe":[1,1], "ye":[1,4]})
source_ar=ColumnDataSource( {"xs":[],"ys":[],"xe":[], "ye":[]})
a1=Arrow(end=NormalHead(size=thick_ann*3, fill_color=col_ann, line_color=col_ann, line_alpha=alpha_ann, fill_alpha=alpha_ann),x_start='xs', y_start='ys', x_end='xs', y_end='ys', source=source_ar, line_color=col_ann, line_width=thick_ann, line_alpha=alpha_ann)
p.add_layout(a1)
### add invisible circle - use this to add and remove arrows
c1=p.circle('xs','ys', size=thick_ann*3,alpha=0.0, source=source_ar)
artool=PointDrawTool(renderers=[c1])
p.add_tools(artool)
#### example callback I think I want to run when adding an arrow via the tool - adjust start
#### values so is actual arrow
def arr_callback(attr, old, new):
source_ar.data["xe"][-1]=source_ar.data["xs"][-1] +5
source_ar.data["ye"][-1]=source_ar.data["ys"][-1] +5
#c1.glyph.data_source.on_change('selected',arr_callback)
doc.add_root(p)
except:
server.stop()
apps = {'/': Application(FunctionHandler(make_document))}
server = Server(apps, port=5003)
server.start()
wb.open('http://localhost:5003', new=2)
Expected result - add a point which adds an invisible circle, an arrow is also drawn and the start point then adjusted so it is an arrow not a triangle.
As far as I know only JS callbacks can be added to the tools in Bokeh in general (CrossHairTool. TapTool, etc...). Unfortunately it is not well documented why some tools doesn't support callbacks at all (like ResetTool or PointDrawTool, etc...). Trying to attach a callback to PointDrawTool gives error.
But if you just want to add a new arrow at each mouse click then another option would be to use e.g. JS callback attached to the plot canvas (see code below for Bokeh v1.0.4). Run the code as python app.py
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource, Arrow, NormalHead, Segment
def make_document(doc):
p = figure(plot_width = 1000, plot_height = 600, x_range = (0, 10),
y_range = (0, 6), tools = "pan, wheel_zoom,box_zoom,reset,undo,redo")
#### set up annotation color and thickness:
thick_ann = 10.0
col_ann = "red"
alpha_ann = 0.7
#### source dataset and associated code for for any added arrows
source = ColumnDataSource(data = {"xs":[1, 2, 3], "ys":[1, 2, 3], "xe":[4, 5, 6], "ye":[1, 2, 3], 'width': [30] * 3, 'color': [col_ann] * 3 })
a1 = Arrow(end = NormalHead(size = thick_ann * 3, fill_color = col_ann, line_color = col_ann, line_alpha = alpha_ann, fill_alpha = alpha_ann), x_start = 'xs', y_start = 'ys', x_end = 'xe', y_end = 'ye', source = source, line_color = col_ann, line_alpha = alpha_ann)
s1 = p.segment(x0 = 'xs', y0 = 'ys', x1 = 'xe', y1 = 'ye', color = 'color', source = source)
p.add_layout(a1)
code = """ new_x = Number(cb_obj.x);
new_y = Number(cb_obj.y);
data = {xe: [new_x], ys: [new_y], ye: [new_y]};
data['xs'] = [Number(data['xe']) - 3];
data['color'] = ['red'];
data['width'] = [90];
source.stream(data); """
p.js_on_event('tap', CustomJS(args = dict(source = source), code = code))
doc.add_root(p)
io_loop = IOLoop.current()
server = Server(applications = {'/': Application(FunctionHandler(make_document))}, io_loop = io_loop, port = 5001)
server.start()
server.show('/')
io_loop.start()
Result:
I am very new to Bokeh but I wanted to do that this morning and came up with that solution:
from bokeh.plotting import figure, output_notebook, show
from bokeh.models import ColumnDataSource, PointDrawTool
from bokeh.events import Tap
output_notebook()
def bkapp(doc):
p = figure(x_range=(0, 10), y_range=(0, 10), width=400,
height=400, tools=[])
source = ColumnDataSource({
'x': [1, 5, 9], 'y': [1, 5, 9], 'color': ['red', 'green', ' yellow']
})
def callback(event):
source.data["x"].append(event.x)
source.data["y"].append(event.y)
print(source.data)
renderer = p.scatter(x="x", y="y", source=source, color="color",
size=10)
draw_tool = PointDrawTool(renderers=[renderer],
empty_value='black')
p.on_event(Tap, callback)
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool
doc.add_root(p)
show(bkapp)
This is using an embedded server in the Jupyter notebook.
I am just drawing points...
Does this help?
I am writing a routine where I can append a set of datafiles. The first part of the code imports a single datafile and displays it using matplotlib. I can then use a slider to define a specific x range (i.e. to exclude noisy or irrelevant parts). The second part of the code involves an edit routine where I edit the range of all the datafiles in the specific folder based on this newly set x range (i.e. removing all of the rows).
The first part of the code is not a problem for me and results in the following interface. My problem is with the second part. I want the Edit button to close the figure and continue the rest of the code (the datafile editing part). This is necessary because I need the slider values to define the new datarange.
My initial thought was to place part 2 of the code after plt.show(). The Edit button would simply close the figure and the rest of the code would continue. An example of my code without using my actual data:
### Part 1 ###
import os
import sys
import six
import tkinter as tk
from tkinter import Tk
from tkinter import filedialog
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider, Button
class MinMaxSlider(Slider):
def __init__(self, ax, label, valmin, valmax, **kwargs):
self.valinit2 = kwargs.pop("valinit2", valmax)
self.val2 = self.valinit2
Slider.__init__(self,ax, label, valmin, valmax, **kwargs)
self.poly.xy = np.array([[self.valinit,0],[self.valinit,1],
[self.valinit2,1],[self.valinit2,0]])
self.vline.set_visible(False)
def set_val(self, val):
if np.abs(val-self.val) < np.abs(val-self.val2):
self.val = val
else:
self.val2 = val
self.poly.xy = np.array([[self.val,0],[self.val,1],
[self.val2,1],[self.val2,0]])
self.valtext.set_text(self.valfmt % self.val +"\n"+self.valfmt % self.val2)
if self.drawon:
self.ax.figure.canvas.draw_idle()
if not self.eventson:
return
for cid, func in six.iteritems(self.observers):
func(self.val,self.val2)
def update(mini,maxi):
ax.set_xlim(mini,maxi)
def edit(event):
plt.close()
def find_nearest(array,value):
return (np.abs(array-value)).argmin()
## Part 1 ##
plt.ion()
x = np.array(range(100))
y_low = 10*np.array(range(100))
y_high = 10*np.array(range(100))
font = {'family' : 'Calibri',
'weight' : 'normal',
'size' : 14}
mpl.rc('font', **font)
axcolor = 'lightgoldenrodyellow'
fig,(ax, sliderax) = plt.subplots(figsize=(12,8),
nrows=2,gridspec_kw={"height_ratios":[1,0.05]})
fig.subplots_adjust(hspace=0.5)
ln1 = ax.plot(x,y_low, color = 'dodgerblue', label = 'Low')
ax.set_xlabel('x', fontsize=24)
ax.xaxis.labelpad = 15
ax.set_ylabel('y', fontsize=24, color = 'dodgerblue')
ax.yaxis.labelpad = 15
for tl in ax.get_yticklabels():
tl.set_color('dodgerblue')
ax1 = ax.twinx()
ln2 = ax1.plot(x,y_high, color = 'darkred', label = 'High')
ax1.set_ylabel('y', fontsize=24, color = 'darkred')
for tl in ax1.get_yticklabels():
tl.set_color('darkred')
lns = ln1+ln2
labs = [l.get_label() for l in lns]
ax1.legend(lns, labs, loc=0)
slider = MinMaxSlider(sliderax,'x(min/max)',x.min(),x.max(),
valinit=x.min(),valinit2=x.max())
slider.on_changed(update)
update(x.min(),x.max())
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Edit', color=axcolor, hovercolor='0.975')
button.on_clicked(edit)
plt.show()
## Part 2 ##
idx_low = find_nearest(x, slider.val)
idx_high = find_nearest(x, slider.val2)
print(idx_low)
print(idx_high)
However the idx_low and idx_high are already calculated and printed while the figure is open, and thus based on the initial values of the slider. I want them to be calculated after clicking the Edit button and closing the figure so that they are based on the slider values set by the user. How can I achieve this? Do I need to edit the Edit event function? Thank you for your help.