@@ -21,18 +21,23 @@ var Color = require('../color');
2121var svgTextUtils = require ( '../../lib/svg_text_utils' ) ;
2222
2323var constants = require ( './constants' ) ;
24+ var interactConstants = require ( '../../constants/interactions' ) ;
2425var getLegendData = require ( './get_legend_data' ) ;
2526var style = require ( './style' ) ;
2627var helpers = require ( './helpers' ) ;
2728var anchorUtils = require ( './anchor_utils' ) ;
2829
30+ var SHOWISOLATETIP = true ;
31+ var DBLCLICKDELAY = interactConstants . DBLCLICKDELAY ;
2932
3033module . exports = function draw ( gd ) {
3134 var fullLayout = gd . _fullLayout ;
3235 var clipId = 'legend' + fullLayout . _uid ;
3336
3437 if ( ! fullLayout . _infolayer || ! gd . calcdata ) return ;
3538
39+ if ( ! gd . _legendMouseDownTime ) gd . _legendMouseDownTime = 0 ;
40+
3641 var opts = fullLayout . legend ,
3742 legendData = fullLayout . showlegend && getLegendData ( gd . calcdata , opts ) ,
3843 hiddenSlices = fullLayout . hiddenlabels || [ ] ;
@@ -325,9 +330,26 @@ module.exports = function draw(gd) {
325330 xf = dragElement . align ( newX , 0 , gs . l , gs . l + gs . w , opts . xanchor ) ;
326331 yf = dragElement . align ( newY , 0 , gs . t + gs . h , gs . t , opts . yanchor ) ;
327332 } ,
328- doneFn : function ( dragged ) {
333+ doneFn : function ( dragged , numClicks , e ) {
329334 if ( dragged && xf !== undefined && yf !== undefined ) {
330335 Plotly . relayout ( gd , { 'legend.x' : xf , 'legend.y' : yf } ) ;
336+ } else {
337+ var clickedTrace =
338+ fullLayout . _infolayer . selectAll ( 'g.traces' ) . filter ( function ( ) {
339+ var bbox = this . getBoundingClientRect ( ) ;
340+ return ( e . clientX >= bbox . left && e . clientX <= bbox . right &&
341+ e . clientY >= bbox . top && e . clientY <= bbox . bottom ) ;
342+ } ) ;
343+ if ( clickedTrace . size ( ) > 0 ) {
344+ if ( numClicks === 1 ) {
345+ legend . _clickTimeout = setTimeout ( function ( ) { handleClick ( clickedTrace , gd , numClicks ) ; } , DBLCLICKDELAY ) ;
346+ } else if ( numClicks === 2 ) {
347+ if ( legend . _clickTimeout ) {
348+ clearTimeout ( legend . _clickTimeout ) ;
349+ }
350+ handleClick ( clickedTrace , gd , numClicks ) ;
351+ }
352+ }
331353 }
332354 }
333355 } ) ;
@@ -395,9 +417,8 @@ function drawTexts(g, gd) {
395417}
396418
397419function setupTraceToggle ( g , gd ) {
398- var hiddenSlices = gd . _fullLayout . hiddenlabels ?
399- gd . _fullLayout . hiddenlabels . slice ( ) :
400- [ ] ;
420+ var newMouseDownTime ,
421+ numClicks = 1 ;
401422
402423 var traceToggle = g . selectAll ( 'rect' )
403424 . data ( [ 0 ] ) ;
@@ -408,41 +429,120 @@ function setupTraceToggle(g, gd) {
408429 . attr ( 'pointer-events' , 'all' )
409430 . call ( Color . fill , 'rgba(0,0,0,0)' ) ;
410431
411- traceToggle . on ( 'click' , function ( ) {
412- if ( gd . _dragged ) return ;
413432
414- var legendItem = g . data ( ) [ 0 ] [ 0 ] ,
415- fullData = gd . _fullData ,
416- trace = legendItem . trace ,
417- legendgroup = trace . legendgroup ,
418- traceIndicesInGroup = [ ] ,
419- tracei ,
420- newVisible ;
433+ traceToggle . on ( 'mousedown' , function ( ) {
434+ newMouseDownTime = ( new Date ( ) ) . getTime ( ) ;
435+ if ( newMouseDownTime - gd . _legendMouseDownTime < DBLCLICKDELAY ) {
436+ // in a click train
437+ numClicks += 1 ;
438+ }
439+ else {
440+ // new click train
441+ numClicks = 1 ;
442+ gd . _legendMouseDownTime = newMouseDownTime ;
443+ }
444+ } ) ;
445+ traceToggle . on ( 'mouseup' , function ( ) {
446+ if ( gd . _dragged || gd . _editing ) return ;
447+ var legend = gd . _fullLayout . legend ;
421448
422- if ( Registry . traceIs ( trace , 'pie' ) ) {
423- var thisLabel = legendItem . label ,
424- thisLabelIndex = hiddenSlices . indexOf ( thisLabel ) ;
449+ if ( ( new Date ( ) ) . getTime ( ) - gd . _legendMouseDownTime > DBLCLICKDELAY ) {
450+ numClicks = Math . max ( numClicks - 1 , 1 ) ;
451+ }
452+
453+ if ( numClicks === 1 ) {
454+ legend . _clickTimeout = setTimeout ( function ( ) { handleClick ( g , gd , numClicks ) ; } , DBLCLICKDELAY ) ;
455+ } else if ( numClicks === 2 ) {
456+ if ( legend . _clickTimeout ) {
457+ clearTimeout ( legend . _clickTimeout ) ;
458+ }
459+ gd . _legendMouseDownTime = 0 ;
460+ handleClick ( g , gd , numClicks ) ;
461+ }
462+ } ) ;
463+ }
464+
465+ function handleClick ( g , gd , numClicks ) {
466+ if ( gd . _dragged || gd . _editing ) return ;
467+ var hiddenSlices = gd . _fullLayout . hiddenlabels ?
468+ gd . _fullLayout . hiddenlabels . slice ( ) :
469+ [ ] ;
470+
471+ var legendItem = g . data ( ) [ 0 ] [ 0 ] ,
472+ fullData = gd . _fullData ,
473+ trace = legendItem . trace ,
474+ legendgroup = trace . legendgroup ,
475+ traceIndicesInGroup = [ ] ,
476+ tracei ,
477+ newVisible ;
425478
479+
480+ if ( numClicks === 1 && SHOWISOLATETIP && gd . data && gd . _context . showTips ) {
481+ Lib . notifier ( 'Double click on legend to isolate individual trace' , 'long' ) ;
482+ SHOWISOLATETIP = false ;
483+ } else {
484+ SHOWISOLATETIP = false ;
485+ }
486+ if ( Registry . traceIs ( trace , 'pie' ) ) {
487+ var thisLabel = legendItem . label ,
488+ thisLabelIndex = hiddenSlices . indexOf ( thisLabel ) ;
489+
490+ if ( numClicks === 1 ) {
426491 if ( thisLabelIndex === - 1 ) hiddenSlices . push ( thisLabel ) ;
427492 else hiddenSlices . splice ( thisLabelIndex , 1 ) ;
493+ } else if ( numClicks === 2 ) {
494+ hiddenSlices = [ ] ;
495+ gd . calcdata [ 0 ] . forEach ( function ( d ) {
496+ if ( thisLabel !== d . label ) {
497+ hiddenSlices . push ( d . label ) ;
498+ }
499+ } ) ;
500+ if ( gd . _fullLayout . hiddenlabels && gd . _fullLayout . hiddenlabels . length === hiddenSlices . length && thisLabelIndex === - 1 ) {
501+ hiddenSlices = [ ] ;
502+ }
503+ }
504+
505+ Plotly . relayout ( gd , 'hiddenlabels' , hiddenSlices ) ;
506+ } else {
507+ var allTraces = [ ] ,
508+ traceVisibility = [ ] ,
509+ i ;
428510
429- Plotly . relayout ( gd , 'hiddenlabels' , hiddenSlices ) ;
511+ for ( i = 0 ; i < fullData . length ; i ++ ) {
512+ allTraces . push ( i ) ;
513+ traceVisibility . push ( 'legendonly' ) ;
514+ }
515+
516+ if ( legendgroup === '' ) {
517+ traceIndicesInGroup = [ trace . index ] ;
518+ traceVisibility [ trace . index ] = true ;
430519 } else {
431- if ( legendgroup === '' ) {
432- traceIndicesInGroup = [ trace . index ] ;
433- } else {
434- for ( var i = 0 ; i < fullData . length ; i ++ ) {
435- tracei = fullData [ i ] ;
436- if ( tracei . legendgroup === legendgroup ) {
437- traceIndicesInGroup . push ( tracei . index ) ;
438- }
520+ for ( i = 0 ; i < fullData . length ; i ++ ) {
521+ tracei = fullData [ i ] ;
522+ if ( tracei . legendgroup === legendgroup ) {
523+ traceIndicesInGroup . push ( tracei . index ) ;
524+ traceVisibility [ allTraces . indexOf ( i ) ] = true ;
439525 }
440526 }
527+ }
441528
529+ if ( numClicks === 1 ) {
442530 newVisible = trace . visible === true ? 'legendonly' : true ;
443531 Plotly . restyle ( gd , 'visible' , newVisible , traceIndicesInGroup ) ;
532+ } else if ( numClicks === 2 ) {
533+ var sameAsLast = true ;
534+ for ( i = 0 ; i < fullData . length ; i ++ ) {
535+ if ( fullData [ i ] . visible !== traceVisibility [ i ] ) {
536+ sameAsLast = false ;
537+ break ;
538+ }
539+ }
540+ if ( sameAsLast ) {
541+ traceVisibility = true ;
542+ }
543+ Plotly . restyle ( gd , 'visible' , traceVisibility , allTraces ) ;
444544 }
445- } ) ;
545+ }
446546}
447547
448548function computeTextDimensions ( g , gd ) {
0 commit comments