Skip to content

Clickable Legend Titles#7698

Open
alexshoe wants to merge 18 commits intoplotly:masterfrom
alexshoe:clickable-legend-titles
Open

Clickable Legend Titles#7698
alexshoe wants to merge 18 commits intoplotly:masterfrom
alexshoe:clickable-legend-titles

Conversation

@alexshoe
Copy link
Contributor

@alexshoe alexshoe commented Jan 28, 2026

Description:

Adds the ability to click/double-click on legend titles to toggle legend visibility, making it easier to manage complex charts with multiple legends.

Example:

layout: {
  legend: {
    title: { text: 'My Legend' },
    titleclick: 'toggle',  // toggles the legend to which the clicked title belongs
    titledoubleclick: 'toggleothers' // toggles on/off all other legends
  }
}

See this codepen for an interactive demo of this feature

Screenshot 2026-01-30 at 2 54 08 PM

New API:

legend.titleclick

  • Type: enumerated
  • Values: 'toggle' | 'toggleothers' | false
  • Default: 'toggle' when there are multiple legends, false otherwise

legend.titledoubleclick

  • Type: enumerated
  • Values: 'toggle' | 'toggleothers' | false
  • Default: 'toggleothers' when there are multiple legends, false otherwise

What each value for titleclick and titledoubleclick does:

  • 'toggle' toggles the visibility of all items in the clicked legend
  • 'toggleothers' toggles the visibility of all other legends
  • false disables legend title double-click interactions

Bug Fixes:

@alexshoe alexshoe self-assigned this Jan 28, 2026
@alexshoe alexshoe changed the title Clickable legend titles Clickable Legend Titles Jan 28, 2026
@alexshoe alexshoe linked an issue Jan 28, 2026 that may be closed by this pull request
@alexshoe alexshoe marked this pull request as ready for review January 29, 2026 18:51
@alexshoe alexshoe requested a review from emilykl January 29, 2026 18:51
@emilykl
Copy link
Contributor

emilykl commented Jan 30, 2026

@alexshoe Can you document the full API for the new properties in this PR description? Basically just the info that's in the plot schema.

@emilykl
Copy link
Contributor

emilykl commented Jan 30, 2026

@alexshoe Can you remove the image test?

You can make a Codepen and link it in the PR description for testing purposes, but this feature doesn't require adding an image test to the test suite.

@alexshoe alexshoe requested a review from alexcjohnson February 2, 2026 16:52
Comment on lines +717 to +731
if(numClicks === 1 && legendObj.titleclick) {
const clickVal = Events.triggerHandler(gd, 'plotly_legendtitleclick', evtData);
if(clickVal === false) return;

legendObj._titleClickTimeout = setTimeout(function() {
if(gd._fullLayout) handleTitleClick(gd, legendObj, legendObj.titleclick);
}, doubleClickDelay);
} else if(numClicks === 2) {
if(legendObj._titleClickTimeout) clearTimeout(legendObj._titleClickTimeout);
gd._legendMouseDownTime = 0;

const dblClickVal = Events.triggerHandler(gd, 'plotly_legendtitledoubleclick', evtData);
if(dblClickVal !== false && legendObj.titledoubleclick) handleTitleClick(gd, legendObj, legendObj.titledoubleclick);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

@alexshoe Could some of the logic in the clickOrDoubleClick() function possibly be reused here? Seems like a fair amount of code duplication, although I'm sure there are some subtle differences.

Comment on lines 734 to 752
function positionTitleToggle(scrollBox, legendObj, legendId) {
const titleToggle = scrollBox.select('.' + legendId + 'titletoggle');
if(!titleToggle.size()) return;

const side = legendObj.title.side || 'top';
const bw = legendObj.borderwidth;
var x = bw;
const width = legendObj._titleWidth + 2 * constants.titlePad;
const height = legendObj._titleHeight + 2 * constants.titlePad;


if(side === 'top center') {
x = bw + 0.5 * (legendObj._width - 2 * bw - width);
} else if(side === 'top right') {
x = legendObj._width - bw - width;
}

titleToggle.attr({ x: x, y: bw, width: width, height: height });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, likewise this function seems like it's duplicating a lot of the title placement logic. I don't think we should be referencing parameters like legendObj.title.side here at all. My feeling is that the titleToggle logic should be more parallel to the traceToggle placement logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I refactored a few things so that the titleToggle positioning now lives at the end of computeLegendDimensions, right next to the traceToggle block. This required that I also move the horizontalAlignTitle call  there too. Couldn't make it fully identical to the traceToggle pattern since the title text and toggle are siblings in the scrollBox rather than children of a shared <g>

}
};

exports.handleTitleClick = function handleTitleClick(gd, legendObj, mode) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Also similar comment here to above -- check whether you can share some of this logic with the handleClick function to avoid TOO much code duplication

@robertcollar-kobold
Copy link

robertcollar-kobold commented Feb 4, 2026

This is looking good on my end @alexshoe! I'll leave the js details to @emilykl. Though qq: why is this disabled for pie chart legends? (not that this is relevant to our use case) Do they not have traditional legend titles (something to do with being multi-category)?

@emilykl
Copy link
Contributor

emilykl commented Feb 4, 2026

@robertcollar-kobold Pie charts do sometimes need unique logic compared to other trace types when it comes to legends, since pie chart legends contain one entry per data point rather than one entry per trace.

That said, @alexshoe I can't see any reason why the titleclick property shouldn't be supported for pie charts.

