Repository: holoviz/panel

Issue:
panel equivalent to hv.Layout().cols(2)
In HoloViews, you can call hv.Layout() on a list of objects and wrap the figures automatically with cols(2).


Discussion:

Resolving patch:
diff --git a/panel/__init__.py b/panel/__init__.py
--- a/panel/__init__.py
+++ b/panel/__init__.py
@@ -12,7 +12,7 @@
 from .config import config, panel_extension as extension # noqa
 from .interact import interact # noqa
 from .io import state # noqa
-from .layout import Row, Column, WidgetBox, Tabs, Spacer, GridSpec # noqa
+from .layout import Row, Column, WidgetBox, Tabs, Spacer, GridSpec, GridBox # noqa
 from .pane import panel, Pane # noqa
 from .param import Param # noqa
 from .template import Template # noqa
diff --git a/panel/layout.py b/panel/layout.py
--- a/panel/layout.py
+++ b/panel/layout.py
@@ -9,6 +9,7 @@
 import param
 import numpy as np
 
+from bokeh.layouts import grid as _bk_grid
 from bokeh.models import (Column as BkColumn, Row as BkRow,
                           Spacer as BkSpacer, GridBox as BkGridBox,
                           Box as BkBox, Markup as BkMarkup)
@@ -353,6 +354,63 @@ class Column(ListPanel):
     _bokeh_model = BkColumn
 
 
+
+class GridBox(ListPanel):
+    """
+    List-like Grid which wraps depending on the specified number of
+    rows or columns.
+    """
+
+    nrows = param.Integer(default=None, bounds=(0, None), doc="""
+      Number of rows to reflow the layout into.""")
+
+    ncols = param.Integer(default=None, bounds=(0, None),  doc="""
+      Number of columns to reflow the layout into.""")
+
+    _bokeh_model = BkGridBox
+
+    def _get_model(self, doc, root=None, parent=None, comm=None):
+        model = self._bokeh_model()
+        if root is None:
+            root = model
+        objects = self._get_objects(model, [], doc, root, comm)
+        grid = _bk_grid(objects, nrows=self.nrows, ncols=self.ncols,
+                        sizing_mode=self.sizing_mode)
+        model.children = grid.children
+        props = {k: v for k, v in self._init_properties().items()
+                 if k not in ('nrows', 'ncols')}
+        model.update(**self._process_param_change(props))
+        self._models[root.ref['id']] = (model, parent)
+        self._link_props(model, self._linked_props, doc, root, comm)
+        return model
+
+    def _update_model(self, events, msg, root, model, doc, comm=None):
+        if self._rename['objects'] in msg or 'ncols' in msg or 'nrows' in msg:
+            if 'objects' in events:
+                old = events['objects'].old
+            else:
+                old = self.objects
+            objects = self._get_objects(model, old, doc, root, comm)
+            grid = _bk_grid(objects, nrows=self.nrows, ncols=self.ncols,
+                            sizing_mode=self.sizing_mode)
+            children = grid.children
+            msg[self._rename['objects']] = children
+
+        held = doc._hold
+        if comm is None and not held:
+            doc.hold()
+        model.update(**{k: v for k, v in msg.items() if k not in ('nrows', 'ncols')})
+
+        from .io import state
+        ref = root.ref['id']
+        if ref in state._views:
+            state._views[ref][0]._preprocess(root)
+
+        if comm is None and not held:
+            doc.unhold()
+
+
+
 class WidgetBox(ListPanel):
     """
     Vertical layout of widgets.


Test patch: