Skip to content

Enable & disable predefined modebar buttons via layout and template #5660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 18, 2021
89 changes: 89 additions & 0 deletions src/components/modebar/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';

var modeBarButtons = require('./buttons');
var buttonList = Object.keys(modeBarButtons);
var backButtons = [
'v1hovermode',
'hoverclosest',
'hovercompare',
'togglehover',
'togglespikelines',
'drawclosedpath',
'drawopenpath',
'drawline',
'drawrect',
'drawcircle',
'eraseshape',
];

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 = {
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: 'flaglist',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a flaglist here is convenient for validation, but it's different from the API we use for config, an array. Here we don't need to support the object form, which obviously wouldn't fit in a flaglist, I just wonder if ease of validation is sufficient reason to differ. @nicolaskruchten thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer an array, like in config

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5c17d6b and 3293097.

flags: backButtons,
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'
].join(' ')
},
remove: {
valType: 'flaglist',
flags: foreButtons,
dflt: '',
editType: 'modebar',
description: [
'Determines which predefined modebar buttons to remove.',
'Similar to `config.modeBarButtonsToRemove` option'
].join(' ')
}
};
24 changes: 24 additions & 0 deletions src/components/modebar/buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ modeBarButtons.editInChartStudio = {

modeBarButtons.zoom2d = {
name: 'zoom2d',
_cat: 'zoom',
title: function(gd) { return _(gd, 'Zoom'); },
attr: 'dragmode',
val: 'zoom',
Expand All @@ -101,6 +102,7 @@ modeBarButtons.zoom2d = {

modeBarButtons.pan2d = {
name: 'pan2d',
_cat: 'pan',
title: function(gd) { return _(gd, 'Pan'); },
attr: 'dragmode',
val: 'pan',
Expand All @@ -110,6 +112,7 @@ modeBarButtons.pan2d = {

modeBarButtons.select2d = {
name: 'select2d',
_cat: 'select',
title: function(gd) { return _(gd, 'Box Select'); },
attr: 'dragmode',
val: 'select',
Expand All @@ -119,6 +122,7 @@ modeBarButtons.select2d = {

modeBarButtons.lasso2d = {
name: 'lasso2d',
_cat: 'lasso',
title: function(gd) { return _(gd, 'Lasso Select'); },
attr: 'dragmode',
val: 'lasso',
Expand Down Expand Up @@ -180,6 +184,7 @@ modeBarButtons.eraseshape = {

modeBarButtons.zoomIn2d = {
name: 'zoomIn2d',
_cat: 'zoomin',
title: function(gd) { return _(gd, 'Zoom in'); },
attr: 'zoom',
val: 'in',
Expand All @@ -189,6 +194,7 @@ modeBarButtons.zoomIn2d = {

modeBarButtons.zoomOut2d = {
name: 'zoomOut2d',
_cat: 'zoomout',
title: function(gd) { return _(gd, 'Zoom out'); },
attr: 'zoom',
val: 'out',
Expand All @@ -198,6 +204,7 @@ modeBarButtons.zoomOut2d = {

modeBarButtons.autoScale2d = {
name: 'autoScale2d',
_cat: 'autoscale',
title: function(gd) { return _(gd, 'Autoscale'); },
attr: 'zoom',
val: 'auto',
Expand All @@ -207,6 +214,7 @@ modeBarButtons.autoScale2d = {

modeBarButtons.resetScale2d = {
name: 'resetScale2d',
_cat: 'resetscale',
title: function(gd) { return _(gd, 'Reset axes'); },
attr: 'zoom',
val: 'reset',
Expand All @@ -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',
Expand All @@ -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) {
Expand Down Expand Up @@ -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',
Expand All @@ -318,6 +329,7 @@ modeBarButtons.zoom3d = {

modeBarButtons.pan3d = {
name: 'pan3d',
_cat: 'pan',
title: function(gd) { return _(gd, 'Pan'); },
attr: 'scene.dragmode',
val: 'pan',
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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',
Expand All @@ -485,6 +501,7 @@ modeBarButtons.zoomInGeo = {

modeBarButtons.zoomOutGeo = {
name: 'zoomOutGeo',
_cat: 'zoomout',
title: function(gd) { return _(gd, 'Zoom out'); },
attr: 'zoom',
val: 'out',
Expand All @@ -494,6 +511,7 @@ modeBarButtons.zoomOutGeo = {

modeBarButtons.resetGeo = {
name: 'resetGeo',
_cat: 'reset',
title: function(gd) { return _(gd, 'Reset'); },
attr: 'reset',
val: null,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -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,
Expand All @@ -671,6 +693,7 @@ modeBarButtons.resetViewMapbox = {

modeBarButtons.zoomInMapbox = {
name: 'zoomInMapbox',
_cat: 'zoomin',
title: function(gd) { return _(gd, 'Zoom in'); },
attr: 'zoom',
val: 'in',
Expand All @@ -680,6 +703,7 @@ modeBarButtons.zoomInMapbox = {

modeBarButtons.zoomOutMapbox = {
name: 'zoomOutMapbox',
_cat: 'zoomout',
title: function(gd) { return _(gd, 'Zoom out'); },
attr: 'zoom',
val: 'out',
Expand Down
24 changes: 24 additions & 0 deletions src/components/modebar/defaults.js
Original file line number Diff line number Diff line change
@@ -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');
};
10 changes: 9 additions & 1 deletion src/components/modebar/index.js
Original file line number Diff line number Diff line change
@@ -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')
};
23 changes: 18 additions & 5 deletions src/components/modebar/manage.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ function getButtonGroups(gd) {
var fullLayout = gd._fullLayout;
var fullData = gd._fullData;
var context = gd._context;
var buttonsToRemove = context.modeBarButtonsToRemove;
var buttonsToAdd = context.modeBarButtonsToAdd;
var buttonsToAdd = context.modeBarButtonsToAdd
.concat(fullLayout.modebar.add.split('+'));
var buttonsToRemove = context.modeBarButtonsToRemove
.concat(fullLayout.modebar.remove.split('+'));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to think if there are any cases we need to explicitly deal with precedence of config over layout. That's the way we decided it should work, right?

If config adds a button that layout has asked to remove, it can add it via the full object, but the string versions aren't defined in the add section below.

And if config removes a button that layout has asked to add, since removal happens first it won't be available yet when it's expected to be removed, so it will appear anyway.

So it seems like these concat need to filter out layout items that are in the opposite context entry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Addressed in 3daa2f3.


var hasCartesian = fullLayout._has('cartesian');
var hasGL3D = fullLayout._has('gl3d');
Expand All @@ -96,9 +98,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);
Expand Down
3 changes: 2 additions & 1 deletion src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading