@@ -84,50 +84,47 @@ module.exports = function draw(gd) {
8484 . classed ( constants . dropdownButtonGroupClassName , true )
8585 . style ( 'pointer-events' , 'all' ) ;
8686
87- // whenever we add new menu, attach 'state' variable to node
88- // to keep track of the active menu ('-1' means no menu is active)
89- // and remove all dropped buttons (if any)
90- if ( headerGroups . enter ( ) . size ( ) ) {
91- gButton
92- . call ( removeAllButtons )
93- . attr ( constants . menuIndexAttrName , '-1' ) ;
87+ // find dimensions before plotting anything (this mutates menuOpts)
88+ for ( var i = 0 ; i < menuData . length ; i ++ ) {
89+ var menuOpts = menuData [ i ] ;
90+ findDimensions ( gd , menuOpts ) ;
9491 }
9592
93+ // setup scrollbox
94+ var scrollBoxId = 'updatemenus' + fullLayout . _uid ,
95+ scrollBox = new ScrollBox ( gd , gButton , scrollBoxId ) ;
96+
9697 // remove exiting header, remove dropped buttons and reset margins
98+ if ( headerGroups . enter ( ) . size ( ) ) {
99+ foldDropdownMenu ( gButton , scrollBox ) ;
100+ }
101+
97102 headerGroups . exit ( ) . each ( function ( menuOpts ) {
98103 d3 . select ( this ) . remove ( ) ;
99104
100- gButton
101- . call ( removeAllButtons )
102- . attr ( constants . menuIndexAttrName , '-1' ) ;
105+ foldDropdownMenu ( gButton , scrollBox ) ;
103106
104107 Plots . autoMargin ( gd , constants . autoMarginIdRoot + menuOpts . _index ) ;
105108 } ) ;
106109
107- // find dimensions before plotting anything (this mutates menuOpts)
108- for ( var i = 0 ; i < menuData . length ; i ++ ) {
109- var menuOpts = menuData [ i ] ;
110- findDimensions ( gd , menuOpts ) ;
111- }
112-
113110 // draw headers!
114111 headerGroups . each ( function ( menuOpts ) {
115112 var gHeader = d3 . select ( this ) ;
116113
117114 var _gButton = menuOpts . type === 'dropdown' ? gButton : null ;
118115 Plots . manageCommandObserver ( gd , menuOpts , menuOpts . buttons , function ( data ) {
119- setActive ( gd , menuOpts , menuOpts . buttons [ data . index ] , gHeader , _gButton , data . index , true ) ;
116+ setActive ( gd , menuOpts , menuOpts . buttons [ data . index ] , gHeader , _gButton , scrollBox , data . index , true ) ;
120117 } ) ;
121118
122119 if ( menuOpts . type === 'dropdown' ) {
123- drawHeader ( gd , gHeader , gButton , menuOpts ) ;
120+ drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
124121
125- // update buttons if they are dropped
126- if ( areMenuButtonsDropped ( gButton , menuOpts ) ) {
127- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
122+ // update dropdown buttons if this menu is active
123+ if ( isActive ( gButton , menuOpts ) ) {
124+ unfoldDropdownMenu ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
128125 }
129126 } else {
130- drawButtons ( gd , gHeader , null , menuOpts ) ;
127+ drawButtons ( gd , gHeader , null , scrollBox , menuOpts ) ;
131128 }
132129
133130 } ) ;
@@ -152,18 +149,104 @@ function makeMenuData(fullLayout) {
152149
153150// Note that '_index' is set at the default step,
154151// it corresponds to the menu index in the user layout update menu container.
155- // This is a more 'consistent' field than e.g. the index in the menuData.
156- function keyFunction ( opts ) {
157- return opts . _index ;
152+ // Because a menu can b set invisible,
153+ // this is a more 'consistent' field than the index in the menuData.
154+ function keyFunction ( menuOpts ) {
155+ return menuOpts . _index ;
156+ }
157+
158+ function isFolded ( gButton ) {
159+ return + gButton . attr ( constants . menuIndexAttrName ) === - 1 ;
160+ }
161+
162+ function isActive ( gButton , menuOpts ) {
163+ return + gButton . attr ( constants . menuIndexAttrName ) === menuOpts . _index ;
164+ }
165+
166+ function unfoldDropdownMenu ( gd , gHeader , gButton , scrollBox , menuOpts ) {
167+ // enable the scrollbox
168+ var direction = menuOpts . direction ,
169+ isUp = ( direction === 'up' ) ,
170+ isDown = ( direction === 'down' ) ,
171+ isLeft = ( direction === 'left' ) ,
172+ isRight = ( direction === 'right' ) ,
173+ isVertical = ( isUp || isDown ) ;
174+
175+ var x0 , y0 ;
176+ if ( isDown ) {
177+ x0 = 0 ;
178+ y0 = menuOpts . headerHeight + constants . gapButtonHeader ;
179+ }
180+ else if ( isUp ) {
181+ x0 = 0 ;
182+ y0 = menuOpts . headerHeight + constants . gapButton - menuOpts . openHeight ;
183+ }
184+ else if ( isRight ) {
185+ x0 = menuOpts . headerWidth + constants . gapButtonHeader ;
186+ y0 = 0 ;
187+ }
188+ else if ( isLeft ) {
189+ x0 = menuOpts . headerWidth + constants . gapButton - menuOpts . openWidth ;
190+ y0 = 0 ;
191+ }
192+
193+ var position = {
194+ l : menuOpts . lx + menuOpts . borderwidth + x0 + menuOpts . pad . l ,
195+ t : menuOpts . ly + menuOpts . borderwidth + y0 + menuOpts . pad . t ,
196+ w : Math . max ( menuOpts . openWidth , menuOpts . headerWidth ) ,
197+ h : menuOpts . openHeight
198+ } ;
199+
200+ var active = menuOpts . active ,
201+ translateX , translateY ,
202+ i ;
203+ if ( isVertical ) {
204+ translateY = 0 ;
205+ for ( i = 0 ; i < active ; i ++ ) {
206+ translateY += menuOpts . heights [ i ] + constants . gapButton ;
207+ }
208+ }
209+ else {
210+ translateX = 0 ;
211+ for ( i = 0 ; i < active ; i ++ ) {
212+ translateX += menuOpts . widths [ i ] + constants . gapButton ;
213+ }
214+ }
215+
216+ scrollBox . enable ( position , translateX , translateY ) ;
217+
218+ // store index of active menu (-1 means dropdown menu is folded)
219+ gButton . attr ( constants . menuIndexAttrName , menuOpts . _index ) ;
220+
221+ drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
158222}
159223
160- function areMenuButtonsDropped ( gButton , menuOpts ) {
161- var droppedIndex = + gButton . attr ( constants . menuIndexAttrName ) ;
224+ function foldDropdownMenu ( gButton , scrollBox ) {
225+ scrollBox . disable ( ) ;
162226
163- return droppedIndex === menuOpts . _index ;
227+ // -1 means dropdown menu is folded
228+ gButton
229+ . attr ( constants . menuIndexAttrName , '-1' )
230+ . call ( removeAllButtons ) ;
164231}
165232
166- function drawHeader ( gd , gHeader , gButton , menuOpts ) {
233+ function setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , scrollBox , buttonIndex , isSilentUpdate ) {
234+ // update 'active' attribute in menuOpts
235+ menuOpts . _input . active = menuOpts . active = buttonIndex ;
236+
237+ if ( menuOpts . type === 'dropdown' ) {
238+ // fold up buttons and redraw header
239+ gButton . attr ( constants . menuIndexAttrName , '-1' ) ;
240+
241+ drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
242+ }
243+
244+ if ( ! isSilentUpdate || menuOpts . type === 'buttons' ) {
245+ drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
246+ }
247+ }
248+
249+ function drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) {
167250 var header = gHeader . selectAll ( 'g.' + constants . headerClassName )
168251 . data ( [ 0 ] ) ;
169252
@@ -200,16 +283,18 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
200283 } ) ;
201284
202285 header . on ( 'click' , function ( ) {
203- gButton . call ( removeAllButtons ) ;
204-
205- // if clicked index is same as dropped index => fold
206- // otherwise => drop buttons associated with header
207- gButton . attr (
208- constants . menuIndexAttrName ,
209- areMenuButtonsDropped ( gButton , menuOpts ) ? '-1' : String ( menuOpts . _index )
210- ) ;
211-
212- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
286+ if ( isFolded ( gButton ) ) {
287+ unfoldDropdownMenu ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
288+ }
289+ else if ( isActive ( gButton , menuOpts ) ) {
290+ foldDropdownMenu ( gButton , scrollBox ) ;
291+ }
292+ else {
293+ // the dropdown menu is unfolded,
294+ // but the clicked header is not the active header
295+ foldDropdownMenu ( gButton , scrollBox ) ;
296+ unfoldDropdownMenu ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
297+ }
213298 } ) ;
214299
215300 header . on ( 'mouseover' , function ( ) {
@@ -224,7 +309,7 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
224309 Lib . setTranslate ( gHeader , menuOpts . lx , menuOpts . ly ) ;
225310}
226311
227- function drawButtons ( gd , gHeader , gButton , menuOpts ) {
312+ function drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) {
228313 // If this is a set of buttons, set pointer events = all since we play
229314 // some minor games with which container is which in order to simplify
230315 // the drawing of *either* buttons or menus
@@ -233,7 +318,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
233318 gButton . attr ( 'pointer-events' , 'all' ) ;
234319 }
235320
236- var buttonData = ( gButton . attr ( constants . menuIndexAttrName ) !== '-1' || menuOpts . type === 'buttons' ) ?
321+ var buttonData = ( ! isFolded ( gButton ) || menuOpts . type === 'buttons' ) ?
237322 menuOpts . buttons :
238323 [ ] ;
239324
@@ -258,7 +343,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
258343 . each ( 'end' , function ( ) {
259344 // remove the scrollbox, if all the buttons have been removed
260345 if ( gButton . selectAll ( 'g.' + klass ) . size ( ) === 0 ) {
261- gButton . call ( removeAllButtons ) ;
346+ foldDropdownMenu ( gButton , scrollBox ) ;
262347 }
263348 } ) ;
264349 } else {
@@ -297,16 +382,6 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
297382 index : 0 ,
298383 } ;
299384
300- var fullLayout = gd . _fullLayout ,
301- scrollBoxId = 'updatemenus' + fullLayout . _uid + menuOpts . _index ,
302- scrollBoxPosition = {
303- l : menuOpts . lx + menuOpts . borderwidth + x0 + menuOpts . pad . l ,
304- t : menuOpts . ly + menuOpts . borderwidth + y0 + menuOpts . pad . t ,
305- w : Math . max ( menuOpts . openWidth , menuOpts . headerWidth ) ,
306- h : menuOpts . openHeight
307- } ,
308- scrollBox = new ScrollBox ( gd , gButton , scrollBoxPosition , scrollBoxId ) ;
309-
310385 buttons . each ( function ( buttonOpts , buttonIndex ) {
311386 var button = d3 . select ( this ) ;
312387
@@ -318,7 +393,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
318393 // skip `dragend` events
319394 if ( d3 . event . defaultPrevented ) return ;
320395
321- setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , buttonIndex ) ;
396+ setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , scrollBox , buttonIndex ) ;
322397
323398 Plots . executeAPICommand ( gd , buttonOpts . method , buttonOpts . args ) ;
324399
@@ -336,49 +411,6 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
336411 } ) ;
337412
338413 buttons . call ( styleButtons , menuOpts ) ;
339-
340- scrollBox . enable ( ) ;
341-
342- var active = menuOpts . active ,
343- i ;
344- if ( isVertical ) {
345- if ( scrollBox . _vbar ) {
346- var translateY = 0 ;
347- for ( i = 0 ; i < active ; i ++ ) {
348- translateY += menuOpts . heights [ i ] + constants . gapButton ;
349- }
350- translateY -= constants . gapButton ;
351-
352- scrollBox . setTranslate ( 0 , translateY ) ;
353- }
354- }
355- else {
356- if ( scrollBox . _hbar ) {
357- var translateX = 0 ;
358- for ( i = 0 ; i < active ; i ++ ) {
359- translateX += menuOpts . widths [ i ] + constants . gapButton ;
360- }
361- translateX -= constants . gapButton ;
362-
363- scrollBox . setTranslate ( translateX , 0 ) ;
364- }
365- }
366- }
367-
368- function setActive ( gd , menuOpts , buttonOpts , gHeader , gButton , buttonIndex , isSilentUpdate ) {
369- // update 'active' attribute in menuOpts
370- menuOpts . _input . active = menuOpts . active = buttonIndex ;
371-
372- if ( menuOpts . type === 'dropdown' ) {
373- // fold up buttons and redraw header
374- gButton . attr ( constants . menuIndexAttrName , '-1' ) ;
375-
376- drawHeader ( gd , gHeader , gButton , menuOpts ) ;
377- }
378-
379- if ( ! isSilentUpdate || menuOpts . type === 'buttons' ) {
380- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
381- }
382414}
383415
384416function drawItem ( item , menuOpts , itemOpts ) {
@@ -611,11 +643,6 @@ function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
611643
612644function removeAllButtons ( gButton ) {
613645 gButton . selectAll ( 'g.' + constants . dropdownButtonClassName ) . remove ( ) ;
614-
615- // remove scrollbox
616- gButton . selectAll ( 'rect.scrollbar-horizontal' ) . remove ( ) ;
617- gButton . selectAll ( 'rect.scrollbar-vertical' ) . remove ( ) ;
618- gButton . call ( Drawing . setClipUrl , null ) ;
619646}
620647
621648function clearPushMargins ( gd ) {
0 commit comments