diff --git a/src/components/updatemenus/attributes.js b/src/components/updatemenus/attributes.js index 715f35ddd30..eb3a55a766b 100644 --- a/src/components/updatemenus/attributes.js +++ b/src/components/updatemenus/attributes.js @@ -57,6 +57,30 @@ module.exports = { ].join(' ') }, + type: { + valType: 'enumerated', + values: ['dropdown', 'buttons'], + dflt: 'dropdown', + role: 'info', + description: [ + 'Determines whether the buttons are accessible via a dropdown menu', + 'or whether the buttons are stacked horizontally or vertically' + ].join(' ') + }, + + direction: { + valType: 'enumerated', + values: ['left', 'right', 'up', 'down'], + dflt: 'down', + role: 'info', + description: [ + 'Determines the direction in which the buttons are laid out, whether', + 'in a dropdown menu or a row/column of buttons. For `left` and `up`,', + 'the buttons will still appear in left-to-right or top-to-bottom order', + 'respectively.' + ].join(' ') + }, + active: { valType: 'integer', role: 'info', @@ -68,6 +92,13 @@ module.exports = { ].join(' ') }, + showactive: { + valType: 'boolean', + role: 'info', + dflt: true, + description: 'Highlights active dropdown item or active button if true.' + }, + buttons: buttonsAttrs, x: { diff --git a/src/components/updatemenus/constants.js b/src/components/updatemenus/constants.js index d9bfa1a9a11..8b6cea3439b 100644 --- a/src/components/updatemenus/constants.js +++ b/src/components/updatemenus/constants.js @@ -21,7 +21,8 @@ module.exports = { headerGroupClassName: 'updatemenu-header-group', headerClassName: 'updatemenu-header', headerArrowClassName: 'updatemenu-header-arrow', - buttonGroupClassName: 'updatemenu-button-group', + dropdownButtonGroupClassName: 'updatemenu-dropdown-button-group', + dropdownButtonClassName: 'updatemenu-dropdown-button', buttonClassName: 'updatemenu-button', itemRectClassName: 'updatemenu-item-rect', itemTextClassName: 'updatemenu-item-text', @@ -41,7 +42,8 @@ module.exports = { minHeight: 30, // padding around item text - textPadX: 40, + textPadX: 24, + arrowPadX: 16, // font size to height scale fontSizeToHeight: 1.3, diff --git a/src/components/updatemenus/defaults.js b/src/components/updatemenus/defaults.js index 5574d5f5040..39f662c9003 100644 --- a/src/components/updatemenus/defaults.js +++ b/src/components/updatemenus/defaults.js @@ -49,6 +49,9 @@ function menuDefaults(menuIn, menuOut, layoutOut) { if(!visible) return; coerce('active'); + coerce('direction'); + coerce('type'); + coerce('showactive'); coerce('x'); coerce('y'); diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js index cbab8c534b6..8acf51820ca 100644 --- a/src/components/updatemenus/draw.js +++ b/src/components/updatemenus/draw.js @@ -77,12 +77,12 @@ module.exports = function draw(gd) { headerGroups.enter().append('g') .classed(constants.headerGroupClassName, true); - // draw button container - var gButton = menus.selectAll('g.' + constants.buttonGroupClassName) + // draw dropdown button container + var gButton = menus.selectAll('g.' + constants.dropdownButtonGroupClassName) .data([0]); gButton.enter().append('g') - .classed(constants.buttonGroupClassName, true) + .classed(constants.dropdownButtonGroupClassName, true) .style('pointer-events', 'all'); // whenever we add new menu, attach 'state' variable to node @@ -114,12 +114,18 @@ module.exports = function draw(gd) { // draw headers! headerGroups.each(function(menuOpts) { var gHeader = d3.select(this); - drawHeader(gd, gHeader, gButton, menuOpts); - // update buttons if they are dropped - if(areMenuButtonsDropped(gButton, menuOpts)) { - drawButtons(gd, gHeader, gButton, menuOpts); + if(menuOpts.type === 'dropdown') { + drawHeader(gd, gHeader, gButton, menuOpts); + + // update buttons if they are dropped + if(areMenuButtonsDropped(gButton, menuOpts)) { + drawButtons(gd, gHeader, gButton, menuOpts); + } + } else { + drawButtons(gd, gHeader, null, menuOpts); } + }); }; @@ -163,11 +169,15 @@ function drawHeader(gd, gHeader, gButton, menuOpts) { var active = menuOpts.active, headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts, - posOpts = { y: 0, yPad: 0 }; + posOpts = { y: 0, yPad: 0, x: 0, xPad: 0, index: 0 }, + positionOverrides = { + width: menuOpts.headerWidth, + height: menuOpts.headerHeight + }; header .call(drawItem, menuOpts, headerOpts) - .call(setItemPosition, menuOpts, posOpts); + .call(setItemPosition, menuOpts, posOpts, positionOverrides); // draw drop arrow at the right edge var arrow = gHeader.selectAll('text.' + constants.headerArrowClassName) @@ -181,8 +191,8 @@ function drawHeader(gd, gHeader, gButton, menuOpts) { .text('▼'); arrow.attr({ - x: menuOpts.width - constants.arrowOffsetX, - y: menuOpts.height1 / 2 + constants.textOffsetY + x: menuOpts.headerWidth - constants.arrowOffsetX, + y: menuOpts.headerHeight / 2 + constants.textOffsetY }); header.on('click', function() { @@ -211,27 +221,65 @@ function drawHeader(gd, gHeader, gButton, menuOpts) { } function drawButtons(gd, gHeader, gButton, menuOpts) { - var buttonData = gButton.attr(constants.menuIndexAttrName) !== '-1' ? + if(!gButton) { + gButton = gHeader; + gButton.attr('pointer-events', 'all'); + } + + var buttonData = (gButton.attr(constants.menuIndexAttrName) !== '-1' || menuOpts.type === 'buttons') ? menuOpts.buttons : []; - var buttons = gButton.selectAll('g.' + constants.buttonClassName) + var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName; + + var buttons = gButton.selectAll('g.' + klass) .data(buttonData); - buttons.enter().append('g') - .classed(constants.buttonClassName, true) - .attr('opacity', '0') - .transition() - .attr('opacity', '1'); + var enter = buttons.enter().append('g') + .classed(klass, true); + + var exit = buttons.exit(); - buttons.exit() - .transition() - .attr('opacity', '0') - .remove(); + if(menuOpts.type === 'dropdown') { + enter.attr('opacity', '0') + .transition() + .attr('opacity', '1'); + + exit.transition() + .attr('opacity', '0') + .remove(); + } else { + exit.remove(); + } + + + var x0 = 0; + var y0 = 0; + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; + + if(menuOpts.type === 'dropdown') { + if(isVertical) { + y0 = menuOpts.headerHeight + constants.gapButtonHeader; + } else { + x0 = menuOpts.headerWidth + constants.gapButtonHeader; + } + } + + if(menuOpts.type === 'dropdown' && menuOpts.direction === 'up') { + y0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openHeight; + } + + if(menuOpts.type === 'dropdown' && menuOpts.direction === 'left') { + x0 = -constants.gapButtonHeader + constants.gapButton - menuOpts.openWidth; + } var posOpts = { - y: menuOpts.height1 + constants.gapButtonHeader, - yPad: constants.gapButton + x: x0, + y: y0, + yPad: constants.gapButton, + xPad: constants.gapButton, + index: 0, }; buttons.each(function(buttonOpts, buttonIndex) { @@ -247,7 +295,11 @@ function drawButtons(gd, gHeader, gButton, menuOpts) { // fold up buttons and redraw header gButton.attr(constants.menuIndexAttrName, '-1'); - drawHeader(gd, gHeader, gButton, menuOpts); + + if(menuOpts.type === 'dropdown') { + drawHeader(gd, gHeader, gButton, menuOpts); + } + drawButtons(gd, gHeader, gButton, menuOpts); // call button method @@ -313,7 +365,7 @@ function styleButtons(buttons, menuOpts) { buttons.each(function(buttonOpts, i) { var button = d3.select(this); - if(i === active) { + if(i === active && menuOpts.showactive) { button.select('rect.' + constants.itemRectClassName) .call(Color.fill, constants.activeColor); } @@ -332,20 +384,27 @@ function styleOnMouseOut(item, menuOpts) { // find item dimensions (this mutates menuOpts) function findDimenstions(gd, menuOpts) { - menuOpts.width = 0; - menuOpts.height = 0; + menuOpts.width1 = 0; menuOpts.height1 = 0; + menuOpts.heights = []; + menuOpts.widths = []; + menuOpts.totalWidth = 0; + menuOpts.totalHeight = 0; + menuOpts.openWidth = 0; + menuOpts.openHeight = 0; menuOpts.lx = 0; menuOpts.ly = 0; - var fakeButtons = gd._tester.selectAll('g.' + constants.buttonClassName) + var fakeButtons = gd._tester.selectAll('g.' + constants.dropdownButtonClassName) .data(menuOpts.buttons); fakeButtons.enter().append('g') - .classed(constants.buttonClassName, true); + .classed(constants.dropdownButtonClassName, true); + + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; // loop over fake buttons to find width / height - fakeButtons.each(function(buttonOpts) { + fakeButtons.each(function(buttonOpts, i) { var button = d3.select(this); button.call(drawItem, menuOpts, buttonOpts); @@ -362,11 +421,51 @@ function findDimenstions(gd, menuOpts) { tLines = tspans[0].length || 1, hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY; - menuOpts.width = Math.max(menuOpts.width, wEff); + hEff = Math.ceil(hEff); + wEff = Math.ceil(wEff); + + // Store per-item sizes since a row of horizontal buttons, for example, + // don't all need to be the same width: + menuOpts.widths[i] = wEff; + menuOpts.heights[i] = hEff; + + // Height and width of individual element: menuOpts.height1 = Math.max(menuOpts.height1, hEff); - menuOpts.height += menuOpts.height1; + menuOpts.width1 = Math.max(menuOpts.width1, wEff); + + if(isVertical) { + menuOpts.totalWidth = Math.max(menuOpts.totalWidth, wEff); + menuOpts.openWidth = menuOpts.totalWidth; + menuOpts.totalHeight += hEff + constants.gapButton; + menuOpts.openHeight += hEff + constants.gapButton; + } else { + menuOpts.totalWidth += wEff + constants.gapButton; + menuOpts.openWidth += wEff + constants.gapButton; + menuOpts.totalHeight = Math.max(menuOpts.totalHeight, hEff); + menuOpts.openHeight = menuOpts.totalHeight; + } }); + if(isVertical) { + menuOpts.totalHeight -= constants.gapButton; + } else { + menuOpts.totalWidth -= constants.gapButton; + } + + + menuOpts.headerWidth = menuOpts.width1 + constants.arrowPadX; + menuOpts.headerHeight = menuOpts.height1; + + if(menuOpts.type === 'dropdown') { + if(isVertical) { + menuOpts.width1 += constants.arrowPadX; + menuOpts.totalHeight = menuOpts.height1; + } else { + menuOpts.totalWidth = menuOpts.width1; + } + menuOpts.totalWidth += constants.arrowPadX; + } + fakeButtons.remove(); var graphSize = gd._fullLayout._size; @@ -375,53 +474,57 @@ function findDimenstions(gd, menuOpts) { var xanchor = 'left'; if(anchorUtils.isRightAnchor(menuOpts)) { - menuOpts.lx -= menuOpts.width; + menuOpts.lx -= menuOpts.totalWidth; xanchor = 'right'; } if(anchorUtils.isCenterAnchor(menuOpts)) { - menuOpts.lx -= menuOpts.width / 2; + menuOpts.lx -= menuOpts.totalWidth / 2; xanchor = 'center'; } var yanchor = 'top'; if(anchorUtils.isBottomAnchor(menuOpts)) { - menuOpts.ly -= menuOpts.height; + menuOpts.ly -= menuOpts.totalHeight; yanchor = 'bottom'; } if(anchorUtils.isMiddleAnchor(menuOpts)) { - menuOpts.ly -= menuOpts.height / 2; + menuOpts.ly -= menuOpts.totalHeight / 2; yanchor = 'middle'; } - menuOpts.width = Math.ceil(menuOpts.width); - menuOpts.height = Math.ceil(menuOpts.height); + menuOpts.totalWidth = Math.ceil(menuOpts.totalWidth); + menuOpts.totalHeight = Math.ceil(menuOpts.totalHeight); menuOpts.lx = Math.round(menuOpts.lx); menuOpts.ly = Math.round(menuOpts.ly); Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, { x: menuOpts.x, y: menuOpts.y, - l: menuOpts.width * ({right: 1, center: 0.5}[xanchor] || 0), - r: menuOpts.width * ({left: 1, center: 0.5}[xanchor] || 0), - b: menuOpts.height * ({top: 1, middle: 0.5}[yanchor] || 0), - t: menuOpts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0) + l: menuOpts.totalWidth * ({right: 1, center: 0.5}[xanchor] || 0), + r: menuOpts.totalWidth * ({left: 1, center: 0.5}[xanchor] || 0), + b: menuOpts.totalHeight * ({top: 1, middle: 0.5}[yanchor] || 0), + t: menuOpts.totalHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0) }); } // set item positions (mutates posOpts) -function setItemPosition(item, menuOpts, posOpts) { +function setItemPosition(item, menuOpts, posOpts, overrideOpts) { + overrideOpts = overrideOpts || {}; var rect = item.select('.' + constants.itemRectClassName), text = item.select('.' + constants.itemTextClassName), tspans = text.selectAll('tspan'), - borderWidth = menuOpts.borderwidth; + borderWidth = menuOpts.borderwidth, + index = posOpts.index; + + Lib.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y); - Lib.setTranslate(item, borderWidth, borderWidth + posOpts.y); + var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; rect.attr({ x: 0, y: 0, - width: menuOpts.width, - height: menuOpts.height1 + width: overrideOpts.width || (isVertical ? menuOpts.width1 : menuOpts.widths[index]), + height: overrideOpts.height || (isVertical ? menuOpts.heights[index] : menuOpts.height1) }); var tHeight = menuOpts.font.size * constants.fontSizeToHeight, @@ -430,17 +533,23 @@ function setItemPosition(item, menuOpts, posOpts) { var textAttrs = { x: constants.textOffsetX, - y: menuOpts.height1 / 2 - spanOffset + constants.textOffsetY + y: menuOpts.heights[index] / 2 - spanOffset + constants.textOffsetY }; text.attr(textAttrs); tspans.attr(textAttrs); - posOpts.y += menuOpts.height1 + posOpts.yPad; + if(isVertical) { + posOpts.y += menuOpts.heights[index] + posOpts.yPad; + } else { + posOpts.x += menuOpts.widths[index] + posOpts.xPad; + } + + posOpts.index++; } function removeAllButtons(gButton) { - gButton.selectAll('g.' + constants.buttonClassName).remove(); + gButton.selectAll('g.' + constants.dropdownButtonClassName).remove(); } function clearPushMargins(gd) { diff --git a/test/image/baselines/updatemenus.png b/test/image/baselines/updatemenus.png index 1dc3c383b2c..81809e1739d 100644 Binary files a/test/image/baselines/updatemenus.png and b/test/image/baselines/updatemenus.png differ diff --git a/test/image/baselines/updatemenus_positioning.png b/test/image/baselines/updatemenus_positioning.png new file mode 100644 index 00000000000..4931a807f68 Binary files /dev/null and b/test/image/baselines/updatemenus_positioning.png differ diff --git a/test/image/mocks/updatemenus_positioning.json b/test/image/mocks/updatemenus_positioning.json new file mode 100644 index 00000000000..ecff0b0c906 --- /dev/null +++ b/test/image/mocks/updatemenus_positioning.json @@ -0,0 +1,202 @@ +{ + "data": [ + { + "y": [ + 0.8894873976401985, + 0.014899350293371638, + 0.6973412552835649, + 0.4322110719369108, + 0.6435204579331169, + 0.7913368293479852, + 0.45608188941724737, + 0.8773020090286707, + 0.7857950507320299, + 0.1565801184767599 + ], + "line": { + "shape": "spline", + "color": "blue" + }, + "name": "Data set 0" + } + ], + "layout": { + "updatemenus": [ + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.3, + "y": 1.0, + "yanchor": "top", + "xanchor": "left" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.3, + "y": 0.66, + "yanchor": "top", + "xanchor": "left", + "direction": "right" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.3, + "y": 0.33, + "yanchor": "top", + "xanchor": "left", + "direction": "left" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.3, + "y": 0.0, + "yanchor": "top", + "xanchor": "left", + "direction": "up" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "type": "buttons", + "x": -0.12, + "y": 1.0, + "yanchor": "top", + "xanchor": "left", + "showactive": false + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "type": "buttons", + "x": -0.12, + "y": 1.01, + "yanchor": "bottom", + "xanchor": "left", + "direction": "right", + "showactive": false + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + ], + "type": "buttons", + "x": 1, + "y": 0.03, + "yanchor": "bottom", + "xanchor": "right" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + ], + "type": "buttons", + "x": 1, + "y": 0.02, + "yanchor": "top", + "xanchor": "right", + "direction": "right" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.6, + "y": 0.9, + "yanchor": "top", + "xanchor": "right" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "green", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "yellow", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "orange", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.6, + "y": 0.9, + "yanchor": "bottom", + "xanchor": "left" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + ], + "x": 0.6, + "y": 0.4, + "type": "buttons", + "yanchor": "top", + "xanchor": "right" + }, + { + "buttons": [ + {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + ], + "type": "buttons", + "x": 0.6, + "y": 0.4, + "yanchor": "bottom", + "xanchor": "left" + } + ], + "xaxis": { + "range": [ + -0.5484683986041102, + 9.54846839860411 + ], + "autorange": true + }, + "yaxis": { + "type": "linear", + "range": [ + 0.14689768330153566, + 0.9369323077136202 + ], + "autorange": true + }, + "height": 450, + "width": 1100, + "autosize": true + } +} diff --git a/test/jasmine/tests/updatemenus_test.js b/test/jasmine/tests/updatemenus_test.js index 36fe6366325..e155bf27eff 100644 --- a/test/jasmine/tests/updatemenus_test.js +++ b/test/jasmine/tests/updatemenus_test.js @@ -147,6 +147,81 @@ describe('update menus defaults', function() { expect(layoutOut.updatemenus[0].bgcolor).toEqual('blue'); expect(layoutOut.updatemenus[1].bgcolor).toEqual('red'); }); + + it('should default \'type\' to \'dropdown\'', function() { + layoutIn.updatemenus = [{ + buttons: [{method: 'relayout', args: ['title', 'Hello World']}] + }]; + + supply(layoutIn, layoutOut); + + expect(layoutOut.updatemenus[0].type).toEqual('dropdown'); + }); + + it('should default \'direction\' to \'down\'', function() { + layoutIn.updatemenus = [{ + buttons: [{method: 'relayout', args: ['title', 'Hello World']}] + }]; + + supply(layoutIn, layoutOut); + + expect(layoutOut.updatemenus[0].direction).toEqual('down'); + }); + + it('should default \'showactive\' to true', function() { + layoutIn.updatemenus = [{ + buttons: [{method: 'relayout', args: ['title', 'Hello World']}] + }]; + + supply(layoutIn, layoutOut); + + expect(layoutOut.updatemenus[0].showactive).toEqual(true); + }); +}); + +describe('update menus buttons', function() { + var mock = require('@mocks/updatemenus_positioning.json'); + var gd; + var allMenus, buttonMenus, dropdownMenus; + + beforeEach(function(done) { + gd = createGraphDiv(); + + // move update menu #2 to click on them separately + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.layout.updatemenus[1].x = 1; + + allMenus = mockCopy.layout.updatemenus; + buttonMenus = allMenus.filter(function(opts) {return opts.type === 'buttons';}); + dropdownMenus = allMenus.filter(function(opts) {return opts.type !== 'buttons';}); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + it('creates button menus', function(done) { + assertNodeCount('.' + constants.containerClassName, 1); + + // 12 menus, but button menus don't have headers, so there are only six headers: + assertNodeCount('.' + constants.headerClassName, dropdownMenus.length); + + // Count the *total* number of buttons we expect for this mock: + var buttonCount = 0; + buttonMenus.forEach(function(menu) {buttonCount += menu.buttons.length;}); + + assertNodeCount('.' + constants.buttonClassName, buttonCount); + + done(); + + }); + + function assertNodeCount(query, cnt) { + expect(d3.selectAll(query).size()).toEqual(cnt); + } }); describe('update menus interactions', function() { @@ -339,7 +414,7 @@ describe('update menus interactions', function() { return Plotly.relayout(gd, 'updatemenus[1].buttons[1].label', 'a looooooooooooong
label'); }).then(function() { - assertItemDims(selectHeader(1), 179, 34.2); + assertItemDims(selectHeader(1), 179, 35); return click(selectHeader(1)); }).then(function() { @@ -381,21 +456,21 @@ describe('update menus interactions', function() { assertNodeCount('.' + constants.containerClassName, 1); assertNodeCount('.' + constants.headerClassName, expectedMenus.length); - var gButton = d3.select('.' + constants.buttonGroupClassName), + var gButton = d3.select('.' + constants.dropdownButtonGroupClassName), actualActiveIndex = +gButton.attr(constants.menuIndexAttrName), hasActive = false; expectedMenus.forEach(function(expected, i) { if(expected) { expect(actualActiveIndex).toEqual(i); - assertNodeCount('.' + constants.buttonClassName, expected); + assertNodeCount('.' + constants.dropdownButtonClassName, expected); hasActive = true; } }); if(!hasActive) { expect(actualActiveIndex).toEqual(-1); - assertNodeCount('.' + constants.buttonClassName, 0); + assertNodeCount('.' + constants.dropdownButtonClassName, 0); } } @@ -448,7 +523,7 @@ describe('update menus interactions', function() { } function selectButton(buttonIndex) { - var buttons = d3.selectAll('.' + constants.buttonClassName), + var buttons = d3.selectAll('.' + constants.dropdownButtonClassName), button = d3.select(buttons[0][buttonIndex]); return button; }