Skip to content

Add gridded heatmap guidance to VegaLiteAgent prompt#1824

Closed
ghostiee-11 wants to merge 9 commits into
holoviz:mainfrom
ghostiee-11:feat/xarray-gridded-vegalite
Closed

Add gridded heatmap guidance to VegaLiteAgent prompt#1824
ghostiee-11 wants to merge 9 commits into
holoviz:mainfrom
ghostiee-11:feat/xarray-gridded-vegalite

Conversation

@ghostiee-11

@ghostiee-11 ghostiee-11 commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator

When the AI receives a gridded xarray-derived dataset and the user asks for a visualisation, the VegaLite agent has no guidance on how to represent 2D fields. It falls back to geographic point charts (longitude/latitude encoding) which render empty for non-geographic grids, or picks an inappropriate mark type.

This adds a guarded block to the VegaLiteAgent prompt that fires only when the pipeline originates from an xarray source. It instructs the LLM to use mark: rect with ordinal x/y axes mapped to the coordinate dimensions and a
viridis colour scale on the value column — the correct Vega-Lite pattern for a
2D heatmap from long-form data.

Demo

How to reproduce

  1. Load a lat/lon/value CSV and ask "plot air temperature over lat and lon"
  2. Before: VegaLite agent uses geographic encoding, chart renders empty
  3. After: agent picks mark: rect with correct x, y, color mapping

How Has This Been Tested?

pytest lumen/tests/ai/test_vega_lite_gridded.py -v    # 2 new prompt tests
pytest lumen/tests/ -x                                 # full suite, no regressions

Addresses #1821 (2/3)

After holoviz#1791 landed, loading a NetCDF file and asking the AI to plot it
produces a scatter or line chart rather than a 2D heatmap, because:

- hvPlotBaseView had no way to pass a value column (C) to hvplot
- The AI agents had no awareness the pipeline was backed by gridded
  xarray data

Changes:
- Add `C` param (value column) to hvPlotBaseView; wire into df.hvplot()
- Add `quadmesh` to the kind selector
- New lumen/ai/gridded.py: detect_gridded() returns coordinate/variable
  metadata when pipeline.source is XArraySQLSource, else None
- BaseViewAgent._generate_yaml_spec: call detect_gridded() once, pass
  result as `gridded` template variable
- BaseViewAgent/main.jinja2 + hvPlotAgent/main.jinja2: conditional
  block guides LLM to use kind='heatmap' with x/y/C for gridded data
- hvPlotAgent._extract_spec: drop by/groupby when kind=heatmap

Addresses holoviz#1821 (1/3)
@codecov

codecov Bot commented Apr 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.63319% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.24%. Comparing base (674e698) to head (d70be68).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
lumen/views/base.py 85.71% 5 Missing ⚠️
lumen/ai/agents/hvplot.py 0.00% 3 Missing ⚠️
lumen/ai/agents/base_view.py 0.00% 1 Missing ⚠️
lumen/ai/utils.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1824      +/-   ##
==========================================
+ Coverage   70.02%   70.24%   +0.21%     
==========================================
  Files         193      196       +3     
  Lines       32445    32808     +363     
==========================================
+ Hits        22721    23045     +324     
- Misses       9724     9763      +39     

☔ View full report in Codecov by Sentry.
📢 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.

Comment thread lumen/views/base.py Outdated
if self.streaming:
processed['stream'] = self._data_stream
if self.C is not None:
processed['C'] = self.C

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

hvPlot generally uses z, only heatmap support C.

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.

Sure thanks will change to use z

ghostiee-11 added a commit to ghostiee-11/lumen that referenced this pull request Apr 21, 2026
Per review on holoviz#1824: hvPlot uses `C` only for heatmap (matplotlib
hexbin convention), and `z` for image, quadmesh, contourf. Exposing
the param as `C` for all gridded kinds would silently pass C= to plot
kinds that expect z=.

