-
-
Notifications
You must be signed in to change notification settings - Fork 508
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
415798d
commit 999f5c5
Showing
6 changed files
with
329 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import panel as pn\n", | ||
"\n", | ||
"from panel.layout.gridstack import GridStack\n", | ||
"\n", | ||
"pn.extension('gridstack')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"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.\n", | ||
"\n", | ||
"#### Parameters:\n", | ||
"\n", | ||
"For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n", | ||
"\n", | ||
"* **``allow_resize``** (bool): Whether to allow resizing grid cells.\n", | ||
"* **``allow_drag``** (bool): Whether to allow dragging grid cells.\n", | ||
"* **``ncols``** (int): Allows specifying a fixed number of columns (otherwise grid expands to match assigned objects)\n", | ||
"* **``nrows``** (int): Allows specifying a fixed number of rows (otherwise grid expands to match assigned objects)\n", | ||
"* **``mode``** (str): Whether to 'warn', 'error', or simply 'override' on overlapping assignment\n", | ||
"* **``objects``** (list): The list of objects to display in the GridSpec. Should not generally be modified directly except when replaced in its entirety.\n", | ||
"\n", | ||
"___" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"A ``GridStack`` can be created either with a fixed size (the default) or with responsive sizing. In both cases the ``GridSpec`` will modify the contents to ensure the objects fill the grid cells assigned to them.\n", | ||
"\n", | ||
"To demonstrate this behavior, let us declare a responsively sized ``GridStack`` and then assign ``Spacer`` objects with distinct colors. We populate a ``6x12`` grid with these objects and display it:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"gstack = GridStack(sizing_mode='stretch_both')\n", | ||
"\n", | ||
"gstack[ : , 0: 3] = pn.Spacer(background='red', margin=0)\n", | ||
"gstack[0:2, 3: 9] = pn.Spacer(background='green', margin=0)\n", | ||
"gstack[2:4, 6:12] = pn.Spacer(background='orange', margin=0)\n", | ||
"gstack[4:6, 3:12] = pn.Spacer(background='blue', margin=0)\n", | ||
"gstack[0:2, 9:12] = pn.Spacer(background='purple', margin=0)\n", | ||
"\n", | ||
"gstack" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"As we can see the fixed-size ``GridStack`` fills the `800x600` pixels assigned to it and each of the Spacer objects has been resized to fill the alloted grid cells, including the empty grid cell in the center. A convenient way to get an overview of the grid without rendering it is to display the ``grid`` property, which returns an array showing which grid cells have been filled:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"gstack.grid" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"In addition to assigning objects to the grid we can also index the grid:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"pn.Row(gstack[2, 2], width=400, height=400)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"And select a subregion using slicing semantics:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"gstack[0, 1:]" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"The behavior when replacing existing grid cells can be controlled using the ``mode`` option. By default the ``GridStack`` will warn when assigning to one or more grid cells that are already occupied. The behavior may be changed to either error or override silently, by setting ``mode='error'`` or ``mode='override'`` respectively.\n", | ||
"\n", | ||
"### Fixed size grids\n", | ||
"\n", | ||
"We can also set explicit `width` and `height` values on a `GridStack`. Just like in the responsive mode, the ``GridStack`` will automatically set the appropriate sizing values on the grid contents to fill the space correctly. This means that when we resize a component and the state is synced with Python the new size is computed there and only then is the display updated:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import holoviews as hv\n", | ||
"import holoviews.plotting.bokeh\n", | ||
"\n", | ||
"from bokeh.plotting import figure\n", | ||
"\n", | ||
"fig = figure()\n", | ||
"fig.scatter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 2, 1, 0, -1, -2, -3])\n", | ||
"\n", | ||
"gstack = GridStack(width=800, height=600)\n", | ||
"\n", | ||
"gstack[0, :3] = pn.Spacer(background='#FF0000')\n", | ||
"gstack[1:3, 0] = pn.Spacer(background='#0000FF')\n", | ||
"gstack[1:3, 1:3] = fig\n", | ||
"gstack[3:5, 0] = hv.Curve([1, 2, 3])\n", | ||
"gstack[3:5, 1] = 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png'\n", | ||
"gstack[3:5, 2] = pn.Column(\n", | ||
" pn.widgets.FloatSlider(),\n", | ||
" pn.widgets.ColorPicker(),\n", | ||
" pn.widgets.Toggle(name='Toggle Me!')\n", | ||
")\n", | ||
"\n", | ||
"gstack" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.8.8" | ||
}, | ||
"widgets": { | ||
"application/vnd.jupyter.widget-state+json": { | ||
"state": {}, | ||
"version_major": 2, | ||
"version_minor": 0 | ||
} | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 4 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
from collections import OrderedDict | ||
|
||
import param | ||
|
||
from ..io.resources import bundled_files | ||
from ..reactive import ReactiveHTML | ||
from ..util import classproperty | ||
from .grid import GridSpec | ||
|
||
|
||
class GridStack(ReactiveHTML, GridSpec): | ||
""" | ||
The GridStack layout builds on the GridSpec component and | ||
gridstack.js to allow resizing and dragging items in the grid. | ||
""" | ||
|
||
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) | ||
|
||
_template = """ | ||
<div id="grid" class="grid-stack"> | ||
{% 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 | ||
if (model.height) | ||
options.cellHeight = Math.floor(model.height/data.nrows) | ||
const gridstack = GridStack.init(options, grid); | ||
function sync_state() { | ||
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) => { | ||
window.dispatchEvent(new Event("resize")); | ||
sync_state() | ||
}) | ||
gridstack.on('dragstop', (event, el) => { | ||
sync_state() | ||
}) | ||
sync_state() | ||
state.gridstack = gridstack | ||
"""], | ||
'allow_drag': ["state.gridstack.enableMove(data.allow_drag)"], | ||
'allow_resize': ["state.gridstack.enableResize(data.allow_resize)"], | ||
'ncols': ["state.gridstack.column(data.ncols)"], | ||
'nrows': [""" | ||
state.gristack.opts.row = data.nrows | ||
if (data.nrows && model.height) | ||
state.gridstack.cellHeight(Math.floor(model.height/data.nrows)) | ||
else | ||
state.gridstack.cellHeight('auto') | ||
"""] | ||
} | ||
|
||
__css_raw__ = [ | ||
'https://cdn.jsdelivr.net/npm/gridstack@4.2.5/dist/gridstack.min.css', | ||
'https://cdn.jsdelivr.net/npm/gridstack@4.2.5/dist/gridstack-extra.min.css' | ||
] | ||
|
||
__javascript_raw__ = [ | ||
'https://cdn.jsdelivr.net/npm/gridstack@4.2.5/dist/gridstack-h5.js' | ||
] | ||
|
||
_rename = {} | ||
|
||
@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: | ||
width = int(float(self.width)/self.ncols) | ||
else: | ||
width = 0 | ||
|
||
if self.nrows: | ||
height = int(float(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 | ||
|
||
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}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters