Skip to content

Enable Custom x/y axis values for heatmap#958

Open
kirandharmar867-maker wants to merge 1 commit intofossasia:masterfrom
kirandharmar867-maker:feature/heatmap-x-y-axes
Open

Enable Custom x/y axis values for heatmap#958
kirandharmar867-maker wants to merge 1 commit intofossasia:masterfrom
kirandharmar867-maker:feature/heatmap-x-y-axes

Conversation

@kirandharmar867-maker
Copy link

@kirandharmar867-maker kirandharmar867-maker commented Feb 10, 2026

Summary:
This PR adds support for explicit x-and y-axis values in visdom.heatmap(), allowing users to provide
properly Scaled axes instead of replying on inferred pixel indices.

Changes:
*Added optional opts["x"] for explicit x-axis values
*Added optional opts["y"] for explicit y-axis values
*validated axis length against the input heatmap shape
*preserved backward compatibility with existing columnnames / rownames

Motivation:
Currently, visdom.heatmap() always infers axis values from pixel indices, which makes it difficult to interpret heatmaps generated from non-integer or non-uniform Grids.

This change enables users to pass meaningful axis values directly, aligning visdom behavior with common
plotting tools like plotly and matplotlib.

Related issue:
Closes #937

Checklist:
*Change is backward compatible
*No API breakage
*Minimal and scoped to heatmap()
*CI passes input Shapes
*Validates input Shapes

Summary by Sourcery

Allow visdom heatmap plots to accept explicit x and y axis values while preserving existing behavior.

New Features:

  • Add support for explicit x-axis values via opts['x'] in visdom.heatmap.
  • Add support for explicit y-axis values via opts['y'] in visdom.heatmap.

Enhancements:

  • Validate custom x and y axis lengths against the input heatmap shape to prevent mismatches.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 10, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds optional explicit x/y axis value support to visdom.heatmap(), with shape validation and backward-compatible fallbacks to existing column/row name handling.

Sequence diagram for visdom.heatmap with explicit x and y axis values

sequenceDiagram
    actor User
    participant Client as VisdomClient
    participant Visdom as Visdom
    participant Backend as PlotBackend

    User->>Client: heatmap(X, opts with x, y)
    Client->>Visdom: heatmap(X, win, env, update, opts)
    Visdom->>Visdom: validate rownames length if present
    Visdom->>Visdom: validate columnnames length if present
    Visdom->>Visdom: assert len(opts.x) == X.shape[1]
    Visdom->>Visdom: assert len(opts.y) == X.shape[0]
    Visdom->>Visdom: build data
    Visdom-->>Backend: data.z, data.x from opts.x, data.y from opts.y
    Backend-->>User: Rendered heatmap with custom axes
Loading

Class diagram for Visdom heatmap options with new x and y axes

classDiagram
    class Visdom {
        +heatmap(X, win, env, update, opts)
    }

    class HeatmapOptions {
        +list x
        +list y
        +list columnnames
        +list rownames
        +float xmin
        +float xmax
    }

    class HeatmapDataPayload {
        +list z
        +list x
        +list y
        +float zmin
        +float zmax
        +string type
    }

    Visdom ..> HeatmapOptions : uses
    Visdom ..> HeatmapDataPayload : constructs

    HeatmapOptions <|-- BackwardCompatibleOptions

    class BackwardCompatibleOptions {
        +list columnnames
        +list rownames
    }

    HeatmapOptions : x optional
    HeatmapOptions : y optional
    HeatmapDataPayload : x = opts.x or opts.columnnames
    HeatmapDataPayload : y = opts.y or opts.rownames
Loading

File-Level Changes

Change Details Files
Add explicit x/y axis support for heatmap with shape validation and backward-compatible fallbacks.
  • Add validation that opts['x'] length matches the number of columns in the heatmap matrix
  • Add validation that opts['y'] length matches the number of rows in the heatmap matrix
  • Update heatmap payload construction to use opts['x']/opts['y'] when provided, otherwise fall back to columnnames/rownames
py/visdom/__init__.py

Assessment against linked issues

Issue Objective Addressed Explanation
#937 Allow visdom.heatmap() to accept optional x-axis and y-axis value lists so users can specify custom axis scales instead of pixel indices.
#937 Validate that any provided x/y axis lists match the heatmap dimensions while preserving backward compatibility with existing columnnames/rownames behavior.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new shape validations for opts['x'] and opts['y'] use assert, which can be stripped with Python optimization flags; consider raising a concrete exception (e.g., ValueError) instead to ensure the checks always run.
  • The error message for the opts['y'] length check reads "number of row in X"; update this to "number of rows in X" for consistency with the rownames assertion message.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new shape validations for `opts['x']` and `opts['y']` use `assert`, which can be stripped with Python optimization flags; consider raising a concrete exception (e.g., `ValueError`) instead to ensure the checks always run.