Fix: rename the public param `C` to `z` and map internally to C= only
when kind='heatmap', otherwise pass through as z=. Updated prompt
guidance in BaseViewAgent/main.jinja2 and hvPlotAgent/main.jinja2 to
use z= and mention the other gridded kinds. Tests updated to use z=
and a new test verifies the z-to-C mapping is conditional on kind.
Per review on holoviz#1824: hvPlot uses `C` only for heatmap (matplotlib
hexbin convention), and `z` for image, quadmesh, contourf. Exposing
the param as `C` for all gridded kinds would silently pass C= to plot
kinds that expect z=.

Fix: rename the public param `C` to `z` and map internally to C= only
when kind='heatmap', otherwise pass through as z=. Updated prompt
guidance in BaseViewAgent/main.jinja2 and hvPlotAgent/main.jinja2 to
use z= and mention the other gridded kinds. Tests updated to use z=
and a new test verifies the z-to-C mapping is conditional on kind.
@ghostiee-11
ghostiee-11 force-pushed the feat/xarray-gridded-vegalite branch from 747554b to d0bdaa0 Compare April 21, 2026 14:49
Data originates from an xarray dataset.
- dimensions: {{ gridded.dims | join(', ') }}
- data variables: {{ gridded.data_vars | join(', ') }}
Use kind='heatmap' (or 'quadmesh'/'image'/'contourf') with x/y set to coordinate dimensions and z set to the value column.

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.

This isn't a valid kind across VegaLiteView I think...

Per review on holoviz#1823: heatmap treats x/y as ordinal categories, which is
semantically wrong for continuous-coordinate gridded data. quadmesh and
image preserve the continuous axes and are the right default for xarray-
derived grids. But hvPlot's quadmesh/image/contourf kinds raise
NotImplementedError on long-form pandas DataFrames (they expect a 2D
array or xarray), and XArraySQLSource returns pandas via SQL.

Fix: hvPlotBaseView.get_plot now pivots the long-form DataFrame to a 2D
xarray DataArray (via set_index([y, x])[z].to_xarray()) when kind is in
_GRIDDED_KINDS = (quadmesh, image, contourf) and x/y/z are set.

Safety guards in _to_gridded:
- Skips pivot if the input is not a pandas DataFrame (xarray objects
  pass through unchanged, so future xarray-returning sources work).
- Skips pivot if hvplot.xarray failed to import (import flag set at
  __init__ time; avoids AttributeError at plot time).
