Skip to content

Commit 9274860

Browse files
committed
add 'plotly_legendclick' and 'plotly_doubleclick' events
... and use their return val to determine whether or not the default handler is called after them. This allows users to disable legend trace toggling and/or augment it!
1 parent 1f3163a commit 9274860

File tree

2 files changed

+137
-20
lines changed

2 files changed

+137
-20
lines changed

src/components/legend/draw.js

+36-19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var d3 = require('d3');
1313
var Lib = require('../../lib');
1414
var Plots = require('../../plots/plots');
1515
var Registry = require('../../registry');
16+
var Events = require('../../lib/events');
1617
var dragElement = require('../dragelement');
1718
var Drawing = require('../drawing');
1819
var Color = require('../color');
@@ -336,22 +337,46 @@ module.exports = function draw(gd) {
336337
e.clientY >= bbox.top && e.clientY <= bbox.bottom);
337338
});
338339
if(clickedTrace.size() > 0) {
339-
if(numClicks === 1) {
340-
legend._clickTimeout = setTimeout(function() {
341-
handleClick(clickedTrace, gd, numClicks);
342-
}, DBLCLICKDELAY);
343-
} else if(numClicks === 2) {
344-
if(legend._clickTimeout) {
345-
clearTimeout(legend._clickTimeout);
346-
}
347-
handleClick(clickedTrace, gd, numClicks);
348-
}
340+
clickOrDoubleClick(gd, legend, clickedTrace, numClicks, e);
349341
}
350342
}
351343
});
352344
}
353345
};
354346

347+
function clickOrDoubleClick(gd, legend, clickedTrace, numClicks, evt) {
348+
var datum = clickedTrace.data()[0][0];
349+
350+
var evtData = {
351+
event: evt,
352+
node: clickedTrace.node(),
353+
itemNumber: datum.i,
354+
curveNumber: datum.trace.index,
355+
data: gd.data,
356+
layout: gd.layout,
357+
frames: gd._transitionData._frames,
358+
config: gd._context,
359+
fullData: gd._fullData,
360+
fullLayout: gd._fullLayout
361+
};
362+
363+
var returnVal;
364+
365+
if(numClicks === 1) {
366+
legend._clickTimeout = setTimeout(function() {
367+
returnVal = Events.triggerHandler(gd, 'plotly_legendclick', evtData);
368+
if(returnVal !== false) handleClick(clickedTrace, gd, numClicks);
369+
}, DBLCLICKDELAY);
370+
}
371+
else if(numClicks === 2) {
372+
if(legend._clickTimeout) clearTimeout(legend._clickTimeout);
373+
gd._legendMouseDownTime = 0;
374+
375+
returnVal = Events.triggerHandler(gd, 'plotly_legenddoubleclick', evtData);
376+
if(returnVal !== false) handleClick(clickedTrace, gd, numClicks);
377+
}
378+
}
379+
355380
function drawTexts(g, gd) {
356381
var legendItem = g.data()[0][0],
357382
fullLayout = gd._fullLayout,
@@ -441,15 +466,7 @@ function setupTraceToggle(g, gd) {
441466
numClicks = Math.max(numClicks - 1, 1);
442467
}
443468

444-
if(numClicks === 1) {
445-
legend._clickTimeout = setTimeout(function() { handleClick(g, gd, numClicks); }, DBLCLICKDELAY);
446-
} else if(numClicks === 2) {
447-
if(legend._clickTimeout) {
448-
clearTimeout(legend._clickTimeout);
449-
}
450-
gd._legendMouseDownTime = 0;
451-
handleClick(g, gd, numClicks);
452-
}
469+
clickOrDoubleClick(gd, legend, g, numClicks, d3.event);
453470
});
454471
}
455472

test/jasmine/tests/legend_test.js

+101-1
Original file line numberDiff line numberDiff line change
@@ -1001,9 +1001,13 @@ describe('legend interaction', function() {
10011001
};
10021002
}
10031003

1004+
function extractVisibilities(data) {
1005+
return data.map(function(trace) { return trace.visible; });
1006+
}
1007+
10041008
function assertVisible(expectation) {
10051009
return function() {
1006-
var actual = gd._fullData.map(function(trace) { return trace.visible; });
1010+
var actual = extractVisibilities(gd._fullData);
10071011
expect(actual).toEqual(expectation);
10081012
};
10091013
}
@@ -1166,5 +1170,101 @@ describe('legend interaction', function() {
11661170
.catch(failTest).then(done);
11671171
});
11681172
});
1173+
1174+
describe('custom legend click/doubleclick handlers', function() {
1175+
var fig, to;
1176+
1177+
beforeEach(function() {
1178+
fig = Lib.extendDeep({}, require('@mocks/0.json'));
1179+
});
1180+
1181+
afterEach(function() {
1182+
clearTimeout(to);
1183+
});
1184+
1185+
function setupFail() {
1186+
to = setTimeout(function() {
1187+
fail('did not trigger plotly_legendclick');
1188+
}, 2 * DBLCLICKDELAY);
1189+
}
1190+
1191+
it('should call custom click handler before default handler', function(done) {
1192+
Plotly.newPlot(gd, fig).then(function() {
1193+
var gotCalled = false;
1194+
1195+
gd.on('plotly_legendclick', function(d) {
1196+
gotCalled = true;
1197+
expect(extractVisibilities(d.fullData)).toEqual([true, true, true]);
1198+
expect(extractVisibilities(gd._fullData)).toEqual([true, true, true]);
1199+
});
1200+
gd.on('plotly_restyle', function() {
1201+
expect(extractVisibilities(gd._fullData)).toEqual([true, 'legendonly', true]);
1202+
if(gotCalled) done();
1203+
});
1204+
setupFail();
1205+
})
1206+
.then(click(1, 1))
1207+
.catch(failTest);
1208+
});
1209+
1210+
it('should call custom doubleclick handler before default handler', function(done) {
1211+
Plotly.newPlot(gd, fig).then(function() {
1212+
var gotCalled = false;
1213+
1214+
gd.on('plotly_legenddoubleclick', function(d) {
1215+
gotCalled = true;
1216+
expect(extractVisibilities(d.fullData)).toEqual([true, true, true]);
1217+
expect(extractVisibilities(gd._fullData)).toEqual([true, true, true]);
1218+
});
1219+
gd.on('plotly_restyle', function() {
1220+
expect(extractVisibilities(gd._fullData)).toEqual(['legendonly', true, 'legendonly']);
1221+
if(gotCalled) done();
1222+
});
1223+
setupFail();
1224+
})
1225+
.then(click(1, 2))
1226+
.catch(failTest);
1227+
});
1228+
1229+
it('should not call default click handler if custom handler return *false*', function(done) {
1230+
Plotly.newPlot(gd, fig).then(function() {
1231+
gd.on('plotly_legendclick', function(d) {
1232+
Plotly.relayout(gd, 'title', 'just clicked on trace #' + d.curveNumber);
1233+
return false;
1234+
});
1235+
gd.on('plotly_relayout', function(d) {
1236+
expect(typeof d).toBe('object');
1237+
expect(d.title).toBe('just clicked on trace #2');
1238+
done();
1239+
});
1240+
gd.on('plotly_restyle', function() {
1241+
fail('should not have triggered plotly_restyle');
1242+
});
1243+
setupFail();
1244+
})
1245+
.then(click(2, 1))
1246+
.catch(failTest);
1247+
});
1248+
1249+
it('should not call default doubleclick handle if custom handler return *false*', function(done) {
1250+
Plotly.newPlot(gd, fig).then(function() {
1251+
gd.on('plotly_legenddoubleclick', function(d) {
1252+
Plotly.relayout(gd, 'title', 'just double clicked on trace #' + d.curveNumber);
1253+
return false;
1254+
});
1255+
gd.on('plotly_relayout', function(d) {
1256+
expect(typeof d).toBe('object');
1257+
expect(d.title).toBe('just double clicked on trace #0');
1258+
done();
1259+
});
1260+
gd.on('plotly_restyle', function() {
1261+
fail('should not have triggered plotly_restyle');
1262+
});
1263+
setupFail();
1264+
})
1265+
.then(click(0, 2))
1266+
.catch(failTest);
1267+
});
1268+
});
11691269
});
11701270
});

0 commit comments

Comments
 (0)