diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 94f530ff..966c9cf8 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -1067,6 +1067,11 @@ def _scatter_points( x, y, s=size, + # No edge stroke: matplotlib's default (edgecolors="face", linewidths=1pt) strokes each + # circle marker with a fixed points-unit ring. When the fill disk goes sub-pixel (tiny + # `size` at high dpi) only that ring survives, rendering markers as hollow outlines. + # linewidths=0 keeps them solid fills whose radius honours `size`. + linewidths=0, rasterized=sc_settings._vector_friendly, alpha=alpha, transform=trans_data, diff --git a/tests/_images/Points_can_annotate_points_with_nan_in_df_categorical.png b/tests/_images/Points_can_annotate_points_with_nan_in_df_categorical.png index dca4da2b..ca355bfd 100644 Binary files a/tests/_images/Points_can_annotate_points_with_nan_in_df_categorical.png and b/tests/_images/Points_can_annotate_points_with_nan_in_df_categorical.png differ diff --git a/tests/_images/Points_can_annotate_points_with_nan_in_df_continuous.png b/tests/_images/Points_can_annotate_points_with_nan_in_df_continuous.png index 9a5dabb3..dfa6aaed 100644 Binary files a/tests/_images/Points_can_annotate_points_with_nan_in_df_continuous.png and b/tests/_images/Points_can_annotate_points_with_nan_in_df_continuous.png differ diff --git a/tests/_images/Points_can_annotate_points_with_nan_in_table_X_continuous.png b/tests/_images/Points_can_annotate_points_with_nan_in_table_X_continuous.png index 126f4660..6800647e 100644 Binary files a/tests/_images/Points_can_annotate_points_with_nan_in_table_X_continuous.png and b/tests/_images/Points_can_annotate_points_with_nan_in_table_X_continuous.png differ diff --git a/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_categorical_matplotlib.png b/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_categorical_matplotlib.png index 0c2fb7b4..3cfbb24f 100644 Binary files a/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_categorical_matplotlib.png and b/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_categorical_matplotlib.png differ diff --git a/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_continuous.png b/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_continuous.png index a36cea06..92ce1e2e 100644 Binary files a/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_continuous.png and b/tests/_images/Points_can_annotate_points_with_nan_in_table_obs_continuous.png differ diff --git a/tests/_images/Points_can_annotate_points_with_table_obs.png b/tests/_images/Points_can_annotate_points_with_table_obs.png index 7a253d5e..03878810 100644 Binary files a/tests/_images/Points_can_annotate_points_with_table_obs.png and b/tests/_images/Points_can_annotate_points_with_table_obs.png differ diff --git a/tests/_images/Points_can_filter_with_groups_custom_palette.png b/tests/_images/Points_can_filter_with_groups_custom_palette.png index 092099fc..90b98956 100644 Binary files a/tests/_images/Points_can_filter_with_groups_custom_palette.png and b/tests/_images/Points_can_filter_with_groups_custom_palette.png differ diff --git a/tests/_images/Points_can_filter_with_groups_default_palette.png b/tests/_images/Points_can_filter_with_groups_default_palette.png index 8bdf767e..ccd5c63e 100644 Binary files a/tests/_images/Points_can_filter_with_groups_default_palette.png and b/tests/_images/Points_can_filter_with_groups_default_palette.png differ diff --git a/tests/_images/Points_can_stack_render_points.png b/tests/_images/Points_can_stack_render_points.png index d38b0eb5..6c3cf44c 100644 Binary files a/tests/_images/Points_can_stack_render_points.png and b/tests/_images/Points_can_stack_render_points.png differ diff --git a/tests/_images/Points_can_use_norm_with_clip.png b/tests/_images/Points_can_use_norm_with_clip.png index 123d0ead..de3d10d3 100644 Binary files a/tests/_images/Points_can_use_norm_with_clip.png and b/tests/_images/Points_can_use_norm_with_clip.png differ diff --git a/tests/_images/Points_can_use_norm_without_clip.png b/tests/_images/Points_can_use_norm_without_clip.png index daa30d2a..5e6cb1af 100644 Binary files a/tests/_images/Points_can_use_norm_without_clip.png and b/tests/_images/Points_can_use_norm_without_clip.png differ diff --git a/tests/_images/Points_datashader_matplotlib_stack.png b/tests/_images/Points_datashader_matplotlib_stack.png index 7c5a6c5d..9767ffab 100644 Binary files a/tests/_images/Points_datashader_matplotlib_stack.png and b/tests/_images/Points_datashader_matplotlib_stack.png differ diff --git a/tests/_images/Points_groups_na_color_none_filters_points.png b/tests/_images/Points_groups_na_color_none_filters_points.png index 4c440ecf..7e506527 100644 Binary files a/tests/_images/Points_groups_na_color_none_filters_points.png and b/tests/_images/Points_groups_na_color_none_filters_points.png differ diff --git a/tests/_images/Points_points_render_permutations.png b/tests/_images/Points_points_render_permutations.png index 71b9198e..f051c64d 100644 Binary files a/tests/_images/Points_points_render_permutations.png and b/tests/_images/Points_points_render_permutations.png differ diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index 5a3731d6..4fb03579 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -3,6 +3,7 @@ import dask.dataframe import datashader as ds import matplotlib +import matplotlib.collections import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -253,6 +254,22 @@ def test_render_points_missing_region_for_table_raises_key_error(self, sdata_blo with pytest.raises(KeyError, match="does not annotate element"): blob.pl.render_points(element="blobs_points", color="table_value", table_name="other_table") + def test_render_points_markers_have_no_edge_stroke(self, sdata_blobs: SpatialData) -> None: + # Regression test for #756: matplotlib's default scatter edge stroke (linewidths=1pt) turns + # small markers at high dpi into hollow "outlined" circles. The scatter collection must carry + # a zero linewidth so markers stay solid fills at every size. + # This is a fast, non-visual guard on the mechanism; the pixel-level "solid disk" appearance + # (whose only tell is a subtle annular minimum, awkward to assert without flakiness) is covered + # by the CI image-comparison baselines. A literal colour avoids the categorical legend's own + # (linewidth-1) scatter handles. + sdata_blobs.pl.render_points("blobs_points", color="red", size=0.1, method="matplotlib").pl.show(dpi=300) + ax = plt.gca() + collections = [c for c in ax.collections if isinstance(c, matplotlib.collections.PathCollection)] + assert collections, "no scatter collection was drawn" + for coll in collections: + assert np.all(coll.get_linewidths() == 0) + plt.close() + def test_plot_datashader_colors_from_table_obs(self, sdata_blobs: SpatialData): n_obs = len(sdata_blobs["blobs_points"]) obs = pd.DataFrame(