Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
4 changes: 2 additions & 2 deletions src/components/dragelement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var Plotly = require('../../plotly');
var Lib = require('../../lib');

var constants = require('../../plots/cartesian/constants');

var interactConstants = require('../../constants/interactions');

var dragElement = module.exports = {};

Expand Down Expand Up @@ -51,7 +51,7 @@ dragElement.unhoverRaw = unhover.raw;
dragElement.init = function init(options) {
var gd = Lib.getPlotDiv(options.element) || {},
numClicks = 1,
DBLCLICKDELAY = constants.DBLCLICKDELAY,
DBLCLICKDELAY = interactConstants.DBLCLICKDELAY,
startX,
startY,
newMouseDownTime,
Expand Down
129 changes: 105 additions & 24 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ var Color = require('../color');
var svgTextUtils = require('../../lib/svg_text_utils');

var constants = require('./constants');
var interactConstants = require('../../constants/interactions');
var getLegendData = require('./get_legend_data');
var style = require('./style');
var helpers = require('./helpers');
var anchorUtils = require('./anchor_utils');

var SHOWISOLATETIP = true;

module.exports = function draw(gd) {
var fullLayout = gd._fullLayout;
var clipId = 'legend' + fullLayout._uid;

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 +399,9 @@ function drawTexts(g, gd) {
}

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

var traceToggle = g.selectAll('rect')
.data([0]);
Expand All @@ -408,41 +412,118 @@ 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;

if((new Date()).getTime() - gd._legendMouseDownTime > DBLCLICKDELAY) {
numClicks = Math.max(numClicks - 1, 1);
}

var legendItem = g.data()[0][0],
fullData = gd._fullData,
trace = legendItem.trace,
legendgroup = trace.legendgroup,
traceIndicesInGroup = [],
tracei,
newVisible;
if(numClicks === 1) {
legend._clickTimeout = setTimeout(function() { handleClick(g, gd, numClicks); }, DBLCLICKDELAY);
} 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() :
[];

if(Registry.traceIs(trace, 'pie')) {
var thisLabel = legendItem.label,
thisLabelIndex = hiddenSlices.indexOf(thisLabel);
var legendItem = g.data()[0][0],
fullData = gd._fullData,
trace = legendItem.trace,
legendgroup = trace.legendgroup,
traceIndicesInGroup = [],
tracei,
newVisible;


if(numClicks === 1 && SHOWISOLATETIP && gd.data && gd._context.showTips) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need gd.data here - SHOWZOOMOUTTIP needed it because you can't autoscale a plot with nothing on it, but there wouldn't be a legend anyway without data!

Lib.notifier('Double click on legend to isolate individual trace', 'long');
SHOWISOLATETIP = false;
} else {
SHOWISOLATETIP = false;
}
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);
}
});
if(gd._fullLayout.hiddenlabels && gd._fullLayout.hiddenlabels.length === hiddenSlices.length && thisLabelIndex === -1) {
hiddenSlices = [];
}
}

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

Plotly.relayout(gd, 'hiddenlabels', hiddenSlices);
for(i = 0; i < fullData.length; i++) {
allTraces.push(i);
traceVisibility.push('legendonly');
}

if(legendgroup === '') {
traceIndicesInGroup = [trace.index];
traceVisibility[trace.index] = true;
} 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);
traceVisibility[allTraces.indexOf(i)] = true;
}
}
}

if(numClicks === 1) {
newVisible = trace.visible === true ? 'legendonly' : true;
Plotly.restyle(gd, 'visible', newVisible, traceIndicesInGroup);
} else if(numClicks === 2) {
var sameAsLast = true;
for(i = 0; i < fullData.length; i++) {
if(fullData[i].visible !== traceVisibility[i]) {
sameAsLast = false;
break;
}
}
if(sameAsLast) {
traceVisibility = true;
}
Plotly.restyle(gd, 'visible', traceVisibility, allTraces);
}
});
}
}

function computeTextDimensions(g, gd) {
Expand Down
6 changes: 5 additions & 1 deletion src/constants/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ module.exports = {
* Timing information for interactive elements
*/
SHOW_PLACEHOLDER: 100,
HIDE_PLACEHOLDER: 1000
HIDE_PLACEHOLDER: 1000,

// ms between first mousedown and 2nd mouseup to constitute dblclick...
// we don't seem to have access to the system setting
DBLCLICKDELAY: 300
};
4 changes: 0 additions & 4 deletions src/plots/cartesian/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ module.exports = {
AX_ID_PATTERN: /^[xyz][0-9]*$/,
AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/,

// ms between first mousedown and 2nd mouseup to constitute dblclick...
// we don't seem to have access to the system setting
DBLCLICKDELAY: 300,

// pixels to move mouse before you stop clamping to starting point
MINDRAG: 8,

Expand Down