I currently work on a program to analyse some data through a standalone html created in bokeh. I would like to give the user the option to load xy-data from a csv/txt file through a JS callback. I am currently doing the following and I am not sure, why it is not working.
import bokeh
import pandas as pd
from bokeh import events
from bokeh.io import show, output_file
from bokeh.plotting import figure, output_file, show
from bokeh.models.widgets import Button
from bokeh.layouts import Column
from bokeh.models import HoverTool, CustomJS,AjaxDataSource, ColumnDataSource
output_file('jsreadcsv.html')
d = {'x': [1000,20000], 'y': [0,1]}
df=pd.DataFrame(data=d)
source=ColumnDataSource(df)
js_upload2 = CustomJS(args=dict(source=source), code="""
var xydat=source.data;
var input = document.createElement('input');
input.type = 'file';
delimiter = '\t'
input.onchange = e => {
for(i=xydat['x'].length-1; i >= 0; i--){
xydat['x'].splice(i,1);
xydat['y'].splice(i,1);
}
// getting a hold of the file reference
var file = e.target.files[0];
// setting up the reader
var reader = new FileReader();
reader.readAsText(file,'UTF-8');
// here we tell the reader what to do when it's done reading...
reader.onload = readerEvent => {
var content = readerEvent.target.result; // this is the content!
///console.log( content );
var lines = content.split('\\n')
for(i=10; i < lines.length; i++){
line = lines[i].split('\\t')
xydat['x'].push(parseFloat(line[0]))
xydat['y'].push(parseFloat(line[1]))
}
/// Find maximum and normalise data
max_y=0.0
for(i=0; i < xydat['y'].length; i++){
if(xydat['y'][i] > max_y){
max_y = xydat['y'][i];
}
}
for(i=0; i < xydat['y'].length; i++){
xydat['y'][i] = xydat['y'][i] / max_y;
}
console.log(xydat['x'])
console.log(xydat['y'])
source.change.emit();
}
}
input.click();
""")
Button_load = Button(name='loadfile')
Button_load.js_on_click(js_upload2)
p=figure(plot_width = 950, plot_height = 600,x_axis_label = 'x', y_axis_label = 'y', tools=['pan,wheel_zoom, reset'], )
p.line(x='x', y='y', color='black', source=source)
layout = Column(Button_load,p)
show(layout)
I am really not sure why it does not work. I get a “inconsistent column length in the JS console”. I don’t understand why.
Thank you very much for your help in advance!
Daniel
I can’t help answer your question but I have an application where a user uploads an excel file, which gets put into a dataframe and various plots are generated. I did it using the FileInput widget. It was discussed in this question.
@d-que It looks like you have some console.log statements in the JS callback code. Can you share some results of “printf” debugging? How far along do things get before the state or outcome is different than your expectations?
Of course! the console log shows me, that I the correct arrays in xydat as arrays of floats. The lengths of both arrays x and y are the same. The only warning I get is
[Warning] [bokeh] – “data source has columns of inconsistent lengths” (bokeh-1.4.0.min.js, line 321, x3)
So I am confused about it. In addition, the plot of p seems to be updating but shows me an empty canvas.
One thing you might try is to update source.data “all at once”, i.e. first construct a new object with the columns you want, then assign it to source.data:
new_data = {x: [], y: []}
// add new data points here
source.data = new_data
If you do this, this avoids any possibility that the columns in the actual data source are ever different lengths, also if you assign to source.data then Bokeh can detect that automatically, there is no need to call source.change.emit()