Skip to content

fill_alpha is ignored in the datashader render path — shapes always render at full opacity #617

Closed

Description

@timtreis

fill_alpha is ignored in the datashader render path — shapes always render at full opacity

Description

When render_shapes uses the datashader path (automatically triggered for >10,000 shapes), fill_alpha has no effect. Shapes always render at full opacity (alpha=255) regardless of the fill_alpha value — including fill_alpha=0.0. The matplotlib path correctly applies fill_alpha. Because the method switch from matplotlib to datashader is automatic and silent, large datasets render differently from small ones with no warning to the user.

Root cause: _ds_shade_categorical passes min_alpha=_convert_alpha_to_datashader_range(alpha) to ds.tf.shade(). min_alpha is a floor on alpha for non-empty pixels, not a scaling factor. Setting min_alpha=0 still allows datashader to render shapes at alpha=255.

Environment

spatialdata-plot: 0.3.4.dev (main, 5cfedc7)
spatialdata: 0.5.0
Python: 3.13

Minimal Reproducible Example

import matplotlib; matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import geopandas as gpd
import dask; dask.config.set({"dataframe.query-planning": False})
import spatialdata as sd
from spatialdata.models import ShapesModel
import spatialdata_plot
from shapely.geometry import box
from PIL import Image

# Create enough shapes to trigger datashader (> 10,000)
n = 12000
shapes = ShapesModel.parse(gpd.GeoDataFrame(
    {
        "geometry": [box(i % 100, i // 100, i % 100 + 0.8, i // 100 + 0.8) for i in range(n)],
        "radius": [0.4] * n,
    },
    geometry="geometry",
))
sdata = sd.SpatialData(shapes={"s": shapes})

fig, ax = plt.subplots()
sdata.pl.render_shapes("s", fill_alpha=0.0).pl.show(ax=ax)
fig.savefig("/tmp/ds_alpha_test.png", dpi=20, facecolor="white")

arr = np.array(Image.open("/tmp/ds_alpha_test.png"))
print(f"Max alpha in image: {arr[:, :, 3].max()}")  # 255 instead of 0

Expected vs. Actual

Expected: fill_alpha=0.0 renders shapes invisible (alpha=0). fill_alpha=0.3 renders at 30% opacity, matching the matplotlib path.

Actual: Shapes render at full opacity (alpha=255) even with fill_alpha=0.0. No warning is emitted that datashader ignores fill_alpha.

Fix Sketch

After _datashader_map_aggregate_to_color returns an RGBA array, post-multiply the alpha channel by fill_alpha:

rgba_array[:, :, 3] = (rgba_array[:, :, 3] * fill_alpha).astype(np.uint8)

This makes the datashader path consistent with the matplotlib path for all fill_alpha values. The fix is applied after datashader compositing, so it does not interfere with datashader's internal alpha handling.

Labels: bug, shapes, priority: high


Triage tier: Tier 2

Activity

  1. added a commit that references this issue on May 8, 2026
    127fc6d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions