Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
88a6c0d
Fix bug where `groupclick`: `toggleitem` doesn't work when there are …
alexshoe Jan 20, 2026
b2ef711
Add `titleclick` and `titledoubleclick` attributes
alexshoe Jan 20, 2026
cdb570a
Add handleTitleClick function for toggling visibility via legend title
alexshoe Jan 28, 2026
089fa73
Add legend title click toggle setup and event handling
alexshoe Jan 28, 2026
207910e
Add mock chart for legend title click feature
alexshoe Jan 28, 2026
2f934ad
Only enable legend title click by default when there are multiple leg…
alexshoe Jan 28, 2026
cd92fea
Add jasmine tests for legend title click
alexshoe Jan 29, 2026
53073d7
Update schema
alexshoe Jan 29, 2026
7c2e878
Merge remote-tracking branch 'origin/master' into clickable-legend-ti…
alexshoe Jan 29, 2026
459e229
Add baseline image
alexshoe Jan 29, 2026
6bec6d1
Convert var to const where applicable
alexshoe Jan 30, 2026
fdf1d65
Move `getId()` to `helpers.js`
alexshoe Feb 5, 2026
0cde808
Refactor handleClick() signature to be consistent with handleTitleClick
alexshoe Feb 5, 2026
3a1336f
Add docstrings for `handleClick` and `handleTitleClick`
alexshoe Feb 5, 2026
42c40f6
Replace null value with early continue and skip non-displayed traces
alexshoe Feb 5, 2026
2b5d2af
Rename `handleClick` to `handleItemClick`
alexshoe Feb 5, 2026
6cbdb49
Test legend title click attributes with non-default values
alexshoe Feb 5, 2026
6f6fd91
Move titleToggle positioning into computeLegendDimensions
alexshoe Feb 6, 2026
e3a068e
Fix group title click resolving to wrong legend by adding missing leg…
alexshoe Feb 12, 2026
28990fb
Update schema
alexshoe Feb 12, 2026
a9809d2
Update src/components/legend/attributes.js
alexshoe Feb 18, 2026
1fc81de
Update src/components/legend/attributes.js
alexshoe Feb 18, 2026
fb5b11f
Merge remote-tracking branch 'origin/master' into clickable-legend-ti…
alexshoe Feb 18, 2026
f820e7f
Modify baseline images and update schema
alexshoe Feb 18, 2026
8ee735e
Merge remote-tracking branch 'origin/master' into clickable-legend-ti…
alexshoe Feb 20, 2026
e0be8c5
Explicitly initialize toggleOtherLegends to false in toggle mode
alexshoe Feb 20, 2026
0223993
Fix legend title opacity incorrectly dimming for pie traces with per-…
alexshoe Feb 20, 2026
e7a1915
Fix pie legend item clicks breaking due to array legend lookup crash
alexshoe Feb 20, 2026
dca2e38
Fix legend traces not toggling when chart is editable by passing lege…
alexshoe Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add jasmine tests for legend title click
  • Loading branch information
alexshoe committed Jan 29, 2026
commit cd92feae2288186a53292f00a0d52354c7dea301
247 changes: 247 additions & 0 deletions test/jasmine/tests/legend_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2794,3 +2794,250 @@ describe('legend with custom legendwidth', function() {
}).then(done, done.fail);
});
});

describe('legend title click', function() {
"use strict";

var gd;

beforeEach(function() {
gd = createGraphDiv();
});
afterEach(destroyGraphDiv);

function clickTitle(legendId, clicks) {
return function() {
return new Promise(function(resolve) {
var selector = '.' + (legendId || 'legend') + 'titletoggle';
var item = d3Select(selector).node();
if(!item) {
fail('Could not find title toggle element: ' + selector);
return resolve();
}
for(var i = 0; i < (clicks || 1); i++) {
item.dispatchEvent(new MouseEvent('mousedown'));
item.dispatchEvent(new MouseEvent('mouseup'));
}
setTimeout(resolve, DBLCLICKDELAY + 100);
});
};
}

function extractVisibilities(data) {
return data.map(function(trace) { return trace.visible; });
}

function assertVisible(expectation) {
return function() {
var actual = extractVisibilities(gd._fullData);
expect(actual).toEqual(expectation);
};
}

function assertVisibleShapes(expectation) {
return function() {
var actual = extractVisibilities(gd._fullLayout.shapes);
expect(actual).toEqual(expectation);
};
}

describe('defaults', function() {
it('should disable title clicking by default for a single legend', function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] }
], {
legend: { title: { text: 'Legend' } }
}).then(function() {
expect(gd._fullLayout.legend.titleclick).toBe(false);
expect(gd._fullLayout.legend.titledoubleclick).toBe(false);
}).then(done, done.fail);
});

