diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index a42e324f010..7d0006d8c00 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -120,5 +120,14 @@ module.exports = { 'or *bottom* of the legend.' ].join(' ') }, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of legend-driven changes in trace and pie label', + 'visibility. Defaults to `layout.uirevision`.' + ].join(' ') + }, editType: 'legend' }; diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index c82d175951f..cd9097f0422 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -66,7 +66,7 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { basePlotLayoutAttributes, 'showlegend', legendReallyHasATrace && legendTraceCount > 1); - if(showLegend === false) return; + if(showLegend === false && !containerIn.uirevision) return; var containerOut = Template.newContainer(layoutOut, 'legend'); @@ -74,6 +74,10 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { return Lib.coerce(containerIn, containerOut, attributes, attr, dflt); } + coerce('uirevision', layoutOut.uirevision); + + if(showLegend === false) return; + coerce('bgcolor', layoutOut.paper_bgcolor); coerce('bordercolor'); coerce('borderwidth'); diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 653176fd260..113cc11a537 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -164,5 +164,17 @@ module.exports = { 'An array of operations that manipulate the trace data,', 'for example filtering or sorting the data arrays.' ].join(' ') + }, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes to the trace:', + 'interactions like `selectedpoints` and type-specific ones such as', + '`constraintrange` in `parcoords` traces, as well as some', + '`editable: true` modifications such as `name` and `colorbar.title`.', + 'Defaults to `layout.uirevision`.' + ].join(' ') } }; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 404f023eb71..64722e535b6 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -766,6 +766,16 @@ module.exports = { 'Used with `categoryorder`.' ].join(' ') }, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in axis `range`,', + '`autorange`, and `title` if in `editable: true` configuration.', + 'Defaults to `layout.uirevision`.' + ].join(' ') + }, editType: 'calc', _deprecated: { diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 63abbc2364b..c56c64c2145 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -176,6 +176,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[id] }; + coerce('uirevision', layoutOut.uirevision); + handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions); handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut); diff --git a/src/plots/geo/layout/layout_attributes.js b/src/plots/geo/layout/layout_attributes.js index f326c816550..848aa52038e 100644 --- a/src/plots/geo/layout/layout_attributes.js +++ b/src/plots/geo/layout/layout_attributes.js @@ -65,7 +65,7 @@ var geoAxesAttrs = { } }; -module.exports = overrideAll({ +var attrs = module.exports = overrideAll({ domain: domainAttrs({name: 'geo'}, { description: [ 'Note that geo subplots are constrained by domain.', @@ -311,3 +311,14 @@ module.exports = overrideAll({ lonaxis: geoAxesAttrs, lataxis: geoAxesAttrs }, 'plot', 'from-root'); + +// set uirevision outside of overrideAll so it can be `editType: 'none'` +attrs.uirevision = { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in the view', + '(projection and center). Defaults to `layout.uirevision`.' + ].join(' ') +}; diff --git a/src/plots/gl3d/layout/layout_attributes.js b/src/plots/gl3d/layout/layout_attributes.js index 870eca0151c..22475483437 100644 --- a/src/plots/gl3d/layout/layout_attributes.js +++ b/src/plots/gl3d/layout/layout_attributes.js @@ -156,6 +156,15 @@ module.exports = { 'Determines the mode of hover interactions for this scene.' ].join(' ') }, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in camera attributes.', + 'Defaults to `layout.uirevision`.' + ].join(' ') + }, editType: 'plot', _deprecated: { diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 103a0b234f7..78b829e47ae 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -202,6 +202,38 @@ module.exports = { 'different identity from its predecessor contains new data.' ].join(' ') }, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Used to allow user interactions with the plot to persist after', + '`Plotly.react` calls that are unaware of these interactions.', + 'If `uirevision` is omitted, or if it is given and it changed from', + 'the previous `Plotly.react` call, the exact new figure is used.', + 'If `uirevision` is truthy and did NOT change, any attribute', + 'that has been affected by user interactions and did not receive a', + 'different value in the new figure will keep the interaction value.', + '`layout.uirevision` attribute serves as the default for', + '`uirevision` attributes in various sub-containers. For finer', + 'control you can set these sub-attributes directly. For example,', + 'if your app separately controls the data on the x and y axes you', + 'might set `xaxis.uirevision=*time*` and `yaxis.uirevision=*cost*`.', + 'Then if only the y data is changed, you can update', + '`yaxis.uirevision=*quantity*` and the y axis range will reset but', + 'the x axis range will retain any user-driven zoom.' + ].join(' ') + }, + editrevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in `editable: true`', + 'configuration, other than trace names and axis titles.', + 'Defaults to `layout.uirevision`.' + ].join(' ') + }, template: { valType: 'any', role: 'info', @@ -252,6 +284,16 @@ module.exports = { editType: 'modebar', description: 'Sets the color of the active or hovered on icons in the modebar.' }, + uirevision: { + valType: 'any', + role: 'info', + 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' } }; diff --git a/src/plots/mapbox/layout_attributes.js b/src/plots/mapbox/layout_attributes.js index 4d59225f208..e07767b913d 100644 --- a/src/plots/mapbox/layout_attributes.js +++ b/src/plots/mapbox/layout_attributes.js @@ -25,7 +25,7 @@ var fontAttr = fontAttrs({ }); fontAttr.family.dflt = 'Open Sans Regular, Arial Unicode MS Regular'; -module.exports = overrideAll({ +var attrs = module.exports = overrideAll({ _arrayAttrRegexps: [Lib.counterRegex('mapbox', '.layers', true)], domain: domainAttrs({name: 'mapbox'}), @@ -245,3 +245,14 @@ module.exports = overrideAll({ } }) }, 'plot', 'from-root'); + +// set uirevision outside of overrideAll so it can be `editType: 'none'` +attrs.uirevision = { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in the view:', + '`center`, `zoom`, `bearing`, `pitch`. Defaults to `layout.uirevision`.' + ].join(' ') +}; diff --git a/src/plots/plots.js b/src/plots/plots.js index c053b8c9c6f..92ca7dd53e5 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1140,6 +1140,8 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac coerce('type'); coerce('name', layout._traceWord + ' ' + traceInIndex); + coerce('uirevision', layout.uirevision); + // we want even invisible traces to make their would-be subplots visible // so coerce the subplot id(s) now no matter what var _module = plots.getModule(traceOut); @@ -1382,12 +1384,15 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) { coerce('colorway'); coerce('datarevision'); + var uirevision = coerce('uirevision'); + coerce('editrevision', 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( 'calendars', diff --git a/src/plots/polar/layout_attributes.js b/src/plots/polar/layout_attributes.js index 8334b0c3d96..94cffb496ca 100644 --- a/src/plots/polar/layout_attributes.js +++ b/src/plots/polar/layout_attributes.js @@ -118,6 +118,17 @@ var radialAxisAttrs = { hoverformat: axesAttrs.hoverformat, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in axis `range`,', + '`autorange`, `angle`, and `title` if in `editable: true` configuration.', + 'Defaults to `polar.uirevision`.' + ].join(' ') + }, + editType: 'calc' }; @@ -215,6 +226,16 @@ var angularAxisAttrs = { hoverformat: axesAttrs.hoverformat, + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in axis `rotation`.', + 'Defaults to `polar.uirevision`.' + ].join(' ') + }, + editType: 'calc' }; @@ -294,5 +315,16 @@ module.exports = { // TODO maybe? // annotations: + uirevision: { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in axis attributes,', + 'if not overridden in the individual axes.', + 'Defaults to `layout.uirevision`.' + ].join(' ') + }, + editType: 'calc' }; diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index 32bf5afff4b..b5cef9b77ce 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -66,6 +66,8 @@ function handleDefaults(contIn, contOut, coerce, opts) { var visible = coerceAxis('visible'); setConvert(axOut, contOut, layoutOut); + coerceAxis('uirevision', contOut.uirevision); + var dfltColor; var dfltFontColor; diff --git a/src/plots/subplot_defaults.js b/src/plots/subplot_defaults.js index 9e83049b773..98f55abeeef 100644 --- a/src/plots/subplot_defaults.js +++ b/src/plots/subplot_defaults.js @@ -67,6 +67,12 @@ module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, o subplotLayoutOut = Template.newContainer(layoutOut, id, baseId); + // All subplot containers get a `uirevision` inheriting from the base. + // Currently all subplots containers have some user interaction + // attributes, but if we ever add one that doesn't, we would need an + // option to skip this step. + coerce('uirevision', layoutOut.uirevision); + var dfltDomains = {}; dfltDomains[partition] = [i / idsLength, (i + 1) / idsLength]; handleDomainDefaults(subplotLayoutOut, layoutOut, coerce, dfltDomains); diff --git a/src/plots/ternary/layout_attributes.js b/src/plots/ternary/layout_attributes.js index 72397f0f876..cd310143ba2 100644 --- a/src/plots/ternary/layout_attributes.js +++ b/src/plots/ternary/layout_attributes.js @@ -66,7 +66,7 @@ var ternaryAxesAttrs = { } }; -module.exports = overrideAll({ +var attrs = module.exports = overrideAll({ domain: domainAttrs({name: 'ternary'}), bgcolor: { @@ -89,3 +89,26 @@ module.exports = overrideAll({ baxis: ternaryAxesAttrs, caxis: ternaryAxesAttrs }, 'plot', 'from-root'); + +// set uirevisions outside of `overrideAll` so we can get `editType: none` +attrs.uirevision = { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in axis `min` and `title`,', + 'if not overridden in the individual axes.', + 'Defaults to `layout.uirevision`.' + ].join(' ') +}; + +attrs.aaxis.uirevision = attrs.baxis.uirevision = attrs.caxis.uirevision = { + valType: 'any', + role: 'info', + editType: 'none', + description: [ + 'Controls persistence of user-driven changes in axis `min`,', + 'and `title` if in `editable: true` configuration.', + 'Defaults to `ternary.uirevision`.' + ].join(' ') +}; diff --git a/src/plots/ternary/layout_defaults.js b/src/plots/ternary/layout_defaults.js index 7952a4559a5..451255856f2 100644 --- a/src/plots/ternary/layout_defaults.js +++ b/src/plots/ternary/layout_defaults.js @@ -46,7 +46,7 @@ function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, option containerOut = Template.newContainer(ternaryLayoutOut, axName); containerOut._name = axName; - handleAxisDefaults(containerIn, containerOut, options); + handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut); } // if the min values contradict each other, set them all to default (0) @@ -65,13 +65,15 @@ function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, option } } -function handleAxisDefaults(containerIn, containerOut, options) { +function handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut) { var axAttrs = layoutAttributes[containerOut._name]; function coerce(attr, dflt) { return Lib.coerce(containerIn, containerOut, axAttrs, attr, dflt); } + coerce('uirevision', ternaryLayoutOut.uirevision); + containerOut.type = 'linear'; // no other types allowed for ternary var dfltColor = coerce('color');