2
votes

Is there any method to save Bokeh dashboard after editing it?

For example, I've loaded my dashboard, created some plots and saved them (last tab). And then I want to save my "progress" to .html-file so that I wouldn't have to do all of this again every time after initializing my script.

This is the screenshot of my dashboard: enter image description here

Thank you!

1
Do you want to be able to just look at it or to continue working where you left off? You screenshot seems like it uses a Jupyter notebook - do you use any Python code with Bokeh or does everything use JavaScript? - Eugene Pakhomov
It's based on js completely. I use it in Jupyter Notebook without launching a server. I would like to be able to use it after that to add some new graphs or make another data-queries. So, I guess it has to be in html. - Higem
As I've found out, saving .html-file after making updates in it doesn't change the source file. I've tried to download my dashboard as .html, then opened it in a browser, made changes and then saved it to my hdd. And after that I had the same file as I'd downloaded in the 1st step. It means that saving dashboard in .html after making changes in it doesn't work. I have to find another solution. - Higem
Yes, HTML file stores just the layout. All the other state is stored in JavaScript memory. I'm trying to come up with a solution - it should be possible. - Eugene Pakhomov
Well, it is possible to save the document with Bokeh.documents[0].to_json_string() and load it with Bokeh.embed.add_document_standalone. But the plots don't render anything in my case. - Eugene Pakhomov

1 Answers

2
votes

So the solution is:
1
Convert your bokeh-document (your whole dashboard) to .json and download it as a file.

For example, you do it by clicking a button.

#  creating button
download_json = Button(label="Download json", width=70)
# callback
download_json_func = CustomJS(args=dict(source=source_fill_groupby),code="""
function saveText(text, filename){
var a = document.createElement('a');
a.setAttribute('href', 'data:text/plain;charset=utf-8,'+encodeURIComponent(text));
a.setAttribute('download', filename);
a.click()
}
var obj = Bokeh.documents[0].to_json_string();
saveText( JSON.stringify(obj), "filename.json" );
""")
# assigning callback to the button
download_json.js_on_event(ButtonClick, download_json_func)

2
After you've downloaded your file, you need to restore it in the next cell of your Jupyter notebook. Let's say there should be FileInput-widget so that we could upload our file and it'd appear in the div-block.

from bokeh.models.widgets import FileInput
# creating div where our saved dashboard will be shown
div = Div(text='<div id="document-container"></div>', width=500, height=500)
# adding widget
l = FileInput(accept='.json')
# callback
l.js_on_change('value', CustomJS(code="""\
const {Document} = Bokeh.require('document/document');
// uploaded .json-file  
const data = JSON.parse(atob(cb_obj.value));            
const doc = Document.from_json_string(data);
// dashboard to show
Bokeh.embed.add_document_standalone(doc, document.getElementById('document-container'), [], true);                                     
cb_obj.disabled = true;
"""))

show(column(l, div))

It still has some issues - it doesn't show ciryllic correctly and plot won't update their ranges from the last one after which doc was saved if you make new queries and creating new plots. Also there's some problem with saving plots to the last tab - it doesn't work like it works in Bokeh. But at least you can save your "progress" in your researches.

Upd1. To show cyrillic correctly you should use next callback for FileInput:

    l.js_on_change('value', CustomJS(code="""
function b64DecodeUnicode(str) {
    // Going backwards: from bytestream, to percent-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

const {Document} = Bokeh.require('document/document');
const data = JSON.parse(b64DecodeUnicode(cb_obj.value));
const doc = Document.from_json_string(data);
Bokeh.embed.add_document_standalone(doc, document.getElementById('document-container1'), [], true);
cb_obj.disabled = true;
"""))