it('should enable title clicking by default for multiple legends', function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: { title: { text: 'Legend 1' } },
legend2: { title: { text: 'Legend 2' } }
}).then(function() {
expect(gd._fullLayout.legend.titleclick).toBe('toggle');
expect(gd._fullLayout.legend.titledoubleclick).toBe('toggleothers');
expect(gd._fullLayout.legend2.titleclick).toBe('toggle');
expect(gd._fullLayout.legend2.titledoubleclick).toBe('toggleothers');
}).then(done, done.fail);
});

it('should allow user to override titleclick and titledoubleclick', function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] }
], {
legend: {
title: { text: 'Legend' },
titleclick: 'toggle',
titledoubleclick: false
}
}).then(function() {
expect(gd._fullLayout.legend.titleclick).toBe('toggle');
expect(gd._fullLayout.legend.titledoubleclick).toBe(false);
Comment thread
alexshoe marked this conversation as resolved.
Outdated
}).then(done, done.fail);
});
});

describe('toggle interactions', function() {
beforeEach(function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: { title: { text: 'Legend 1' } },
legend2: { title: { text: 'Legend 2' }, y: 0.5 }
}).then(done);
});

it('should hide all traces in legend when clicking title (all visible)', function(done) {
Promise.resolve()
.then(assertVisible([true, true, true, true]))
.then(clickTitle('legend'))
.then(assertVisible(['legendonly', 'legendonly', true, true]))
.then(done, done.fail);
});

it('should show all traces in legend when clicking title (all hidden)', function(done) {
Plotly.restyle(gd, 'visible', 'legendonly', [0, 1])
.then(assertVisible(['legendonly', 'legendonly', true, true]))
.then(clickTitle('legend'))
.then(assertVisible([true, true, true, true]))
.then(done, done.fail);
});

it('should not affect traces with visible: false', function(done) {
Plotly.restyle(gd, 'visible', false, [0])
.then(assertVisible([false, true, true, true]))
.then(clickTitle('legend'))
.then(assertVisible([false, 'legendonly', true, true]))
.then(done, done.fail);
});
});

describe('toggleothers interactions', function() {
beforeEach(function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: { title: { text: 'Legend 1' } },
legend2: { title: { text: 'Legend 2' }, y: 0.5 }
}).then(done);
});

it('should isolate this legend (hide others)', function(done) {
Promise.resolve()
.then(assertVisible([true, true, true, true]))
.then(clickTitle('legend', 2))
.then(assertVisible([true, true, 'legendonly', 'legendonly']))
.then(done, done.fail);
});

it('should restore all when already isolated', function(done) {
Plotly.restyle(gd, 'visible', 'legendonly', [2, 3])
.then(assertVisible([true, true, 'legendonly', 'legendonly']))
.then(clickTitle('legend', 2))
.then(assertVisible([true, true, true, true]))
.then(done, done.fail);
});
});

describe('interactions with shapes', function() {
beforeEach(function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: { title: { text: 'Legend 1' } },
legend2: { title: { text: 'Legend 2' }, y: 0.5 },
shapes: [
{ showlegend: true, type: 'line', x0: 0, y0: 0, x1: 1, y1: 1 },
{ showlegend: true, type: 'rect', x0: 0, y0: 0, x1: 1, y1: 1, legend: 'legend2' }
]
}).then(done);
});

it('should toggle shapes with traces', function(done) {
Promise.resolve()
.then(assertVisible([true, true, true, true]))
.then(assertVisibleShapes([true, true]))
.then(clickTitle('legend'))
.then(assertVisible(['legendonly', 'legendonly', true, true]))
.then(assertVisibleShapes(['legendonly', true]))
.then(done, done.fail);
});
});

it('should not create click target when no title text', function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: {},
legend2: { y: 0.5 }
}).then(function() {
var titleToggle = d3Select('.legendtitletoggle');
expect(titleToggle.size()).toBe(0);
}).then(done, done.fail);
});

it('should have a pointer cursor on hover for clickable titles', function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: { title: { text: 'Legend 1' } },
legend2: { title: { text: 'Legend 2' }, y: 0.5 }
}).then(function() {
var titleToggle = d3Select('.legendtitletoggle').node();
expect(titleToggle.style.cursor).toBe('pointer');
}).then(done, done.fail);
});

it('should not have pointer cursor on static plots', function(done) {
Plotly.newPlot(gd, [
{ x: [1, 2], y: [1, 2] },
{ x: [1, 2], y: [2, 3] },
{ x: [1, 2], y: [3, 4], legend: 'legend2' },
{ x: [1, 2], y: [4, 5], legend: 'legend2' }
], {
showlegend: true,
legend: { title: { text: 'Legend 1' } },
legend2: { title: { text: 'Legend 2' }, y: 0.5 }
}, {
staticPlot: true
}).then(function() {
var titleToggle = d3Select('.legendtitletoggle').node();
// On static plots, the title toggle rect is created but without pointer cursor
if(titleToggle) {
expect(titleToggle.style.cursor).not.toBe('pointer');
}
}).then(done, done.fail);
});
});