Skip to content

Commit 97e05c6

Browse files
committed
Add button type to updatemenus
1 parent 907b90e commit 97e05c6

File tree

4 files changed

+181
-42
lines changed

4 files changed

+181
-42
lines changed

src/components/updatemenus/attributes.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,37 @@ module.exports = {
5757
].join(' ')
5858
},
5959

60+
openreverse: {
61+
valType: 'boolean',
62+
role: 'info',
63+
dflt: false,
64+
description: [
65+
'For dropdown menus, opens the menu in the reverse direction'
66+
]
67+
},
68+
69+
type: {
70+
valType: 'enumerated',
71+
values: ['dropdown', 'buttons'],
72+
dflt: 'dropdown',
73+
role: 'info',
74+
description: [
75+
'Determines whether the buttons are accessible via a dropdown menu',
76+
'or whether the buttons are stacked horizontally or vertically'
77+
].join(' ')
78+
},
79+
80+
orientation: {
81+
valType: 'enumerated',
82+
values: ['h', 'v'],
83+
dflt: 'v',
84+
role: 'info',
85+
description: [
86+
'Determines whether the menu and buttons are laid out vertically',
87+
'or horizontally'
88+
]
89+
},
90+
6091
active: {
6192
valType: 'integer',
6293
role: 'info',

src/components/updatemenus/constants.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ module.exports = {
2121
headerGroupClassName: 'updatemenu-header-group',
2222
headerClassName: 'updatemenu-header',
2323
headerArrowClassName: 'updatemenu-header-arrow',
24-
buttonGroupClassName: 'updatemenu-button-group',
24+
dropdownButtonGroupClassName: 'updatemenu-button-group',
25+
dropdownButtonClassName: 'updatemenu-dropdown-button',
2526
buttonClassName: 'updatemenu-button',
2627
itemRectClassName: 'updatemenu-item-rect',
2728
itemTextClassName: 'updatemenu-item-text',
@@ -41,7 +42,8 @@ module.exports = {
4142
minHeight: 30,
4243

4344
// padding around item text
44-
textPadX: 40,
45+
textPadX: 24,
46+
arrowPadX: 16,
4547

4648
// font size to height scale
4749
fontSizeToHeight: 1.3,

src/components/updatemenus/defaults.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ function menuDefaults(menuIn, menuOut, layoutOut) {
4949
if(!visible) return;
5050

5151
coerce('active');
52+
coerce('orientation');
53+
coerce('type');
54+
coerce('openreverse');
5255

5356
coerce('x');
5457
coerce('y');

src/components/updatemenus/draw.js

Lines changed: 143 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ module.exports = function draw(gd) {
4343
* <g item header />
4444
* <text item header-arrow />
4545
* <g header-group />
46-
* <g item header />
46+
*
4747
* <text item header-arrow />
4848
* ...
4949
*
5050
* <g button-group />
51-
* <g item button />
51+
*
5252
* <g item button />
5353
* ...
5454
*/
@@ -77,12 +77,12 @@ module.exports = function draw(gd) {
7777
headerGroups.enter().append('g')
7878
.classed(constants.headerGroupClassName, true);
7979

80-
// draw button container
81-
var gButton = menus.selectAll('g.' + constants.buttonGroupClassName)
80+
// draw dropdown button container
81+
var gButton = menus.selectAll('g.' + constants.dropdownButtonGroupClassName)
8282
.data([0]);
8383

8484
gButton.enter().append('g')
85-
.classed(constants.buttonGroupClassName, true)
85+
.classed(constants.dropdownButtonGroupClassName, true)
8686
.style('pointer-events', 'all');
8787

8888
// whenever we add new menu, attach 'state' variable to node
@@ -114,12 +114,18 @@ module.exports = function draw(gd) {
114114
// draw headers!
115115
headerGroups.each(function(menuOpts) {
116116
var gHeader = d3.select(this);
117-
drawHeader(gd, gHeader, gButton, menuOpts);
118117

119-
// update buttons if they are dropped
120-
if(areMenuButtonsDropped(gButton, menuOpts)) {
121-
drawButtons(gd, gHeader, gButton, menuOpts);
118+
if(menuOpts.type === 'dropdown') {
119+
drawHeader(gd, gHeader, gButton, menuOpts);
120+
121+
// update buttons if they are dropped
122+
if(areMenuButtonsDropped(gButton, menuOpts)) {
123+
drawButtons(gd, gHeader, gButton, menuOpts);
124+
}
125+
} else {
126+
drawButtons(gd, gHeader, null, menuOpts);
122127
}
128+
123129
});
124130
};
125131

@@ -163,11 +169,15 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
163169

164170
var active = menuOpts.active,
165171
headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts,
166-
posOpts = { y: 0, yPad: 0 };
172+
posOpts = { y: 0, yPad: 0, x: 0, xPad: 0, index: 0 },
173+
positionOverrides = {
174+
width: menuOpts.headerWidth,
175+
height: menuOpts.headerHeight
176+
};
167177

168178
header
169179
.call(drawItem, menuOpts, headerOpts)
170-
.call(setItemPosition, menuOpts, posOpts);
180+
.call(setItemPosition, menuOpts, posOpts, positionOverrides);
171181

172182
// draw drop arrow at the right edge
173183
var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName)
@@ -181,8 +191,8 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
181191
.text('▼');
182192

183193
arrow.attr({
184-
x: menuOpts.width - constants.arrowOffsetX,
185-
y: menuOpts.height1 / 2 + constants.textOffsetY
194+
x: menuOpts.headerWidth - constants.arrowOffsetX,
195+
y: menuOpts.headerHeight / 2 + constants.textOffsetY
186196
});
187197

188198
header.on('click', function() {
@@ -211,27 +221,63 @@ function drawHeader(gd, gHeader, gButton, menuOpts) {
211221
}
212222

213223
function drawButtons(gd, gHeader, gButton, menuOpts) {
214-
var buttonData = gButton.attr(constants.menuIndexAttrName) !== '-1' ?
224+
if(!gButton) {
225+
gButton = gHeader;
226+
gButton.attr('pointer-events', 'all');
227+
}
228+
229+
var buttonData = (gButton.attr(constants.menuIndexAttrName) !== '-1' || menuOpts.type === 'buttons') ?
215230
menuOpts.buttons :
216231
[];
217232

218-
var buttons = gButton.selectAll('g.' + constants.buttonClassName)
233+
var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName;
234+
235+
var buttons = gButton.selectAll('g.' + klass)
219236
.data(buttonData);
220237

221-
buttons.enter().append('g')
222-
.classed(constants.buttonClassName, true)
223-
.attr('opacity', '0')
224-
.transition()
225-
.attr('opacity', '1');
238+
var enter = buttons.enter().append('g')
239+
.classed(klass, true);
240+
241+
var exit = buttons.exit();
242+
243+
if(menuOpts.type === 'dropdown') {
244+
enter.attr('opacity', '0')
245+
.transition()
246+
.attr('opacity', '1');
247+
248+
exit.transition()
249+
.attr('opacity', '0')
250+
.remove();
251+
} else {
252+
exit.remove();
253+
}
254+
226255

227-
buttons.exit()
228-
.transition()
229-
.attr('opacity', '0')
230-
.remove();
256+
var x0 = 0;
257+
var y0 = 0;
258+
259+
if(menuOpts.type === 'dropdown') {
260+
if(menuOpts.orientation === 'v') {
261+
y0 = menuOpts.headerHeight + constants.gapButtonHeader;
262+
} else {
263+
x0 = menuOpts.headerWidth + constants.gapButtonHeader;
264+
}
265+
}
266+
267+
if(menuOpts.type === 'dropdown' && menuOpts.openreverse) {
268+
if(menuOpts.orientation === 'v') {
269+
y0 = -2 * constants.gapButtonHeader - constants.gapButton - menuOpts.totalHeight;
270+
} else {
271+
x0 = -2 * constants.gapButtonHeader - constants.gapButton - menuOpts.totalWidth;
272+
}
273+
}
231274

232275
var posOpts = {
233-
y: menuOpts.height1 + constants.gapButtonHeader,
234-
yPad: constants.gapButton
276+
x: x0,
277+
y: y0,
278+
yPad: constants.gapButton,
279+
xPad: constants.gapButton,
280+
index: 0,
235281
};
236282

237283
buttons.each(function(buttonOpts, buttonIndex) {
@@ -247,7 +293,11 @@ function drawButtons(gd, gHeader, gButton, menuOpts) {
247293

248294
// fold up buttons and redraw header
249295
gButton.attr(constants.menuIndexAttrName, '-1');
250-
drawHeader(gd, gHeader, gButton, menuOpts);
296+
297+
if(menuOpts.type === 'dropdown') {
298+
drawHeader(gd, gHeader, gButton, menuOpts);
299+
}
300+
251301
drawButtons(gd, gHeader, gButton, menuOpts);
252302

253303
// call button method
@@ -332,20 +382,27 @@ function styleOnMouseOut(item, menuOpts) {
332382

333383
// find item dimensions (this mutates menuOpts)
334384
function findDimenstions(gd, menuOpts) {
385+
var i;
386+
335387
menuOpts.width = 0;
388+
menuOpts.width1 = 0;
336389
menuOpts.height = 0;
337390
menuOpts.height1 = 0;
391+
menuOpts.heights = [];
392+
menuOpts.widths = [];
393+
menuOpts.totalWidth = 0;
394+
menuOpts.totalHeight = 0;
338395
menuOpts.lx = 0;
339396
menuOpts.ly = 0;
340397

341-
var fakeButtons = gd._tester.selectAll('g.' + constants.buttonClassName)
398+
var fakeButtons = gd._tester.selectAll('g.' + constants.dropdownButtonClassName)
342399
.data(menuOpts.buttons);
343400

344401
fakeButtons.enter().append('g')
345-
.classed(constants.buttonClassName, true);
402+
.classed(constants.dropdownButtonClassName, true);
346403

347404
// loop over fake buttons to find width / height
348-
fakeButtons.each(function(buttonOpts) {
405+
fakeButtons.each(function(buttonOpts, i) {
349406
var button = d3.select(this);
350407

351408
button.call(drawItem, menuOpts, buttonOpts);
@@ -362,11 +419,49 @@ function findDimenstions(gd, menuOpts) {
362419
tLines = tspans[0].length || 1,
363420
hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY;
364421

365-
menuOpts.width = Math.max(menuOpts.width, wEff);
422+
// Store per-item sizes since a row of horizontal buttons, for example,
423+
// don't all need to be the same width:
424+
menuOpts.widths[i] = wEff;
425+
menuOpts.heights[i] = hEff;
426+
427+
if(menuOpts.orientation === 'v' | menuOpts.type === 'dropdown') {
428+
menuOpts.width = Math.max(menuOpts.width, wEff);
429+
} else {
430+
menuOpts.width += wEff;
431+
}
432+
// Height and width of individual element:
366433
menuOpts.height1 = Math.max(menuOpts.height1, hEff);
367-
menuOpts.height += menuOpts.height1;
434+
menuOpts.width1 = Math.max(menuOpts.width1, wEff);
435+
436+
if(menuOpts.orientation === 'v') {
437+
menuOpts.totalWidth = Math.max(menuOpts.totalWidth, wEff);
438+
menuOpts.totalHeight += hEff;
439+
} else {
440+
menuOpts.totalWidth += wEff;
441+
menuOpts.totalHeight += Math.max(menuOpts.totalWidth, hEff);
442+
}
368443
});
369444

445+
menuOpts.headerWidth = menuOpts.width1 + constants.arrowPadX;
446+
menuOpts.headerHeight = menuOpts.height1;
447+
448+
if(menuOpts.orientation === 'v') {
449+
menuOpts.width = menuOpts.width1;
450+
451+
if(menuOpts.type === 'dropdown') {
452+
menuOpts.width1 += constants.arrowPadX;
453+
}
454+
455+
for(i = 0; i < menuOpts.heights; i++) {
456+
menuOpts.height += menuOpts.heights[i];
457+
}
458+
} else {
459+
menuOpts.height = menuOpts.height1;
460+
for(i = 0; i < menuOpts.heights; i++) {
461+
menuOpts.width += menuOpts.width[i];
462+
}
463+
}
464+
370465
fakeButtons.remove();
371466

372467
var graphSize = gd._fullLayout._size;
@@ -409,19 +504,21 @@ function findDimenstions(gd, menuOpts) {
409504
}
410505

411506
// set item positions (mutates posOpts)
412-
function setItemPosition(item, menuOpts, posOpts) {
507+
function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
508+
overrideOpts = overrideOpts || {};
413509
var rect = item.select('.' + constants.itemRectClassName),
414510
text = item.select('.' + constants.itemTextClassName),
415511
tspans = text.selectAll('tspan'),
416-
borderWidth = menuOpts.borderwidth;
512+
borderWidth = menuOpts.borderwidth,
513+
index = posOpts.index;
417514

418-
Lib.setTranslate(item, borderWidth, borderWidth + posOpts.y);
515+
Lib.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y);
419516

420517
rect.attr({
421518
x: 0,
422519
y: 0,
423-
width: menuOpts.width,
424-
height: menuOpts.height1
520+
width: overrideOpts.width || (menuOpts.orientation === 'v' ? menuOpts.width1 : menuOpts.widths[index]),
521+
height: overrideOpts.height || (menuOpts.orientation === 'v' ? menuOpts.heights[index] : menuOpts.height1)
425522
});
426523

427524
var tHeight = menuOpts.font.size * constants.fontSizeToHeight,
@@ -430,17 +527,23 @@ function setItemPosition(item, menuOpts, posOpts) {
430527

431528
var textAttrs = {
432529
x: constants.textOffsetX,
433-
y: menuOpts.height1 / 2 - spanOffset + constants.textOffsetY
530+
y: menuOpts.heights[index] / 2 - spanOffset + constants.textOffsetY
434531
};
435532

436533
text.attr(textAttrs);
437534
tspans.attr(textAttrs);
438535

439-
posOpts.y += menuOpts.height1 + posOpts.yPad;
536+
if(menuOpts.orientation === 'v') {
537+
posOpts.y += menuOpts.heights[index] + posOpts.yPad;
538+
} else {
539+
posOpts.x += menuOpts.widths[index] + posOpts.xPad;
540+
}
541+
542+
posOpts.index++;
440543
}
441544

442545
function removeAllButtons(gButton) {
443-
gButton.selectAll('g.' + constants.buttonClassName).remove();
546+
gButton.selectAll('g.' + constants.dropdownButtonClassName).remove();
444547
}
445548

446549
function clearPushMargins(gd) {

0 commit comments

Comments
 (0)