I have a Bokeh plotting app, and I need to allow the user to upload a CSV file and modify the plots according to the data in it. Is it possible to do this with the availabl
Although there is no native Bokeh widget for file input. It is quite doable to extend the current tools provided by Bokeh. This answer will try to guide you through the steps of creating a custom widget and modifying the bokeh javascript to read, parse and output the file.
First though a lot of the credit goes to bigreddot's previous answer on creating the widget. I simply extended the coffescript in his answer to add a file handling function.
Now we begin by creating a new bokeh class on python which will link up to the javascript class and hold the information generated by the file input.
models.py
from bokeh.core.properties import List, String, Dict, Int
from bokeh.models import LayoutDOM
class FileInput(LayoutDOM):
__implementation__ = 'static/js/extensions_file_input.coffee'
__javascript__ = './input_widget/static/js/papaparse.js'
value = String(help="""
Selected input file.
""")
file_name = String(help="""
Name of the input file.
""")
accept = String(help="""
Character string of accepted file types for the input. This should be
written like normal html.
""")
data = List(Dict(keys_type=String, values_type=Int), default=[], help="""
List of dictionary containing the inputed data. This the output of the parser.
""")
Then we create the coffeescript implementation for our new python class. In this new class, there is an added file handler function which triggers on change of the file input widget. This file handler uses PapaParse to parse the csv and then saves the result in the class's data property. The javascript for PapaParse can be downloaded on their website.
You can extend and modify the parser for your desired application and data format.
extensions_file_input.coffee
import * as p from "core/properties"
import {WidgetBox, WidgetBoxView} from "models/layouts/widget_box"
export class FileInputView extends WidgetBoxView
initialize: (options) ->
super(options)
input = document.createElement("input")
input.type = "file"
input.accept = @model.accept
input.id = @model.id
input.style = "width:" + @model.width + "px"
input.onchange = () =>
@model.value = input.value
@model.file_name = input.files[0].name
@file_handler(input)
@el.appendChild(input)
file_handler: (input) ->
file = input.files[0]
opts =
header: true,
dynamicTyping: true,
delimiter: ",",
newline: "\r\n",
complete: (results) =>
input.data = results.data
@.model.data = results.data
Papa.parse(file, opts)
export class FileInput extends WidgetBox
default_view: FileInputView
type: "FileInput"
@define {
value: [ p.String ]
file_name: [ p.String ]
accept: [ p.String ]
data : [ p.Array ]
}
A Back on the python side we can then attach a bokeh on_change to our new input class to trigger when it's data property changes. This will happen after the csv parsing is done. This example showcases the desired interaction.
main.py
from bokeh.core.properties import List, String, Dict, Int
from bokeh.models import LayoutDOM
from bokeh.layouts import column
from bokeh.models import Button, ColumnDataSource
from bokeh.io import curdoc
from bokeh.plotting import Figure
import pandas as pd
from models import FileInput
# Starting data
x = [1, 2, 3, 4]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
plot = Figure(plot_width=400, plot_height=400)
plot.circle('x', 'y', source=source, color="navy", alpha=0.5, size=20)
button_input = FileInput(id="fileSelect",
accept=".csv")
def change_plot_data(attr, old, new):
new_df = pd.DataFrame(new)
source.data = source.from_df(new_df[['x', 'y']])
button_input.on_change('data', change_plot_data)
layout = column(plot, button_input)
curdoc().add_root(layout)
An example of a .csv file for this application would be. Make sure there is no extra line at the end of the csv.
x,y
0,2
2,3
6,4
7,5
10,25
To run this example properly, bokeh must be set up in it's proper application file tree format.
input_widget
|
+---main.py
+---models.py
+---static
+---js
+--- extensions_file_input.coffee
+--- papaparse.js
To run this example, you need to be in the directory above the top most file and execute bokeh serve input_widget
in the terminal.