2

I've used previous posts here and here to try and run code whereby I can select a row in a DataTable in Bokeh, and get the row number. However I'm finding that using that code, once I get past row 6 or 7 the row number generated is wrong - for example I could be clicking on row 17 and it'll say it's row 6. How do I resolve this issue?

Note for the code in this post, the error only occurs once I increase the range in the 'source' ColumnDataSource from 10 to 20.

from random import randint
from datetime import date
from bokeh.models import ColumnDataSource, TableColumn, DateFormatter, DataTable, CustomJS
from bokeh.layouts import column
from bokeh.models.widgets import TextInput
from bokeh.plotting import curdoc

source = ColumnDataSource(dict(dates = [date(2014, 3, i + 1) for i in range(20)], downloads = [randint(0, 100) for i in range(20)]))
columns = [TableColumn(field = "dates", title = "Date", formatter = DateFormatter()), TableColumn(field = "downloads", title = "Downloads")]
data_table = DataTable(source = source, columns = columns, width = 400, height = 280, editable = True, reorderable = False)

text_row = TextInput(value = None, title = "Row index:", width = 420)
text_column = TextInput(value = None, title = "Column Index:", width = 420)
text_date = TextInput(value = None, title = "Date:", width = 420)
text_downloads = TextInput(value = None, title = "Downloads:", width = 420)
test_cell = TextInput(value = None, title = "Cell Contents:", width = 420)

source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0].children;
var row, column = '';

for (var i = 0,max = grid.length; i < max; i++){
    if (grid[i].outerHTML.includes('active')){
        row = i;
        for (var j = 0, jmax = grid[i].children.length; j < jmax; j++)
            if(grid[i].children[j].outerHTML.includes('active')) 
                { column = j }
    }
}
text_row.value = String(row);
text_column.value = String(column);
text_date.value = String(new Date(source.data['dates'][row]));
text_downloads.value = String(source.data['downloads'][row]); 
test_cell.value = column == 1 ? text_date.value : text_downloads.value; """

def py_callback(attr, old, new):
    print(test_cell.value)
    print(text_date.value)
    source.selected.update(indices = [])

source.selected.on_change('indices', py_callback)
callback = CustomJS(args = dict(source = source, text_row = text_row, text_column = text_column, text_date = text_date, text_downloads = text_downloads, test_cell = test_cell), code = source_code)
source.selected.js_on_change('indices', callback)
curdoc().add_root(column(data_table, text_row, text_column, text_date, text_downloads, test_cell))

I've attached a picture of the error I get when running the code. As you can see in this picture I've clicked on row 16 and it is saying row index 10.

Alternatively my other code (references a number of different dataframes etc. that I have already created from data on my local work server):

import pandas as pd
pd.options.mode.chained_assignment = None
import datetime as dt
import math
import random
import pandas as pd
import itertools
import pickle
from bokeh.layouts import layout
from collections import OrderedDict
from bokeh.models import ColumnDataSource, Column, TableColumn, DateFormatter, DataTable, CustomJS, DataRange1d
from bokeh.plotting import figure, curdoc

source = ColumnDataSource(dict(products=dfNew['Products'], prices=dfNew['Current Prices']))
columns = [TableColumn(field="products", title="Products"), TableColumn(field="prices", title="Current Prices")]
data_table = DataTable(source=source, columns=columns, width=400, height=350, editable=True, reorderable=False)
location_source = ColumnDataSource(dict(row=[], column=[]))

prodPx = OrderedDict()
pVal = 0
for i in products:
    key = str(i)
    prodPx[key] = [(dfNew['Current Prices'])[pVal]]
    pVal += 1

noProd = OrderedDict()
kVal = 0
for i in products:
    key = str(kVal)
    noProd[key] = [i]
    kVal += 1

prodpx_source = ColumnDataSource(prodPx)
noprod_source = ColumnDataSource(noProd)


#initial chart
x = new_dates
y = df[products[0]]
sourceChart = ColumnDataSource(data=dict(x=x, y=y))

chart = figure(title=ccy + ' Charting', x_axis_type='datetime', plot_width = 1200, plot_height=500)
chart.line('x', 'y', source=sourceChart, line_width=3, line_alpha=0.6)

#callbacks
source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0].children;
var row, column = '';

for (var i = 0,max = grid.length; i < max; i++){
    if (grid[i].outerHTML.includes('active')){
        row = i;
        for (var j = 0, jmax = grid[i].children.length; j < jmax; j++)
            if(grid[i].children[j].outerHTML.includes('active')) {
                column = j; 
                source2.data = {row: [row], column: [column]};
            }
    }
}
source.change.emit();
source2.change.emit();
source3.change.emit();
source4.change.emit();
"""

