Upload a CSV file and read it in Bokeh Web app

后端 未结 2 1543
借酒劲吻你
借酒劲吻你 2021-01-11 16:06

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

2条回答
  •  生来不讨喜
    2021-01-11 16:24

    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.

提交回复
热议问题