Source code for panel.layout.gridstack

from __future__ import annotations

from collections import OrderedDict
from typing import ClassVar, List, Mapping

import param

from ..config import config
from ..io.resources import CDN_DIST, bundled_files
from ..reactive import ReactiveHTML
from ..util import classproperty
from .grid import GridSpec


[docs]class GridStack(ReactiveHTML, GridSpec): """ The `GridStack` layout allows arranging multiple Panel objects in a grid using a simple API to assign objects to individual grid cells or to a grid span. Other layout containers function like lists, but a `GridSpec` has an API similar to a 2D array, making it possible to use 2D assignment to populate, index, and slice the grid. Reference: https://panel.holoviz.org/reference/layouts/GridStack.html :Example: >>> pn.extension('gridstack') >>> gstack = GridStack(sizing_mode='stretch_both') >>> gstack[ : , 0: 3] = pn.Spacer(styles=dict(background='red')) >>> gstack[0:2, 3: 9] = pn.Spacer(styles=dict(background='green')) >>> gstack[2:4, 6:12] = pn.Spacer(styles=dict(background='orange')) >>> gstack[4:6, 3:12] = pn.Spacer(styles=dict(background='blue')) >>> gstack[0:2, 9:12] = pn.Spacer(styles=dict(background='purple')) """ allow_resize = param.Boolean(default=True, doc=""" Allow resizing the grid cells.""") allow_drag = param.Boolean(default=True, doc=""" Allow dragging the grid cells.""") state = param.List(doc=""" Current state of the grid (updated as items are resized and dragged).""") width = param.Integer(default=None) height = param.Integer(default=None) _extension_name = 'gridstack' _template = """ <div id="grid" class="grid-stack" style="width: 100%; height: 100%"> {% for key, obj in objects.items() %} <div data-id="{{ id(obj) }}" class="grid-stack-item" gs-h="{{ (key[2] or nrows)-(key[0] or 0) }}" gs-w="{{ (key[3] or ncols)-(key[1] or 0) }}" gs-y="{{ (key[0] or 0) }}" gs-x="{{ (key[1] or 0) }}"> <div id="content" class="grid-stack-item-content">${obj}</div> </div> {% endfor %} </div> """ # noqa _scripts = { 'render': """ const options = { column: data.ncols, disableResize: !data.allow_resize, disableDrag: !data.allow_drag, margin: 0 } if (data.nrows) { options.row = data.nrows const height = model.height || grid.offsetHeight; options.cellHeight = Math.floor(height/data.nrows); } const gridstack = GridStack.init(options, grid); function sync_state(load=false) { const items = [] for (const node of gridstack.engine.nodes) { items.push({id: node.el.getAttribute('data-id'), x0: node.x, y0: node.y, x1: node.x+node.w, y1: node.y+node.h}) } data.state = items } gridstack.on('resizestop', (event, el) => { sync_state() view.invalidate_layout() }) gridstack.on('dragstop', (event, el) => { sync_state() }) sync_state() state.gridstack = gridstack state.init = false """, 'after_layout': """ self.nrows() if (!state.init) { state.init = true view.invalidate_layout() } state.gridstack.engine._notify() """, 'allow_drag': "state.gridstack.enableMove(data.allow_drag)", 'allow_resize': "state.gridstack.enableResize(data.allow_resize)", 'ncols': "state.gridstack.column(data.ncols)", 'nrows': """ state.gridstack.opts.row = data.nrows if (data.nrows) { const height = model.height || grid.offsetHeight || model.min_height; state.gridstack.cellHeight(Math.floor(height/data.nrows)) } else { state.gridstack.cellHeight('auto') } """, "remove": "state.gridstack.destroy()" } __css_raw__ = [ f'{config.npm_cdn}/gridstack@7.2.3/dist/gridstack.min.css', f'{config.npm_cdn}/gridstack@7.2.3/dist/gridstack-extra.min.css' ] __javascript_raw__ = [ f'{config.npm_cdn}/gridstack@7.2.3/dist/gridstack-all.js' ] __js_require__ = { 'paths': { 'gridstack': f'{config.npm_cdn}/gridstack@7.2.3/dist/gridstack-all' }, 'exports': { 'gridstack': 'GridStack' }, 'shim': { 'gridstack': { 'exports': 'GridStack' } } } _rename: ClassVar[Mapping[str, str | None]] = { 'nrows': 'nrows', 'ncols': 'ncols', 'objects': 'objects' } _stylesheets: ClassVar[List[str]] = [ f'{CDN_DIST}css/gridstack.css' ] @classproperty def __js_skip__(cls): return { 'GridStack': cls.__javascript__[0:1], } @classproperty def __javascript__(cls): return bundled_files(cls) @classproperty def __css__(cls): return bundled_files(cls, 'css') @param.depends('state', watch=True) def _update_objects(self): objects = OrderedDict() object_ids = {str(id(obj)): obj for obj in self} for p in self.state: objects[(p['y0'], p['x0'], p['y1'], p['x1'])] = object_ids[p['id']] self.objects.clear() self.objects.update(objects) self._update_sizing() @param.depends('objects', watch=True) def _update_sizing(self): if self.ncols and self.width: width = self.width/self.ncols else: width = 0 if self.nrows and self.height: height = self.height/self.nrows else: height = 0 for i, ((y0, x0, y1, x1), obj) in enumerate(self.objects.items()): x0 = 0 if x0 is None else x0 x1 = (self.ncols) if x1 is None else x1 y0 = 0 if y0 is None else y0 y1 = (self.nrows) if y1 is None else y1 h, w = y1-y0, x1-x0 properties = {} if self.sizing_mode in ['fixed', None]: if width: properties['width'] = int(w*width) if height: properties['height'] = int(h*height) else: properties['sizing_mode'] = self.sizing_mode if 'width' in self.sizing_mode and height: properties['height'] = int(h*height) elif 'height' in self.sizing_mode and width: properties['width'] = int(w*width) obj.param.update(**{ k: v for k, v in properties.items() if not obj.param[k].readonly })