Here's an example of a pie chart with multiple legends:

Plot def:

Details
{
  "data": [
    {
      "type": "pie",
      "labels": ["a", "b", "c"],
      "values": [3,5,4],
      "showlegend": true,
      "domain": {"row": 0, "column": 0},
    },
    {
      "type": "pie",
      "labels": ["d", "e", "f"],
      "values": [1,3,7],
      "legend": "legend2",
      "showlegend": true,
      "domain": {"row": 0, "column": 1},
    },
  ],
  "layout": {
    "grid": {"rows": 1, "columns": 2},
    "legend": {
      "title": {"text": "Mylegend"},
      "visible": true,
    },
    "legend2": {
      "title": {"text": "Mylegend2"},
      "visible": true,
      "y": 0.15,
      "itemclick": false,
    },
  }
}

Image:

Screenshot 2026-02-04 at 2 58 56 PM

@emilykl
Copy link
Contributor

emilykl commented Feb 4, 2026

@alexshoe @robertcollar-kobold One question I just thought of, whose answer is not immediately obvious to me (but maybe it is to you):

Should clicking the legend title toggle the visibility of all traces listed in the legend (i.e. those with showlegend: true implicitly or explicitly), or should it toggle the visibility of all traces associated with that legend, regardless of whether they are visually listed?

Here's a mock to make it clearer. Each legend has one trace for which showlegend is set to false.

Screenshot 2026-02-04 at 3 52 17 PM
Details
var plotDef = {
  "data": [
    {
      "x": [1,2,3],
      "y": [2,4,3],
      "showlegend": false,
    },
    {
      "x": [1,2,3],
      "y": [3,5,4],
      "showlegend": true,
    },
    {
      "x": [2,4,6],
      "y": [2,1,5],
      "legend": "legend2",
      "showlegend": true,
    },
    {
      "x": [2,4,6],
      "y": [3,2,6],
      "yaxis": "yaxis2",
      "legend": "legend2",
      "showlegend": false,
    },
  ],
  "layout": {
    "shapes": [
      {
        "type": "rect",
        "x0": 1,
        "x1": 2.5,
        "y0": 4,
        "y1": 5.5,
        "line": {"color": "#FF0000"},
        "showlegend": true,
      },
      {
        "type": "rect",
        "x0": 5,
        "x1": 6,
        "y0": 1,
        "y1": 2,
        "line": {"color": "#FF00FF"},
        "legend": "legend2",
        "showlegend": false,
      },
    ],
    "legend": {
      "title": {"text": "Mylegend"},
      "visible": true,
    },
    "legend2": {
      "title": {"text": "Mylegend2"},
      "visible": true,
      "y": 0.25,
    },
    "yaxis2": {
      "overlaying": "yaxis",
    }
  }
};

@robertcollar-kobold
Copy link

robertcollar-kobold commented Feb 4, 2026

Should clicking the legend title toggle the visibility of all traces listed in the legend (i.e. those with showlegend: true implicitly or explicitly), or should it toggle the visibility of all traces associated with that legend, regardless of whether they are visually listed?

@emilykl I'll have to think about this a bit, but my first thought is that there must already be precedent with legendgroup, no? A trace may be assigned to a legend group but not be displayed in the legend — when you toggle the visibility of that legend group, is that trace's visibility toggled as well?

Okay update: I thought about it, and I would want a trace associated with, though not visible in, a legend to have its visibility be toggled when clicking on the legend title. I believe that's already the precedent today with legendgroup. You can go confirm, but in our work we already represent a multi-trace visual element using a single legend item, and this is done by assigning all constituent traces to the same legend group and setting showlegend=False for all but one trace. The result is a single "multi-trace" legend item, and clicking on it toggles the visibility of all traces in the legend group.

For our use case, we might have multiple such multi-trace legend items under one legend, and clicking on the legend title should toggle the visibility of all associated traces, not just the ones for which showlegend=True.

@emilykl
Copy link
Contributor

emilykl commented Feb 5, 2026

I thought about it, and I would want a trace associated with, though not visible in, a legend to have its visibility be toggled when clicking on the legend title.

@robertcollar-kobold Cool, yes, this makes sense to me and I think it's the most logically consistent behavior.

@emilykl
Copy link
Contributor

emilykl commented Feb 5, 2026

@alexshoe Thanks for the updates, I'll take another pass tomorrow

@alexshoe
Copy link
Contributor Author

alexshoe commented Feb 6, 2026

@emilykl thanks for the comments! I addressed most of the things you flagged but re: the comments on reducing duplication: I think the seperate functions are different enough that merging them into a new helper function and adding new branching logic could make more difficult to read. Maybe you can help me outline your vision for how to clean these funcitons up?

@alexshoe
Copy link
Contributor Author

alexshoe commented Feb 6, 2026

That said, @alexshoe I can't see any reason why the titleclick property shouldn't be supported for pie charts.

Yeah, agreed that pie charts should be supported for this feature eventually. The reason I left it out for now is that pie visibility is different from other trace types in that it uses hiddenlabels rather than a per-trace visible property. Legend group title clicking is also disabled for pie-like traces and it would be a lot more work to support both legend title and group title toggling for pies. I felt like it was a bit out of scope for this Kobold deliverable and that maybe we could write-up an issue for a follow-up. Would that be ok?

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.

[FEATURE]: Clickable Legend Titles incorrect item toggle behavior when using multiple legends

3 participants