Responsive layout with reflow for screen rotations?

Hi,

I currently have a layout like this:


grid = gridplot([
                  [ plot1, plot3, plot5, plot7 ],
                  [ plot2, plot4, plot6, plot8 ] ],
                  plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width')

which works fine on resolutions where width is greater than height (horizontal)

For vertical resolutions I would like this layout to be:

grid = gridplot([
                  [ plot1, plot3, ],
                  [ plot5, plot7  ],
                  [ plot2, plot4, ], 
                  [ plot6, plot8 ] ],
                  plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width')

Is it possible to have the layout conditional to the screen orientation?

Note that in this case scaling is not enough, as scaling horizontal content on a vertical device makes it unreadable. Ideally I would like to lay things out vertically if the resolution is vertical or becomes vertical due to a rotation.

Bokeh’s simple built-in layouts are not sophisticated enough to do this, you would need to use one of the embedding APIs so embed Bokeh components individually into a responsive template.

Hi @Bryan ,

Thanks for the answer.

I understand what you are saying in regards to the current situation. Would it be acceptable if I opened a feature request for this?

Allow me a moment to make the case.

I am using a good looking HTML/CSS template that essentially has a place holder for Bokeh layouts in the middle. With this, I am combining the tremendous technical power of Bokeh plots with the perks of a pretty looking template that works almost without HTML/CSS adjustments. This

<div class="content2">{{ embed(roots.section1) }}</div>
<div style="display: flex; justify-content: flex-end">{{ embed(roots.section1_controls) }}</div>

is enough to make things work, without having to fiddle with unfamiliar and potentially complicated HTML. Example: https://coviz.io/

Chopping the Bokeh layouts into individual plots which are fed into a responsive HTML grid increases the difficulty by an order of magnitude for non HTML specialists. It also breaks a bit the concern separation between the HTML and python part, which I imagine that sometime happens (a team works on the template around the charts, another team works in the technical Bokeh part).

I would speculate that supporting conditional layouts would help many users, especially since mobile is growing.

Of course, if this is technically not possible I shall not bother you more :slight_smile:

Thanks for the support. And thanks for Bokeh.

Cheers
Gustavo

Speaking plainly and directly, probably not.

The history of managed DOM layout and Bokeh has more or less been one of repeated attempts to re-invent a very expensive wheel, but always coming up short in various ways. Browsers and the common front-end libraries have had person-decades (if not person-centuries) of effort poured into them. We will never be able to match their levels of development, testing, or platform coverage. Meanwhile, there has been a near constant stream of calls for “I just want to put Bokeh things in React” (or Angular, or Vue, take your pick) that have not been adequately addressed while we tried to boil the ocean ourselves.

Rather that trying to more, our plan for 3.x is specifically the opposite: to get out of the layout business as much as possible, and focus on the things we can be best at. We will always have to manage the canvas layout, and we will continue to offer simple, “dumb” row, column, grid, etc. for simple cases. But trying to manage sophisticated DOM layout ourselves has not been a valuable use of very limited resources. There are existing, popular, highly-developed tools for designing responsive pages, and the best thing we can do is to get out of the way of users trying to take advantage of them.

Hi @Bryan ,

I appreciate the honest reply and I understand what you mean. Is it also out of question to expose the browser window size to the Bokeh code, so that we can do

if width > = length:
    grid = make_horizontal_layout()
else:
    grid = make_vertical_layout()

?

This would be clean enough on the python side of things :slight_smile:

Thank you.

My offhand speculation (based purely on past experiences, not any concrete investigation) is that that might prove difficult to make completely reliable. But it seems worth looking at cc @mateusz or thoughts. You might also consider opening a GitHub development discussion which is a good place to propose features that might require more up-front discussion or consideration.

I currently can “pull” the dimensions from the Javascript document like this:

# this callback runs on the browser and updates the server side data source from the dimensions data got via Javascript
mycallback = CustomJS( args=dict(ds=window_size_data_source), code="""
var width, height;
var new_data = {};
height = window.innerHeight|| document.documentElement.clientHeight|| document.body.clientHeight;
width  = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
console.log("Javascript callback", height, width);

// for some reason the right side needs to be a list, otherwise we have a server side error
new_data['height'] = [height];
new_data['width' ] = [width];
ds.data = new_data;
ds.change.emit();

""")

# this callbacks takes action on the server side upon dimensions change
def on_source_change(attr, old, new):

    width  = new['width'][0]
    height = new['height'][0]

    print('current dimensions', width, height)

    if width >= height:
        print('orientation is horizontal')
        # switch layout to verticals python side

    else:
        print('orientation is vertical')
        # switch layout to horizontal python side


# this connects a data change on the data source to the server side callback
window_size_data_source.on_change('data', on_source_change)

# TODO: how to have this on a window resize event and during the page load
date_slider_map.js_on_change('value_throttled', mycallback)

The problem (see the last code line) is that this is being triggered by a widget and I’d need this on page load and /or window resize event.

Right, this would entail new development for sure, which is my suggestion for opening a GH discussion. cc also @Philipp_Rudiger who I believe is also interested in more document-level events.

Edit: I guess for completeness I will mention the terrible option, which is that a Python callback for document init is currently available. Presumably that callback could set some random property value just in order to then trigger a CustomJS callback in the client. I don’t think this is an especially good idea.

1 Like

It seems I don’t have permissions to create a discussion. Can you give me permissions? Otherwise, if you can create the discussion I could comment there.

You are the second person to report this, I’m really at a loss as to what might be going on. There are no settings I can find related to GH disucssions permissions and GH docs explicity state that “anyone with access to the repository” should be able to post. I guess I will have to open a support ticket with GH about it.

@ghomem try now, it was an org setting, not a repo setting (and also “beta”)

1 Like

To be more accurate: I do not find a button to create a new discussion. I concluded it would be due to lack of permissions but who knows.

here it is:

1 Like

Nice app !!

you can check it panel flexbox component, which is compatible with bokeh server

https://panel.holoviz.org/reference/layouts/FlexBox.html#layouts-gallery-flexbox

Thank you @nghenzi . Sounds interesting.

After installing panel with pip do I need to run

panel serve ...

instead of

bokeh serve ...

?

Or do I have do to deeper changes in the application?

yes, you can run it with both commands, but you need to change your Row and columns by pn.flexbox,

I am not sure flexbox does what I am after. I want to keep the current grid layout, with scaling, unless the browser window becomes “vertical”. Or, better said, until a minimum width, for which scaling makes the plots too small, is hit. Like here:

This sounds like a hybrid between flexbox and gridbox, because it is a grid that flexes only after a certain width threshold is hit.

The grid on the app above has a certain layout that follows a certain logic and it makes sense to retain the grid as it is (scaled) until a certain width threshold is hit, below which we accept graceful degradation, i.e., we sacrifice the grid layout for readability because scaling would not longer help.

Hi @Bryan ,

As there is no progress on the github discussion yet and I understand such things take time to implement properly, could you please share how to use a python callback for document init?

I would at least run an experiment with this.

Thanks in advance.

@ghomem it’s a standard event from bokeh.events that can be used to add a callback with on_event

from bokeh.events import DocumentReady

def callback(evt):
    pass

curdoc().on_event(DocumentReady, callback)

Thanks @Bryan.

Then how would that call back allow me to pull the window size from the Javascript world? It would maybe toggle state on a hidden Toggle that was connected to a CustomJS callback, that would in turn invoke the call back that sets the layout?