@@ -2794,3 +2794,250 @@ describe('legend with custom legendwidth', function() {
27942794 } ) . then ( done , done . fail ) ;
27952795 } ) ;
27962796} ) ;
2797+
2798+ describe ( 'legend title click' , function ( ) {
2799+ "use strict" ;
2800+
2801+ var gd ;
2802+
2803+ beforeEach ( function ( ) {
2804+ gd = createGraphDiv ( ) ;
2805+ } ) ;
2806+ afterEach ( destroyGraphDiv ) ;
2807+
2808+ function clickTitle ( legendId , clicks ) {
2809+ return function ( ) {
2810+ return new Promise ( function ( resolve ) {
2811+ var selector = '.' + ( legendId || 'legend' ) + 'titletoggle' ;
2812+ var item = d3Select ( selector ) . node ( ) ;
2813+ if ( ! item ) {
2814+ fail ( 'Could not find title toggle element: ' + selector ) ;
2815+ return resolve ( ) ;
2816+ }
2817+ for ( var i = 0 ; i < ( clicks || 1 ) ; i ++ ) {
2818+ item . dispatchEvent ( new MouseEvent ( 'mousedown' ) ) ;
2819+ item . dispatchEvent ( new MouseEvent ( 'mouseup' ) ) ;
2820+ }
2821+ setTimeout ( resolve , DBLCLICKDELAY + 100 ) ;
2822+ } ) ;
2823+ } ;
2824+ }
2825+
2826+ function extractVisibilities ( data ) {
2827+ return data . map ( function ( trace ) { return trace . visible ; } ) ;
2828+ }
2829+
2830+ function assertVisible ( expectation ) {
2831+ return function ( ) {
2832+ var actual = extractVisibilities ( gd . _fullData ) ;
2833+ expect ( actual ) . toEqual ( expectation ) ;
2834+ } ;
2835+ }
2836+
2837+ function assertVisibleShapes ( expectation ) {
2838+ return function ( ) {
2839+ var actual = extractVisibilities ( gd . _fullLayout . shapes ) ;
2840+ expect ( actual ) . toEqual ( expectation ) ;
2841+ } ;
2842+ }
2843+
2844+ describe ( 'defaults' , function ( ) {
2845+ it ( 'should disable title clicking by default for a single legend' , function ( done ) {
2846+ Plotly . newPlot ( gd , [
2847+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2848+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] }
2849+ ] , {
2850+ legend : { title : { text : 'Legend' } }
2851+ } ) . then ( function ( ) {
2852+ expect ( gd . _fullLayout . legend . titleclick ) . toBe ( false ) ;
2853+ expect ( gd . _fullLayout . legend . titledoubleclick ) . toBe ( false ) ;
2854+ } ) . then ( done , done . fail ) ;
2855+ } ) ;
2856+
2857+ it ( 'should enable title clicking by default for multiple legends' , function ( done ) {
2858+ Plotly . newPlot ( gd , [
2859+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2860+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
2861+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
2862+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
2863+ ] , {
2864+ showlegend : true ,
2865+ legend : { title : { text : 'Legend 1' } } ,
2866+ legend2 : { title : { text : 'Legend 2' } }
2867+ } ) . then ( function ( ) {
2868+ expect ( gd . _fullLayout . legend . titleclick ) . toBe ( 'toggle' ) ;
2869+ expect ( gd . _fullLayout . legend . titledoubleclick ) . toBe ( 'toggleothers' ) ;
2870+ expect ( gd . _fullLayout . legend2 . titleclick ) . toBe ( 'toggle' ) ;
2871+ expect ( gd . _fullLayout . legend2 . titledoubleclick ) . toBe ( 'toggleothers' ) ;
2872+ } ) . then ( done , done . fail ) ;
2873+ } ) ;
2874+
2875+ it ( 'should allow user to override titleclick and titledoubleclick' , function ( done ) {
2876+ Plotly . newPlot ( gd , [
2877+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2878+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] }
2879+ ] , {
2880+ legend : {
2881+ title : { text : 'Legend' } ,
2882+ titleclick : 'toggle' ,
2883+ titledoubleclick : false
2884+ }
2885+ } ) . then ( function ( ) {
2886+ expect ( gd . _fullLayout . legend . titleclick ) . toBe ( 'toggle' ) ;
2887+ expect ( gd . _fullLayout . legend . titledoubleclick ) . toBe ( false ) ;
2888+ } ) . then ( done , done . fail ) ;
2889+ } ) ;
2890+ } ) ;
2891+
2892+ describe ( 'toggle interactions' , function ( ) {
2893+ beforeEach ( function ( done ) {
2894+ Plotly . newPlot ( gd , [
2895+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2896+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
2897+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
2898+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
2899+ ] , {
2900+ showlegend : true ,
2901+ legend : { title : { text : 'Legend 1' } } ,
2902+ legend2 : { title : { text : 'Legend 2' } , y : 0.5 }
2903+ } ) . then ( done ) ;
2904+ } ) ;
2905+
2906+ it ( 'should hide all traces in legend when clicking title (all visible)' , function ( done ) {
2907+ Promise . resolve ( )
2908+ . then ( assertVisible ( [ true , true , true , true ] ) )
2909+ . then ( clickTitle ( 'legend' ) )
2910+ . then ( assertVisible ( [ 'legendonly' , 'legendonly' , true , true ] ) )
2911+ . then ( done , done . fail ) ;
2912+ } ) ;
2913+
2914+ it ( 'should show all traces in legend when clicking title (all hidden)' , function ( done ) {
2915+ Plotly . restyle ( gd , 'visible' , 'legendonly' , [ 0 , 1 ] )
2916+ . then ( assertVisible ( [ 'legendonly' , 'legendonly' , true , true ] ) )
2917+ . then ( clickTitle ( 'legend' ) )
2918+ . then ( assertVisible ( [ true , true , true , true ] ) )
2919+ . then ( done , done . fail ) ;
2920+ } ) ;
2921+
2922+ it ( 'should not affect traces with visible: false' , function ( done ) {
2923+ Plotly . restyle ( gd , 'visible' , false , [ 0 ] )
2924+ . then ( assertVisible ( [ false , true , true , true ] ) )
2925+ . then ( clickTitle ( 'legend' ) )
2926+ . then ( assertVisible ( [ false , 'legendonly' , true , true ] ) )
2927+ . then ( done , done . fail ) ;
2928+ } ) ;
2929+ } ) ;
2930+
2931+ describe ( 'toggleothers interactions' , function ( ) {
2932+ beforeEach ( function ( done ) {
2933+ Plotly . newPlot ( gd , [
2934+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2935+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
2936+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
2937+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
2938+ ] , {
2939+ showlegend : true ,
2940+ legend : { title : { text : 'Legend 1' } } ,
2941+ legend2 : { title : { text : 'Legend 2' } , y : 0.5 }
2942+ } ) . then ( done ) ;
2943+ } ) ;
2944+
2945+ it ( 'should isolate this legend (hide others)' , function ( done ) {
2946+ Promise . resolve ( )
2947+ . then ( assertVisible ( [ true , true , true , true ] ) )
2948+ . then ( clickTitle ( 'legend' , 2 ) )
2949+ . then ( assertVisible ( [ true , true , 'legendonly' , 'legendonly' ] ) )
2950+ . then ( done , done . fail ) ;
2951+ } ) ;
2952+
2953+ it ( 'should restore all when already isolated' , function ( done ) {
2954+ Plotly . restyle ( gd , 'visible' , 'legendonly' , [ 2 , 3 ] )
2955+ . then ( assertVisible ( [ true , true , 'legendonly' , 'legendonly' ] ) )
2956+ . then ( clickTitle ( 'legend' , 2 ) )
2957+ . then ( assertVisible ( [ true , true , true , true ] ) )
2958+ . then ( done , done . fail ) ;
2959+ } ) ;
2960+ } ) ;
2961+
2962+ describe ( 'interactions with shapes' , function ( ) {
2963+ beforeEach ( function ( done ) {
2964+ Plotly . newPlot ( gd , [
2965+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2966+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
2967+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
2968+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
2969+ ] , {
2970+ showlegend : true ,
2971+ legend : { title : { text : 'Legend 1' } } ,
2972+ legend2 : { title : { text : 'Legend 2' } , y : 0.5 } ,
2973+ shapes : [
2974+ { showlegend : true , type : 'line' , x0 : 0 , y0 : 0 , x1 : 1 , y1 : 1 } ,
2975+ { showlegend : true , type : 'rect' , x0 : 0 , y0 : 0 , x1 : 1 , y1 : 1 , legend : 'legend2' }
2976+ ]
2977+ } ) . then ( done ) ;
2978+ } ) ;
2979+
2980+ it ( 'should toggle shapes with traces' , function ( done ) {
2981+ Promise . resolve ( )
2982+ . then ( assertVisible ( [ true , true , true , true ] ) )
2983+ . then ( assertVisibleShapes ( [ true , true ] ) )
2984+ . then ( clickTitle ( 'legend' ) )
2985+ . then ( assertVisible ( [ 'legendonly' , 'legendonly' , true , true ] ) )
2986+ . then ( assertVisibleShapes ( [ 'legendonly' , true ] ) )
2987+ . then ( done , done . fail ) ;
2988+ } ) ;
2989+ } ) ;
2990+
2991+ it ( 'should not create click target when no title text' , function ( done ) {
2992+ Plotly . newPlot ( gd , [
2993+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
2994+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
2995+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
2996+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
2997+ ] , {
2998+ showlegend : true ,
2999+ legend : { } ,
3000+ legend2 : { y : 0.5 }
3001+ } ) . then ( function ( ) {
3002+ var titleToggle = d3Select ( '.legendtitletoggle' ) ;
3003+ expect ( titleToggle . size ( ) ) . toBe ( 0 ) ;
3004+ } ) . then ( done , done . fail ) ;
3005+ } ) ;
3006+
3007+ it ( 'should have a pointer cursor on hover for clickable titles' , function ( done ) {
3008+ Plotly . newPlot ( gd , [
3009+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
3010+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
3011+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
3012+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
3013+ ] , {
3014+ showlegend : true ,
3015+ legend : { title : { text : 'Legend 1' } } ,
3016+ legend2 : { title : { text : 'Legend 2' } , y : 0.5 }
3017+ } ) . then ( function ( ) {
3018+ var titleToggle = d3Select ( '.legendtitletoggle' ) . node ( ) ;
3019+ expect ( titleToggle . style . cursor ) . toBe ( 'pointer' ) ;
3020+ } ) . then ( done , done . fail ) ;
3021+ } ) ;
3022+
3023+ it ( 'should not have pointer cursor on static plots' , function ( done ) {
3024+ Plotly . newPlot ( gd , [
3025+ { x : [ 1 , 2 ] , y : [ 1 , 2 ] } ,
3026+ { x : [ 1 , 2 ] , y : [ 2 , 3 ] } ,
3027+ { x : [ 1 , 2 ] , y : [ 3 , 4 ] , legend : 'legend2' } ,
3028+ { x : [ 1 , 2 ] , y : [ 4 , 5 ] , legend : 'legend2' }
3029+ ] , {
3030+ showlegend : true ,
3031+ legend : { title : { text : 'Legend 1' } } ,
3032+ legend2 : { title : { text : 'Legend 2' } , y : 0.5 }
3033+ } , {
3034+ staticPlot : true
3035+ } ) . then ( function ( ) {
3036+ var titleToggle = d3Select ( '.legendtitletoggle' ) . node ( ) ;
3037+ // On static plots, the title toggle rect is created but without pointer cursor
3038+ if ( titleToggle ) {
3039+ expect ( titleToggle . style . cursor ) . not . toBe ( 'pointer' ) ;
3040+ }
3041+ } ) . then ( done , done . fail ) ;
3042+ } ) ;
3043+ } ) ;
0 commit comments