☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

Layout with Drop Down Select Option without Bokeh Server

Community Support
Vishal_Agarwal
Vishal_Agarwal

HI,
I have created 10 different layouts in Bokeh which are similar to each other with changes in numbers and content. I want to create a drop down which will help select one of the layouts. I am able to do it with Bokeh server but I want to save it as a single HTML file, which does not seem to be an option.

Any way around it?

⌁   Link to post
Bryan
Bryan

Certainly, but the HTML file will have to contain everything all together, so it might be fairly large depending on what your data is. Your best bet is to combine all ten sub-layouts in one high level layout like a row or column and then use a CustomJS callback on the the drop down to control the visible property on the sub-layouts appropriately (e.g. make all of them invisible except the one you want to currently display). There are several questions on this forum that have touched on this pattern so I would suggest searching here for “visible” as a first step.

⌁   Link to post
Bryan
Bryan

@Vishal_Agarwal I am not sure what you are asking. My answer was above was explicitly for accomplishing what you want using a dropdown: Add everything (all the different layouts) up front as children of one top-level column, and use the JS callback on the dropdown to control the visibility of the children in the column. That does not require a Bokeh server.

⌁   Link to post
Vishal_Agarwal
Vishal_Agarwal

That answers the question but I am not getting the right response from the callback. I am not sure if something else is required. Is this the right place to post the code for checking?


I don’t want to duplicate by uploading on stackoverflow. I am attaching the screenshot of the code which is set for JS Callback for the layouts. Can you please check and point out the error!

⌁   Link to post
Bryan
Bryan

@Vishal_Agarwal please do not ever post screenshots of code. Besides making it more difficult for people to use to help you (because it cannot be copy and pasted to investigate), it is also completely inaccessible to users that employ screen reader or other assistive technologies. Always paste code as actual text.

You CustomJS code should do the following:

  • First, loop over every item in layout.children and set .visible = false. for all of them This will hide all the layouts.
  • Second, based on the value of the Select widget, set the .visible = true for whichever one of the layouts you want to currently be visible.

Note, based on your description of the task, there is nothing to do with sources or data or change events on those.

⌁   Link to post
Vishal_Agarwal
Vishal_Agarwal

I am trying to incorporate as you mentioned here. I am not familiar with JS scripts so some help on getting the right code will be helpful. Here is the code:

Code:

layouts = column([layout_single(customer_1),layout_single(customer_2)])

df = pd.DataFrame.from_dict(
    {
        'F company_green': {'value':layout_single(customer_1)},
        'F company_red': {'value': layout_single(customer_2)}
    }
)

d_map = {
    'F company': ['F company_green'],
    'I company': ['I company_red']
}
selection_list = list(d_map.keys()) 
initial_value = selection_list[0]

initial_col = [i for i in df.columns.tolist() if i.startswith(initial_value)]
source = ColumnDataSource(df)
opts = selection_list
select = Select(value = opts[0], options = opts)

callback = CustomJS(args = dict(graph=source, source= df.to_dict()), code =
                    
            """
            for (var c=0; c < layouts.children.length; c++){
                c.visible = false
                }
            
            graph.data = source[cb_obj.value];
            graph.change.emit();
            cb_obj.value.visible = true;
            
            """)
select.js_on_change('value', callback)
layout = column([select, layouts])
show(layout)
⌁   Link to post
Bryan
Bryan

