Skip to content

Allow toggling legend to show just 1 series (or group) by double clicking #1432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 13, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 2 additions & 1 deletion src/components/legend/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ module.exports = {
scrollBarWidth: 4,
scrollBarHeight: 20,
scrollBarColor: '#808BA4',
scrollBarMargin: 4
scrollBarMargin: 4,
DBLCLICKDELAY: 300
Copy link
Collaborator

Choose a reason for hiding this comment

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

To avoid duplication we should probably put this (and a few other things) into constants/interactions and reference it from legend and dragelement - which for obsolete reasons currently gets these constants out of cartesian/constants

};
109 changes: 84 additions & 25 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ module.exports = function draw(gd) {

if(!fullLayout._infolayer || !gd.calcdata) return;

if(!gd._legendMouseDownTime) gd._legendMouseDownTime = 0;

var opts = fullLayout.legend,
legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts),
hiddenSlices = fullLayout.hiddenlabels || [];
Expand Down Expand Up @@ -395,9 +397,9 @@ function drawTexts(g, gd) {
}

function setupTraceToggle(g, gd) {
var hiddenSlices = gd._fullLayout.hiddenlabels ?
gd._fullLayout.hiddenlabels.slice() :
[];
var newMouseDownTime,
numClicks = 1,
DBLCLICKDELAY = constants.DBLCLICKDELAY;

var traceToggle = g.selectAll('rect')
.data([0]);
Expand All @@ -408,41 +410,98 @@ function setupTraceToggle(g, gd) {
.attr('pointer-events', 'all')
.call(Color.fill, 'rgba(0,0,0,0)');

traceToggle.on('click', function() {

traceToggle.on('mousedown', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we could reuse the dragelement abstraction here? This is a non- ⛔ comment of course.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Possibly - but this reminds me that we had better ensure that this change is compatible with the additional interactions the legend supports in {editable: true} config. For clarity, here's how that works currently and should continue to work:

  • clicking on legend symbols toggles the trace (so doubleclicking them should isolate the trace)
  • clicking on legend text, which outside of editable mode also toggles the trace, instead edits the text (so doubleclick should do nothing there)
  • dragging anywhere in the legend - symbol, text, or background/border - moves the legend and does not toggle anything.

@rpaskowitz you can convert the plot you're testing to editable by calling:

Plotly.newPlot(gd, gd.data, gd.layout, {editable: true})

newMouseDownTime = (new Date()).getTime();
if(newMouseDownTime - gd._legendMouseDownTime < DBLCLICKDELAY) {
// in a click train
numClicks += 1;
}
else {
// new click train
numClicks = 1;
gd._legendMouseDownTime = newMouseDownTime;
}
});
traceToggle.on('mouseup', function() {
if(gd._dragged) return;
var legend = gd._fullLayout.legend;

var legendItem = g.data()[0][0],
fullData = gd._fullData,
trace = legendItem.trace,
legendgroup = trace.legendgroup,
traceIndicesInGroup = [],
tracei,
newVisible;
if((new Date()).getTime() - gd._legendMouseDownTime > DBLCLICKDELAY) {
numClicks = Math.max(numClicks - 1, 1);
}

if(Registry.traceIs(trace, 'pie')) {
var thisLabel = legendItem.label,
thisLabelIndex = hiddenSlices.indexOf(thisLabel);
if(numClicks === 1) {
legend._clickTimeout = setTimeout(function() { handleClick(g, gd, numClicks); }, 300);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be DBLCLICKDELAY, will correct when refactoring where that's defined.

} else if(numClicks === 2) {
if(legend._clickTimeout) {
clearTimeout(legend._clickTimeout);
}
handleClick(g, gd, numClicks);
}
});
}

function handleClick(g, gd, numClicks) {
var hiddenSlices = gd._fullLayout.hiddenlabels ?
gd._fullLayout.hiddenlabels.slice() :
[];

var legendItem = g.data()[0][0],
fullData = gd._fullData,
trace = legendItem.trace,
legendgroup = trace.legendgroup,
traceIndicesInGroup = [],
tracei,
newVisible;

if(Registry.traceIs(trace, 'pie')) {
var thisLabel = legendItem.label,
thisLabelIndex = hiddenSlices.indexOf(thisLabel);

if(numClicks === 1) {
if(thisLabelIndex === -1) hiddenSlices.push(thisLabel);
else hiddenSlices.splice(thisLabelIndex, 1);
} else if(numClicks === 2) {
hiddenSlices = [];
gd.calcdata[0].forEach(function(d) {
if(thisLabel !== d.label) {
hiddenSlices.push(d.label);
}
});
}

Plotly.relayout(gd, 'hiddenlabels', hiddenSlices);
} else {
var otherTraces = [],
traceIndex,
i;

for(i = 0; i < fullData.length; i++) {
otherTraces.push(i);
}

Plotly.relayout(gd, 'hiddenlabels', hiddenSlices);
if(legendgroup === '') {
traceIndicesInGroup = [trace.index];
otherTraces.splice(trace.index, 1);
} else {
if(legendgroup === '') {
traceIndicesInGroup = [trace.index];
} else {
for(var i = 0; i < fullData.length; i++) {
tracei = fullData[i];
if(tracei.legendgroup === legendgroup) {
traceIndicesInGroup.push(tracei.index);
}
for(i = 0; i < fullData.length; i++) {
tracei = fullData[i];
if(tracei.legendgroup === legendgroup) {
traceIndicesInGroup.push(tracei.index);
traceIndex = otherTraces.indexOf(i);
otherTraces.splice(traceIndex, 1);
}
}

}
if(numClicks === 1) {
newVisible = trace.visible === true ? 'legendonly' : true;
Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup);
} else if(numClicks === 2) {
Plotly.restyle(gd, 'visible', true, traceIndicesInGroup);
Plotly.restyle(gd, 'visible', 'legendonly', otherTraces);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you work this into a single call Plotly.restyle(gd, 'visible', [true, 'legendonly', ...], allTraces);?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a way this may make things easier, no more array splicing, I can just build up a traceVisibility array as I iterate.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. Please convert this to a single restyle call 🐎

}
});
}
}

function computeTextDimensions(g, gd) {
Expand Down