Skip to content

feat: expose datashader categorical blending on hvPlot views - #2035

Merged
ghostiee-11 merged 10 commits into
mainfrom
feat/hvplot-categorical-datashade
Aug 24, 2026
Merged

feat: expose datashader categorical blending on hvPlot views#2035
ghostiee-11 merged 10 commits into
mainfrom
feat/hvplot-categorical-datashade

Conversation

@ghostiee-11

Copy link
Copy Markdown
Collaborator

Coloring a dense scatter by a category is a datashader job, and hvPlot derives the count_cat aggregator from by whenever datashade is set, but the keywords were only reachable as loose kwargs and so invisible to the agent's schema; this promotes datashade, dynspread and color_key to params and points the large-frame default at datashade rather than rasterize when by is set. It also carries the param_to_pydantic fix needed to build the agent's response model at all, which overlaps #1935 and #1931 by a different route (fixing the recursion rather than replacing it with an explicit create_model), so those should be reconciled before either lands.

Colors are best with holoviz/hvplot#1752, but degrade to the default palette without it.

@ghostiee-11
ghostiee-11 requested a review from ahuang11 August 19, 2026 09:33
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.35347% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.50%. Comparing base (0e33053) to head (7d378d1).

Files with missing lines Patch % Lines
lumen/tests/views/test_hvplot_datashade.py 87.41% 19 Missing ⚠️
lumen/views/base.py 96.07% 2 Missing ⚠️
lumen/ai/translate.py 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2035      +/-   ##
==========================================
+ Coverage   75.27%   75.50%   +0.22%     
==========================================
  Files         212      214       +2     
  Lines       39238    39556     +318     
==========================================
+ Hits        29538    29865     +327     
+ Misses       9700     9691       -9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Coloring a dense scatter by a category is a datashader job: aggregate
per pixel with count_cat and blend the categories present in each one.
hvPlot derives that aggregator from by= whenever datashade is set, so
the whole plot is expressible declaratively, but the keywords were only
reachable as loose kwargs and so invisible to anything reading the
params.

Promote datashade, dynspread and color_key to params. View.__init__
strips params out of kwargs, so each consumer now forwards them
explicitly: get_plot puts them back for hvPlot, _get_args lets the
nested explorer controls claim them, and the render-size cap reads the
param rather than the kwarg it used to find there.
The agent derives its output schema from the view's params, so the
datashade, dynspread and color_key params are now fields the model can
fill; the prompt says when to reach for them.

Above the row threshold the agent forced rasterize, which reduces each
pixel to a single number and so discards the category it was asked to
color by. Pick datashade instead whenever by is set, and leave the
scalar path alone otherwise.
Asking for one view's schema converted the entire component tree and
then failed on the first param type it could not map, so hvPlotAgent
could not produce a response model at all.

Three things were wrong. Exclusions stopped at the class they were given
for, but the generated models inherit, so an excluded name still arrived
through its parent's model and, worse, the parent kept recursing into
whatever that param pointed at. Subclass expansion is for callers that
want a union over a taxonomy, not for describing a single view, and it
reached down into Panel and Bokeh objects that have no JSON schema.
Field dispatch matched the exact parameter class, so any subclass of a
mapped type aborted the whole model rather than the one field.

Propagate exclusions to base classes, describe the view on its own, and
resolve unmapped parameter types through the MRO.
The constructor already coerced a single column name into a list, but
spec validation runs first and the ListSelector rejected the string
before that coercion could run, so a spec saying `by: family` failed
while the equivalent Python call worked.

Coerce in the validation hook as well, sharing one helper so the two
paths cannot drift.
Three things the model does that the plot cannot survive.

It repeats a column across the axis roles despite being told not to. A
groupby naming x or y raises while the plot is built; one naming the
same column as by is worse, because it pages every category into its own
frame and the datashaded plot then renders successfully having blended
nothing. Both are dropped before the spec is validated.

It sets z on kinds that have no z, which hvPlot answers with a warning
nobody reads.

It names colors for the categories the user mentioned and leaves the
rest out, which datashader rejects outright since it needs one per
category. Fill the remainder from a palette of distinct hues, keeping
the colors that were asked for.
_get_args forwards a param only when one of the explorer's controls claims
it, but the color_key completion was written outside that gate. hvPlot gained
the control after this was written, so on an older hvPlot the keyword reached
hvPlotExplorer.__init__ and was rejected outright rather than falling back to
the default palette.
Two assumptions held on my machine and not in CI. The color_key
forwarding test asserted the keyword is always passed on, but the view
deliberately withholds it when the installed hvPlot has no such control,
which is the case CI runs. And building the explorer builds an hvPlot
converter, which asks for datashader straight away; datashader is not a
Lumen dependency.

Split the version-dependent assertion into its own test, add one that
pins the withholding behaviour either way, and gate the explorer tests
on datashader being present. hvPlotView still covers the same forwarding
without it.
tests/utils.py already exists to hold an optional dependency guard once
rather than per file, so requires_datashader belongs there too.

Also hoists a set out of a 256 iteration comprehension and a repeated
spec lookup out of its condition.
@ghostiee-11
ghostiee-11 force-pushed the feat/hvplot-categorical-datashade branch from 1cb9846 to 012d315 Compare August 19, 2026 15:45
Comment thread lumen/views/base.py
Comment on lines +976 to +979
datashade = param.Boolean(default=False, doc="""
Aggregate the data server-side with datashader and send an image
instead of one glyph per row. Combined with `by` this blends the
categories present in each pixel, rather than overplotting them.""")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ghostiee-11 ghostiee-11 Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup.. i think it will be good addition :)

Thanks for pointing it out...

Datashading reduced each pixel to a row count with no way to ask for
anything else, so shading by an average or a sum was out of reach.

The reductions offered are the ones hvPlot's own explorer names.
count_cat is left out: hvPlot builds it from by, and naming it here only
gets as far as a bare string that never becomes a categorical reduction.
Everything except count and any reduces a value column, which hvPlot
takes as color; asking for one of those without it is answered from
inside the datashader operation with a message that never mentions the
spec, so it is caught up front instead. The agent drops such an
aggregator, having no field to name the column with.
@ghostiee-11
ghostiee-11 requested a review from ahuang11 August 20, 2026 18:30
…al-datashade

# Conflicts:
#	lumen/views/base.py
@ghostiee-11
ghostiee-11 enabled auto-merge (squash) August 24, 2026 17:43
@ghostiee-11
ghostiee-11 merged commit 1470fe2 into main Aug 24, 2026
14 checks passed
@ghostiee-11
ghostiee-11 deleted the feat/hvplot-categorical-datashade branch August 24, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants