Skip to content

Commit

Permalink
Added support to accept split param in hconcat and vconcat functions (#…
Browse files Browse the repository at this point in the history
…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 <7525285+keller-mark@users.noreply.github.com>
  • Loading branch information
tkakar and keller-mark authored Nov 27, 2024
1 parent fcc6ab1 commit 4496e74
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 43 deletions.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,14 @@ dev = [
'pytest>=6.2.4',
'loompy>=3.0.6',
'coverage>=6.3.2',
'flake8==3.8.4',
'flake8>=3.8.4',
#'spatialdata>=0.2.2',
'jupyterlab',
'numba>=0.53.0',
'jupyterlab>=3',
'boto3>=1.16.30',
'scikit-misc>=0.1.3',
'autopep8>=2.0.2',
]

[tool.uv]
Expand Down
64 changes: 35 additions & 29 deletions src/vitessce/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,12 @@ class VitessceConfigViewHConcat:
A class to represent a horizontal concatenation of view instances.
"""

def __init__(self, views):
def __init__(self, views, split=None):
"""
Not meant to be instantiated directly, but instead created and returned by the ``hconcat`` helper function.
"""
self.views = views
self.split = split

def __or__(self, other):
return hconcat(self, other)
Expand All @@ -271,12 +272,14 @@ def __truediv__(self, other):
return vconcat(self, other)


def hconcat(*views):
def hconcat(*views, split=None):
"""
A helper function to create a ``VitessceConfigViewHConcat`` instance.
:param \\*views: A variable number of views to concatenate horizontally.
:type \\*views: VitessceConfigView or VitessceConfigViewVConcat or VitessceConfigViewHConcat
:param split: Optional list of relative width fractions for each view.
:type split: list of int
:returns: The concatenated view instance.
:rtype: VitessceConfigViewHConcat
Expand All @@ -291,21 +294,24 @@ def hconcat(*views):
v1 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
v2 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
v3 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
vc.layout(hconcat(v1, vconcat(v2, v3)))
vc.layout(hconcat(v1, vconcat(v2, v3), split = [1,2]))
split = [1,2] would give v1 (1/3) width, and vconcat(v2, v3) 2/3 width
"""
return VitessceConfigViewHConcat(views)

return VitessceConfigViewHConcat(views, split=split)


class VitessceConfigViewVConcat:
"""
A class to represent a vertical concatenation of view instances.
"""

def __init__(self, views):
def __init__(self, views, split=None):
"""
Not meant to be instantiated directly, but instead created and returned by the ``vconcat`` helper function.
"""
self.views = views
self.split = split

def __or__(self, other):
return hconcat(self, other)
Expand All @@ -314,12 +320,14 @@ def __truediv__(self, other):
return vconcat(self, other)


def vconcat(*views):
def vconcat(*views, split=None):
"""
A helper function to create a ``VitessceConfigViewVConcat`` instance.
:param \\*views: A variable number of views to concatenate vertically.
:type \\*views: VitessceConfigView or VitessceConfigViewVConcat or VitessceConfigViewHConcat
:param split: Optional list of relative height fractions for each view.
:type split: list of int
:returns: The concatenated view instance.
:rtype: VitessceConfigViewVConcat
Expand All @@ -334,9 +342,10 @@ def vconcat(*views):
v1 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
v2 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
v3 = vc.add_view(vt.SPATIAL, dataset=my_dataset)
vc.layout(hconcat(v1, vconcat(v2, v3)))
vc.layout(hconcat(v1, vconcat(v2, v3, [1,2])))
split = [1,2] would give v2 (1/3) height, and v3, 2/3 height
"""
return VitessceConfigViewVConcat(views)
return VitessceConfigViewVConcat(views, split=split)


def _use_coordination_by_dict_helper(scopes, coordination_scopes, coordination_scopes_by):
Expand Down Expand Up @@ -1450,28 +1459,25 @@ def _layout(obj, x_min, x_max, y_min, y_max):
h = y_max - y_min
if isinstance(obj, VitessceConfigView):
obj.set_xywh(x_min, y_min, w, h)
elif isinstance(obj, VitessceConfigViewHConcat):
views = obj.views
num_views = len(views)
for i in range(num_views):
_layout(
views[i],
x_min + (w / num_views) * i,
x_min + (w / num_views) * (i + 1),
y_min,
y_max
)
elif isinstance(obj, VitessceConfigViewVConcat):
else:
views = obj.views
num_views = len(views)
for i in range(num_views):
_layout(
views[i],
x_min,
x_max,
y_min + (h / num_views) * i,
y_min + (h / num_views) * (i + 1),
)
# If the split parameter is provided, it must have the same length as the views.
assert obj.split is None or len(obj.split) == len(views)
split = obj.split or [1] * len(views) # Default to equal split if not provided
total = sum(split)

if isinstance(obj, VitessceConfigViewHConcat):
widths = [int(s / total * w) for s in split]
x_pos = x_min
for view, width in zip(views, widths):
_layout(view, x_pos, x_pos + width, y_min, y_max)
x_pos += width
if isinstance(obj, VitessceConfigViewVConcat):
heights = [int(s / total * h) for s in split]
y_pos = y_min
for view, height in zip(views, heights):
_layout(view, x_min, x_max, y_pos, y_pos + height)
y_pos += height

# Recursively set the values (x,y,w,h) for each view.
_layout(view_concat, 0, 12, 0, 12)
Expand Down
63 changes: 63 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,69 @@ def test_config_set_layout_multi_view():
}


def test_config_set_layout_multi_view_custom_split():
vc = VitessceConfig(schema_version="1.0.15")
my_dataset = vc.add_dataset(name='My Dataset')
v1 = vc.add_view(cm.SPATIAL, dataset=my_dataset)
v2 = vc.add_view(cm.SPATIAL, dataset=my_dataset)
v3 = vc.add_view(cm.SPATIAL, dataset=my_dataset)

vc.layout(hconcat(v1, vconcat(v2, v3, split=[1, 2]), split=[3, 1]))

vc_dict = vc.to_dict()

assert vc_dict == {
"version": "1.0.15",
"name": "",
"description": "",
"datasets": [
{
'uid': 'A',
'name': 'My Dataset',
'files': []
}
],
'coordinationSpace': {
'dataset': {
'A': 'A'
},
},
"layout": [
{
'component': 'spatial',
'coordinationScopes': {
'dataset': 'A',
},
'x': 0,
'y': 0,
'h': 12,
'w': 9,
},
{
'component': 'spatial',
'coordinationScopes': {
'dataset': 'A',
},
'x': 9,
'y': 0,
'h': 4,
'w': 3,
},
{
'component': 'spatial',
'coordinationScopes': {
'dataset': 'A',
},
'x': 9,
'y': 4,
'h': 8,
'w': 3,
}
],
"initStrategy": "auto"
}


def test_config_set_layout_multi_view_magic():
vc = VitessceConfig(schema_version="1.0.15")
my_dataset = vc.add_dataset(name='My Dataset')
Expand Down
41 changes: 28 additions & 13 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4496e74

Please sign in to comment.