Extract CSV from button in bokeh 2.1.1

Hi all,

I am quite novice at bokeh but I am starting to get the hang of it.
I created a function which allowed users to extract a csv from a plot using a Button.

I created an adaptation to the code from stack overflow:

It was working in the past, however, now since 2.1.1 the button.callback does not seem to work.

from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button
from bokeh.io import show

source = ColumnDataSource({'list1':[0,1,2,3],'list2':[4,5,6,7]})
button = Button(label="Download", button_type="success")

javaScript="""
function table_to_csv(source) {
    const columns = Object.keys(source.data)
    const nrows = source.get_length()
    const lines = [columns.join(',')]

    for (let i = 0; i < nrows; i++) {
        let row = [];
        for (let j = 0; j < columns.length; j++) {
            const column = columns[j]
            row.push(source.data[column][i].toString())
        }
        lines.push(row.join(','))
    }
    return lines.join('\\n').concat('\\n')
}


const filename = 'data_result.csv'
filetext = table_to_csv(source)
const blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' })

//addresses IE
if (navigator.msSaveBlob) {
    navigator.msSaveBlob(blob, filename)
} else {
    const link = document.createElement('a')
    link.href = URL.createObjectURL(blob)
    link.download = filename
    link.target = '_blank'
    link.style.visibility = 'hidden'
    link.dispatchEvent(new MouseEvent('click'))
}
"""

button.callback = CustomJS(args=dict(source=source),code=javaScript)
show(button)

I have tried to do the following:

instead of :

button.callback = CustomJS(args=dict(source=source),code=javaScript)

I replaced it with:

button.js_on_event(ButtonClick, CustomJS(args=dict(source=source),code=javaScript))

and

button.js_on_click(CustomJS(args=dict(source=source),code=javaScript))

( I of course added the needed modules if applicable)
but with no success.

I get no errors, but whenever the button shows which I click nothing happens.

Is there anyone that has a solution to this?

Many thanks in advance!!

Kind regards.

I’m not sure what’s on SO. The latest version of the officially maintained export_csv example for version 2.1.1 (which I also just tested with version 2.1.1 on OSX), is here:

bokeh/examples/app/export_csv at 2.1.1 · bokeh/bokeh · GitHub

Thank you for your reply!

I have looked at the code, and used the same sample dataset you used and changed it to my use. I actually only want the button without the sliders etc. So I did the following:

import pandas as pd
from bokeh import events
from bokeh.io import show
from bokeh.models import Button, CustomJS, ColumnDataSource

df = pd.read_csv('salarydata.txt', sep=',')
source = ColumnDataSource(data=df)

jscript = """
function table_to_csv(source) {
    const columns = Object.keys(source.data)
    const nrows = source.get_length()
    const lines = [columns.join(',')]

    for (let i = 0; i < nrows; i++) {
        let row = [];
        for (let j = 0; j < columns.length; j++) {
            const column = columns[j]
            row.push(source.data[column][i].toString())
        }
        lines.push(row.join(','))
    }
    return lines.join('\n').concat('\n')
}


const filename = 'data_result.csv'
const filetext = table_to_csv(source)
const blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' })

//addresses IE
if (navigator.msSaveBlob) {
    navigator.msSaveBlob(blob, filename)
} else {
    const link = document.createElement('a')
    link.href = URL.createObjectURL(blob)
    link.download = filename
    link.target = '_blank'
    link.style.visibility = 'hidden'
    link.dispatchEvent(new MouseEvent('click'))
}
"""

button = Button(label="Download", button_type="success")
button.js_on_click(CustomJS(args=dict(source=source),
                            code=jscript))

show(button)

However I still get the button without it doing anything when I click on it.
Is it due to me not using the curdoc functionality, or due to some other problem?

I hope I am not coming on as too ignorant here. I have tried a lot of different variations all with no success.

If anyone knows what causes it that would be of great help.

Thanks!

@fzt

The example you posted is throwing an error in the JavaScript console. If you open the console for the browser you’re using, you can see this and debug further. (On Chrome, right-mouse-click and select Inspect to get information for the page being viewed, including a console tab.)

