Skip to content

Commit 2fd6668

Browse files
author
Sonja Stockhaus
committed
add user warning and tests
1 parent 7641a69 commit 2fd6668

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

src/spatialdata_plot/pl/render.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ def _render_shapes(
166166
current_type = shapes["geometry"].type
167167
if not (render_params.shape == "circle" and (current_type == "Point").all()):
168168
logger.info(f"Converting {shapes.shape[0]} shapes to {render_params.shape}.")
169-
shapes = _convert_shapes(shapes, render_params.shape)
169+
max_extent = np.max(
170+
[shapes.total_bounds[2] - shapes.total_bounds[0], shapes.total_bounds[3] - shapes.total_bounds[1]]
171+
)
172+
shapes = _convert_shapes(shapes, render_params.shape, max_extent)
170173

171174
# Determine which method to use for rendering
172175
method = render_params.method

src/spatialdata_plot/pl/utils.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2493,8 +2493,16 @@ def _hex_no_alpha(hex: str) -> str:
24932493
raise ValueError("Invalid hex color length: must be either '#RRGGBB' or '#RRGGBBAA'")
24942494

24952495

2496-
def _convert_shapes(shapes: GeoDataFrame, target_shape: str) -> GeoDataFrame:
2496+
def _convert_shapes(
2497+
shapes: GeoDataFrame, target_shape: str, max_extent: float, warn_above_extent_fraction: float = 0.5
2498+
) -> GeoDataFrame:
24972499
"""Convert the shapes stored in a GeoDataFrame (geometry column) to the target_shape."""
2500+
# NOTE: possible follow-up: when converting equally sized shapes to hex, automatically scale resulting hexagons
2501+
# so that they are perfectly adjacent to each other
2502+
2503+
if warn_above_extent_fraction < 0.0 or warn_above_extent_fraction > 1.0:
2504+
warn_above_extent_fraction = 0.5 # set to default if the value is outside [0, 1]
2505+
warn_shape_size = False
24982506

24992507
# define individual conversion methods
25002508
def _circle_to_hexagon(center: shapely.Point, radius: float) -> tuple[shapely.Polygon, None]:
@@ -2528,6 +2536,9 @@ def _polygon_to_circle(polygon: shapely.Polygon) -> tuple[shapely.Point, float]:
25282536
center = np.mean(circle_points, axis=0)
25292537
radius = max(float(np.linalg.norm(p - center)) for p in circle_points)
25302538
assert isinstance(radius, float) # shut up mypy
2539+
if 2 * radius > max_extent * warn_above_extent_fraction:
2540+
nonlocal warn_shape_size
2541+
warn_shape_size = True
25312542
return shapely.Point(center), radius
25322543

25332544
def _multipolygon_to_hexagon(multipolygon: shapely.MultiPolygon) -> tuple[shapely.Polygon, None]:
@@ -2547,6 +2558,9 @@ def _multipolygon_to_circle(multipolygon: shapely.MultiPolygon) -> tuple[shapely
25472558
center = np.mean(circle_points, axis=0)
25482559
radius = max(float(np.linalg.norm(p - center)) for p in circle_points)
25492560
assert isinstance(radius, float) # shut up mypy
2561+
if 2 * radius > max_extent * warn_above_extent_fraction:
2562+
nonlocal warn_shape_size
2563+
warn_shape_size = True
25502564
return shapely.Point(center), radius
25512565

25522566
# define dict with all conversion methods
@@ -2587,4 +2601,11 @@ def _multipolygon_to_circle(multipolygon: shapely.MultiPolygon) -> tuple[shapely
25872601
shapes["radius"] = np.nan
25882602
shapes["radius"][i] = radius
25892603

2604+
if warn_shape_size:
2605+
logger.info(
2606+
f"When converting the shapes, the size of at least one target shape extends "
2607+
f"{warn_above_extent_fraction * 100}% of the original total bound of the shapes. The conversion"
2608+
" might not give satisfying results in this scenario."
2609+
)
2610+
25902611
return shapes

tests/pl/test_render_shapes.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,51 @@ def test_plot_can_annotate_shapes_with_table_layer(self, sdata_blobs: SpatialDat
562562
sdata_blobs["circle_table"].layers["normalized"] = RNG.random((nrows, ncols))
563563

564564
sdata_blobs.pl.render_shapes("blobs_circles", color="feature0", table_layer="normalized").pl.show()
565+
566+
def test_plot_can_render_circles_to_hex(self, sdata_blobs: SpatialData):
567+
sdata_blobs.pl.render_shapes(element="blobs_circles", shape="hex").pl.show()
568+
569+
def test_plot_can_render_circles_to_square(self, sdata_blobs: SpatialData):
570+
sdata_blobs.pl.render_shapes(element="blobs_circles", shape="square").pl.show()
571+
572+
def test_plot_can_render_polygons_to_hex(self, sdata_blobs: SpatialData):
573+
sdata_blobs.pl.render_shapes(element="blobs_polygons", shape="hex").pl.show()
574+
575+
def test_plot_can_render_polygons_to_square(self, sdata_blobs: SpatialData):
576+
sdata_blobs.pl.render_shapes(element="blobs_polygons", shape="square").pl.show()
577+
578+
def test_plot_can_render_polygons_to_circle(self, sdata_blobs: SpatialData):
579+
sdata_blobs.pl.render_shapes(element="blobs_polygons", shape="circle").pl.show()
580+
581+
def test_plot_can_render_multipolygons_to_hex(self, sdata_blobs: SpatialData):
582+
sdata_blobs.pl.render_shapes(element="blobs_multipolygons", shape="hex").pl.show()
583+
584+
def test_plot_can_render_multipolygons_to_square(self, sdata_blobs: SpatialData):
585+
sdata_blobs.pl.render_shapes(element="blobs_multipolygons", shape="square").pl.show()
586+
587+
def test_plot_can_render_multipolygons_to_circle(self, sdata_blobs: SpatialData):
588+
sdata_blobs.pl.render_shapes(element="blobs_multipolygons", shape="circle").pl.show()
589+
590+
def test_plot_datashader_can_render_circles_to_hex(self, sdata_blobs: SpatialData):
591+
sdata_blobs.pl.render_shapes(element="blobs_circles", shape="hex", method="datashader").pl.show()
592+
593+
def test_plot_datashader_can_render_circles_to_square(self, sdata_blobs: SpatialData):
594+
sdata_blobs.pl.render_shapes(element="blobs_circles", shape="square", method="datashader").pl.show()
595+
596+
def test_plot_datashader_can_render_polygons_to_hex(self, sdata_blobs: SpatialData):
597+
sdata_blobs.pl.render_shapes(element="blobs_polygons", shape="hex", method="datashader").pl.show()
598+
599+
def test_plot_datashader_can_render_polygons_to_square(self, sdata_blobs: SpatialData):
600+
sdata_blobs.pl.render_shapes(element="blobs_polygons", shape="square", method="datashader").pl.show()
601+
602+
def test_plot_datashader_can_render_polygons_to_circle(self, sdata_blobs: SpatialData):
603+
sdata_blobs.pl.render_shapes(element="blobs_polygons", shape="circle", method="datashader").pl.show()
604+
605+
def test_plot_datashader_can_render_multipolygons_to_hex(self, sdata_blobs: SpatialData):
606+
sdata_blobs.pl.render_shapes(element="blobs_multipolygons", shape="hex", method="datashader").pl.show()
607+
608+
def test_plot_datashader_can_render_multipolygons_to_square(self, sdata_blobs: SpatialData):
609+
sdata_blobs.pl.render_shapes(element="blobs_multipolygons", shape="square", method="datashader").pl.show()
610+
611+
def test_plot_datashader_can_render_multipolygons_to_circle(self, sdata_blobs: SpatialData):
612+
sdata_blobs.pl.render_shapes(element="blobs_multipolygons", shape="circle", method="datashader").pl.show()

0 commit comments

Comments
 (0)