diff --git a/src/components/modebar/attributes.js b/src/components/modebar/attributes.js new file mode 100644 index 00000000000..06435784c42 --- /dev/null +++ b/src/components/modebar/attributes.js @@ -0,0 +1,63 @@ +'use strict'; + +var constants = require('./constants'); + +module.exports = { + editType: 'modebar', + + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'h', + editType: 'modebar', + description: 'Sets the orientation of the modebar.' + }, + bgcolor: { + valType: 'color', + editType: 'modebar', + description: 'Sets the background color of the modebar.' + }, + color: { + valType: 'color', + editType: 'modebar', + description: 'Sets the color of the icons in the modebar.' + }, + activecolor: { + valType: 'color', + editType: 'modebar', + description: 'Sets the color of the active or hovered on icons in the modebar.' + }, + uirevision: { + valType: 'any', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes related to the modebar,', + 'including `hovermode`, `dragmode`, and `showspikes` at both the', + 'root level and inside subplots. Defaults to `layout.uirevision`.' + ].join(' ') + }, + add: { + valType: 'string', + arrayOk: true, + dflt: '', + editType: 'modebar', + description: [ + 'Determines which predefined modebar buttons to add.', + 'Please note that these buttons will only be shown if they are', + 'compatible with all trace types used in a graph.', + 'Similar to `config.modeBarButtonsToAdd` option.', + 'This may include *' + constants.backButtons.join('*, *') + '*.' + ].join(' ') + }, + remove: { + valType: 'string', + arrayOk: true, + dflt: '', + editType: 'modebar', + description: [ + 'Determines which predefined modebar buttons to remove.', + 'Similar to `config.modeBarButtonsToRemove` option.', + 'This may include *' + constants.foreButtons.join('*, *') + '*.' + ].join(' ') + } +}; diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 6558abe05c5..3428489b25b 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -92,6 +92,7 @@ modeBarButtons.editInChartStudio = { modeBarButtons.zoom2d = { name: 'zoom2d', + _cat: 'zoom', title: function(gd) { return _(gd, 'Zoom'); }, attr: 'dragmode', val: 'zoom', @@ -101,6 +102,7 @@ modeBarButtons.zoom2d = { modeBarButtons.pan2d = { name: 'pan2d', + _cat: 'pan', title: function(gd) { return _(gd, 'Pan'); }, attr: 'dragmode', val: 'pan', @@ -110,6 +112,7 @@ modeBarButtons.pan2d = { modeBarButtons.select2d = { name: 'select2d', + _cat: 'select', title: function(gd) { return _(gd, 'Box Select'); }, attr: 'dragmode', val: 'select', @@ -119,6 +122,7 @@ modeBarButtons.select2d = { modeBarButtons.lasso2d = { name: 'lasso2d', + _cat: 'lasso', title: function(gd) { return _(gd, 'Lasso Select'); }, attr: 'dragmode', val: 'lasso', @@ -180,6 +184,7 @@ modeBarButtons.eraseshape = { modeBarButtons.zoomIn2d = { name: 'zoomIn2d', + _cat: 'zoomin', title: function(gd) { return _(gd, 'Zoom in'); }, attr: 'zoom', val: 'in', @@ -189,6 +194,7 @@ modeBarButtons.zoomIn2d = { modeBarButtons.zoomOut2d = { name: 'zoomOut2d', + _cat: 'zoomout', title: function(gd) { return _(gd, 'Zoom out'); }, attr: 'zoom', val: 'out', @@ -198,6 +204,7 @@ modeBarButtons.zoomOut2d = { modeBarButtons.autoScale2d = { name: 'autoScale2d', + _cat: 'autoscale', title: function(gd) { return _(gd, 'Autoscale'); }, attr: 'zoom', val: 'auto', @@ -207,6 +214,7 @@ modeBarButtons.autoScale2d = { modeBarButtons.resetScale2d = { name: 'resetScale2d', + _cat: 'resetscale', title: function(gd) { return _(gd, 'Reset axes'); }, attr: 'zoom', val: 'reset', @@ -216,6 +224,7 @@ modeBarButtons.resetScale2d = { modeBarButtons.hoverClosestCartesian = { name: 'hoverClosestCartesian', + _cat: 'hoverclosest', title: function(gd) { return _(gd, 'Show closest data on hover'); }, attr: 'hovermode', val: 'closest', @@ -226,6 +235,7 @@ modeBarButtons.hoverClosestCartesian = { modeBarButtons.hoverCompareCartesian = { name: 'hoverCompareCartesian', + _cat: 'hoverCompare', title: function(gd) { return _(gd, 'Compare data on hover'); }, attr: 'hovermode', val: function(gd) { @@ -309,6 +319,7 @@ function handleCartesian(gd, ev) { modeBarButtons.zoom3d = { name: 'zoom3d', + _cat: 'zoom', title: function(gd) { return _(gd, 'Zoom'); }, attr: 'scene.dragmode', val: 'zoom', @@ -318,6 +329,7 @@ modeBarButtons.zoom3d = { modeBarButtons.pan3d = { name: 'pan3d', + _cat: 'pan', title: function(gd) { return _(gd, 'Pan'); }, attr: 'scene.dragmode', val: 'pan', @@ -365,6 +377,7 @@ function handleDrag3d(gd, ev) { modeBarButtons.resetCameraDefault3d = { name: 'resetCameraDefault3d', + _cat: 'resetCameraDefault', title: function(gd) { return _(gd, 'Reset camera to default'); }, attr: 'resetDefault', icon: Icons.home, @@ -373,6 +386,7 @@ modeBarButtons.resetCameraDefault3d = { modeBarButtons.resetCameraLastSave3d = { name: 'resetCameraLastSave3d', + _cat: 'resetCameraLastSave', title: function(gd) { return _(gd, 'Reset camera to last save'); }, attr: 'resetLastSave', icon: Icons.movie, @@ -422,6 +436,7 @@ function handleCamera3d(gd, ev) { modeBarButtons.hoverClosest3d = { name: 'hoverClosest3d', + _cat: 'hoverclosest', title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, attr: 'hovermode', val: null, @@ -476,6 +491,7 @@ function handleHover3d(gd, ev) { modeBarButtons.zoomInGeo = { name: 'zoomInGeo', + _cat: 'zoomin', title: function(gd) { return _(gd, 'Zoom in'); }, attr: 'zoom', val: 'in', @@ -485,6 +501,7 @@ modeBarButtons.zoomInGeo = { modeBarButtons.zoomOutGeo = { name: 'zoomOutGeo', + _cat: 'zoomout', title: function(gd) { return _(gd, 'Zoom out'); }, attr: 'zoom', val: 'out', @@ -494,6 +511,7 @@ modeBarButtons.zoomOutGeo = { modeBarButtons.resetGeo = { name: 'resetGeo', + _cat: 'reset', title: function(gd) { return _(gd, 'Reset'); }, attr: 'reset', val: null, @@ -503,6 +521,7 @@ modeBarButtons.resetGeo = { modeBarButtons.hoverClosestGeo = { name: 'hoverClosestGeo', + _cat: 'hoverclosest', title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, attr: 'hovermode', val: null, @@ -538,6 +557,7 @@ function handleGeo(gd, ev) { modeBarButtons.hoverClosestGl2d = { name: 'hoverClosestGl2d', + _cat: 'hoverclosest', title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, attr: 'hovermode', val: null, @@ -549,6 +569,7 @@ modeBarButtons.hoverClosestGl2d = { modeBarButtons.hoverClosestPie = { name: 'hoverClosestPie', + _cat: 'hoverclosest', title: function(gd) { return _(gd, 'Toggle show closest data on hover'); }, attr: 'hovermode', val: 'closest', @@ -661,6 +682,7 @@ function setSpikelineVisibility(gd) { modeBarButtons.resetViewMapbox = { name: 'resetViewMapbox', + _cat: 'resetView', title: function(gd) { return _(gd, 'Reset view'); }, attr: 'reset', icon: Icons.home, @@ -671,6 +693,7 @@ modeBarButtons.resetViewMapbox = { modeBarButtons.zoomInMapbox = { name: 'zoomInMapbox', + _cat: 'zoomin', title: function(gd) { return _(gd, 'Zoom in'); }, attr: 'zoom', val: 'in', @@ -680,6 +703,7 @@ modeBarButtons.zoomInMapbox = { modeBarButtons.zoomOutMapbox = { name: 'zoomOutMapbox', + _cat: 'zoomout', title: function(gd) { return _(gd, 'Zoom out'); }, attr: 'zoom', val: 'out', diff --git a/src/components/modebar/constants.js b/src/components/modebar/constants.js new file mode 100644 index 00000000000..788cbb5ae4e --- /dev/null +++ b/src/components/modebar/constants.js @@ -0,0 +1,41 @@ +'use strict'; + +var modeBarButtons = require('./buttons'); +var buttonList = Object.keys(modeBarButtons); + +var DRAW_MODES = [ + 'drawline', + 'drawopenpath', + 'drawclosedpath', + 'drawcircle', + 'drawrect', + 'eraseshape' +]; + +var backButtons = [ + 'v1hovermode', + 'hoverclosest', + 'hovercompare', + 'togglehover', + 'togglespikelines' +].concat(DRAW_MODES); + +var foreButtons = []; +var addToForeButtons = function(b) { + if(backButtons.indexOf(b._cat || b.name) !== -1) return; + // for convenience add lowercase shotname e.g. zoomin as well fullname zoomInGeo + var name = b.name; + var _cat = (b._cat || b.name).toLowerCase(); + if(foreButtons.indexOf(name) === -1) foreButtons.push(name); + if(foreButtons.indexOf(_cat) === -1) foreButtons.push(_cat); +}; +buttonList.forEach(function(k) { + addToForeButtons(modeBarButtons[k]); +}); +foreButtons.sort(); + +module.exports = { + DRAW_MODES: DRAW_MODES, + backButtons: backButtons, + foreButtons: foreButtons +}; diff --git a/src/components/modebar/defaults.js b/src/components/modebar/defaults.js new file mode 100644 index 00000000000..a9930fa3fb6 --- /dev/null +++ b/src/components/modebar/defaults.js @@ -0,0 +1,24 @@ +'use strict'; + +var Lib = require('../../lib'); +var Color = require('../color'); +var Template = require('../../plot_api/plot_template'); +var attributes = require('./attributes'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + var containerIn = layoutIn.modebar || {}; + var containerOut = Template.newContainer(layoutOut, 'modebar'); + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); + } + + coerce('orientation'); + coerce('bgcolor', Color.addOpacity(layoutOut.paper_bgcolor, 0.5)); + var defaultColor = Color.contrast(Color.rgb(layoutOut.modebar.bgcolor)); + coerce('color', Color.addOpacity(defaultColor, 0.3)); + coerce('activecolor', Color.addOpacity(defaultColor, 0.7)); + coerce('uirevision', layoutOut.uirevision); + coerce('add'); + coerce('remove'); +}; diff --git a/src/components/modebar/index.js b/src/components/modebar/index.js index 06a2d6dae7b..1bae66bdf3b 100644 --- a/src/components/modebar/index.js +++ b/src/components/modebar/index.js @@ -1,3 +1,11 @@ 'use strict'; -exports.manage = require('./manage'); +module.exports = { + moduleType: 'component', + name: 'modebar', + + layoutAttributes: require('./attributes'), + supplyLayoutDefaults: require('./defaults'), + + manage: require('./manage') +}; diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 1e4f9f8fc50..718a790f5e3 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -7,6 +7,7 @@ var isUnifiedHover = require('../fx/helpers').isUnifiedHover; var createModeBar = require('./modebar'); var modeBarButtons = require('./buttons'); +var DRAW_MODES = require('./constants').DRAW_MODES; /** * ModeBar wrapper around 'create' and 'update', @@ -58,22 +59,47 @@ module.exports = function manageModeBar(gd) { else fullLayout._modeBar = createModeBar(gd, buttonGroups); }; -var DRAW_MODES = [ - 'drawline', - 'drawopenpath', - 'drawclosedpath', - 'drawcircle', - 'drawrect', - 'eraseshape' -]; - // logic behind which buttons are displayed by default function getButtonGroups(gd) { var fullLayout = gd._fullLayout; var fullData = gd._fullData; var context = gd._context; - var buttonsToRemove = context.modeBarButtonsToRemove; - var buttonsToAdd = context.modeBarButtonsToAdd; + + function match(name, B) { + if(typeof B === 'string') { + if(B.toLowerCase() === name.toLowerCase()) return true; + } else { + var v0 = B.name; + var v1 = (B._cat || B.name); + + if(v0 === name || v1 === name.toLowerCase()) return true; + } + return false; + } + + var layoutAdd = fullLayout.modebar.add; + if(typeof layoutAdd === 'string') layoutAdd = [layoutAdd]; + + var layoutRemove = fullLayout.modebar.remove; + if(typeof layoutRemove === 'string') layoutRemove = [layoutRemove]; + + var buttonsToAdd = context.modeBarButtonsToAdd.concat( + layoutAdd.filter(function(e) { + for(var i = 0; i < context.modeBarButtonsToRemove.length; i++) { + if(match(e, context.modeBarButtonsToRemove[i])) return false; + } + return true; + }) + ); + + var buttonsToRemove = context.modeBarButtonsToRemove.concat( + layoutRemove.filter(function(e) { + for(var i = 0; i < context.modeBarButtonsToAdd.length; i++) { + if(match(e, context.modeBarButtonsToAdd[i])) return false; + } + return true; + }) + ); var hasCartesian = fullLayout._has('cartesian'); var hasGL3D = fullLayout._has('gl3d'); @@ -96,9 +122,20 @@ function getButtonGroups(gd) { var out = []; for(var i = 0; i < newGroup.length; i++) { - var button = newGroup[i]; - if(buttonsToRemove.indexOf(button) !== -1) continue; - out.push(modeBarButtons[button]); + var name = newGroup[i]; + var B = modeBarButtons[name]; + var v0 = B.name.toLowerCase(); + var v1 = (B._cat || B.name).toLowerCase(); + var found = false; + for(var q = 0; q < buttonsToRemove.length; q++) { + var t = buttonsToRemove[q].toLowerCase(); + if(t === v0 || t === v1) { + found = true; + break; + } + } + if(found) continue; + out.push(modeBarButtons[name]); } groups.push(out); diff --git a/src/core.js b/src/core.js index 42419221789..d9ea5c74115 100644 --- a/src/core.js +++ b/src/core.js @@ -44,7 +44,8 @@ register([ require('./components/grid'), require('./components/errorbars'), require('./components/colorscale'), - require('./components/colorbar') + require('./components/colorbar'), + require('./components/modebar') ]); // locales en and en-US are required for default behavior diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 545bac3845b..100503d9c21 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -388,40 +388,6 @@ module.exports = { 'make an item with matching `templateitemname` and `visible: false`.' ].join(' ') }, - modebar: { - orientation: { - valType: 'enumerated', - values: ['v', 'h'], - dflt: 'h', - editType: 'modebar', - description: 'Sets the orientation of the modebar.' - }, - bgcolor: { - valType: 'color', - editType: 'modebar', - description: 'Sets the background color of the modebar.' - }, - color: { - valType: 'color', - editType: 'modebar', - description: 'Sets the color of the icons in the modebar.' - }, - activecolor: { - valType: 'color', - editType: 'modebar', - description: 'Sets the color of the active or hovered on icons in the modebar.' - }, - uirevision: { - valType: 'any', - editType: 'none', - description: [ - 'Controls persistence of user-driven changes related to the modebar,', - 'including `hovermode`, `dragmode`, and `showspikes` at both the', - 'root level and inside subplots. Defaults to `layout.uirevision`.' - ].join(' ') - }, - editType: 'modebar' - }, newshape: drawNewShapeAttrs.newshape, activeshape: drawNewShapeAttrs.activeshape, diff --git a/src/plots/plots.js b/src/plots/plots.js index a3789026460..e350ccc75ef 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1517,12 +1517,10 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) { coerce('editrevision', uirevision); coerce('selectionrevision', uirevision); - coerce('modebar.orientation'); - coerce('modebar.bgcolor', Color.addOpacity(layoutOut.paper_bgcolor, 0.5)); - var modebarDefaultColor = Color.contrast(Color.rgb(layoutOut.modebar.bgcolor)); - coerce('modebar.color', Color.addOpacity(modebarDefaultColor, 0.3)); - coerce('modebar.activecolor', Color.addOpacity(modebarDefaultColor, 0.7)); - coerce('modebar.uirevision', uirevision); + Registry.getComponentMethod( + 'modebar', + 'supplyLayoutDefaults' + )(layoutIn, layoutOut); Registry.getComponentMethod( 'shapes', diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 5c0457d602a..d4f60d1147c 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -42,6 +42,8 @@ describe('ModeBar', function() { _has: Plots._hasPlotType, _subplots: {xaxis: xaxes || [], yaxis: yaxes || []}, modebar: { + add: '', + remove: '', orientation: 'h', bgcolor: 'rgba(255,255,255,0.7)', color: 'rgba(0, 31, 95, 0.3)', @@ -850,7 +852,7 @@ describe('ModeBar', function() { expect(countButtons(gd._fullLayout._modeBar)).toEqual(6); }); - it('updates mode bar buttons if modeBarButtonsToRemove changes', function() { + it('updates mode bar buttons if modeBarButtonsToRemove changes (exact camel case)', function() { var gd = setupGraphInfo(); manageModeBar(gd); var initialButtonCount = countButtons(gd._fullLayout._modeBar); @@ -862,6 +864,18 @@ describe('ModeBar', function() { .toEqual(initialButtonCount - 2); }); + it('updates mode bar buttons if modeBarButtonsToRemove changes (lowercase and short names)', function() { + var gd = setupGraphInfo(); + manageModeBar(gd); + var initialButtonCount = countButtons(gd._fullLayout._modeBar); + + gd._context.modeBarButtonsToRemove = ['toimage', 'zoom']; + manageModeBar(gd); + + expect(countButtons(gd._fullLayout._modeBar)) + .toEqual(initialButtonCount - 2); + }); + it('updates mode bar buttons if modeBarButtonsToAdd changes', function() { var gd = setupGraphInfo(); manageModeBar(gd); @@ -1434,7 +1448,7 @@ describe('ModeBar', function() { }); }); - describe('modebar styling', function() { + describe('modebar relayout', function() { var gd; var colors = ['rgba(128, 128, 128, 0.7)', 'rgba(255, 0, 128, 0.2)']; var targetBtn = 'pan2d'; @@ -1558,6 +1572,229 @@ describe('ModeBar', function() { }) .then(done, done.fail); }); + + it('add predefined shape drawing and hover buttons via layout.modebar.add', function(done) { + function countButtons() { + var modeBarEl = gd._fullLayout._modeBar.element; + return d3Select(modeBarEl).selectAll('a.modebar-btn').size(); + } + + var initial = 10; + Plotly.newPlot(gd, [{y: [1, 2]}], {}) + .then(function() { + expect(countButtons()).toBe(initial); + + return Plotly.relayout(gd, 'modebar.add', [ + 'drawline', + 'drawopenpath', + 'drawclosedpath', + 'drawcircle', + 'drawrect', + 'eraseshape' + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial + 6); + + return Plotly.relayout(gd, 'modebar.add', ''); + }) + .then(function() { + expect(countButtons()).toBe(initial); + + return Plotly.relayout(gd, 'modebar.add', [ + 'hovercompare', + 'hoverclosest', + 'togglespikelines' + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial + 3); + + return Plotly.relayout(gd, 'modebar.add', ''); + }) + .then(function() { + expect(countButtons()).toBe(initial); + + return Plotly.relayout(gd, 'modebar.add', [ + 'v1hovermode', + 'togglespikelines' + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial + 3); + + return Plotly.relayout(gd, 'modebar.add', [ + 'v1hovermode', + 'togglespikelines', + 'togglehover', + 'hovercompare', + 'hoverclosest', + 'eraseshape' + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial + 4, 'skip duplicates'); + + return Plotly.relayout(gd, 'modebar.add', [ + 'drawline', + 'invalid' + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial + 1, 'skip invalid'); + + return Plotly.relayout(gd, 'modebar.add', ''); + }) + .then(function() { + expect(countButtons()).toBe(initial); + }) + .then(done, done.fail); + }); + + it('remove buttons using exact (camel case) and short (lower case) names via layout.modebar.remove and template', function(done) { + function countButtons() { + var modeBarEl = gd._fullLayout._modeBar.element; + return d3Select(modeBarEl).selectAll('a.modebar-btn').size(); + } + + var initial = 10; + Plotly.newPlot(gd, [{y: [1, 2]}], {}) + .then(function() { + expect(countButtons()).toBe(initial); + + return Plotly.relayout(gd, 'modebar.remove', [ + 'zoom', + 'zoomin', + 'zoomout', + 'pan', + 'select', + 'lasso', + 'autoscale', + 'resetscale', + 'toimage', + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial - 9); + + return Plotly.relayout(gd, 'modebar.remove', ''); + }) + .then(function() { + expect(countButtons()).toBe(initial); + + return Plotly.relayout(gd, 'modebar.remove', [ + 'zoom2d', + 'zoomIn2d', + 'zoomOut2d', + 'pan2d', + 'select2d', + 'lasso2d', + 'autoScale2d', + 'resetScale2d', + 'toImage', + ]); + }) + .then(function() { + expect(countButtons()).toBe(initial - 9); + }) + .then(done, done.fail); + }); + + it('remove buttons using template', function(done) { + function countButtons() { + var modeBarEl = gd._fullLayout._modeBar.element; + return d3Select(modeBarEl).selectAll('a.modebar-btn').size(); + } + + var initial = 10; + Plotly.newPlot(gd, [{y: [1, 2]}], { + template: { + layout: { + modebar: { + remove: [ + 'zoom', + 'zoomin', + 'zoomout', + 'pan', + 'select', + 'lasso', + 'autoscale', + 'resetscale', + 'toimage', + ] + } + } + } + }) + .then(function() { + expect(countButtons()).toBe(initial - 9); + }) + .then(done, done.fail); + }); + + it('add buttons using template', function(done) { + function countButtons() { + var modeBarEl = gd._fullLayout._modeBar.element; + return d3Select(modeBarEl).selectAll('a.modebar-btn').size(); + } + + var initial = 10; + Plotly.newPlot(gd, [{y: [1, 2]}], { + template: { + layout: { + modebar: { + add: 'drawcircle' + } + } + } + }) + .then(function() { + expect(countButtons()).toBe(initial + 1); + }) + .then(done, done.fail); + }); + + ['zoom', 'zoomin', 'zoomOut'].forEach(function(t) { + it('add ' + t + ' button if removed by layout and added by config', function(done) { + function countButtons() { + var modeBarEl = gd._fullLayout._modeBar.element; + return d3Select(modeBarEl).selectAll('a.modebar-btn').size(); + } + + var initial = 10; + Plotly.newPlot(gd, [{y: [1, 2]}], { + modebar: { + remove: t + } + }, { + modeBarButtonsToAdd: [t] + }) + .then(function() { + expect(countButtons()).toBe(initial); + }) + .then(done, done.fail); + }); + }); + + it('remove button if added by layout and removed by config', function(done) { + function countButtons() { + var modeBarEl = gd._fullLayout._modeBar.element; + return d3Select(modeBarEl).selectAll('a.modebar-btn').size(); + } + + var initial = 10; + Plotly.newPlot(gd, [{y: [1, 2]}], { + modebar: { + add: 'drawline' + } + }, { + modeBarButtonsToRemove: ['drawline'] + }) + .then(function() { + expect(countButtons()).toBe(initial); + }) + .then(done, done.fail); + }); }); describe('modebar html', function() {