Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File Open Dialog #6096

Closed
harrispilton opened this issue Apr 4, 2017 · 17 comments

Comments

@harrispilton
Copy link

commented Apr 4, 2017

Hello,

I would like to have a "File Open Dialog" to interactively specify local data files.
This was previously discussed in the Bokeh Google Groups.

Best regards

@bryevdv

This comment has been minimized.

Copy link
Member

commented Apr 4, 2017

Just FYI. This might be a reasonable addition, but I can't speculate about when it might make it into the core library. In the mean time your option is to create custom extension:

http://bokeh.pydata.org/en/latest/docs/user_guide/extensions.html

@harrispilton harrispilton referenced this issue Apr 5, 2017
@kevinsa5

This comment has been minimized.

Copy link
Contributor

commented Apr 7, 2017

For anyone searching for help: in the meantime before this is added to bokeh, here's how I implemented sending a file from the user's computer to a Bokeh server:

  • Using templates (Bokeh's, Flask's, whatever you're using), insert an HTML file choosing input: <input id="file-input" type="file">
  • Attach a javascript callback to a button that reads the selected file's contents
  • If the file is complex like an excel document, you can base64 encode it. If it's something like a text file or a csv, you might as well leave it as-is
  • Insert it into a dedicated ColumnDataSource, treating it as a dictionary to transfer data from browser to server
  • On the server side, implement an on_change("data", callback) for the data source
  • Base64 decode the file if necessary
  • If you just need the file contents as a string, you're done. If you need it as a proper file (for instance, to use with pandas.read_excel), you can wrap it in a StringIO object to make it file-like

It's a bit complicated, but the upcoming namespace model for Bokeh would make it a bit easier. Feel free to ping me for code samples.

@divyamounika

This comment has been minimized.

Copy link

commented May 2, 2017

Hello @kevinsa5 ,

could you provide code samples. Thank you
The following is my code

import pandas as pd
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button
from bokeh.io import curdoc

source = ColumnDataSource(dict())

print(source.data.keys)

def callback1(attr,old,new):
print('data got changed')

button = Button(label="Upload", button_type="success")
button.callback = CustomJS(args=dict(source=source),
code="""
fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file');

                        selectDialogueLink = document.createElement('a');
                        selectDialogueLink.setAttribute('href', '');

                        selectDialogueLink.onclick = fileSelector.click();

                        document.body.appendChild(selectDialogueLink);

                        if ('files' in fileSelector) {
                            if (fileSelector.files.length == 0) {
                                alert("Select file.");
                            } else {
                                for (var i = 0; i < fileSelector.files.length; i++) {
                                    var file = fileSelector.files[i];
                                    var reader = new FileReader();
                                    source.data = reader.readAsArrayBuffer(file);
                                    if ('name' in file) {
                                        alert("name: " + file.name);
                                    }
                                    if ('size' in file) {
                                        alert("size: " + file.size);
                                    }
                                }
                            }
                         } 
                       """) 

source.on_change('data',callback1)

curdoc().add_root(row(button))

@kevinsa5

This comment has been minimized.

Copy link
Contributor

commented May 3, 2017

Hi @divyamounika, here is an example script that you can work from. Try uploading a simple excel file to see if it works for you. I also added the filename, as you can see. This code is largely untested -- you may want to add filesize limits, only allow certain file types, etc.

# -*- coding: utf-8 -*-
"""
Created on Wed May 03 11:26:21 2017

@author: Kevin Anderson
"""

import pandas as pd
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button
from bokeh.io import curdoc

import StringIO
import base64

file_source = ColumnDataSource({'file_contents':[], 'file_name':[]})

def file_callback(attr,old,new):
    print 'filename:', file_source.data['file_name']
    raw_contents = file_source.data['file_contents'][0]
    # remove the prefix that JS adds  
    prefix, b64_contents = raw_contents.split(",", 1)
    file_contents = base64.b64decode(b64_contents)
    file_io = StringIO.StringIO(file_contents)
    df = pd.read_excel(file_io)
    print "file contents:"
    print df

file_source.on_change('data', file_callback)

button = Button(label="Upload", button_type="success")
button.callback = CustomJS(args=dict(file_source=file_source), code = """
function read_file(filename) {
    var reader = new FileReader();
    reader.onload = load_handler;
    reader.onerror = error_handler;
    // readAsDataURL represents the file's data as a base64 encoded string
    reader.readAsDataURL(filename);
}

function load_handler(event) {
    var b64string = event.target.result;
    file_source.data = {'file_contents' : [b64string], 'file_name':[input.files[0].name]};
    file_source.trigger("change");
}

function error_handler(evt) {
    if(evt.target.error.name == "NotReadableError") {
        alert("Can't read file!");
    }
}

var input = document.createElement('input');
input.setAttribute('type', 'file');
input.onchange = function(){
    if (window.FileReader) {
        read_file(input.files[0]);
    } else {
        alert('FileReader is not supported in this browser');
    }
}
input.click();
""")

curdoc().add_root(row(button))
@divyamounika

This comment has been minimized.

Copy link

commented May 10, 2017

Hello @kevinsa5 ,

Thank you for the code. I was able to upload .csv files data with a minor modification on my system.

modification -->file_io = io.StringIO(bytes.decode(file_contents))
It worked like magic.

@ijstokes

This comment has been minimized.

Copy link

commented Sep 21, 2017

There is some content here that may be useful to this issue:

https://stackoverflow.com/questions/40794180/upload-a-csv-file-and-read-it-in-bokeh-web-app

@joel-wilson

This comment has been minimized.

Copy link

commented Jan 17, 2018

@kevinsa5 and @divyamounika - could you throw some light on what ‘file_io’ contents would be? Because this works for csv file but for .xlsx throws TypeError. Could you guide me here?

@kevinsa5

This comment has been minimized.

Copy link
Contributor

commented Jan 17, 2018

@joel-wilson file_io is a StringIO object, basically an in-memory file. StringIO behaves slightly different between python 2 and 3, fyi. You should narrow down what exactly the issue on your end is, since it probably isn't a problem with Bokeh based on your description. Try making the smallest possible script that still has the issue, that should help you to figure it out.

@joel-wilson

This comment has been minimized.

Copy link

commented Jan 18, 2018

For those you would infuture reference this thread : Here is a solution to have a upload button to handle .xlsx file . This was ofr python3

import pandas as pd
import io
import base64


def file_callback_dt1(attr,old,new):
    print('filename:', file_source_dt1.data['file_name'])
    raw_contents = file_source_dt1.data['file_contents'][0]
    prefix, b64_contents = raw_contents.split(",", 1)
    file_contents = base64.b64decode(b64_contents)
    file_io = io.BytesIO(file_contents)
    excel_object = pd.ExcelFile(file_io, engine='xlrd')
    dt_1 = excel_object.parse(sheet_name = 'Sheet1', index_col = 0)
    # rest is upto you :)
@amishra159

This comment has been minimized.

Copy link

commented Jun 8, 2018

I am creating a pie chart but the problem is that I want to load a tsv file from the file open dialog box when the link is clicked.how it can be possible to get the data file from dialog box and as the user opens it a pie chart is displayed.

@bryevdv bryevdv added this to the short-term milestone Sep 11, 2018

@joaochenriques

This comment has been minimized.

Copy link

commented Dec 16, 2018

Help please!
I need to get the filename and the folder where the file is stored for the "kevinsa5 commented on May 3, 2017"

@kevinsa5

This comment has been minimized.

Copy link
Contributor

commented Dec 16, 2018

@joaochenriques that snippet of code does not store the file -- it only brings the file contents into memory. If you want to store the file somewhere on disk, you can try something like

with open("/path/to/output_file") as f:
    f.write(file_contents)
@joaochenriques

This comment has been minimized.

Copy link

commented Dec 17, 2018

Thank you @kevinsa5. I am a new user of Bokeh, and I forgot that it is a client/server architecture!

@bryevdv bryevdv modified the milestones: short-term, 1.1.2 May 8, 2019

@bryevdv

This comment has been minimized.

Copy link
Member

commented May 8, 2019

Noting the SO example to create a custom extension it far out of date:

https://stackoverflow.com/a/42613897/3406693

Should either add a file dialog directly or create an updated example (and update that SO answer accordingly either way)

@philippjfr

This comment has been minimized.

Copy link
Contributor

commented May 8, 2019

Note that panel has an implementation of a FileInput widget, which could be moved into bokeh.

@bryevdv

This comment has been minimized.

Copy link
Member

commented May 8, 2019

Note that panel has an implementation of a FileInput widget, which could be moved into bokeh.

It's not my birthday but I wouldn't turn down this present

@bryevdv

This comment has been minimized.

Copy link
Member

commented Jul 12, 2019

@philippjfr I am going to see about doing this now, so it can be in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.