-
-
Notifications
You must be signed in to change notification settings - Fork 477
/
gridstack.py
208 lines (178 loc) · 6.59 KB
/
gridstack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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
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 (y0, x0, y1, x1), obj in 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
})