diff --git a/src/lib/loggers.js b/src/lib/loggers.js
index c4a42af99d8..ad6e8f756a4 100644
--- a/src/lib/loggers.js
+++ b/src/lib/loggers.js
@@ -10,7 +10,7 @@
/* eslint-disable no-console */
-var config = require('../plot_api/plot_config');
+var dfltConfig = require('../plot_api/plot_config').dfltConfig;
var loggers = module.exports = {};
@@ -21,7 +21,7 @@ var loggers = module.exports = {};
*/
loggers.log = function() {
- if(config.logging > 1) {
+ if(dfltConfig.logging > 1) {
var messages = ['LOG:'];
for(var i = 0; i < arguments.length; i++) {
@@ -33,7 +33,7 @@ loggers.log = function() {
};
loggers.warn = function() {
- if(config.logging > 0) {
+ if(dfltConfig.logging > 0) {
var messages = ['WARN:'];
for(var i = 0; i < arguments.length; i++) {
@@ -45,7 +45,7 @@ loggers.warn = function() {
};
loggers.error = function() {
- if(config.logging > 0) {
+ if(dfltConfig.logging > 0) {
var messages = ['ERROR:'];
for(var i = 0; i < arguments.length; i++) {
diff --git a/src/lib/queue.js b/src/lib/queue.js
index b08b57c3509..eb94df6f67d 100644
--- a/src/lib/queue.js
+++ b/src/lib/queue.js
@@ -6,12 +6,10 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var Lib = require('../lib');
-var config = require('../plot_api/plot_config');
-
+var dfltConfig = require('../plot_api/plot_config').dfltConfig;
/**
* Copy arg array *without* removing `undefined` values from objects.
@@ -91,7 +89,7 @@ queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) {
queueObj.redo.args.push(redoArgs);
}
- if(gd.undoQueue.queue.length > config.queueLength) {
+ if(gd.undoQueue.queue.length > dfltConfig.queueLength) {
gd.undoQueue.queue.shift();
gd.undoQueue.index--;
}
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 4bcc6676fa5..d8948fcb50e 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -33,7 +33,7 @@ var initInteractions = require('../plots/cartesian/graph_interact').initInteract
var xmlnsNamespaces = require('../constants/xmlns_namespaces');
var svgTextUtils = require('../lib/svg_text_utils');
-var defaultConfig = require('./plot_config');
+var dfltConfig = require('./plot_config').dfltConfig;
var manageArrays = require('./manage_arrays');
var helpers = require('./helpers');
var subroutines = require('./subroutines');
@@ -405,7 +405,7 @@ function emitAfterPlot(gd) {
}
exports.setPlotConfig = function setPlotConfig(obj) {
- return Lib.extendFlat(defaultConfig, obj);
+ return Lib.extendFlat(dfltConfig, obj);
};
function setBackground(gd, bgColor) {
@@ -423,7 +423,7 @@ function opaqueSetBackground(gd, bgColor) {
function setPlotContext(gd, config) {
if(!gd._context) {
- gd._context = Lib.extendDeep({}, defaultConfig);
+ gd._context = Lib.extendDeep({}, dfltConfig);
// stash href, used to make robust clipPath URLs
var base = d3.select('base');
@@ -507,6 +507,25 @@ function setPlotContext(gd, config) {
// Check if gd has a specified widht/height to begin with
context._hasZeroHeight = context._hasZeroHeight || gd.clientHeight === 0;
context._hasZeroWidth = context._hasZeroWidth || gd.clientWidth === 0;
+
+ // fill context._scrollZoom helper to help manage scrollZoom flaglist
+ var szIn = context.scrollZoom;
+ var szOut = context._scrollZoom = {};
+ if(szIn === true) {
+ szOut.cartesian = 1;
+ szOut.gl3d = 1;
+ szOut.geo = 1;
+ szOut.mapbox = 1;
+ } else if(typeof szIn === 'string') {
+ var parts = szIn.split('+');
+ for(i = 0; i < parts.length; i++) {
+ szOut[parts[i]] = 1;
+ }
+ } else if(szIn !== false) {
+ szOut.gl3d = 1;
+ szOut.geo = 1;
+ szOut.mapbox = 1;
+ }
}
function plotLegacyPolar(gd, data, layout) {
diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js
index bf40a1c92b8..c78a955280f 100644
--- a/src/plot_api/plot_config.js
+++ b/src/plot_api/plot_config.js
@@ -14,212 +14,430 @@
*
* The defaults are the appropriate settings for plotly.js,
* so we get the right experience without any config argument.
+ *
+ * N.B. the config options are not coerced using Lib.coerce so keys
+ * like `valType` and `values` are only set for documentation purposes
+ * at the moment.
*/
-module.exports = {
-
- // no interactivity, for export or image generation
- staticPlot: false,
+var configAttributes = {
+ staticPlot: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Determines whether the graphs are interactive or not.',
+ 'If *false*, no interactivity, for export or image generation.'
+ ].join(' ')
+ },
- // base URL for the 'Edit in Chart Studio' (aka sendDataToCloud) mode bar button
- // and the showLink/sendData on-graph link
- plotlyServerURL: 'https://plot.ly',
+ plotlyServerURL: {
+ valType: 'string',
+ dflt: 'https://plot.ly',
+ description: [
+ 'Sets base URL for the \'Edit in Chart Studio\' (aka sendDataToCloud) mode bar button',
+ 'and the showLink/sendData on-graph link'
+ ].join(' ')
+ },
- /*
- * we can edit titles, move annotations, etc - sets all pieces of `edits`
- * unless a separate `edits` config item overrides individual parts
- */
- editable: false,
+ editable: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Determines whether the graph is editable or not.',
+ 'Sets all pieces of `edits`',
+ 'unless a separate `edits` config item overrides individual parts.'
+ ].join(' ')
+ },
edits: {
- /*
- * annotationPosition: the main anchor of the annotation, which is the
- * text (if no arrow) or the arrow (which drags the whole thing leaving
- * the arrow length & direction unchanged)
- */
- annotationPosition: false,
- // just for annotations with arrows, change the length and direction of the arrow
- annotationTail: false,
- annotationText: false,
- axisTitleText: false,
- colorbarPosition: false,
- colorbarTitleText: false,
- legendPosition: false,
- // edit the trace name fields from the legend
- legendText: false,
- shapePosition: false,
- // the global `layout.title`
- titleText: false
- },
-
- /*
- * DO autosize once regardless of layout.autosize
- * (use default width or height values otherwise)
- */
- autosizable: false,
-
- /*
- * responsive: determines whether to change the layout size when window is resized.
- * In v2, this option will be removed and will always be true.
- */
- responsive: false,
-
- // set the length of the undo/redo queue
- queueLength: 0,
-
- // if we DO autosize, do we fill the container or the screen?
- fillFrame: false,
-
- // if we DO autosize, set the frame margins in percents of plot size
- frameMargins: 0,
-
- // mousewheel or two-finger scroll zooms the plot
- scrollZoom: false,
-
- // double click interaction (false, 'reset', 'autosize' or 'reset+autosize')
- doubleClick: 'reset+autosize',
-
- // new users see some hints about interactivity
- showTips: true,
-
- // enable axis pan/zoom drag handles
- showAxisDragHandles: true,
-
- /*
- * enable direct range entry at the pan/zoom drag points
- * (drag handles must be enabled above)
- */
- showAxisRangeEntryBoxes: true,
-
- /*
- * Add a text link to open this plot in plotly?
- * This link shows up in the bottom right corner of the plot, and works
- * identically to the newer ModeBar button controlled by `showSendToCloud`
- * unless `sendData: false` is used.
- */
- showLink: false,
-
- /*
- * If we show a text link (`showLink: true`), does it contain data or just
- * a reference to a plotly cloud file? This option should only be used on
- * plot.ly or another plotly server, and is not supported by the newer
- * ModeBar button `showSendToCloud`.
- */
- sendData: true,
-
- /*
- * Should we include a ModeBar button, labeled "Edit in Chart Studio",
- * that sends this chart to plot.ly or another plotly server as specified
- * by `plotlyServerURL` for editing, export, etc? Prior to version 1.43.0
- * this button was included by default, now it is opt-in using this flag.
- *
- * Note that this button can (depending on `plotlyServerURL`) send your data
- * to an external server. However that server doesn't persist your data
- * until you arrive at the Chart Studio and explicitly click "Save".
- */
- showSendToCloud: false,
-
- // text appearing in the sendData link
- linkText: 'Edit chart',
-
- // false or function adding source(s) to linkText
- showSources: false,
-
- // display the mode bar (true, false, or 'hover')
- displayModeBar: 'hover',
-
- /*
- * remove mode bar button by name
- * (see ../components/modebar/buttons.js for the list of names)
- */
- modeBarButtonsToRemove: [],
-
- /*
- * add mode bar button using config objects
- * (see ./components/modebar/buttons.js for list of arguments)
- */
- modeBarButtonsToAdd: [],
-
- /*
- * fully custom mode bar buttons as nested array,
- * where the outer arrays represents button groups, and
- * the inner arrays have buttons config objects or names of default buttons
- * (see ../components/modebar/buttons.js for more info)
- */
- modeBarButtons: false,
-
- // statically override options for toImage modebar button
- // allowed keys are format, filename, width, height, scale
- // see ../components/modebar/buttons.js
- toImageButtonOptions: {},
-
- // add the plotly logo on the end of the mode bar
- displaylogo: true,
-
- // watermark the images with the company's logo
- watermark: false,
-
- // increase the pixel ratio for Gl plot images
- plotGlPixelRatio: 2,
-
- /*
- * background setting function
- * 'transparent' sets the background `layout.paper_color`
- * 'opaque' blends bg color with white ensuring an opaque background
- * or any other custom function of gd
- */
- setBackground: 'transparent',
-
- // URL to topojson files used in geo charts
- topojsonURL: 'https://cdn.plot.ly/',
-
- /*
- * Mapbox access token (required to plot mapbox trace types)
- * If using an Mapbox Atlas server, set this option to '',
- * so that plotly.js won't attempt to authenticate to the public Mapbox server.
- */
- mapboxAccessToken: null,
-
- /*
- * Turn all console logging on or off (errors will be thrown)
- * This should ONLY be set via Plotly.setPlotConfig
- * 0: no logs
- * 1: warnings and errors, but not informational messages
- * 2: verbose logs
- */
- logging: 1,
-
- /*
- * Set global transform to be applied to all traces with no
- * specification needed
- */
- globalTransforms: [],
-
- /*
- * Which localization should we use?
- * Should be a string like 'en' or 'en-US'.
- */
- locale: 'en-US',
-
- /*
- * Localization definitions
- * Locales can be provided either here (specific to one chart) or globally
- * by registering them as modules.
- * Should be an object of objects {locale: {dictionary: {...}, format: {...}}}
- * {
- * da: {
- * dictionary: {'Reset axes': 'Nulstil aksler', ...},
- * format: {months: [...], shortMonths: [...]}
- * },
- * ...
- * }
- * All parts are optional. When looking for translation or format fields, we
- * look first for an exact match in a config locale, then in a registered
- * module. If those fail, we strip off any regionalization ('en-US' -> 'en')
- * and try each (config, registry) again. The final fallback for translation
- * is untranslated (which is US English) and for formats is the base English
- * (the only consequence being the last fallback date format %x is DD/MM/YYYY
- * instead of MM/DD/YYYY). Currently `grouping` and `currency` are ignored
- * for our automatic number formatting, but can be used in custom formats.
- */
- locales: {}
+ annotationPosition: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Determines if the main anchor of the annotation is editable.',
+ 'The main anchor corresponds to the',
+ 'text (if no arrow) or the arrow (which drags the whole thing leaving',
+ 'the arrow length & direction unchanged).'
+ ].join(' ')
+ },
+ annotationTail: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Has only an effect for annotations with arrows.',
+ 'Enables changing the length and direction of the arrow.'
+ ].join(' ')
+ },
+ annotationText: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables editing annotation text.'
+ },
+ axisTitleText: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables editing axis title text.'
+ },
+ colorbarPosition: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables moving colorbars.'
+ },
+ colorbarTitleText: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables editing colorbar title text.'
+ },
+ legendPosition: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables moving the legend.'
+ },
+ legendText: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables editing the trace name fields from the legend'
+ },
+ shapePosition: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables moving shapes.'
+ },
+ titleText: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'Enables editing the global layout title.'
+ }
+ },
+
+ autosizable: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Determines whether the graphs are plotted with respect to',
+ 'layout.autosize:true and infer its container size.'
+ ].join(' ')
+ },
+ responsive: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Determines whether to change the layout size when window is resized.',
+ 'In v2, this option will be removed and will always be true.'
+ ].join(' ')
+ },
+ fillFrame: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'When `layout.autosize` is turned on, determines whether the graph',
+ 'fills the container (the default) or the screen (if set to *true*).'
+ ].join(' ')
+ },
+ frameMargins: {
+ valType: 'number',
+ dflt: 0,
+ min: 0,
+ max: 0.5,
+ description: [
+ 'When `layout.autosize` is turned on, set the frame margins',
+ 'in fraction of the graph size.'
+ ].join(' ')
+ },
+
+ scrollZoom: {
+ valType: 'flaglist',
+ flags: ['cartesian', 'gl3d', 'geo', 'mapbox'],
+ extras: [true, false],
+ dflt: 'gl3d+geo+mapbox',
+ description: [
+ 'Determines whether mouse wheel or two-finger scroll zooms is enable.',
+ 'Turned on by default for gl3d, geo and mapbox subplots',
+ '(as these subplot types do not have zoombox via pan),',
+ 'but turned off by default for cartesian subplots.',
+ 'Set `scrollZoom` to *false* to disable scrolling for all subplots.'
+ ].join(' ')
+ },
+ doubleClick: {
+ valType: 'enumerated',
+ values: [false, 'reset', 'autosize', 'reset+autosize'],
+ dflt: 'reset+autosize',
+ description: [
+ 'Sets the double click interaction mode.',
+ 'Has an effect only in cartesian plots.',
+ 'If *false*, double click is disable.',
+ 'If *reset*, double click resets the axis ranges to their initial values.',
+ 'If *autosize*, double click set the axis ranges to their autorange values.',
+ 'If *reset+autosize*, the odd double clicks resets the axis ranges',
+ 'to their initial values and even double clicks set the axis ranges',
+ 'to their autorange values.'
+ ].join(' ')
+ },
+
+ showAxisDragHandles: {
+ valType: 'boolean',
+ dflt: true,
+ description: [
+ 'Set to *false* to omit cartesian axis pan/zoom drag handles.'
+ ].join(' ')
+ },
+ showAxisRangeEntryBoxes: {
+ valType: 'boolean',
+ dflt: true,
+ description: [
+ 'Set to *false* to omit direct range entry at the pan/zoom drag points,',
+ 'note that `showAxisDragHandles` must be enabled to have an effect.'
+ ].join(' ')
+ },
+
+ showTips: {
+ valType: 'boolean',
+ dflt: true,
+ description: [
+ 'Determines whether or not tips are shown while interacting',
+ 'with the resulting graphs.'
+ ].join(' ')
+ },
+
+ showLink: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Determines whether a link to plot.ly is displayed',
+ 'at the bottom right corner of resulting graphs.',
+ 'Use with `sendData` and `linkText`.'
+ ].join(' ')
+ },
+ linkText: {
+ valType: 'string',
+ dflt: 'Edit chart',
+ noBlank: true,
+ description: [
+ 'Sets the text appearing in the `showLink` link.'
+ ].join(' ')
+ },
+ sendData: {
+ valType: 'boolean',
+ dflt: true,
+ description: [
+ 'If *showLink* is true, does it contain data',
+ 'just link to a plot.ly file?'
+ ].join(' ')
+ },
+ showSources: {
+ valType: 'any',
+ dflt: false,
+ description: [
+ 'Adds a source-displaying function to show sources on',
+ 'the resulting graphs.'
+ ].join(' ')
+ },
+
+ displayModeBar: {
+ valType: 'enumerated',
+ values: ['hover', true, false],
+ dflt: 'hover',
+ description: [
+ 'Determines the mode bar display mode.',
+ 'If *true*, the mode bar is always visible.',
+ 'If *false*, the mode bar is always hidden.',
+ 'If *hover*, the mode bar is visible while the mouse cursor',
+ 'is on the graph container.'
+ ].join(' ')
+ },
+ showSendToCloud: {
+ valType: 'boolean',
+ dflt: false,
+ description: [
+ 'Should we include a ModeBar button, labeled "Edit in Chart Studio",',
+ 'that sends this chart to plot.ly or another plotly server as specified',
+ 'by `plotlyServerURL` for editing, export, etc? Prior to version 1.43.0',
+ 'this button was included by default, now it is opt-in using this flag.',
+ 'Note that this button can (depending on `plotlyServerURL`) send your data',
+ 'to an external server. However that server does not persist your data',
+ 'until you arrive at the Chart Studio and explicitly click "Save".'
+ ].join(' ')
+ },
+ modeBarButtonsToRemove: {
+ valType: 'any',
+ dflt: [],
+ description: [
+ 'Remove mode bar buttons by name.',
+ 'See ./components/modebar/buttons.js for the list of names.'
+ ].join(' ')
+ },
+ modeBarButtonsToAdd: {
+ valType: 'any',
+ dflt: [],
+ description: [
+ 'Add mode bar button using config objects',
+ 'See ./components/modebar/buttons.js for list of arguments.'
+ ].join(' ')
+ },
+ modeBarButtons: {
+ valType: 'any',
+ dflt: false,
+ description: [
+ 'Define fully custom mode bar buttons as nested array,',
+ 'where the outer arrays represents button groups, and',
+ 'the inner arrays have buttons config objects or names of default buttons',
+ 'See ./components/modebar/buttons.js for more info.'
+ ].join(' ')
+ },
+ toImageButtonOptions: {
+ valType: 'any',
+ dflt: {},
+ description: [
+ 'Statically override options for toImage modebar button',
+ 'allowed keys are format, filename, width, height, scale',
+ 'see ../components/modebar/buttons.js'
+ ].join(' ')
+ },
+ displaylogo: {
+ valType: 'boolean',
+ dflt: true,
+ description: [
+ 'Determines whether or not the plotly logo is displayed',
+ 'on the end of the mode bar.'
+ ].join(' ')
+ },
+ watermark: {
+ valType: 'boolean',
+ dflt: false,
+ description: 'watermark the images with the company\'s logo'
+ },
+
+ plotGlPixelRatio: {
+ valType: 'number',
+ dflt: 2,
+ min: 1,
+ max: 4,
+ description: [
+ 'Set the pixel ratio during WebGL image export.',
+ 'This config option was formerly named `plot3dPixelRatio`',
+ 'which is now deprecated.'
+ ].join(' ')
+ },
+
+ setBackground: {
+ valType: 'any',
+ dflt: 'transparent',
+ description: [
+ 'Set function to add the background color (i.e. `layout.paper_color`)',
+ 'to a different container.',
+ 'This function take the graph div as first argument and the current background',
+ 'color as second argument.',
+ 'Alternatively, set to string *opaque* to ensure there is white behind it.'
+ ].join(' ')
+ },
+
+ topojsonURL: {
+ valType: 'string',
+ noBlank: true,
+ dflt: 'https://cdn.plot.ly/',
+ description: [
+ 'Set the URL to topojson used in geo charts.',
+ 'By default, the topojson files are fetched from cdn.plot.ly.',
+ 'For example, set this option to:',
+ '/dist/topojson/',
+ 'to render geographical feature using the topojson files',
+ 'that ship with the plotly.js module.'
+ ].join(' ')
+ },
+
+ mapboxAccessToken: {
+ valType: 'string',
+ dflt: null,
+ description: [
+ 'Mapbox access token (required to plot mapbox trace types)',
+ 'If using an Mapbox Atlas server, set this option to \'\'',
+ 'so that plotly.js won\'t attempt to authenticate to the public Mapbox server.'
+ ].join(' ')
+ },
+
+ logging: {
+ valType: 'boolean',
+ dflt: 1,
+ description: [
+ 'Turn all console logging on or off (errors will be thrown)',
+ 'This should ONLY be set via Plotly.setPlotConfig',
+ 'Available levels:',
+ '0: no logs',
+ '1: warnings and errors, but not informational messages',
+ '2: verbose logs'
+ ].join(' ')
+ },
+
+ queueLength: {
+ valType: 'integer',
+ min: 0,
+ dflt: 0,
+ description: 'Sets the length of the undo/redo queue.'
+ },
+
+ globalTransforms: {
+ valType: 'any',
+ dflt: [],
+ description: [
+ 'Set global transform to be applied to all traces with no',
+ 'specification needed'
+ ].join(' ')
+ },
+
+ locale: {
+ valType: 'string',
+ dflt: 'en-US',
+ description: [
+ 'Which localization should we use?',
+ 'Should be a string like \'en\' or \'en-US\'.'
+ ].join(' ')
+ },
+
+ locales: {
+ valType: 'any',
+ dflt: {},
+ description: [
+ 'Localization definitions',
+ 'Locales can be provided either here (specific to one chart) or globally',
+ 'by registering them as modules.',
+ 'Should be an object of objects {locale: {dictionary: {...}, format: {...}}}',
+ '{',
+ ' da: {',
+ ' dictionary: {\'Reset axes\': \'Nulstil aksler\', ...},',
+ ' format: {months: [...], shortMonths: [...]}',
+ ' },',
+ ' ...',
+ '}',
+ 'All parts are optional. When looking for translation or format fields, we',
+ 'look first for an exact match in a config locale, then in a registered',
+ 'module. If those fail, we strip off any regionalization (\'en-US\' -> \'en\')',
+ 'and try each (config, registry) again. The final fallback for translation',
+ 'is untranslated (which is US English) and for formats is the base English',
+ '(the only consequence being the last fallback date format %x is DD/MM/YYYY',
+ 'instead of MM/DD/YYYY). Currently `grouping` and `currency` are ignored',
+ 'for our automatic number formatting, but can be used in custom formats.'
+ ].join(' ')
+ }
+};
+
+var dfltConfig = {};
+
+function crawl(src, target) {
+ for(var k in src) {
+ var obj = src[k];
+ if(obj.valType) {
+ target[k] = obj.dflt;
+ } else {
+ if(!target[k]) {
+ target[k] = {};
+ }
+ crawl(obj, target[k]);
+ }
+ }
+}
+
+crawl(configAttributes, dfltConfig);
+
+module.exports = {
+ configAttributes: configAttributes,
+ dfltConfig: dfltConfig
};
diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js
index 39623ea315c..28da62b3009 100644
--- a/src/plot_api/plot_schema.js
+++ b/src/plot_api/plot_schema.js
@@ -16,6 +16,7 @@ var baseAttributes = require('../plots/attributes');
var baseLayoutAttributes = require('../plots/layout_attributes');
var frameAttributes = require('../plots/frame_attributes');
var animationAttributes = require('../plots/animation_attributes');
+var configAttributes = require('./plot_config').configAttributes;
// polar attributes are not part of the Registry yet
var polarAreaAttrs = require('../plots/polar/legacy/area_attributes');
@@ -47,7 +48,7 @@ exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS;
* - transforms
* - frames
* - animations
- * - config (coming soon ...)
+ * - config
*/
exports.get = function() {
var traces = {};
@@ -96,7 +97,9 @@ exports.get = function() {
transforms: transforms,
frames: getFramesAttributes(),
- animation: formatAttributes(animationAttributes)
+ animation: formatAttributes(animationAttributes),
+
+ config: formatAttributes(configAttributes)
};
};
diff --git a/src/plot_api/template_api.js b/src/plot_api/template_api.js
index a719cfd51f7..18fd5830009 100644
--- a/src/plot_api/template_api.js
+++ b/src/plot_api/template_api.js
@@ -15,7 +15,7 @@ var PlotSchema = require('./plot_schema');
var Plots = require('../plots/plots');
var plotAttributes = require('../plots/attributes');
var Template = require('./plot_template');
-var dfltConfig = require('./plot_config');
+var dfltConfig = require('./plot_config').dfltConfig;
/**
* Plotly.makeTemplate: create a template off an existing figure to reuse
diff --git a/src/plot_api/validate.js b/src/plot_api/validate.js
index 058134bb82e..123214bf4d0 100644
--- a/src/plot_api/validate.js
+++ b/src/plot_api/validate.js
@@ -11,7 +11,7 @@
var Lib = require('../lib');
var Plots = require('../plots/plots');
var PlotSchema = require('./plot_schema');
-var dfltConfig = require('./plot_config');
+var dfltConfig = require('./plot_config').dfltConfig;
var isPlainObject = Lib.isPlainObject;
var isArray = Array.isArray;
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index 77c6a36648f..21157d0636f 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -417,7 +417,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
// deactivate mousewheel scrolling on embedded graphs
// devs can override this with layout._enablescrollzoom,
// but _ ensures this setting won't leave their page
- if(!gd._context.scrollZoom && !gd._fullLayout._enablescrollzoom) {
+ if(!gd._context._scrollZoom.cartesian && !gd._fullLayout._enablescrollzoom) {
return;
}
diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js
index ce4fb293994..9a0961f1661 100644
--- a/src/plots/geo/geo.js
+++ b/src/plots/geo/geo.js
@@ -420,6 +420,9 @@ proto.updateFx = function(fullLayout, geoLayout) {
bgRect.node().onmousedown = null;
bgRect.call(createGeoZoom(_this, geoLayout));
bgRect.on('dblclick.zoom', zoomReset);
+ if(!gd._context._scrollZoom.geo) {
+ bgRect.on('wheel.zoom', null);
+ }
}
else if(dragMode === 'select' || dragMode === 'lasso') {
bgRect.on('.zoom', null);
diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js
index 0b8ea837ea8..789efdfe096 100644
--- a/src/plots/gl2d/scene2d.js
+++ b/src/plots/gl2d/scene2d.js
@@ -38,7 +38,7 @@ function Scene2D(options, fullLayout) {
this.pixelRatio = options.plotGlPixelRatio || window.devicePixelRatio;
this.id = options.id;
this.staticPlot = !!options.staticPlot;
- this.scrollZoom = this.graphDiv._context.scrollZoom;
+ this.scrollZoom = this.graphDiv._context._scrollZoom.cartesian;
this.fullData = null;
this.updateRefs(fullLayout);
diff --git a/src/plots/gl3d/camera.js b/src/plots/gl3d/camera.js
index 0331cd3f0b3..32e796a8760 100644
--- a/src/plots/gl3d/camera.js
+++ b/src/plots/gl3d/camera.js
@@ -48,6 +48,7 @@ function createCamera(element, options) {
var camera = {
keyBindingMode: 'rotate',
+ enableWheel: true,
view: view,
element: element,
delay: options.delay || 16,
@@ -257,7 +258,9 @@ function createCamera(element, options) {
}
camera.wheelListener = mouseWheel(element, function(dx, dy) {
+ // TODO remove now that we can disable scroll via scrollZoom?
if(camera.keyBindingMode === false) return;
+ if(!camera.enableWheel) return;
var flipX = camera.flipX ? 1 : -1;
var flipY = camera.flipY ? 1 : -1;
diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js
index bb3dea88c8c..a6bc7c91a56 100644
--- a/src/plots/gl3d/scene.js
+++ b/src/plots/gl3d/scene.js
@@ -227,8 +227,15 @@ function initializeGLPlot(scene, canvas, gl) {
scene.graphDiv.emit('plotly_relayout', update);
};
- scene.glplot.canvas.addEventListener('mouseup', relayoutCallback.bind(null, scene));
- scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene), passiveSupported ? {passive: false} : false);
+ scene.glplot.canvas.addEventListener('mouseup', function() {
+ relayoutCallback(scene);
+ });
+
+ scene.glplot.canvas.addEventListener('wheel', function() {
+ if(gd._context._scrollZoom.gl3d) {
+ relayoutCallback(scene);
+ }
+ }, passiveSupported ? {passive: false} : false);
if(!scene.staticMode) {
scene.glplot.canvas.addEventListener('webglcontextlost', function(event) {
@@ -385,7 +392,6 @@ function computeTraceBounds(scene, trace, bounds) {
}
proto.plot = function(sceneData, fullLayout, layout) {
-
// Save parameters
this.plotArgs = [sceneData, fullLayout, layout];
@@ -412,6 +418,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
// Update camera and camera mode
this.setCamera(fullSceneLayout.camera);
this.updateFx(fullSceneLayout.dragmode, fullSceneLayout.hovermode);
+ this.camera.enableWheel = this.graphDiv._context._scrollZoom.gl3d;
// Update scene
this.glplot.update({});
@@ -776,7 +783,6 @@ proto.updateFx = function(dragmode, hovermode) {
fullCamera.up = zUp;
Lib.nestedProperty(layout, attr).set(zUp);
} else {
-
// none rotation modes [pan or zoom]
camera.keyBindingMode = dragmode;
}
diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js
index 78509140a2c..51e8ea9372d 100644
--- a/src/plots/mapbox/mapbox.js
+++ b/src/plots/mapbox/mapbox.js
@@ -258,6 +258,12 @@ proto.updateMap = function(calcData, fullLayout, resolve, reject) {
self.updateLayout(fullLayout);
self.resolveOnRender(resolve);
}
+
+ if(this.gd._context._scrollZoom.mapbox) {
+ map.scrollZoom.enable();
+ } else {
+ map.scrollZoom.disable();
+ }
};
proto.updateData = function(calcData) {
diff --git a/test/jasmine/bundle_tests/plotschema_test.js b/test/jasmine/bundle_tests/plotschema_test.js
index 40b99b1a86a..8fb836176e9 100644
--- a/test/jasmine/bundle_tests/plotschema_test.js
+++ b/test/jasmine/bundle_tests/plotschema_test.js
@@ -328,6 +328,11 @@ describe('plot schema', function() {
expect(plotSchema.frames.items.frames_entry.role).toEqual('object');
});
+ it('should list config attributes', function() {
+ expect(plotSchema.config).toBeDefined();
+ expect(plotSchema.config.scrollZoom).toBeDefined();
+ });
+
it('should list trace-dependent & direction-dependent error bar attributes', function() {
var scatterSchema = plotSchema.traces.scatter.attributes;
expect(scatterSchema.error_x.copy_ystyle).toBeDefined();
diff --git a/test/jasmine/tests/config_test.js b/test/jasmine/tests/config_test.js
index 2a06969f162..645a2576775 100644
--- a/test/jasmine/tests/config_test.js
+++ b/test/jasmine/tests/config_test.js
@@ -764,6 +764,65 @@ describe('config argument', function() {
});
});
+ describe('scrollZoom:', function() {
+ var gd;
+
+ beforeEach(function() { gd = createGraphDiv(); });
+
+ afterEach(destroyGraphDiv);
+
+ function plot(config) {
+ return Plotly.plot(gd, [], {}, config);
+ }
+
+ it('should fill in scrollZoom default', function(done) {
+ plot(undefined).then(function() {
+ expect(gd._context.scrollZoom).toBe('gl3d+geo+mapbox');
+ expect(gd._context._scrollZoom).toEqual({gl3d: 1, geo: 1, mapbox: 1});
+ expect(gd._context._scrollZoom.cartesian).toBe(undefined, 'no cartesian!');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('should fill in blank scrollZoom value', function(done) {
+ plot({scrollZoom: null}).then(function() {
+ expect(gd._context.scrollZoom).toBe(null);
+ expect(gd._context._scrollZoom).toEqual({gl3d: 1, geo: 1, mapbox: 1});
+ expect(gd._context._scrollZoom.cartesian).toBe(undefined, 'no cartesian!');
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('should honor scrollZoom:true', function(done) {
+ plot({scrollZoom: true}).then(function() {
+ expect(gd._context.scrollZoom).toBe(true);
+ expect(gd._context._scrollZoom).toEqual({gl3d: 1, geo: 1, cartesian: 1, mapbox: 1});
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('should honor scrollZoom:false', function(done) {
+ plot({scrollZoom: false}).then(function() {
+ expect(gd._context.scrollZoom).toBe(false);
+ expect(gd._context._scrollZoom).toEqual({});
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('should honor scrollZoom flaglist', function(done) {
+ plot({scrollZoom: 'mapbox+cartesian'}).then(function() {
+ expect(gd._context.scrollZoom).toBe('mapbox+cartesian');
+ expect(gd._context._scrollZoom).toEqual({mapbox: 1, cartesian: 1});
+ })
+ .catch(failTest)
+ .then(done);
+ });
+ });
+
describe('scrollZoom interactions:', function() {
var gd;
diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js
index 0c57fc6e444..be42d2abc08 100644
--- a/test/jasmine/tests/geo_test.js
+++ b/test/jasmine/tests/geo_test.js
@@ -1961,4 +1961,52 @@ describe('Test geo zoom/pan/drag interactions:', function() {
.catch(failTest)
.then(done);
});
+
+ it('should respect scrollZoom config option', function(done) {
+ var fig = Lib.extendDeep({}, require('@mocks/geo_winkel-tripel'));
+ fig.layout.width = 700;
+ fig.layout.height = 500;
+ fig.layout.dragmode = 'pan';
+
+ function _assert(step, attr, proj, eventKeys) {
+ var msg = '[' + step + '] ';
+
+ var geoLayout = gd._fullLayout.geo;
+ var scale = geoLayout.projection.scale;
+ expect(scale).toBeCloseTo(attr[0], 1, msg + 'zoom');
+
+ var geo = geoLayout._subplot;
+ var _scale = geo.projection.scale();
+ expect(_scale).toBeCloseTo(proj[0], 0, msg + 'scale');
+
+ assertEventData(msg, eventKeys);
+ }
+
+ plot(fig)
+ .then(function() {
+ _assert('base', [1], [101.9], undefined);
+ })
+ .then(function() { return scroll([200, 250], [-200, -200]); })
+ .then(function() {
+ _assert('with scroll enable (by default)',
+ [1.3], [134.4],
+ ['geo.projection.rotation.lon', 'geo.center.lon', 'geo.center.lat', 'geo.projection.scale']
+ );
+ })
+ .then(function() { return Plotly.plot(gd, [], {}, {scrollZoom: false}); })
+ .then(function() { return scroll([200, 250], [-200, -200]); })
+ .then(function() {
+ _assert('with scrollZoom:false', [1.3], [134.4], undefined);
+ })
+ .then(function() { return Plotly.plot(gd, [], {}, {scrollZoom: 'geo'}); })
+ .then(function() { return scroll([200, 250], [-200, -200]); })
+ .then(function() {
+ _assert('with scrollZoom:geo',
+ [1.74], [177.34],
+ ['geo.projection.rotation.lon', 'geo.center.lon', 'geo.center.lat', 'geo.projection.scale']
+ );
+ })
+ .catch(failTest)
+ .then(done);
+ });
});
diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js
index 65de62cce8f..7578762f3a2 100644
--- a/test/jasmine/tests/gl3d_plot_interact_test.js
+++ b/test/jasmine/tests/gl3d_plot_interact_test.js
@@ -1097,6 +1097,11 @@ describe('Test gl3d drag and wheel interactions', function() {
}
};
+ function _assertAndReset(cnt) {
+ expect(relayoutCallback).toHaveBeenCalledTimes(cnt);
+ relayoutCallback.calls.reset();
+ }
+
Plotly.plot(gd, mock)
.then(function() {
relayoutCallback = jasmine.createSpy('relayoutCallback');
@@ -1115,48 +1120,32 @@ describe('Test gl3d drag and wheel interactions', function() {
return scroll(sceneTarget);
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(1);
- relayoutCallback.calls.reset();
-
+ _assertAndReset(1);
return scroll(sceneTarget2);
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(1);
- relayoutCallback.calls.reset();
-
+ _assertAndReset(1);
return drag(sceneTarget2, [0, 0], [100, 100]);
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(1);
- relayoutCallback.calls.reset();
-
+ _assertAndReset(1);
return drag(sceneTarget, [0, 0], [100, 100]);
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(1);
- relayoutCallback.calls.reset();
-
- return Plotly.relayout(gd, {
- 'scene.dragmode': false,
- 'scene2.dragmode': false
- });
+ _assertAndReset(1);
+ return Plotly.relayout(gd, {'scene.dragmode': false, 'scene2.dragmode': false});
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(1);
- relayoutCallback.calls.reset();
-
+ _assertAndReset(1);
return drag(sceneTarget, [0, 0], [100, 100]);
})
.then(function() {
return drag(sceneTarget2, [0, 0], [100, 100]);
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(0);
+ _assertAndReset(0);
- return Plotly.relayout(gd, {
- 'scene.dragmode': 'orbit',
- 'scene2.dragmode': 'turntable'
- });
+ return Plotly.relayout(gd, {'scene.dragmode': 'orbit', 'scene2.dragmode': 'turntable'});
})
.then(function() {
expect(relayoutCallback).toHaveBeenCalledTimes(1);
@@ -1168,7 +1157,27 @@ describe('Test gl3d drag and wheel interactions', function() {
return drag(sceneTarget2, [0, 0], [100, 100]);
})
.then(function() {
- expect(relayoutCallback).toHaveBeenCalledTimes(2);
+ _assertAndReset(2);
+ return Plotly.plot(gd, [], {}, {scrollZoom: false});
+ })
+ .then(function() {
+ return scroll(sceneTarget);
+ })
+ .then(function() {
+ return scroll(sceneTarget2);
+ })
+ .then(function() {
+ _assertAndReset(0);
+ return Plotly.plot(gd, [], {}, {scrollZoom: 'gl3d'});
+ })
+ .then(function() {
+ return scroll(sceneTarget);
+ })
+ .then(function() {
+ return scroll(sceneTarget2);
+ })
+ .then(function() {
+ _assertAndReset(2);
})
.catch(failTest)
.then(done);
diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js
index 0b9d0604e35..00351710619 100644
--- a/test/jasmine/tests/lib_test.js
+++ b/test/jasmine/tests/lib_test.js
@@ -1,7 +1,7 @@
var Lib = require('@src/lib');
var setCursor = require('@src/lib/setcursor');
var overrideCursor = require('@src/lib/override_cursor');
-var config = require('@src/plot_api/plot_config');
+var config = require('@src/plot_api/plot_config').dfltConfig;
var d3 = require('d3');
var Plotly = require('@lib');
diff --git a/test/jasmine/tests/mapbox_test.js b/test/jasmine/tests/mapbox_test.js
index 9d6f6f8e53a..a0881b6c17b 100644
--- a/test/jasmine/tests/mapbox_test.js
+++ b/test/jasmine/tests/mapbox_test.js
@@ -976,6 +976,51 @@ describe('@noCI, mapbox plots', function() {
.then(done);
}, LONG_TIMEOUT_INTERVAL);
+ it('should respect scrollZoom config option', function(done) {
+ var relayoutCnt = 0;
+ gd.on('plotly_relayout', function() { relayoutCnt++; });
+
+ function _scroll() {
+ relayoutCnt = 0;
+ return new Promise(function(resolve) {
+ mouseEvent('mousemove', pointPos[0], pointPos[1]);
+ mouseEvent('scroll', pointPos[0], pointPos[1], {deltaY: -400});
+ setTimeout(resolve, 500);
+ });
+ }
+
+ var zoom = getMapInfo(gd).zoom;
+ expect(zoom).toBeCloseTo(1.234);
+
+ _scroll().then(function() {
+ expect(relayoutCnt).toBe(1, 'scroll relayout cnt');
+
+ var zoomNew = getMapInfo(gd).zoom;
+ expect(zoomNew).toBeGreaterThan(zoom);
+ zoom = zoomNew;
+ })
+ .then(function() { return Plotly.plot(gd, [], {}, {scrollZoom: false}); })
+ .then(_scroll)
+ .then(function() {
+ expect(relayoutCnt).toBe(0, 'no additional relayout call');
+
+ var zoomNew = getMapInfo(gd).zoom;
+ expect(zoomNew).toBe(zoom);
+ zoom = zoomNew;
+ })
+ .then(function() { return Plotly.plot(gd, [], {}, {scrollZoom: true}); })
+ .then(_scroll)
+ .then(function() {
+ expect(relayoutCnt).toBe(1, 'scroll relayout cnt');
+
+ var zoomNew = getMapInfo(gd).zoom;
+ expect(zoomNew).toBeGreaterThan(zoom);
+ zoom = zoomNew;
+ })
+ .catch(failTest)
+ .then(done);
+ }, LONG_TIMEOUT_INTERVAL);
+
function getMapInfo(gd) {
var subplot = gd._fullLayout.mapbox._subplot;
var map = subplot.map;