@@ -337,6 +337,90 @@ def _ds_shade_categorical(
337337 return _apply_user_alpha (shaded , alpha )
338338
339339
340+ def _shade_datashader_aggregate (
341+ cvs : ds .Canvas ,
342+ frame : Any ,
343+ * ,
344+ col_for_color : str | None ,
345+ color_vector : Any ,
346+ color_by_categorical : bool ,
347+ norm : Normalize | None ,
348+ cmap : Any ,
349+ na_color : Color ,
350+ alpha : float ,
351+ ds_reduction : _DsReduction | None ,
352+ default_reduction : _DsReduction ,
353+ kind : Literal ["shapes" , "points" ],
354+ spread_px : int | None = None ,
355+ shade_how : str = "linear" ,
356+ density : bool = False ,
357+ uniform_alpha : bool = False ,
358+ strip_alpha_hex : bool = False ,
359+ ) -> tuple [Any , Any | None , tuple [Any , Any ] | None , Any ]:
360+ """Aggregate ``frame`` over ``cvs`` and shade it; the core shared by shapes and points.
361+
362+ Runs the steps every datashader caller repeats: ``_ds_aggregate`` -> ``_apply_ds_norm`` ->
363+ transparent-NA guard -> ``_build_color_key`` -> categorical/continuous shade dispatch. Returns
364+ ``(shaded, nan_shaded, reduction_bounds, color_vector)``; ``color_vector`` is returned because the
365+ points path strips hex alpha off it and the caller's legend must match. Callers keep their
366+ element-specific prep (geometry transform / point parse), outline rendering, ``_render_ds_image``
367+ and ``_build_ds_colorbar``.
368+ """
369+ agg , reduction_bounds , nan_agg = _ds_aggregate (
370+ cvs , frame , col_for_color , color_by_categorical , ds_reduction , default_reduction , kind
371+ )
372+ agg , color_span = _apply_ds_norm (agg , norm )
373+ na_color_hex = _hex_no_alpha (na_color .get_hex ())
374+ if na_color .is_fully_transparent ():
375+ nan_agg = None
376+ color_key = _build_color_key (frame , col_for_color , color_by_categorical , color_vector , na_color_hex )
377+
378+ if (
379+ strip_alpha_hex
380+ and color_vector is not None
381+ and len (color_vector ) > 0
382+ and isinstance (color_vector [0 ], str )
383+ and color_vector [0 ].startswith ("#" )
384+ ):
385+ # color_vector usually holds only a few distinct hex strings (one per category), so strip
386+ # alpha on the unique values and map back rather than parsing once per point.
387+ unique_hex , inverse = np .unique (color_vector , return_inverse = True )
388+ color_vector = np .asarray ([_hex_no_alpha (c ) for c in unique_hex ])[inverse ]
389+
390+ # density without a color column collapses to a sequential count gradient; everything else with no
391+ # explicit continuous value (categorical or no color) goes through the categorical shader.
392+ plain_density = density and col_for_color is None
393+ nan_shaded = None
394+ if not plain_density and (color_by_categorical or col_for_color is None ):
395+ shaded = _ds_shade_categorical (
396+ agg ,
397+ color_key ,
398+ color_vector ,
399+ alpha ,
400+ spread_px = spread_px ,
401+ how = shade_how ,
402+ density = density ,
403+ uniform_alpha = uniform_alpha ,
404+ )
405+ else :
406+ shaded , nan_shaded , reduction_bounds = _ds_shade_continuous (
407+ agg ,
408+ color_span ,
409+ norm ,
410+ cmap ,
411+ alpha ,
412+ reduction_bounds ,
413+ nan_agg ,
414+ na_color_hex ,
415+ spread_px = spread_px ,
416+ ds_reduction = ds_reduction ,
417+ how = shade_how ,
418+ uniform_alpha = uniform_alpha ,
419+ )
420+
421+ return shaded , nan_shaded , reduction_bounds , color_vector
422+
423+
340424# ---------------------------------------------------------------------------
341425# Image rendering
342426# ---------------------------------------------------------------------------
0 commit comments