Skip to content

Commit 4496e74

Browse files
tkakarkeller-mark
andauthored
Added support to accept split param in hconcat and vconcat functions (#392)
* Added support to accept split param in hconcat and vconcat functions * Add assertion * Add test and ensure int values * Linting --------- Co-authored-by: Mark Keller <[email protected]>
1 parent fcc6ab1 commit 4496e74

File tree

4 files changed

+128
-43
lines changed

4 files changed

+128
-43
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,14 @@ dev = [
100100
'pytest>=6.2.4',
101101
'loompy>=3.0.6',
102102
'coverage>=6.3.2',
103-
'flake8==3.8.4',
103+
'flake8>=3.8.4',
104104
#'spatialdata>=0.2.2',
105105
'jupyterlab',
106106
'numba>=0.53.0',
107107
'jupyterlab>=3',
108108
'boto3>=1.16.30',
109109
'scikit-misc>=0.1.3',
110+
'autopep8>=2.0.2',
110111
]
111112

112113
[tool.uv]

src/vitessce/config.py

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,12 @@ class VitessceConfigViewHConcat:
258258
A class to represent a horizontal concatenation of view instances.
259259
"""
260260

261-
def __init__(self, views):
261+
def __init__(self, views, split=None):
262262
"""
263263
Not meant to be instantiated directly, but instead created and returned by the ``hconcat`` helper function.
264264
"""
265265
self.views = views
266+
self.split = split
266267

267268
def __or__(self, other):
268269
return hconcat(self, other)
@@ -271,12 +272,14 @@ def __truediv__(self, other):
271272
return vconcat(self, other)
272273

273274

274-
def hconcat(*views):
275+
def hconcat(*views, split=None):
275276
"""
276277
A helper function to create a ``VitessceConfigViewHConcat`` instance.
277278
278279
:param \\*views: A variable number of views to concatenate horizontally.
279280
:type \\*views: VitessceConfigView or VitessceConfigViewVConcat or VitessceConfigViewHConcat
281+
:param split: Optional list of relative width fractions for each view.
282+
:type split: list of int
280283
281284
:returns: The concatenated view instance.
282285
:rtype: VitessceConfigViewHConcat
@@ -291,21 +294,24 @@ def hconcat(*views):
291294
v1 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
292295
v2 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
293296
v3 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
294-
vc.layout(hconcat(v1, vconcat(v2, v3)))
297+
vc.layout(hconcat(v1, vconcat(v2, v3), split = [1,2]))
298+
split = [1,2] would give v1 (1/3) width, and vconcat(v2, v3) 2/3 width
295299
"""
296-
return VitessceConfigViewHConcat(views)
300+
301+
return VitessceConfigViewHConcat(views, split=split)
297302

298303

299304
class VitessceConfigViewVConcat:
300305
"""
301306
A class to represent a vertical concatenation of view instances.
302307
"""
303308

304-
def __init__(self, views):
309+
def __init__(self, views, split=None):
305310
"""
306311
Not meant to be instantiated directly, but instead created and returned by the ``vconcat`` helper function.
307312
"""
308313
self.views = views
314+
self.split = split
309315

310316
def __or__(self, other):
311317
return hconcat(self, other)
@@ -314,12 +320,14 @@ def __truediv__(self, other):
314320
return vconcat(self, other)
315321

316322

317-
def vconcat(*views):
323+
def vconcat(*views, split=None):
318324
"""
319325
A helper function to create a ``VitessceConfigViewVConcat`` instance.
320326
321327
:param \\*views: A variable number of views to concatenate vertically.
322328
:type \\*views: VitessceConfigView or VitessceConfigViewVConcat or VitessceConfigViewHConcat
329+
:param split: Optional list of relative height fractions for each view.
330+
:type split: list of int
323331
324332
:returns: The concatenated view instance.
325333
:rtype: VitessceConfigViewVConcat
@@ -334,9 +342,10 @@ def vconcat(*views):
334342
v1 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
335343
v2 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
336344
v3 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
337-
vc.layout(hconcat(v1, vconcat(v2, v3)))
345+
vc.layout(hconcat(v1, vconcat(v2, v3, [1,2])))
346+
split = [1,2] would give v2 (1/3) height, and v3, 2/3 height
338347
"""
339-
return VitessceConfigViewVConcat(views)
348+
return VitessceConfigViewVConcat(views, split=split)
340349

341350

342351
def _use_coordination_by_dict_helper(scopes, coordination_scopes, coordination_scopes_by):
@@ -1450,28 +1459,25 @@ def _layout(obj, x_min, x_max, y_min, y_max):
14501459
h = y_max - y_min
14511460
if isinstance(obj, VitessceConfigView):
14521461
obj.set_xywh(x_min, y_min, w, h)
1453-
elif isinstance(obj, VitessceConfigViewHConcat):
1454-
views = obj.views
1455-
num_views = len(views)
1456-
for i in range(num_views):
1457-
_layout(
1458-
views[i],
1459-
x_min + (w / num_views) * i,
1460-
x_min + (w / num_views) * (i + 1),
1461-
y_min,
1462-
y_max
1463-
)
1464-
elif isinstance(obj, VitessceConfigViewVConcat):
1462+
else:
14651463
views = obj.views
1466-
num_views = len(views)
1467-
for i in range(num_views):
1468-
_layout(
1469-
views[i],
1470-
x_min,
1471-
x_max,
1472-
y_min + (h / num_views) * i,
1473-
y_min + (h / num_views) * (i + 1),
1474-
)
1464+
# If the split parameter is provided, it must have the same length as the views.
1465+
assert obj.split is None or len(obj.split) == len(views)
1466+
split = obj.split or [1] * len(views) # Default to equal split if not provided
1467+
total = sum(split)
1468+
1469+
if isinstance(obj, VitessceConfigViewHConcat):
1470+
widths = [int(s / total * w) for s in split]
1471+
x_pos = x_min
1472+
for view, width in zip(views, widths):
1473+
_layout(view, x_pos, x_pos + width, y_min, y_max)
1474+
x_pos += width
1475+
if isinstance(obj, VitessceConfigViewVConcat):
1476+
heights = [int(s / total * h) for s in split]
1477+
y_pos = y_min
1478+
for view, height in zip(views, heights):
1479+
_layout(view, x_min, x_max, y_pos, y_pos + height)
1480+
y_pos += height
14751481

14761482
# Recursively set the values (x,y,w,h) for each view.
14771483
_layout(view_concat, 0, 12, 0, 12)

tests/test_config.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,69 @@ def test_config_set_layout_multi_view():
556556
}
557557

558558

559+
def test_config_set_layout_multi_view_custom_split():
560+
vc = VitessceConfig(schema_version="1.0.15")
561+
my_dataset = vc.add_dataset(name='My Dataset')
562+
v1 = vc.add_view(cm.SPATIAL, dataset=my_dataset)
563+
v2 = vc.add_view(cm.SPATIAL, dataset=my_dataset)
564+
v3 = vc.add_view(cm.SPATIAL, dataset=my_dataset)
565+
566+
vc.layout(hconcat(v1, vconcat(v2, v3, split=[1, 2]), split=[3, 1]))
567+
568+
vc_dict = vc.to_dict()
569+
570+
assert vc_dict == {
571+
"version": "1.0.15",
572+
"name": "",
573+
"description": "",
574+
"datasets": [
575+
{
576+
'uid': 'A',
577+
'name': 'My Dataset',
578+
'files': []
579+
}
580+
],
581+
'coordinationSpace': {
582+
'dataset': {
583+
'A': 'A'
584+
},
585+
},
586+
"layout": [
587+
{
588+
'component': 'spatial',
589+
'coordinationScopes': {
590+
'dataset': 'A',
591+
},
592+
'x': 0,
593+
'y': 0,
594+
'h': 12,
595+
'w': 9,
596+
},
597+
{
598+
'component': 'spatial',
599+
'coordinationScopes': {
600+
'dataset': 'A',
601+
},
602+
'x': 9,
603+
'y': 0,
604+
'h': 4,
605+
'w': 3,
606+
},
607+
{
608+
'component': 'spatial',
609+
'coordinationScopes': {
610+
'dataset': 'A',
611+
},
612+
'x': 9,
613+
'y': 4,
614+
'h': 8,
615+
'w': 3,
616+
}
617+
],
618+
"initStrategy": "auto"
619+
}
620+
621+
559622
def test_config_set_layout_multi_view_magic():
560623
vc = VitessceConfig(schema_version="1.0.15")
561624
my_dataset = vc.add_dataset(name='My Dataset')

uv.lock

Lines changed: 28 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)