Код JS для ссылки на строку / столбец в Bokeh DataTable содержит ошибку - PullRequest
2 голосов
/ 23 мая 2019

Я использовал предыдущие посты здесь и здесь , чтобы попытаться выполнить код, с помощью которого я могу выбрать строку в DataTable в Bokeh и получить номер строки.Тем не менее, я обнаружил, что, используя этот код, как только я пройду 6 или 7 строку, сгенерированный номер строки будет неправильным - например, я могу щелкнуть строку 17, и она скажет, что это строка 6. Как мне решить эту проблему?

Примечание: для кода в этом сообщении ошибка возникает только после увеличения диапазона в «исходном» столбце ColourDataSource с 10 до 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))

Я приложил изображение ошибки, полученной при запуске кода.Как вы можете see in this picture Я нажал на строку 16, и она говорит индекс строки 10.

В качестве альтернативы мой другой код (ссылается на ряд различных кадров данных и т. Д., КоторыеЯ уже создал данные на локальном рабочем сервере):

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)


1 Ответ

1 голос
/ 24 мая 2019

Мой код обратного вызова не работал с таблицами, имеющими полосы прокрутки.С тех пор я обновил его до чего-то более надежного (но оно работает только для 1-го виджета таблицы в документе)

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))

Вы можете использовать active_row_ID и active_col_ID

cb_obj.indices = []чтобы разрешить обратный вызов при двойном нажатии на одну и ту же ячейку подряд.Но это вызывает обратный вызов во второй раз.

Вот почему последний цикл над active_cells предназначен для удаления .active из имени класса активных ячеек, что сделает active_row неопределенным ввторое выполнение.

if (active_row!=undefined) предназначен для отмены второго выполнения обратного вызова.

...