Skip to content

Add heatmap click workaround - #7869

Closed
nojaf wants to merge 1 commit into
marimo-team:mainfrom
nojaf:heatmap-click
Closed

Add heatmap click workaround#7869
nojaf wants to merge 1 commit into
marimo-team:mainfrom
nojaf:heatmap-click

Conversation

@nojaf

@nojaf nojaf commented Jan 16, 2026

Copy link
Copy Markdown
Contributor

📝 Summary

This PR serves as a starting point to discuss a problem I noticed with click on a heatmap.

Sample notebook:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "marimo",
#     "plotly==6.5.2",
# ]
# ///

import marimo

__generated_with = "0.19.2"
app = marimo.App(width="medium")


@app.cell
def _():
    import marimo as mo
    import plotly.graph_objects as go

    # Create the heatmap
    fig = go.Figure(
        data=go.Heatmap(
            z=[[1, 20, 30, 40], [20, 1, 60, 80], [30, 60, 1, 10], [40, 80, 10, 1]],
            x=["A", "B", "C", "D"],
            y=["W", "X", "Y", "Z"],
            colorscale="Viridis",
            showscale=True,
        )
    )

    fig.update_layout(
        title="Heatmap Click Event Test - PR #7686",
        xaxis=dict(title="X Axis (Categories)", type="category"),
        yaxis=dict(title="Y Axis (Categories)", type="category"),
        dragmode="select",
    )

    # Wrap with marimo's UI
    plot = mo.ui.plotly(fig)
    return mo, plot


@app.cell
def _(plot):
    # Display the plot
    plot
    return


@app.cell
def _(mo, plot):
    # Show selected points/values
    mo.md(f"""
    **Selected points:** {plot.points}

    **Selected indices:** {plot.indices}

    **Value:** {plot.value}
    """)
    return


if __name__ == "__main__":
    app.run()
image

🔍 Description of Changes

I have a workaround in Marimo (in this PR), to fix this.

image

But I can't pinpoint the problem outside of Marimo in either Plotly or react-plotly.

Heatmap Click Event Investigation

Heatmap Click Event Investigation

Summary

Investigation into GitHub issue #7685 and PR #7686 regarding heatmap click events not working properly when dragmode: 'select' is enabled in marimo notebooks.

Conclusion

The bug is NOT in plotly.js or react-plotly.js. It is specific to marimo's build/bundling environment.

Test Results

Environment plotly.js version onClick fires?
Vanilla JS CodePen 2.35.0 Yes
React + react-plotly.js CodePen 3.3.1 Yes
React + react-plotly.js CodePen 2.35.3 Yes
Marimo 2.35.3 No

Evidence

CodePen with exact same versions as marimo (plotly.js 2.35.3 + react-plotly.js 2.6.0):

Plotly version: 2.35.3
dragmode: select

Click a heatmap cell...

onSelected: undefined
onClick: {"x":"C","y":"Z","z":1}   ← WORKS!

Marimo with same versions:

plotly_selected: undefined
(onClick NEVER fires)

Root Cause

The issue is in marimo's build/bundling environment, likely one of:

  1. Vite bundling configuration - how modules are resolved/bundled
  2. The react-plotly.js patch - adds TreemapClick support, may have side effects
  3. Module import style - marimo uses a lazy import pattern for react-plotly.js

Workaround Implemented in Marimo

Since plotly_selected fires with undefined data on single clicks and gd._hoverdata is still populated at that moment, we extract heatmap click data from there:

onInitialized={useEvent((figure, graphDiv) => {
  const gd = graphDiv as any;
  if (gd && gd.on) {
    gd.on("plotly_selected", (data: unknown) => {
      // Only handle single clicks (data is undefined), not drag selections
      if (data !== undefined) {
        return;
      }

      // Check if we have hoverdata from a heatmap
      const hoverData = gd._hoverdata;
      if (!hoverData || !Array.isArray(hoverData) || hoverData.length === 0) {
        return;
      }

      // Check if the hovered point is from a heatmap trace
      const heatmapPoints = hoverData.filter(
        (point: any) =>
          point.data?.type === "heatmap" ||
          point.fullData?.type === "heatmap",
      );

      if (heatmapPoints.length === 0) {
        return;
      }

      // Extract heatmap point data
      const points = heatmapPoints.map((point: any) => ({
        x: point.x,
        y: point.y,
        z: point.z,
        curveNumber: point.curveNumber,
      }));

      const indices = heatmapPoints.map((point: any) => point.pointIndex);

      // Update the selection state
      setValue((prev) => ({
        ...prev,
        selections: [],
        points: points,
        indices: indices,
        range: undefined,
      }));
    });
  }
})}

CodePen for Verification

This CodePen proves plotly.js + react-plotly.js work correctly:

import React, { useState } from "https://esm.sh/react@19";
import ReactDOM from "https://esm.sh/react-dom@19/client";
import Plotly from "https://esm.sh/plotly.js-dist@2.35.3";
import createPlotlyComponent from "https://esm.sh/react-plotly.js@2.6.0/factory";

const Plot = createPlotlyComponent(Plotly);

function App() {
  const [log, setLog] = useState([]);

  const addLog = (msg) => {
    setLog((prev) => [...prev, msg]);
    console.log(msg);
  };

  const data = [
    {
      type: "heatmap",
      z: [
        [1, 20, 30],
        [20, 1, 60],
        [30, 60, 1],
      ],
      x: ["A", "B", "C"],
      y: ["X", "Y", "Z"],
    },
  ];

  const layout = {
    title: "Heatmap Click Test (React + plotly.js 2.35.3)",
    dragmode: "select",
  };

  return (
    <div>
      <Plot
        data={data}
        layout={layout}
        onClick={(evt) => {
          addLog(
            "onClick: " +
              JSON.stringify(
                evt?.points?.[0]
                  ? {
                      x: evt.points[0].x,
                      y: evt.points[0].y,
                      z: evt.points[0].z,
                    }
                  : "no points",
              ),
          );
        }}
        onSelected={(evt) => {
          addLog("onSelected: " + (evt ? "has data" : "undefined"));
        }}
        onInitialized={(figure, graphDiv) => {
          addLog("Plotly version: " + Plotly.version);
          addLog("dragmode: " + graphDiv._fullLayout.dragmode);
          addLog("");
          addLog("Click a heatmap cell - onClick DOES fire!");
          addLog("");
        }}
      />
      <pre style={{ fontFamily: "monospace", marginTop: "20px" }}>
        {log.join("\n")}
      </pre>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

Files Changed in Marimo

  • frontend/src/plugins/impl/plotly/PlotlyPlugin.tsx - Added workaround in onInitialized to capture heatmap clicks via plotly_selected event

Next Steps for Marimo

The workaround is functional, but the root cause in marimo's build should be investigated:

  1. Check if the react-plotly.js patch affects event handling
  2. Investigate Vite's bundling of plotly.js
  3. Check the lazy import pattern for react-plotly.js in LazyPlot

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.

@mscolnick do you have any thoughts on this?

@vercel

vercel Bot commented Jan 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jan 16, 2026 1:27pm

Review with Vercel Agent

@nojaf

nojaf commented Feb 10, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #8255

@nojaf nojaf closed this Feb 10, 2026
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.

1 participant