diff --git a/src/components/updatemenus/attributes.js b/src/components/updatemenus/attributes.js index eb3a55a766b..5efac53243b 100644 --- a/src/components/updatemenus/attributes.js +++ b/src/components/updatemenus/attributes.js @@ -11,6 +11,7 @@ var fontAttrs = require('../../plots/font_attributes'); var colorAttrs = require('../color/attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var padAttrs = require('../../plots/pad_attributes'); var buttonsAttrs = { _isLinkedToArray: true, @@ -140,6 +141,10 @@ module.exports = { ].join(' ') }, + pad: extendFlat({}, padAttrs, { + description: 'Sets the padding around the buttons or dropdown menu.' + }), + font: extendFlat({}, fontAttrs, { description: 'Sets the font of the update menu button text.' }), diff --git a/src/components/updatemenus/defaults.js b/src/components/updatemenus/defaults.js index 39f662c9003..f97fdd34297 100644 --- a/src/components/updatemenus/defaults.js +++ b/src/components/updatemenus/defaults.js @@ -60,6 +60,11 @@ function menuDefaults(menuIn, menuOut, layoutOut) { coerce('xanchor'); coerce('yanchor'); + coerce('pad.t'); + coerce('pad.r'); + coerce('pad.b'); + coerce('pad.l'); + Lib.coerceFont(coerce, 'font', layoutOut.font); coerce('bgcolor', layoutOut.paper_bgcolor); diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js index 8acf51820ca..afd4d809081 100644 --- a/src/components/updatemenus/draw.js +++ b/src/components/updatemenus/draw.js @@ -169,7 +169,7 @@ function drawHeader(gd, gHeader, gButton, menuOpts) { var active = menuOpts.active, headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts, - posOpts = { y: 0, yPad: 0, x: 0, xPad: 0, index: 0 }, + posOpts = { y: menuOpts.pad.t, yPad: 0, x: menuOpts.pad.l, xPad: 0, index: 0 }, positionOverrides = { width: menuOpts.headerWidth, height: menuOpts.headerHeight @@ -191,8 +191,8 @@ function drawHeader(gd, gHeader, gButton, menuOpts) { .text('▼'); arrow.attr({ - x: menuOpts.headerWidth - constants.arrowOffsetX, - y: menuOpts.headerHeight / 2 + constants.textOffsetY + x: menuOpts.headerWidth - constants.arrowOffsetX + menuOpts.pad.l, + y: menuOpts.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t }); header.on('click', function() { @@ -275,8 +275,8 @@ function drawButtons(gd, gHeader, gButton, menuOpts) { } var posOpts = { - x: x0, - y: y0, + x: x0 + menuOpts.pad.l, + y: y0 + menuOpts.pad.t, yPad: constants.gapButton, xPad: constants.gapButton, index: 0, @@ -468,27 +468,30 @@ function findDimenstions(gd, menuOpts) { fakeButtons.remove(); + var paddedWidth = menuOpts.totalWidth + menuOpts.pad.l + menuOpts.pad.r; + var paddedHeight = menuOpts.totalHeight + menuOpts.pad.t + menuOpts.pad.b; + var graphSize = gd._fullLayout._size; menuOpts.lx = graphSize.l + graphSize.w * menuOpts.x; menuOpts.ly = graphSize.t + graphSize.h * (1 - menuOpts.y); var xanchor = 'left'; if(anchorUtils.isRightAnchor(menuOpts)) { - menuOpts.lx -= menuOpts.totalWidth; + menuOpts.lx -= paddedWidth; xanchor = 'right'; } if(anchorUtils.isCenterAnchor(menuOpts)) { - menuOpts.lx -= menuOpts.totalWidth / 2; + menuOpts.lx -= paddedWidth / 2; xanchor = 'center'; } var yanchor = 'top'; if(anchorUtils.isBottomAnchor(menuOpts)) { - menuOpts.ly -= menuOpts.totalHeight; + menuOpts.ly -= paddedHeight; yanchor = 'bottom'; } if(anchorUtils.isMiddleAnchor(menuOpts)) { - menuOpts.ly -= menuOpts.totalHeight / 2; + menuOpts.ly -= paddedHeight / 2; yanchor = 'middle'; } @@ -500,10 +503,10 @@ function findDimenstions(gd, menuOpts) { Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, { x: menuOpts.x, y: menuOpts.y, - 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) + l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0), + r: paddedWidth * ({left: 1, center: 0.5}[xanchor] || 0), + b: paddedHeight * ({top: 1, middle: 0.5}[yanchor] || 0), + t: paddedHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0) }); } diff --git a/src/plots/pad_attributes.js b/src/plots/pad_attributes.js new file mode 100644 index 00000000000..bfadb4c54b0 --- /dev/null +++ b/src/plots/pad_attributes.js @@ -0,0 +1,36 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = { + t: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) along the top of the component.' + }, + r: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) on the right side of the component.' + }, + b: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) along the bottom of the component.' + }, + l: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'The amount of padding (in px) on the left side of the component.' + } +}; diff --git a/test/image/baselines/updatemenus_positioning.png b/test/image/baselines/updatemenus_positioning.png index 4931a807f68..27699ee4298 100644 Binary files a/test/image/baselines/updatemenus_positioning.png 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 index ecff0b0c906..77048b4b6d3 100644 --- a/test/image/mocks/updatemenus_positioning.json +++ b/test/image/mocks/updatemenus_positioning.json @@ -24,11 +24,11 @@ "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"]} + {"label": "A0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "A1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "A2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "A3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "A4", "method": "restyle", "args": ["marker.color", "orange"]} ], "x": 0.3, "y": 1.0, @@ -37,11 +37,11 @@ }, { "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"]} + {"label": "B1", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "B2", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "B3", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "B4", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "B5", "method": "restyle", "args": ["marker.color", "orange"]} ], "x": 0.3, "y": 0.66, @@ -51,11 +51,11 @@ }, { "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"]} + {"label": "C0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "C1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "C2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "C3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "C4", "method": "restyle", "args": ["marker.color", "orange"]} ], "x": 0.3, "y": 0.33, @@ -65,25 +65,28 @@ }, { "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"]} + {"label": "D0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "D1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "D2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "D3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "D4", "method": "restyle", "args": ["marker.color", "orange"]} ], "x": 0.3, "y": 0.0, "yanchor": "top", "xanchor": "left", - "direction": "up" + "direction": "up", + "pad": { + "t": 40 + } }, { "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"]} + {"label": "E0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "E1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "E2longgg", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "E3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "E4", "method": "restyle", "args": ["marker.color", "orange"]} ], "type": "buttons", "x": -0.12, @@ -94,11 +97,11 @@ }, { "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"]} + {"label": "F0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "F1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "F2longgg", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "F3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "F4", "method": "restyle", "args": ["marker.color", "orange"]} ], "type": "buttons", "x": -0.12, @@ -110,8 +113,8 @@ }, { "buttons": [ - {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, - {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + {"label": "G0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "G1", "method": "restyle", "args": ["marker.color", "blue"]} ], "type": "buttons", "x": 1, @@ -121,8 +124,8 @@ }, { "buttons": [ - {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, - {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + {"label": "H0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "H1", "method": "restyle", "args": ["marker.color", "blue"]} ], "type": "buttons", "x": 1, @@ -133,11 +136,11 @@ }, { "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"]} + {"label": "I0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "I1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "I2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "I3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "I4", "method": "restyle", "args": ["marker.color", "orange"]} ], "x": 0.6, "y": 0.9, @@ -146,11 +149,11 @@ }, { "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"]} + {"label": "J0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "J1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "J2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "J3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "J4", "method": "restyle", "args": ["marker.color", "orange"]} ], "x": 0.6, "y": 0.9, @@ -159,8 +162,42 @@ }, { "buttons": [ - {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, - {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + {"label": "N0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "N1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "N2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "N3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "N4", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.6, + "y": 0.9, + "yanchor": "bottom", + "xanchor": "right", + "pad": { + "b": 10, + "r": 20 + } + }, + { + "buttons": [ + {"label": "O0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "O1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "O2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "O3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "O4", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.6, + "y": 0.9, + "yanchor": "top", + "xanchor": "left", + "pad": { + "t": 10, + "l": 20 + } + }, + { + "buttons": [ + {"label": "K0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "K1", "method": "restyle", "args": ["marker.color", "blue"]} ], "x": 0.6, "y": 0.4, @@ -170,14 +207,82 @@ }, { "buttons": [ - {"label": "red", "method": "restyle", "args": ["marker.color", "red"]}, - {"label": "blue", "method": "restyle", "args": ["marker.color", "blue"]} + {"label": "P0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "P1", "method": "restyle", "args": ["marker.color", "blue"]} + ], + "type": "buttons", + "x": 0.6, + "y": 0.4, + "yanchor": "top", + "xanchor": "left", + "pad": { + "r": 50, + "b": 80 + } + }, + { + "buttons": [ + {"label": "Q0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "Q1", "method": "restyle", "args": ["marker.color", "blue"]} + ], + "type": "buttons", + "x": 0.6, + "y": 0.4, + "yanchor": "bottom", + "xanchor": "right", + "pad": { + "l": 50, + "t": 80 + } + }, + { + "buttons": [ + {"label": "L0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "L1", "method": "restyle", "args": ["marker.color", "blue"]} ], "type": "buttons", "x": 0.6, "y": 0.4, "yanchor": "bottom", "xanchor": "left" + }, + { + "buttons": [ + {"label": "M0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "M1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "M2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "M3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "M4", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 1.0, + "y": 0.5, + "yanchor": "middle", + "xanchor": "center", + "pad": { + "t": 80, + "r": 100, + "b": 80, + "l": 80 + } + }, + { + "buttons": [ + {"label": "R0", "method": "restyle", "args": ["marker.color", "red"]}, + {"label": "R1", "method": "restyle", "args": ["marker.color", "blue"]}, + {"label": "R2", "method": "restyle", "args": ["marker.color", "green"]}, + {"label": "R3", "method": "restyle", "args": ["marker.color", "yellow"]}, + {"label": "R4", "method": "restyle", "args": ["marker.color", "orange"]} + ], + "x": 0.9, + "y": 0.5, + "yanchor": "middle", + "xanchor": "center", + "pad": { + "t": 2, + "r": 50, + "b": 2, + "l": 40 + } } ], "xaxis": { diff --git a/test/jasmine/tests/updatemenus_test.js b/test/jasmine/tests/updatemenus_test.js index e155bf27eff..f3e1e95a06e 100644 --- a/test/jasmine/tests/updatemenus_test.js +++ b/test/jasmine/tests/updatemenus_test.js @@ -7,6 +7,7 @@ var Lib = require('@src/lib'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var TRANSITION_DELAY = 100; +var fail = require('../assets/fail_test'); describe('update menus defaults', function() { 'use strict'; @@ -446,6 +447,60 @@ describe('update menus interactions', function() { }); }); + it('applies padding on all sides', function(done) { + var xy1, xy2; + var firstMenu = d3.select('.' + constants.headerGroupClassName); + var xpad = 80; + var ypad = 60; + + // Position it center-anchored and in the middle of the plot: + Plotly.relayout(gd, { + 'updatemenus[0].x': 0.2, + 'updatemenus[0].y': 0.5, + 'updatemenus[0].xanchor': 'center', + 'updatemenus[0].yanchor': 'middle', + }).then(function() { + // Convert to xy: + xy1 = firstMenu.attr('transform').match(/translate\(([^,]*),\s*([^\)]*)\)/).slice(1).map(parseFloat); + + // Set three of four paddings. This should move it. + return Plotly.relayout(gd, { + 'updatemenus[0].pad.t': ypad, + 'updatemenus[0].pad.r': xpad, + 'updatemenus[0].pad.b': ypad, + 'updatemenus[0].pad.l': xpad, + }); + }).then(function() { + xy2 = firstMenu.attr('transform').match(/translate\(([^,]*),\s*([^\)]*)\)/).slice(1).map(parseFloat); + + expect(xy1[0] - xy2[0]).toEqual(xpad); + expect(xy1[1] - xy2[1]).toEqual(ypad); + }).catch(fail).then(done); + }); + + it('appliesy padding on relayout', function(done) { + var x1, x2; + var firstMenu = d3.select('.' + constants.headerGroupClassName); + var padShift = 40; + + // Position the menu in the center of the plot horizontal so that + // we can test padding updates without worrying about margin pushing. + Plotly.relayout(gd, { + 'updatemenus[0].x': 0.5, + 'updatemenus[0].pad.r': 0, + }).then(function() { + // Extract the x-component of the translation: + x1 = parseInt(firstMenu.attr('transform').match(/translate\(([^,]*).*/)[1]); + + return Plotly.relayout(gd, 'updatemenus[0].pad.r', 40); + }).then(function() { + // Extract the x-component of the translation: + x2 = parseInt(firstMenu.attr('transform').match(/translate\(([^,]*).*/)[1]); + + expect(x1 - x2).toBeCloseTo(padShift, 1); + }).catch(fail).then(done); + }); + function assertNodeCount(query, cnt) { expect(d3.selectAll(query).size()).toEqual(cnt); }