@@ -18,6 +18,7 @@ var svgTextUtils = require('../../lib/svg_text_utils');
18
18
var anchorUtils = require ( '../legend/anchor_utils' ) ;
19
19
20
20
var constants = require ( './constants' ) ;
21
+ var ScrollBox = require ( './scrollbox' ) ;
21
22
22
23
module . exports = function draw ( gd ) {
23
24
var fullLayout = gd . _fullLayout ,
@@ -82,16 +83,23 @@ module.exports = function draw(gd) {
82
83
. classed ( constants . dropdownButtonGroupClassName , true )
83
84
. style ( 'pointer-events' , 'all' ) ;
84
85
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
88
97
if ( headerGroups . enter ( ) . size ( ) ) {
89
98
gButton
90
99
. call ( removeAllButtons )
91
100
. attr ( constants . menuIndexAttrName , '-1' ) ;
92
101
}
93
102
94
- // remove exiting header, remove dropped buttons and reset margins
95
103
headerGroups . exit ( ) . each ( function ( menuOpts ) {
96
104
d3 . select ( this ) . remove ( ) ;
97
105
@@ -102,30 +110,24 @@ module.exports = function draw(gd) {
102
110
Plots . autoMargin ( gd , constants . autoMarginIdRoot + menuOpts . _index ) ;
103
111
} ) ;
104
112
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
-
111
113
// draw headers!
112
114
headerGroups . each ( function ( menuOpts ) {
113
115
var gHeader = d3 . select ( this ) ;
114
116
115
117
var _gButton = menuOpts . type === 'dropdown' ? gButton : null ;
116
118
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 ) ;
118
120
} ) ;
119
121
120
122
if ( menuOpts . type === 'dropdown' ) {
121
- drawHeader ( gd , gHeader , gButton , menuOpts ) ;
123
+ drawHeader ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
122
124
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 ) ;
126
128
}
127
129
} else {
128
- drawButtons ( gd , gHeader , null , menuOpts ) ;
130
+ drawButtons ( gd , gHeader , null , null , menuOpts ) ;
129
131
}
130
132
131
133
} ) ;
@@ -150,18 +152,40 @@ function makeMenuData(fullLayout) {
150
152
151
153
// Note that '_index' is set at the default step,
152
154
// 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 ;
156
159
}
157
160
158
- function areMenuButtonsDropped ( gButton , menuOpts ) {
159
- var droppedIndex = + gButton . attr ( constants . menuIndexAttrName ) ;
161
+ function isFolded ( gButton ) {
162
+ return + gButton . attr ( constants . menuIndexAttrName ) === - 1 ;
163
+ }
160
164
161
- return droppedIndex === menuOpts . _index ;
165
+ function isActive ( gButton , menuOpts ) {
166
+ return + gButton . attr ( constants . menuIndexAttrName ) === menuOpts . _index ;
162
167
}
163
168
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 ) {
165
189
var header = gHeader . selectAll ( 'g.' + constants . headerClassName )
166
190
. data ( [ 0 ] ) ;
167
191
@@ -200,14 +224,17 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
200
224
header . on ( 'click' , function ( ) {
201
225
gButton . call ( removeAllButtons ) ;
202
226
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
205
230
gButton . attr (
206
231
constants . menuIndexAttrName ,
207
- areMenuButtonsDropped ( gButton , menuOpts ) ? '-1' : String ( menuOpts . _index )
232
+ isActive ( gButton , menuOpts ) ?
233
+ - 1 :
234
+ String ( menuOpts . _index )
208
235
) ;
209
236
210
- drawButtons ( gd , gHeader , gButton , menuOpts ) ;
237
+ drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) ;
211
238
} ) ;
212
239
213
240
header . on ( 'mouseover' , function ( ) {
@@ -222,7 +249,7 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
222
249
Drawing . setTranslate ( gHeader , menuOpts . lx , menuOpts . ly ) ;
223
250
}
224
251
225
- function drawButtons ( gd , gHeader , gButton , menuOpts ) {
252
+ function drawButtons ( gd , gHeader , gButton , scrollBox , menuOpts ) {
226
253
// If this is a set of buttons, set pointer events = all since we play
227
254
// some minor games with which container is which in order to simplify
228
255
// the drawing of *either* buttons or menus
@@ -231,7 +258,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
231
258
gButton . attr ( 'pointer-events' , 'all' ) ;
232
259
}
233
260
234
- var buttonData = ( gButton . attr ( constants . menuIndexAttrName ) !== '-1' || menuOpts . type === 'buttons' ) ?
261
+ var buttonData = ( ! isFolded ( gButton ) || menuOpts . type === 'buttons' ) ?
235
262
menuOpts . buttons :
236
263
[ ] ;
237
264
@@ -257,7 +284,6 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
257
284
exit . remove ( ) ;
258
285
}
259
286
260
-
261
287
var x0 = 0 ;
262
288
var y0 = 0 ;
263
289
@@ -280,13 +306,18 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
280
306
}
281
307
282
308
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 ,
285
311
yPad : constants . gapButton ,
286
312
xPad : constants . gapButton ,
287
313
index : 0 ,
288
314
} ;
289
315
316
+ var scrollBoxPosition = {
317
+ l : posOpts . x + menuOpts . borderwidth ,
318
+ t : posOpts . y + menuOpts . borderwidth
319
+ } ;
320
+
290
321
buttons . each ( function ( buttonOpts , buttonIndex ) {
291
322
var button = d3 . select ( this ) ;
292
323
@@ -295,7 +326,10 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
295
326
. call ( setItemPosition , menuOpts , posOpts ) ;
296
327
297
328
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 ) ;
299
333
300
334
Plots . executeAPICommand ( gd , buttonOpts . method , buttonOpts . args ) ;
301
335
@@ -314,23 +348,87 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
314
348
315
349
buttons . call ( styleButtons , menuOpts ) ;
316
350
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
+ }
319
370
}
320
371
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' ) ;
324
376
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
+ }
328
392
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' ) ;
330
400
}
331
401
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
+ } ) ;
334
432
}
335
433
}
336
434
0 commit comments