How to update dataframe with bokeh callback?

460 Views Asked by At

I’m trying to recalculate the distance between two coordinates, one is given through 2 TextInput classes and the other is in a dataframe. I want to resolve this issue in order to select the data based on proximity conditions, maybe select options, but none of that can work unless the distance is dynamically updated based on the 2 TextInput inputs.

I followed the docs and samples, but for some reason the df[‘distance’] is not changing.

My code is below, I’m defining a function inside the callback to calculate the distance.

import numpy as np
import pandas as pd
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CustomJS, TextInput
import math
from bokeh.layouts import column, row
from bokeh.io import show

df = pd.DataFrame(np.random.rand(100,2),columns=list('Xy'))

def distance(origin, destination):
   
    lat1, lon1 = origin
    lat2, lon2 = destination
    radius = 6371  # km

    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)
    a = (math.sin(dlat / 2) * math.sin(dlat / 2) +
         math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
         math.sin(dlon / 2) * math.sin(dlon / 2))
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    d = radius * c

    return d


cord_1, cord_2 = 30.56289,70.38185

df['distance'] = [distance((cord_1,cord_2),(df['X'][i],df['y'][i])) for i in range(0,len(df['X']))]
source=ColumnDataSource(df)



cord_x = TextInput(title="X-Coordinates")
cord_y = TextInput(title="Y-Coordinates")


TOOLTIPS = [

    ('Distance','@distance')
        ]


p = figure(title='Sample Distance',width = 800, height = 600,tooltips = TOOLTIPS)
p.circle(x='X',y='y',size = 10,source=source)

callback = CustomJS(args=dict(source=source, cord_x=cord_x, cord_y=cord_y),
                    code="""
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}
                     
var data = source.data;
var distance = 0;
var A = cord_x.value;
var B = cord_y.value;
    
 //apply function
for(var i = 1; i < data['X'].length ; i++) {
    distance = getDistanceFromLatLonInKm(A,B,data['X'][i],data['y']);
    data['distance'][i] = distance;
}
source.change.emit()

""")

source.js_on_change('data', callback)

layout = row(
    p,
    column(cord_x, cord_y),
)

show(layout)

1

There are 1 best solutions below

0
lesk_s On

So if I'm understanding this right, you're looking to dynamically update cord_x and cord_y which serves as the base coordinate to calculate the distance from the points in your dataframe? And the coordinates themselves remain the same, while the distance in the tooltip changes.

Made a couple small changes: First, the js_on_change needs to be applied to the widget. Second, it needs to call 'value' as the first argument. And last, you were missing the [i] part in data['y'][i]. Here is the full code:

import numpy as np
import pandas as pd
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CustomJS, TextInput
import math
from bokeh.layouts import column, row
from bokeh.io import show

df = pd.DataFrame(np.random.rand(100,2),columns=list('Xy'))

def distance(origin, destination):
   
    lat1, lon1 = origin
    lat2, lon2 = destination
    radius = 6371  # km

    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)
    a = (math.sin(dlat / 2) * math.sin(dlat / 2) +
         math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
         math.sin(dlon / 2) * math.sin(dlon / 2))
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    d = radius * c

    return d


cord_1, cord_2 = 30.56289,70.38185

df['distance'] = [distance((cord_1,cord_2),(df['X'][i],df['y'][i])) for i in range(0,len(df['X']))]
source=ColumnDataSource(df)

cord_x = TextInput(title="X-Coordinates")
cord_y = TextInput(title="Y-Coordinates")


TOOLTIPS = [

    ('Distance','@distance')
        ]


p = figure(title='Sample Distance',width = 800, height = 600,tooltips = TOOLTIPS)
p.circle(x='X',y='y',size = 10,source=source)

callback = CustomJS(args=dict(source=source, cord_x=cord_x, cord_y=cord_y),
                    code="""
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}
                     
var data = source.data;
var distance = 0;
var A = cord_x.value;
var B = cord_y.value;
    
 //apply function
for(var i = 1; i < data['X'].length ; i++) {
    distance = getDistanceFromLatLonInKm(A,B,data['X'][i],data['y'][i]);
    data['distance'][i] = distance;
}
source.change.emit()

""")

cord_x.js_on_change('value', callback)
cord_y.js_on_change('value', callback)

layout = row(
    p,
    column(cord_x, cord_y),
)

show(layout)