Here is what I see with your example.

The official example referenced earlier in this topic is using the bokeh server, which is related to your question about the curdoc() functionality. I am not certain if this is a de facto requirement for it to run, but I suspect it is the more realistic use case as running locally wouldn’t really offer anything useful for downloading a file.

@_jm
Hi _jm,

Thank you for your reply.
The reason I wanted to skip the curdoc() functionality was due to the fact that I want an html file as output with a line plot and a button to download the data underlying the plot in CSV format.

The functionality used to work with the .callback functionality. However I cannot seem to get it to work with the js_on_event or js_on_click. That is why I thought it could not be due to the JavaScript not working.

However if there is no other way I might try to dive into the curdoc functionality and try to get this to work.

Anyhow, many thanks for your input!

@fzt

I see. If you have something that works in a prior version of bokeh but no longer does in 2.1.1, that information, in the form of a minimal reproducible example, would help the developers determine if the change is intentional or a regression.

@_jm

The example in the OP works in bokeh version 1.4.0

from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button
from bokeh.io import show

source = ColumnDataSource({'list1':[0,1,2,3],'list2':[4,5,6,7]})
button = Button(label="Download", button_type="success")

javaScript="""
function table_to_csv(source) {
    const columns = Object.keys(source.data)
    const nrows = source.get_length()
    const lines = [columns.join(',')]

    for (let i = 0; i < nrows; i++) {
        let row = [];
        for (let j = 0; j < columns.length; j++) {
            const column = columns[j]
            row.push(source.data[column][i].toString())
        }
        lines.push(row.join(','))
    }
    return lines.join('\\n').concat('\\n')
}


const filename = 'data_result.csv'
filetext = table_to_csv(source)
const blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' })

//addresses IE
if (navigator.msSaveBlob) {
    navigator.msSaveBlob(blob, filename)
} else {
    const link = document.createElement('a')
    link.href = URL.createObjectURL(blob)
    link.download = filename
    link.target = '_blank'
    link.style.visibility = 'hidden'
    link.dispatchEvent(new MouseEvent('click'))
}
"""

button.callback = CustomJS(args=dict(source=source),code=javaScript)
show(button) 

In version 2.1.1, when I run the above code I get the following error:
AttributeError: unexpected attribute 'callback' to Button, similar attributes are js_event_callbacks

Thanks.

@fzt

The following example works in bokeh 2.0.2 (I have to use this version currently b/c of a regression in 2.1.0 that affects one of my apps.)

I made two changes to the provided example to get it to work.

First:

filetext = table_to_csv(source) is replaced with var filetext = table_to_csv(source) to address an unhandled reference exception in the JavaScript.
and

replace the callback registration with a js_on_click registration.

FULL EXAMPLE

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""

from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button
from bokeh.io import show

source = ColumnDataSource({'list1':[0,1,2,3],'list2':[4,5,6,7]})
button = Button(label="Download", button_type="success")

javaScript="""
function table_to_csv(source) {
    const columns = Object.keys(source.data)
    const nrows = source.get_length()
    const lines = [columns.join(',')]

    for (let i = 0; i < nrows; i++) {
        let row = [];
        for (let j = 0; j < columns.length; j++) {
            const column = columns[j]
            row.push(source.data[column][i].toString())
        }
        lines.push(row.join(','))
    }
    return lines.join('\\n').concat('\\n')
}


const filename = 'data_result.csv'
var filetext = table_to_csv(source)
const blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' })

//addresses IE
if (navigator.msSaveBlob) {
    navigator.msSaveBlob(blob, filename)
} else {
    const link = document.createElement('a')
    link.href = URL.createObjectURL(blob)
    link.download = filename
    link.target = '_blank'
    link.style.visibility = 'hidden'
    link.dispatchEvent(new MouseEvent('click'))
}
"""

button.js_on_click(CustomJS(args=dict(source=source),code=javaScript))
show(button)
1 Like

It seems to work now indeed.

So it was due to the ‘var’ missing in the script.

Many thanks!