pythondataframebokehbokehjspandas-bokeh

Python-Bokeh application:Unable to export updated data from Webapp to local system by clicking on Bokeh Button widget


I am currently working on creating a Python-Bokeh based webapp Application running on server.In this application,user can preview the data from a pandas dataframe(shown using BOKEH DATATABLE) and can modify the data as per business needs. After refreshing the dataframe,the user will need to export the dataframe to his local system(in csv format) by clicking a BOKEH BUTTON widget.

Now i was able to create the webapp application but it is causing issues while exporting the data to the local system. When i click on the button to download, a csv file gets downloaded having the intial default data present in the dataframe.After that even though I update the dataframe and again click on the Download BUTTON,same old default data is getting downloaded instead of the updated dataframe.

Below is the code which I tried at my end. Please suggest, what changes need to be done to the below snippet so that everytime the data gets refreshed and download button is clicked it will export the latest data showing in the datatable.

from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable, NumberFormatter, TableColumn, Button
from bokeh.layouts import column,row
import pandas as pd
import numpy as np

# Create Default data
data = {'COL_1_DEFAULT': np.random.randint(200, size=100),
        'COL_2_DEFAULT': np.random.randint(200, size=100),
        'COL_3_DEFAULT': np.random.randint(200, size=100),
        'COL_4_DEFAULT': np.random.randint(200, size=100)}

TABLE1_DATA = pd.DataFrame(data)
source_new = ColumnDataSource(TABLE1_DATA)
Columns_tab1 = [TableColumn(field=Ci, title=Ci) for Ci in TABLE1_DATA.columns] # bokeh columns
data_table1 = DataTable(columns=Columns_tab1, source=source_new,height = 200) 

# Javascript for exporting data
js_code="""
var data = source.data;
var columns = Object.keys(source.data);
var filetext = [columns.join(',')].shift().concat('\\n');
var nrows = source.get_length();

for (let i=0; i < nrows; i++) {
        let currRow = [];

        for (let j = 0; j < columns.length; j++) {
            var column = columns[j]
            currRow.push(source.data[column][i].toString())
            }
        currRow = currRow.concat('\\n')
        var joined = currRow.join();
        filetext = filetext.concat(joined);
        }

var filename = 'data_output.csv';
var blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' });

//addresses IE
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}

else {
var link = document.createElement("a");
link = document.createElement('a')
link.href = URL.createObjectURL(blob);
link.download = filename
link.target = "_blank";
link.style.visibility = 'hidden';
link.click();
URL.revokeObjectURL(link.href);
}
"""

# Created Button for Export
Exportbutton = Button(label="Download", button_type="success")
Exportbutton.js_on_click(CustomJS(args=dict(source=source_new),code = js_code  ))

def refresh_table():
    global source_new,Columns_tab1,data_table1,button
    global TABLE1_DATA
    df = []
    for ii in range(1, 11):
        df.append({'x': ii, 
                   'y': 1000 * np.random.rand(),
                   'z' : 11 * np.random.rand()
                  })
    df = pd.DataFrame(df)
    source_new = ColumnDataSource(df)

    Columns_tab1 = [TableColumn(field='x', title='Col 1'),TableColumn(field='y', title='Col 2',formatter=NumberFormatter(format='$0,0.00',text_align='right')),TableColumn(field='z', title='Col 3')]
    data_table1.update(source=source_new, columns=Columns_tab1, width=500, height=200)

    Exportbutton.js_on_click(CustomJS(args=dict(source=ColumnDataSource(df)),code = js_code  ))

# Created Button for Refreshing dataframe data        
table_Refresh_Button = Button(label='Refresh  Table',height = 30,width = 200,button_type="success")
table_Refresh_Button.on_click(refresh_table)


layout = column(data_table1,table_Refresh_Button, Exportbutton)
curdoc().add_root(layout)

Solution

  • It's a bug. I just created https://github.com/bokeh/bokeh/issues/10146

    As a workaround, replace

    Exportbutton.js_on_click(CustomJS(args=dict(source=ColumnDataSource(df)),code = js_code  ))
    

    with

    Exportbutton.js_event_callbacks['button_click'] = [CustomJS(args=dict(source=ColumnDataSource(df)), code=js_code)]