Search code examples
pythonsliderbokehpython-interactive

Dynamically change the coordinates and the text of annotation with slider in Bokeh plot


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.


Solution

  • 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

    Moving Lable

    If you want to modify the text of the lable, you can use a similar approach.