@@ -18,6 +18,7 @@ var svgTextUtils = require('../../lib/svg_text_utils');
1818var anchorUtils = require ( '../legend/anchor_utils' ) ;
1919
2020var constants = require ( './constants' ) ;
21+ var ScrollBox = require ( './scrollbox' ) ;
2122
2223module . exports = function draw ( gd ) {
2324 var fullLayout = gd . _fullLayout ,
@@ -82,16 +83,23 @@ module.exports = function draw(gd) {
8283 . classed ( constants . dropdownButtonGroupClassName , true )
8384 . style ( 'pointer-events' , 'all' ) ;
8485
85- // whenever we add new menu, attach 'state' variable to node
86- // to keep track of the active menu ('-1' means no menu is active)
87- // and remove all dropped buttons (if any)
86+ // find dimensions before plotting anything (this mutates menuOpts)
87+ for ( var i = 0 ; i < menuData . length ; i ++ ) {
88+ var menuOpts = menuData [ i ] ;
89+ findDimensions ( gd , menuOpts ) ;
90+ }
91+
92+ // setup scrollbox
93+ var scrollBoxId = 'updatemenus' + fullLayout . _uid ,
94+ scrollBox = new ScrollBox ( gd , gButton , scrollBoxId ) ;
95+
96+ // remove exiting header, remove dropped buttons and reset margins
8897 if ( headerGroups . enter ( ) . size ( ) ) {
8998 gButton
9099 . call ( removeAllButtons )
91100 . attr ( constants . menuIndexAttrName , '-1' ) ;
92101 }
93102
94- // remove exiting header, remove dropped buttons and reset margins
95103 headerGroups . exit ( ) . each ( function ( menuOpts ) {
96104 d3 . select ( this ) . remove ( ) ;
97105
@@ -102,30 +110,24 @@ module.exports = function draw(gd) {
102110 Plots . autoMargin ( gd , constants . autoMarginIdRoot + menuOpts . _index ) ;
103111 } ) ;
104112
105- // find dimensions before plotting anything (this mutates menuOpts)
106- for ( var i = 0 ; i < menuData . length ; i ++ ) {
107- var menuOpts = menuData [ i ] ;
108- findDimensions ( gd , menuOpts ) ;
109- }
110-
111113 // draw headers!
112114 headerGroups . each ( function ( menuOpts ) {
113115 var gHeader = d3 . select ( this ) ;
114116
115117 var _gButton = menuOpts . type === 'dropdown' ? gButton : null ;
116118 Plots . manageCommandObserver ( gd , menuOpts , menuOpts . buttons , function ( data ) {
117- setActive ( gd , menuOpts , menuOpts . buttons [ data . index ] , gHeader , _gButton , data . index , true ) ;
119+ setActive ( gd , menuOpts , menuOpts . buttons [ data . index ] , gHeader , _gButton , scrollBox , data . index , true ) ;
118120 } ) ;
119121
120122 if ( menuOpts . type === 'dropdown' ) {
121- drawHeader ( gd , gHeader , gButton , menuOpts ) ;
123+ drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
122124
123- // update buttons if they are dropped
124- if ( areMenuButtonsDropped ( gButton , menuOpts ) ) {
125- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
125+ // if this menu is active, update the dropdown container
126+ if ( isActive ( gButton , menuOpts ) ) {
127+ drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
126128 }
127129 } else {
128- drawButtons ( gd , gHeader , null , menuOpts ) ;
130+ drawButtons ( gd , gHeader , null , null , menuOpts ) ;
129131 }
130132
131133 } ) ;
@@ -150,18 +152,40 @@ function makeMenuData(fullLayout) {
150152
151153// Note that '_index' is set at the default step,
152154// it corresponds to the menu index in the user layout update menu container.
153- // This is a more 'consistent' field than e.g. the index in the menuData.
154- function keyFunction ( opts ) {
155- return opts . _index ;
155+ // Because a menu can b set invisible,
156+ // this is a more 'consistent' field than the index in the menuData.
157+ function keyFunction ( menuOpts ) {
158+ return menuOpts . _index ;
156159}
157160
158- function areMenuButtonsDropped ( gButton , menuOpts ) {
159- var droppedIndex = + gButton . attr ( constants . menuIndexAttrName ) ;
161+ function isFolded ( gButton ) {
162+ return + gButton . attr ( constants . menuIndexAttrName ) === - 1 ;
163+ }
160164
161- return droppedIndex === menuOpts . _index ;
165+ function isActive ( gButton , menuOpts ) {
166+ return + gButton . attr ( constants . menuIndexAttrName ) === menuOpts . _index ;
162167}
163168
164- function drawHeader ( gd , gHeader , gButton , menuOpts ) {
169+ function setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , scrollBox , buttonIndex , isSilentUpdate ) {
170+ // update 'active' attribute in menuOpts
171+ menuOpts . _input . active = menuOpts . active = buttonIndex ;
172+
173+ if ( menuOpts . type === 'buttons' ) {
174+ drawButtons ( gd , gHeader , null , null , menuOpts ) ;
175+ }
176+ else if ( menuOpts . type === 'dropdown' ) {
177+ // fold up buttons and redraw header
178+ gButton . attr ( constants . menuIndexAttrName , '-1' ) ;
179+
180+ drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
181+
182+ if ( ! isSilentUpdate ) {
183+ drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
184+ }
185+ }
186+ }
187+
188+ function drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) {
165189 var header = gHeader . selectAll ( 'g.' + constants . headerClassName )
166190 . data ( [ 0 ] ) ;
167191
@@ -200,14 +224,17 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
200224 header . on ( 'click' , function ( ) {
201225 gButton . call ( removeAllButtons ) ;
202226
203- // if clicked index is same as dropped index => fold
204- // otherwise => drop buttons associated with header
227+
228+ // if this menu is active, fold the dropdown container
229+ // otherwise, make this menu active
205230 gButton . attr (
206231 constants . menuIndexAttrName ,
207- areMenuButtonsDropped ( gButton , menuOpts ) ? '-1' : String ( menuOpts . _index )
232+ isActive ( gButton , menuOpts ) ?
233+ - 1 :
234+ String ( menuOpts . _index )
208235 ) ;
209236
210- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
237+ drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
211238 } ) ;
212239
213240 header . on ( 'mouseover' , function ( ) {
@@ -222,7 +249,7 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
222249 Drawing . setTranslate ( gHeader , menuOpts . lx , menuOpts . ly ) ;
223250}
224251
225- function drawButtons ( gd , gHeader , gButton , menuOpts ) {
252+ function drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) {
226253 // If this is a set of buttons, set pointer events = all since we play
227254 // some minor games with which container is which in order to simplify
228255 // the drawing of *either* buttons or menus
@@ -231,7 +258,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
231258 gButton . attr ( 'pointer-events' , 'all' ) ;
232259 }
233260
234- var buttonData = ( gButton . attr ( constants . menuIndexAttrName ) !== '-1' || menuOpts . type === 'buttons' ) ?
261+ var buttonData = ( ! isFolded ( gButton ) || menuOpts . type === 'buttons' ) ?
235262 menuOpts . buttons :
236263 [ ] ;
237264
@@ -257,7 +284,6 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
257284 exit . remove ( ) ;
258285 }
259286
260-
261287 var x0 = 0 ;
262288 var y0 = 0 ;
263289
@@ -280,13 +306,18 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
280306 }
281307
282308 var posOpts = {
283- x : x0 + menuOpts . pad . l ,
284- y : y0 + menuOpts . pad . t ,
309+ x : menuOpts . lx + x0 + menuOpts . pad . l ,
310+ y : menuOpts . ly + y0 + menuOpts . pad . t ,
285311 yPad : constants . gapButton ,
286312 xPad : constants . gapButton ,
287313 index : 0 ,
288314 } ;
289315
316+ var scrollBoxPosition = {
317+ l : posOpts . x + menuOpts . borderwidth ,
318+ t : posOpts . y + menuOpts . borderwidth
319+ } ;
320+
290321 buttons . each ( function ( buttonOpts , buttonIndex ) {
291322 var button = d3 . select ( this ) ;
292323
@@ -295,7 +326,10 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
295326 . call ( setItemPosition , menuOpts , posOpts ) ;
296327
297328 button . on ( 'click' , function ( ) {
298- setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , buttonIndex ) ;
329+ // skip `dragend` events
330+ if ( d3 . event . defaultPrevented ) return ;
331+
332+ setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , scrollBox , buttonIndex ) ;
299333
300334 Plots . executeAPICommand ( gd , buttonOpts . method , buttonOpts . args ) ;
301335
@@ -314,23 +348,87 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
314348
315349 buttons . call ( styleButtons , menuOpts ) ;
316350
317- // translate button group
318- Drawing . setTranslate ( gButton , menuOpts . lx , menuOpts . ly ) ;
351+ if ( isVertical ) {
352+ scrollBoxPosition . w = Math . max ( menuOpts . openWidth , menuOpts . headerWidth ) ;
353+ scrollBoxPosition . h = posOpts . y - scrollBoxPosition . t ;
354+ }
355+ else {
356+ scrollBoxPosition . w = posOpts . x - scrollBoxPosition . l ;
357+ scrollBoxPosition . h = Math . max ( menuOpts . openHeight , menuOpts . headerHeight ) ;
358+ }
359+
360+ scrollBoxPosition . direction = menuOpts . direction ;
361+
362+ if ( scrollBox ) {
363+ if ( buttons . size ( ) ) {
364+ drawScrollBox ( gd , gHeader , gButton , scrollBox , menuOpts , scrollBoxPosition ) ;
365+ }
366+ else {
367+ hideScrollBox ( scrollBox ) ;
368+ }
369+ }
319370}
320371
321- function setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , buttonIndex , isSilentUpdate ) {
322- // update 'active' attribute in menuOpts
323- menuOpts . _input . active = menuOpts . active = buttonIndex ;
372+ function drawScrollBox ( gd , gHeader , gButton , scrollBox , menuOpts , position ) {
373+ // enable the scrollbox
374+ var direction = menuOpts . direction ,
375+ isVertical = ( direction === 'up' || direction === 'down' ) ;
324376
325- if ( menuOpts . type === 'dropdown' ) {
326- // fold up buttons and redraw header
327- gButton . attr ( constants . menuIndexAttrName , '-1' ) ;
377+ var active = menuOpts . active ,
378+ translateX , translateY ,
379+ i ;
380+ if ( isVertical ) {
381+ translateY = 0 ;
382+ for ( i = 0 ; i < active ; i ++ ) {
383+ translateY += menuOpts . heights [ i ] + constants . gapButton ;
384+ }
385+ }
386+ else {
387+ translateX = 0 ;
388+ for ( i = 0 ; i < active ; i ++ ) {
389+ translateX += menuOpts . widths [ i ] + constants . gapButton ;
390+ }
391+ }
328392
329- drawHeader ( gd , gHeader , gButton , menuOpts ) ;
393+ scrollBox . enable ( position , translateX , translateY ) ;
394+
395+ if ( scrollBox . hbar ) {
396+ scrollBox . hbar
397+ . attr ( 'opacity' , '0' )
398+ . transition ( )
399+ . attr ( 'opacity' , '1' ) ;
330400 }
331401
332- if ( ! isSilentUpdate || menuOpts . type === 'buttons' ) {
333- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
402+ if ( scrollBox . vbar ) {
403+ scrollBox . vbar
404+ . attr ( 'opacity' , '0' )
405+ . transition ( )
406+ . attr ( 'opacity' , '1' ) ;
407+ }
408+ }
409+
410+ function hideScrollBox ( scrollBox ) {
411+ var hasHBar = ! ! scrollBox . hbar ,
412+ hasVBar = ! ! scrollBox . vbar ;
413+
414+ if ( hasHBar ) {
415+ scrollBox . hbar
416+ . transition ( )
417+ . attr ( 'opacity' , '0' )
418+ . each ( 'end' , function ( ) {
419+ hasHBar = false ;
420+ if ( ! hasVBar ) scrollBox . disable ( ) ;
421+ } ) ;
422+ }
423+
424+ if ( hasVBar ) {
425+ scrollBox . vbar
426+ . transition ( )
427+ . attr ( 'opacity' , '0' )
428+ . each ( 'end' , function ( ) {
429+ hasVBar = false ;
430+ if ( ! hasHBar ) scrollBox . disable ( ) ;
431+ } ) ;
334432 }
335433}
336434
0 commit comments