Set column widths with GridSpec

I want to create an array of widgets that behaves like a table - exactly what GridSpec does. However, I want the rows & columns to be different fixed widths, and I’m not sure how to do this.

I’ve tried playing with a bunch of different sizing options on the GridSpec and the child widgets. However, it seems that GridSpec overrides all of the children’s sizing options on load. If I set an entire column of children to a fixed width after loading, the GridSpec column will resize to respect that. But if I try to do that while composing the panel, before the GridSpec loads, it seemingly has no effect.

I know that I can have one widget simply take multiple rows/columns, but this feels awkward/kludgy and I’m stuck with rational ratios which isn’t great when I just want relatively minor adjustments.

I’m wondering if I should create, say, a Column of Rows and enforce the “table” alignment via the widths of the widgets or Spacers in each Row. However, this feels like it would be pretty fragile.

Can anyone tell me what the best way or the canon way to achieve something like this would be? Has anyone else had such a use case? Thanks in advance!

Hi @nitrocalcite

Welcome to the community.

If you have a lot of widgets I would actually take a look at creating a custom template https://panel.holoviz.org/user_guide/Templates.html.

That will give just the most performant app.

If you could post a small code example based on Gridspec it would be a good starting point for getting help.

@Marc Thank you! I’ve been really enjoying how high-level and modern Panel is.

Thanks for the tips about Templates - I didn’t know about that, and certainly that seems like a good idea if there’s no other way.

Here’s a bit of code to demonstrate what I’m talking about:

import panel as pn
import time

panel = pn.GridSpec()

cols = ["Name", "Value", "Min", "Max", "Vary", "Expr"]
for n, col in enumerate(cols):
    panel[0, n] = pn.pane.Markdown(f"### {col}", height_policy='min')

for n in range(1,11):
    panel[n, 0] = pn.pane.Str(f"Label {n*'l'}ength", width=100, width_policy='fit')
    panel[n, 1] = pn.widgets.FloatInput(value=0, width=150, width_policy='fit')
    panel[n, 2] = pn.widgets.FloatInput(value=1e308, width=150, width_policy='fit')
    panel[n, 3] = pn.widgets.FloatInput(value=-1e308, width=150, width_policy='fit')
    panel[n, 4] = pn.widgets.Checkbox(value=True, width_policy='fit')
    panel[n, 5] = pn.widgets.TextInput(value="" if n != 10 else "long long long string", width=150, width_policy='fit')

panel.show(threaded=True)

time.sleep(10)

for widget in panel[:, 3]:
    widget.width = 150

When it first loads, you’ll notice that the spacing is pretty ugly & that none of the parameters I’ve set in in the widget constructors are followed. Specifically, I would want to change:

  1. Less space under the first row
  2. Make the column of checkboxes narrower
  3. Make the column with text wider
    (etc)

After 10s, I re-set the widths of one column, and you’ll notice that GridSpec will resize to fit. However, if you call the same code before the GridSpec displays, it apparently has no effect.

1 Like

As a workaround hack you Can try running the resizing as a callback to the new ‘on_load’ function. Then it will run once when everything is loaded.

I believe its pn.state.on_load or something similar.

Hmm, thanks for the suggestion. However, pn.state.curdoc is always None for me, though everything works as expected. Thus, pn.state.onload just runs the callback directly, so I see the same behavior.

I was thinking about how to modify the resizing behavior, and I figured it was JS-side. To my surprise though, I found the resizing code Python-side in GridSpec’s _get_object:

            if self.sizing_mode in ['fixed', None]:
                properties = {'width': w*width, 'height': h*height}
            else:
                properties = {'sizing_mode': self.sizing_mode}
                if 'width' in self.sizing_mode:
                    properties['height'] = h*height
                elif 'height' in self.sizing_mode:
                    properties['width'] = w*width
            obj.param.set_param(**{k: v for k, v in properties.items()
                                   if not obj.param[k].readonly})

So actually I can just set the widget’s width/height/scaling params to readonly, or use sizing_mode='scale_both'/'stretch_both' to disable this behavior. Or even subclass GridSpec.

This is working for me, so thanks a lot for your help!