1

I'm trying to use flask and bokeh to build a web application where users can manually classify data. The UI, right now, makes use of a custom javascript callback to render changes visually, but I'm not sure how to use javascript to send a post request back to my flask app to log the user's activity.

An alternate solution that I would like to consider is to convert the ColumnDataSource to a .csv file that the user can download once they've finished with a particular page, but I'm not sure how to access the ColumnDataSource after the api call in flask.

I've included a basic example of what I'm trying to accomplish.

from bokeh.embed import file_html
from bokeh.events import DoubleTap
from bokeh.models import CategoricalColorMapper, ColumnDataSource, CustomJS
from bokeh.plotting import figure
from bokeh.resources import CDN 
from flask import Flask
import numpy as np

# color map
cmap = CategoricalColorMapper(
    factors=[0, 1], 
    palette=['red', 'green']
)

# custom javascript callback
js_code = """ 
    var x = cb_obj['x'];
    var left = src.data['left'];
    var right = src.data['right'];
    var color = src.data['color'];
    for (i=0; i < left.length; i++) {
        if ((x > left[i]) && (x < right[i])) {
            var c = color[i]
            if (c < 1) {
                color[i] = 1
            } else {
                color[i] = 0
            }
        }
    }
    src.data['color'] = color
    src.trigger('change')
"""

# flask app
app = Flask(__name__)

@app.route("/")
def main():
    N = 10
    left = np.arange(N)
    right = left + 1 
    color = np.random.randint(2, size=N)

    src = ColumnDataSource({
        'left': left,
        'right': right,
        'color': color
    })  

    fig = figure(
        width=500, height=200,
        title='Double Click to Change Color'
    )   
    fig.quad(
        'left', 'right', 0, 1,
        source=src,
        fill_color={'field': 'color', 'transform': cmap},
        line_color='black'
    )   

    callback = CustomJS(code=js_code, args={'src': src})
    fig.js_on_event(DoubleTap, callback)
    title = 'test'
    html = file_html(fig, CDN, title)

    return html

if __name__ == "__main__":
    app.run(debug=True)

Thanks for reading!

Mister Brainley
  • 632
  • 1
  • 7
  • 20

1 Answers1

3

For sending POST requests from JS, you can use XMLHttpRequest.

However, in your scenario I'd use a Bokeh server (example of usage with Flask) - this way, you shouldn't need any manual POST requests as the ColumnDataSource from your example would be synched to the Bokeh server after you trigger the change signal (by the way, "trigger" is deprecated, you should use src.change.emit()).

In a custom GET handler, you can access any model of your Bokeh document on Bokeh server by using get_model_by_name:

@app.route('/cds-data/<cds_name>')
def get_cds_data(cds_name):
    data = curdoc().get_model_by_name(cds_name).data
    return some_dict_to_csv_string_function_you_have_to_write(data)

If you still don't want to use Bokeh server, you still don't need any POST requests - you can convert the data to a string and then use something like the example here: https://stackoverflow.com/a/29534664/564509

Eugene Pakhomov
  • 9,309
  • 3
  • 27
  • 53