@Vishal_Agarwal please also make use of the code formatting options in the editor so that your code is intelligible (you can go back and edit your previous post). This is especially important for Python code, since blank space is significant. You can use either the </> icon on the editing toolbar, or add triple backtick ``` fences around the code blocks, to make them appear properly formatted.

⌁   Link to post
Bryan
Bryan

@Vishal_Agarwal This is what I meant:

from bokeh.layouts import row, column
from bokeh.models import CustomJS, Select
from bokeh.plotting import figure, show

p1 = figure()
p1.circle([1,2,3], [1,3,2], size=20)

p2 = figure(visible=False)
p2.line([1,2,3], [1,3,2], line_width=3)

col = column(p1, p2)

s = Select(value="circle", options=["circle", "line"])

s.js_on_change('value', CustomJS(args=dict(s=s, col=col), code="""
    for (const plot of col.children) {
        plot.visible = false
    }

    // Pick one of the column children to make visible. 
    //
    // *You* will have to provide the logic to decide which one of the
    // children to make visible, based on the select value. That is a 
    // policy decision that depends completely on your requirements. 
    if (s.value == "circle") {
        col.children[0].visible = true
    }
    else if (s.value == "line") {
        col.children[1].visible = true
    }
"""))

show(row(s, col))

This uses single plots for simplicity, but replace them with any more complicated layout and it is the same.

ScreenFlow

⌁   Link to post
Vishal_Agarwal
Vishal_Agarwal

Thank you so much. This solves the issue.
One final question: If I have to just make the selected value visible, do I need to do a if-else for all the 10/20 different layouts or can I do something like:

if (s.value == "Cust1") {
        col.children[0].visible = true
    }
    else {
        index = options.indexof(s.value)
        col.children[index].visible = true
    }
⌁   Link to post
Bryan
Bryan

You could set the name property of each child layout to match one of the options in the select, and then compare the current select value to the child.name. In fact you could accomplish this in one loop. Something like (untested)

    for (const child of col.children) {
        child.visible = (child.name == s.value)
    }
⌁   Link to post
Vishal_Agarwal
Vishal_Agarwal

Here are the screenshots. The code is the same. The layout is created in such a way that blue is called banner 1 and red is called banner 2.

layout_single= row([banner1, banner2])

The rest of the code is the same. The numbers are different for the 2 layout_single for cust1 and 2 and each customer has one blue and one red.

Screenshot Default-

Screenshot after making a selection-

Note: THe layout is simplified to focus on the roots problem.

⌁   Link to post
Vishal_Agarwal
Vishal_Agarwal

Here is the reproducible sample code which clearly shows the issues:

import numpy as np
import pandas as pd
from bokeh.models.widgets import Div
from bokeh.layouts import row, column
from bokeh.models import CustomJS, Select
from bokeh.plotting import figure, show, ColumnDataSource

cust1 = {
    'industry' : 'Construction',
    'biz_type' : 'Manufacturing',
    'total_loan': '20,000',
    'location': 'Ohio'}

cust2 = {
    'industry' : 'Services',
    'biz_type' : 'Plumbing Services',
    'total_loan': '354,000',
    'location': 'San Jose'}

def layout_single(cust):
    div_leftpanel1 = Div(text = f'&nbsp;&nbsp;Customer Profile\
                <br><br><i class="fa fa-file"></i>&nbsp;&nbsp;Contact Us</a> \
                <br><br><i class="fa fa-file"></i>&nbsp;&nbsp;Blogs</a> \
                <br><br><br><br><br> \
                <p style="font-size:14px;font-family:verdana"><u>Customer Info</u> <br><br> Industry: {cust["industry"]}\
                <br> Type of Business: {cust["biz_type"]} <br> Location: {cust["location"]} </p> \
                <b>', 
          
                style={'width':'350px','height':'680px','border-radius': '15px',
                 'text-align': 'left','padding-left': '50px','font-size': '150%',
                 'color': 'green', 'background-color': '#91EBE8','font-family':'verdana',
                 'padding-top': '10px'})
    
    banner1 = Div(text = f'<b><p style="font-size:15px;">${cust["total_loan"]}</p>\
                    <hr>\
                    Loan Amount<b>',\
              style={'width':'250px','height':'90px','border-radius': '15px 15px 0px 0px',
                 'text-align': 'left','padding-left': '50px','padding-right': '50px',
                 'font-family':'verdana'})
    
    return row([div_leftpanel1, banner1])


col = column([layout_single(cust1), layout_single(cust2)])

s = Select(value="Customer1", options=["Customer1", "Customer2"])

s.js_on_change('value', CustomJS(args=dict(s=s, col=col), code="""
    for (const plot of col.children) {
        plot.visible = false
    }

    if (s.value == "Customer1") {
        col.children[0].visible = true
    }
    else if (s.value == "Customer2") {
        col.children[1].visible = true
    }
"""))

final_layout = column(s, col)
show(final_layout)
⌁   Link to post
Bryan
Bryan

@Vishal_Agarwal Bokeh manages the layout and size of its own components. If you start setting CSS width, height, padding, etc. you will almost certainly interfere with that, and all bets are off. If you have need for this level of control you might be better off using a custom Jinja template and having the CustomJS manipulate real DOM elements directly.

⌁   Link to post
Vishal_Agarwal
Vishal_Agarwal

Even if I remove the styling, the issue remains. So in that case, I cannot create layouts for drop-down menus. With the use of Jinja Template, I will need to use a server but I want to save it as a single HTML file. Is there a way around it?

⌁   Link to post
Bryan
Bryan

This is some layout bug in 2.3.x the following works for me with version 2.2.3

    div_leftpanel1 = Div(text = f'&nbsp;&nbsp;Customer Profile\
                <br><br><i class="fa fa-file"></i>&nbsp;&nbsp;Contact Us</a> \
                <br><br><i class="fa fa-file"></i>&nbsp;&nbsp;Blogs</a> \
                <br><br><br><br><br> \
                <p style="font-size:14px;font-family:verdana"><u>Customer Info</u> <br><br> Industry: {cust["industry"]}\
                <br> Type of Business: {cust["biz_type"]} <br> Location: {cust["location"]} </p> \
                <b>', 
                width=350, height=650,
                style={'border-radius': '15px',
                 'text-align': 'left','padding-left': '50px','font-size': '150%',
                 'color': 'green', 'background-color': '#91EBE8','font-family':'verdana'}
                 )
    
    banner1 = Div(text = f'<b><p style="font-size:15px;">${cust["total_loan"]}</p>\
                    <hr>\
                    Loan Amount<b>',
                    width=250, height=90,
              style={'border-radius': '15px 15px 0px 0px',
                 'text-align': 'left','padding-left': '50px','padding-right': '50px',
                 'font-family':'verdana'}
                 )

So your immediate option is to downgrade to the previous 2.2.3 version. Or wait for a future version with a fix.

With the use of Jinja Template, I will need to use a server

This is not at all true. file_html generates static HTML output and accepts a template. There are several example of using file_html in the examples directory.

⌁   Link to post
_jm
_jm

@Vishal_Agarwal

This seems to be related to the following issue on bokeh GitHub if you want to track progress and see when it is addressed.

⌁   Link to post

End of archived discussion · Replies are disabled