- Skips pivot if x/y/z are missing or required columns are absent
  (falls back to hvPlot's own NotImplementedError).
- Skips pivot if duplicate (y, x) pairs exist (pivot would be ambiguous).
- Raises ValueError if the pivoted grid would exceed
  _GRIDDED_MAX_CELLS = 10M cells (~80MB for float64) to prevent OOM.

Prompt guidance updated: recommends kind='quadmesh' as the primary
choice for xarray-derived gridded data, with image/contourf as
alternatives and heatmap as a fallback for ordinal/categorical axes.
heatmap still stays on the pandas path (it works fine there).

hvplot.xarray is imported in hvPlotBaseView.__init__ alongside
hvplot.pandas so the .hvplot accessor is available on DataArray.

Tests (10 in test_hvplot_gridded.py):
- test_hvplot_quadmesh_from_longform_df: renders hv.QuadMesh
- test_hvplot_image_from_longform_df: renders hv.Image
- test_hvplot_gridded_size_guard_raises: ValueError over cap
- test_hvplot_gridded_skips_pivot_if_xarray_unavailable: flag fallback
- test_hvplot_gridded_passes_through_non_dataframe: xarray input skip
- existing z-param and heatmap tests updated
Pre-commit ruff auto-fix flagged the # noqa as unused; the try/except
already silences the ImportError, so the # type: ignore is enough.
The previous lint cleanup over-stripped the noqa on hvplot.dask, which
is a genuine F401 (conditional side-effect import for the .hvplot
accessor on dask DataFrames). Restored as scoped # noqa: F401 so
RUF100 recognises it as a targeted suppression.
ghostiee-11 added a commit to ghostiee-11/lumen that referenced this pull request Apr 23, 2026
Two review responses bundled:

1. Per review on holoviz#1824: BaseViewAgent/main.jinja2 is the base template
   inherited by hvPlotAgent, VegaLiteAgent, and DeckGLAgent. The previous
   gridded block named kind='quadmesh'/'image'/'contourf'/'heatmap' which
   are hvPlot-only concepts. VegaLite uses mark: rect, DeckGL uses layer
   types, so injecting hvPlot kind values into their prompt context is
   misleading. Kept the metadata (xarray source, dims, data_vars) in the
   base block; each agent's own prompt carries the syntax-specific
   guidance. Updated test to assert no hvPlot kinds leak into the base.

2. Style cleanup for lint discipline: moved holoviews/pandas/xarray/
   unittest.mock imports to the top of test_hvplot_gridded.py (was
   inline inside test bodies). Replaced two em dashes in hvPlotAgent
   prompt and test docstring.
@ghostiee-11
ghostiee-11 force-pushed the feat/xarray-gridded-vegalite branch from d0bdaa0 to 3bb8c6c Compare April 23, 2026 19:40
@ghostiee-11
ghostiee-11 marked this pull request as draft April 23, 2026 19:42
Two review responses bundled:

1. Per review on holoviz#1824: BaseViewAgent/main.jinja2 is the base template
   inherited by hvPlotAgent, VegaLiteAgent, and DeckGLAgent. The previous
   gridded block named kind='quadmesh'/'image'/'contourf'/'heatmap' which
   are hvPlot-only concepts. VegaLite uses mark: rect, DeckGL uses layer
   types, so injecting hvPlot kind values into their prompt context is
   misleading. Kept the metadata (xarray source, dims, data_vars) in the
   base block; each agent's own prompt carries the syntax-specific
   guidance. Updated test to assert no hvPlot kinds leak into the base.

2. Style cleanup for lint discipline: moved MagicMock/patch/holoviews/
   numpy/pandas imports to the top of test_hvplot_gridded.py (was inline
   inside test bodies). xarray is an optional dep, so it is guarded with
   pytest.importorskip inside the tests that actually use it to keep
   test-core collection working. Replaced two em dashes in hvPlotAgent
   prompt and a test docstring.
@ghostiee-11
ghostiee-11 force-pushed the feat/xarray-gridded-vegalite branch from 3bb8c6c to a64310f Compare April 23, 2026 19:58
ghostiee-11 added a commit to ghostiee-11/lumen that referenced this pull request Apr 23, 2026
Two review responses bundled:

1. Per review on holoviz#1824: BaseViewAgent/main.jinja2 is the base template
   inherited by hvPlotAgent, VegaLiteAgent, and DeckGLAgent. The previous
   gridded block named kind='quadmesh'/'image'/'contourf'/'heatmap' which
   are hvPlot-only concepts. VegaLite uses mark: rect, DeckGL uses layer
   types, so injecting hvPlot kind values into their prompt context is
   misleading. Kept the metadata (xarray source, dims, data_vars) in the
   base block; each agent's own prompt carries the syntax-specific
   guidance. Updated test to assert no hvPlot kinds leak into the base.

2. Style cleanup for lint discipline: moved holoviews/pandas/xarray/
   unittest.mock imports to the top of test_hvplot_gridded.py (was
   inline inside test bodies). Replaced two em dashes in hvPlotAgent
   prompt and test docstring.
@ghostiee-11
ghostiee-11 marked this pull request as ready for review April 23, 2026 20:10
Per review on holoviz#1823: detect_gridded is a small one-function helper;
it does not warrant its own module. Moved into lumen/ai/utils.py
alongside the other AI helpers and removed the standalone gridded.py.

Updated imports in lumen/ai/agents/base_view.py and
lumen/tests/ai/test_gridded.py.
@ghostiee-11
ghostiee-11 force-pushed the feat/xarray-gridded-vegalite branch from a64310f to d70be68 Compare April 24, 2026 12:22
@ahuang11
ahuang11 marked this pull request as draft May 6, 2026 21:51
@ghostiee-11

Copy link
Copy Markdown
Collaborator Author

Folding this into #1823, which combines the hvPlot, VegaLite, and DeckGL gridded support into one PR. Closing to keep it in a single reviewable PR.

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.

3 participants