Skip to content

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

@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 +234 to +235
// Don't create a click targets for group titles when groupclick is 'toggleitem'
if(d[0].groupTitle && legendObj.groupclick === 'toggleitem') return;
Copy link
Contributor

Choose a reason for hiding this comment

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

@alexshoe Is this line an unrelated bugfix for groupclick? Or is it related to the titleclick functionality?

(it's fine either way just want to clarify)

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.

const legendItem = g.data()[0][0];
if(legendItem.groupTitle && legendItem.noClick) return;

const legendId = legendItem.trace.legend || 'legend';
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, this fixes a bug where 'secondary' legends (e.g. legend2, legend3, etc.) used the click settings of legend rather than using their own settings, is that right? I've tried it out in Codepen and that seems to be the case.

I believe that fixes this issue, can you confirm @alexshoe ? If so, please tag in PR description.

exports.handleTitleClick = function handleTitleClick(gd, legendObj, mode) {
const fullLayout = gd._fullLayout;
const fullData = gd._fullData;
const legendId = legendObj._id || 'legend';
Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps move the getId() function in src/components/legend/draw.js to helpers.js and use it here, since it does exactly this

}
};

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

}
};

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.

It does bother me that this function signature isn't the same as the one for handleClick() -- maybe this signature is better and/or more appropriate for legend titles! But just double check that the signature makes sense.

Copy link
Contributor

@emilykl emilykl Feb 3, 2026

Choose a reason for hiding this comment

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

Like for the sake of consistency, would it be better to accept numClicks here? Or alternatively modify handleClick() to accept a mode argument?

var toggleThisLegend;
var toggleOtherLegends;

if(mode === 'toggle') {
Copy link
Contributor

Choose a reason for hiding this comment

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

it's unclear what are the possible values for mode. Can you add a docstring for the handleTitleClick function?

});

toggleThisLegend = !anyVisibleHere;
toggleOtherLegends = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
toggleOtherLegends = null;
toggleOtherLegends = false;

I don't see why you wouldn't make this false


for(var i = 0; i < allLegendItems.length; i++) {
const item = allLegendItems[i];
const shouldShow = isInLegend(item) ? toggleThisLegend : toggleOtherLegends;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this right? I feel like this might give the wrong result when item.showlegend is false. I can try to come up with a reproducible example if that would help.

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

2 participants