#js callback
callback = CustomJS(args=dict(source=source, source2=location_source, source3=prodpx_source,
                              source4=noprod_source), code=source_code)

source.selected.js_on_change('indices', callback)

#python callback
def py_callback(attr, old, new):
    row = str((location_source.data['row'][0]))
    chartVals = (noprod_source.data[row][0])
    try:
        yVar = df[chartVals]
    except:
        yVar = df[totalProducts[0]]
    sourceChart.data = dict(x=x, y=yVar)
    source.selected.update(indices=[])
    print(location_source.data)

source.selected.on_change('indices', py_callback)
layout = layout([data_table], [chart])
curdoc().add_root(layout)


5
  • 1
    You need to post your code otherwise it is impossible to guess. Commented May 23, 2019 at 10:51
  • Hi Tony - have posted the code from one of the linked posts - in the 'source' ColumnDataSource have changed 'for i in range(10)' to 'for i in range(20)' and get the error. Eg. when I click on row number 14, the 'Row Index' generated is 8. Commented May 23, 2019 at 21:36
  • Sorry, but I asked you to post your code that is not working, not the code from other post that is working. Commented May 23, 2019 at 21:49
  • Hi Tony - sorry to clarify but the above code from the other post that I re-posted (with the slight change of range(10) to range (20)) is not working for me. I can run it and it'll perform as expected whilst clicking out to about row 10, but after that the 'Row' it spits out is incorrect. I've also posted my own code above which has the same issue, but it references a number of data frames and variables etc. from a lot of very long previous code so won't be able to run as is. I appreciate the assistance - thanks! Commented May 23, 2019 at 22:09
  • Tony - I've added a screenshot of what happens when I run the code from the other post above to assist with the explanation of the error. Commented May 23, 2019 at 22:15

1 Answer 1

2

My callback code did not work with tables that have scrollbars. Since then I updated it to something a bit more robust (but it works only for the 1st table widget in the document)

from bokeh.io import show
from bokeh.layouts import widgetbox
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable,TableColumn

column_list = ['col1','col2','col3']

source = ColumnDataSource(data = {key:range(20) for key in column_list})

columns = [TableColumn(field=col, title=col) for col in column_list]

data_table = DataTable(source=source, columns=columns, width=400, height=280,selectable=True)

source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0];

var active_row = grid.querySelectorAll('.active')[0];

if (active_row!=undefined){

    var active_row_ID = Number(active_row.children[0].innerText);

    for (var i=1, imax=active_row.children.length; i<imax; i++){
        if (active_row.children[i].className.includes('active')){
            var active_col_ID = i-1;
        }
    }

    console.log('row',active_row_ID);
    console.log('col',active_col_ID);

    var active_cells = grid.querySelectorAll('.active');
    for (i=0, imax=active_cells.length;i<imax;i++){
        active_cells[i].classList.remove('active');
    }

    cb_obj.indices = [];
}
"""

source.selected.js_on_change('indices', CustomJS(args={'source':source},code= source_code) )

show(widgetbox(data_table))

You can use active_row_ID and active_col_ID

cb_obj.indices = [] is there to allow the callback to trigger when clicking the same cell twice in a row. But this triggers the callback a second time.

This is why the last loop over active_cells is there to strip the .active from the classname of the active cells, which will make active_row undefined in the second execution.

if (active_row!=undefined) is there to discard the second execution of the callback.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.