- The error message for the `opts['y']` length check reads "number of row in X"; update this to "number of rows in X" for consistency with the `rownames` assertion message.

## Individual Comments

### Comment 1
<location> `py/visdom/__init__.py:1883-1885` </location>
<code_context>
+
+        if opts.get("y") is not None:
+            assert len(opts["y"]) == X.shape[0], \
+                "length of y must match number of row in X"
+
         data = [
</code_context>

<issue_to_address>
**nitpick (typo):** Fix grammar in the error message for the `y` length assertion.

The message is slightly ungrammatical. Please change it to: `"length of y must match number of rows in X"` to align with the earlier rownames assertion and improve clarity.

```suggestion
        if opts.get("y") is not None:
            assert len(opts["y"]) == X.shape[0], \
                "length of y must match number of rows in X"
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1883 to +1885
if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of row in X"
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick (typo): Fix grammar in the error message for the y length assertion.

The message is slightly ungrammatical. Please change it to: "length of y must match number of rows in X" to align with the earlier rownames assertion and improve clarity.

Suggested change
if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of row in X"
if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of rows in X"

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for passing explicit x/y axis values into Visdom.heatmap() so heatmaps can display meaningful, scaled axes instead of implicit pixel indices.

Changes:

  • Adds optional opts["x"] and opts["y"] handling for heatmap axis values.
  • Validates provided axis lengths against the heatmap shape.
  • Preserves legacy behavior by falling back to columnnames/rownames when explicit axes are not provided.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1879 to +1885
if opts.get("x") is not None:
assert len(opts["x"]) == X.shape[1], \
"length of x must match number of columns in X"

if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of row in X"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The heatmap() docstring lists opts.columnnames/opts.rownames but doesn’t mention the newly supported opts.x/opts.y. Please update the supported opts list (and expected types/shapes) so users discover this feature via help(viz.heatmap).

Copilot uses AI. Check for mistakes.
Comment on lines 1889 to +1891
"z": nan2none(X.tolist()),
"x": opts.get("columnnames"),
"y": opts.get("rownames"),
"x": opts.get("x", opts.get("columnnames")),
"y": opts.get("y", opts.get("rownames")),
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

opts.get("x", opts.get("columnnames")) will return None (and override columnnames) if the caller includes the key x with value None. That breaks the intended fallback behavior / backward compatibility. Consider using an explicit if opts.get("x") is not None conditional (same for y) so columnnames/rownames are used when x/y are absent or None.

Copilot uses AI. Check for mistakes.
Comment on lines +1880 to +1885
assert len(opts["x"]) == X.shape[1], \
"length of x must match number of columns in X"

if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of row in X"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

opts["x"] / opts["y"] are only length-checked, but not coerced/validated to JSON-serializable types. Since _send uses json.dumps(msg) directly, passing numpy arrays / tensors (a likely use case for axis values) will raise a TypeError: Object of type ndarray is not JSON serializable. Consider validating x/y types and converting to a plain Python list (e.g., np.asarray(...).ravel().tolist()) before constructing data.

Suggested change
assert len(opts["x"]) == X.shape[1], \
"length of x must match number of columns in X"
if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of row in X"
x_vals = np.asarray(opts["x"]).ravel()
assert x_vals.shape[0] == X.shape[1], \
"length of x must match number of columns in X"
opts["x"] = x_vals.tolist()
if opts.get("y") is not None:
y_vals = np.asarray(opts["y"]).ravel()
assert y_vals.shape[0] == X.shape[0], \
"length of y must match number of row in X"
opts["y"] = y_vals.tolist()

Copilot uses AI. Check for mistakes.

if opts.get("y") is not None:
assert len(opts["y"]) == X.shape[0], \
"length of y must match number of row in X"
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The assertion message for y has a grammar issue: "number of row in X" should be "number of rows in X" (and ideally match the phrasing used for rownames). This will be surfaced to users on invalid inputs, so it’s worth correcting.

Suggested change
"length of y must match number of row in X"
"length of y must match number of rows in X"

Copilot uses AI. Check for mistakes.
@norbusan
Copy link
Member

A few issues to address before this can be merged:

  1. numpy serialisationopts["x"] and opts["y"] are likely numpy arrays. Passing them directly into the data dict will cause JSON serialisation errors. Convert them with .tolist(), following the same pattern already used for z (X.tolist()).

  2. Fallback with Noneopts.get("x", opts.get("columnnames")) silently falls back to columnnames even if the caller explicitly passed opts["x"] = None. Use an explicit if "x" in opts: check instead to respect intentional None values.

  3. Grammar in assertion message — "number of row in X" should read "number of rows in X", consistent with the style of the existing rownames assertion above.

  4. Docstring — The method docstring (around line 1827–1836) lists opts.columnnames and opts.rownames but does not mention the newly supported opts.x and opts.y. Please add entries for both.

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.

Include axes lists in heatmap

3 participants