3838 _color_vector_to_rgba ,
3939 _get_colors_for_categorical_obs ,
4040 _get_linear_colormap ,
41- _make_continuous_mappable ,
4241 _map_color_seg ,
4342 _maybe_set_colors ,
4443 _prepare_cmap_norm ,
44+ _resolve_continuous_norm ,
4545 _set_color_source_vec ,
4646)
4747from spatialdata_plot .pl ._datashader import (
7777 PointsRenderParams ,
7878 ShapesRenderParams ,
7979 _DsReduction ,
80+ colormap_with_alpha ,
8081)
8182from spatialdata_plot .pl .utils import (
8283 _decorate_axs ,
@@ -515,21 +516,17 @@ def _append_outline_colorbar(
515516) -> None :
516517 """Append a `ColorbarSpec` for a continuous outline column.
517518
518- No-op when ``outline_color_vector`` has no finite values. Honors user-supplied
519- `vmin`/`vmax` on ``cmap_params.norm``; falls back to data range. Mirrors the
520- `vmin == vmax` ±0.5 expansion used by the fill colorbar.
519+ No-op when ``outline_color_vector`` has no finite values; derives the bar from the same resolved
520+ norm the outline pixels use.
521521 """
522522 arr = pd .to_numeric (pd .Series (np .asarray (outline_color_vector )), errors = "coerce" ).to_numpy ()
523- finite = np .isfinite (arr )
524- if not finite .any ():
523+ if not np .isfinite (arr ).any ():
525524 return
526- norm = cmap_params .norm
527- vmin = norm .vmin if norm .vmin is not None else float (np .nanmin (arr [finite ]))
528- vmax = norm .vmax if norm .vmax is not None else float (np .nanmax (arr [finite ]))
525+ used_norm = _resolve_continuous_norm (outline_color_vector , cmap_params )
529526 colorbar_requests .append (
530527 ColorbarSpec (
531528 ax = ax ,
532- mappable = _make_continuous_mappable ( vmin , vmax , cmap_params .cmap ),
529+ mappable = ScalarMappable ( norm = used_norm , cmap = cmap_params .cmap ),
533530 params = colorbar_params ,
534531 label = outline_col ,
535532 alpha = alpha ,
@@ -747,7 +744,7 @@ def _render_shapes(
747744
748745 color_vector = _maybe_apply_transfunc (color_source_vector , color_vector , render_params .transfunc )
749746
750- norm = copy ( render_params .cmap_params .norm )
747+ norm = render_params .cmap_params .fresh_norm ( )
751748
752749 if len (color_vector ) == 0 :
753750 color_vector = [render_params .cmap_params .na_color .get_hex_with_alpha ()]
@@ -968,7 +965,6 @@ def _render_shapes(
968965 render_params = render_params ,
969966 rasterized = sc_settings ._vector_friendly ,
970967 cmap = None ,
971- norm = None ,
972968 fill_alpha = 0.0 ,
973969 outline_alpha = render_params .outline_alpha [0 ],
974970 outline_color = outline_rgba ,
@@ -987,7 +983,6 @@ def _render_shapes(
987983 render_params = render_params ,
988984 rasterized = sc_settings ._vector_friendly ,
989985 cmap = None ,
990- norm = None ,
991986 fill_alpha = 0.0 ,
992987 outline_alpha = render_params .outline_alpha [0 ],
993988 outline_color = render_params .outline_params .outer_outline_color .get_hex (),
@@ -1008,7 +1003,6 @@ def _render_shapes(
10081003 render_params = render_params ,
10091004 rasterized = sc_settings ._vector_friendly ,
10101005 cmap = None ,
1011- norm = None ,
10121006 fill_alpha = 0.0 ,
10131007 outline_alpha = render_params .outline_alpha [1 ],
10141008 outline_color = render_params .outline_params .inner_outline_color .get_hex (),
@@ -1030,7 +1024,6 @@ def _render_shapes(
10301024 render_params = render_params ,
10311025 rasterized = sc_settings ._vector_friendly ,
10321026 cmap = render_params .cmap_params .cmap ,
1033- norm = norm ,
10341027 fill_alpha = render_params .fill_alpha ,
10351028 outline_alpha = 0.0 ,
10361029 zorder = render_params .zorder ,
@@ -1043,25 +1036,9 @@ def _render_shapes(
10431036 path .vertices = trans .transform (path .vertices )
10441037
10451038 if not values_are_categorical :
1046- # Respect explicit vmin/vmax; otherwise derive from finite numeric values, falling back to [0, 1] if unavailable
1047- vmin = render_params .cmap_params .norm .vmin
1048- vmax = render_params .cmap_params .norm .vmax
1049- if vmin is None or vmax is None :
1050- numeric_values = pd .to_numeric (np .asarray (color_vector ), errors = "coerce" )
1051- finite_mask = np .isfinite (numeric_values )
1052- if finite_mask .any ():
1053- data_min = float (np .nanmin (numeric_values [finite_mask ]))
1054- data_max = float (np .nanmax (numeric_values [finite_mask ]))
1055- if vmin is None :
1056- vmin = data_min
1057- if vmax is None :
1058- vmax = data_max
1059- else :
1060- if vmin is None :
1061- vmin = 0.0
1062- if vmax is None :
1063- vmax = 1.0
1064- _cax .set_clim (vmin = vmin , vmax = vmax )
1039+ # Colorbar range from the same resolved norm the fill pixels use.
1040+ used_norm = _resolve_continuous_norm (color_vector , render_params .cmap_params )
1041+ _cax .set_clim (vmin = used_norm .vmin , vmax = used_norm .vmax )
10651042
10661043 _add_legend_and_colorbar (
10671044 ax = ax ,
@@ -1511,7 +1488,7 @@ def _render_points(
15111488
15121489 trans , trans_data = _prepare_transformation (sdata .points [element ], coordinate_system , ax )
15131490
1514- norm = copy ( render_params .cmap_params .norm )
1491+ norm = render_params .cmap_params .fresh_norm ( )
15151492
15161493 method = render_params .method
15171494
@@ -1972,9 +1949,8 @@ def _render_images(
19721949 else render_params .cmap_params .cmap
19731950 )
19741951
1975- # Overwrite alpha in cmap: https://stackoverflow.com/a/10127675
1976- cmap ._init ()
1977- cmap ._lut [:, - 1 ] = render_params .alpha
1952+ # Bake a uniform alpha into a fresh cmap (no shared-cmap mutation).
1953+ cmap = colormap_with_alpha (cmap , render_params .alpha , render_params .cmap_params .na_color .get_hex_with_alpha ())
19781954
19791955 # norm needs to be passed directly to ax.imshow(). If we normalize before, that method would always clip.
19801956 _ax_show_and_transform (
@@ -2432,7 +2408,7 @@ def _render_labels(
24322408 y = xy [:, 1 ],
24332409 color_vector = point_color_vector ,
24342410 color_source_vector = point_color_source_vector ,
2435- norm = copy ( render_params .cmap_params .norm ), # ax.scatter autoscales in place; don't mutate the shared norm
2411+ norm = render_params .cmap_params .fresh_norm ( ), # ax.scatter autoscales in place; don't mutate the shared norm
24362412 na_color = na_color ,
24372413 adata = table if table_name is not None else None ,
24382414 col_for_color = col_for_color ,
@@ -2465,11 +2441,12 @@ def _draw_labels(
24652441 outline_color_source_vector = outline_color_source_vector if seg_boundaries else None ,
24662442 )
24672443
2444+ # labels is pre-baked RGB; cmap/norm only drive the colorbar, so feed the same resolved norm.
24682445 cax = ax .imshow (
24692446 labels ,
24702447 rasterized = True ,
24712448 cmap = None if categorical else render_params .cmap_params .cmap ,
2472- norm = None if categorical else render_params .cmap_params . norm ,
2449+ norm = None if categorical else _resolve_continuous_norm ( color_vector , render_params .cmap_params ) ,
24732450 alpha = alpha ,
24742451 origin = "lower" ,
24752452 zorder = render_params .zorder ,
0 commit comments