From 1df34bd7add625704c9ecf542308ab16b61d6e92 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Fri, 2 Mar 2018 12:06:43 -0500 Subject: [PATCH] add editType to clear automargins from pushmargins --- src/plot_api/edit_types.js | 3 +- src/plot_api/helpers.js | 9 +++ src/plot_api/plot_api.js | 3 + src/plots/cartesian/axes.js | 45 +++++++-------- src/plots/cartesian/layout_attributes.js | 72 ++++++++++++------------ test/jasmine/tests/axes_test.js | 33 +++++++++++ 6 files changed, 102 insertions(+), 63 deletions(-) diff --git a/src/plot_api/edit_types.js b/src/plot_api/edit_types.js index 91c59ba4c7a..24701a9a443 100644 --- a/src/plot_api/edit_types.js +++ b/src/plot_api/edit_types.js @@ -35,7 +35,7 @@ var layoutOpts = { valType: 'flaglist', extras: ['none'], flags: [ - 'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', + 'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', 'margins', 'layoutstyle', 'modebar', 'camera', 'arraydraw' ], description: [ @@ -47,6 +47,7 @@ var layoutOpts = { '*plot* calls `Plotly.plot` but without first clearing `gd.calcdata`.', '*legend* only redraws the legend.', '*ticks* only redraws axis ticks, labels, and gridlines.', + '*margins* recomputes ticklabel automargins.', '*layoutstyle* reapplies global and SVG cartesian axis styles.', '*modebar* just updates the modebar.', '*camera* just updates the camera settings for gl3d scenes.', diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 38a5d715dd5..d4f44af785a 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -561,3 +561,12 @@ exports.clearAxisTypes = function(gd, traces, layoutUpdate) { } } }; + +exports.clearAxisAutomargins = function(gd) { + var keys = Object.keys(gd._fullLayout._pushmargin); + for(var i = 0; i < keys.length; i++) { + if(keys[i].indexOf('automargin') !== -1) { + delete gd._fullLayout._pushmargin[keys[i]]; + } + } +}; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c52bdc44cd5..db10ce3b1fb 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1742,6 +1742,7 @@ Plotly.relayout = function relayout(gd, astr, val) { // clear calcdata if required if(flags.calc) gd.calcdata = undefined; + if(flags.margins) helpers.clearAxisAutomargins(gd); // fill in redraw sequence @@ -2182,6 +2183,7 @@ Plotly.update = function update(gd, traceUpdate, layoutUpdate, _traces) { // clear calcdata and/or axis types if required if(restyleFlags.clearCalc || relayoutFlags.calc) gd.calcdata = undefined; if(restyleFlags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, layoutUpdate); + if(relayoutFlags.margins) helpers.clearAxisAutomargins(gd); // fill in redraw sequence var seq = []; @@ -2309,6 +2311,7 @@ Plotly.react = function(gd, data, layout, config) { // clear calcdata if required if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined; + if(relayoutFlags.margins) helpers.clearAxisAutomargins(gd); // Note: what restyle/relayout use impliedEdits and clearAxisTypes for // must be handled by the user when using Plotly.react. diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index ef6ce6dae07..4e1556f119e 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -2219,38 +2219,31 @@ axes.doTicks = function(gd, axid, skipTitle) { } function doAutoMargins() { + if(!ax.automargin) { return; } if(axLetter !== 'x' && axLetter !== 'y') { return; } - var pushKey = ax._name + '.automargin'; - var sideLetter = ax.side[0]; - var existingPush = fullLayout._pushmargin[pushKey]; - var pushParams = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0}; - - if(!ax.automargin || ax.anchor === 'free' || !ax._anchorAxis) { - if(existingPush && !( - existingPush.r.size === 0 && existingPush.l.size === 0 && - existingPush.b.size === 0 && existingPush.t.size === 0)) { - Plots.autoMargin(gd, pushKey, pushParams); - } - return; - } - var axisDim; + var s = ax.side[0]; + var push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0}; + if(axLetter === 'x') { - pushParams.y = ax._anchorAxis.domain[sideLetter === 't' ? 1 : 0]; - axisDim = ax._boundingBox.height; + push.y = (ax.anchor === 'free' ? ax.position : + ax._anchorAxis.domain[s === 't' ? 1 : 0]); + push[s] += ax._boundingBox.height; } else { - pushParams.x = ax._anchorAxis.domain[sideLetter === 'r' ? 1 : 0]; - axisDim = ax._boundingBox.width; + push.x = (ax.anchor === 'free' ? ax.position : + ax._anchorAxis.domain[s === 'r' ? 1 : 0]); + push[s] += ax._boundingBox.width; } - var axisTitleDim = (ax.title !== fullLayout._dfltTitle[axLetter] ? - ax.titlefont.size : 0); - var marginPush = axisTitleDim + axisDim; - - if(!fullLayout._replotting || - !existingPush || existingPush[sideLetter].size < marginPush) { - pushParams[sideLetter] = marginPush; - Plots.autoMargin(gd, pushKey, pushParams); + + if(ax.title !== fullLayout._dfltTitle[axLetter]) { + push[s] += ax.titlefont.size; + } + + var pushKey = ax._name + '.automargin'; + var prevPush = fullLayout._pushmargin[pushKey]; + if(!prevPush || prevPush[s].size < push[s]) { + Plots.autoMargin(gd, pushKey, push); } } diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 66dffce7f05..4d7dfe34d8a 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -42,11 +42,11 @@ module.exports = { title: { valType: 'string', role: 'info', - editType: 'ticks', + editType: 'ticks+margins', description: 'Sets the title of this axis.' }, titlefont: fontAttrs({ - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Sets this axis\' title font.' ].join(' ') @@ -100,10 +100,10 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'any', editType: 'plot', impliedEdits: {'^autorange': false}}, - {valType: 'any', editType: 'plot', impliedEdits: {'^autorange': false}} + {valType: 'any', editType: 'plot+margins', impliedEdits: {'^autorange': false}}, + {valType: 'any', editType: 'plot+margins', impliedEdits: {'^autorange': false}} ], - editType: 'plot', + editType: 'plot+margins', impliedEdits: {'autorange': false}, description: [ 'Sets the range of this axis.', @@ -198,7 +198,7 @@ module.exports = { valType: 'enumerated', values: ['auto', 'linear', 'array'], role: 'info', - editType: 'ticks', + editType: 'ticks+margins', impliedEdits: {tick0: undefined, dtick: undefined}, description: [ 'Sets the tick mode for this axis.', @@ -216,7 +216,7 @@ module.exports = { min: 0, dflt: 0, role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Specifies the maximum number of ticks for the particular axis.', 'The actual number of ticks will be chosen automatically to be', @@ -227,7 +227,7 @@ module.exports = { tick0: { valType: 'any', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', impliedEdits: {tickmode: 'linear'}, description: [ 'Sets the placement of the first tick on this axis.', @@ -243,7 +243,7 @@ module.exports = { dtick: { valType: 'any', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', impliedEdits: {tickmode: 'linear'}, description: [ 'Sets the step in-between ticks on this axis. Use with `tick0`.', @@ -269,7 +269,7 @@ module.exports = { }, tickvals: { valType: 'data_array', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Sets the values at which ticks on this axis appear.', 'Only has an effect if `tickmode` is set to *array*.', @@ -278,7 +278,7 @@ module.exports = { }, ticktext: { valType: 'data_array', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Sets the text displayed at the ticks position via `tickvals`.', 'Only has an effect if `tickmode` is set to *array*.', @@ -289,7 +289,7 @@ module.exports = { valType: 'enumerated', values: ['outside', 'inside', ''], role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Determines whether ticks are drawn or not.', 'If **, this axis\' ticks are not drawn.', @@ -341,14 +341,14 @@ module.exports = { valType: 'boolean', dflt: true, role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: 'Determines whether or not the tick labels are drawn.' }, automargin: { valType: 'boolean', dflt: false, role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Determines whether long tick labels automatically grow the figure', 'margins.' @@ -406,14 +406,14 @@ module.exports = { description: 'Determines whether spikelines are stuck to the cursor or to the closest datapoints.' }, tickfont: fontAttrs({ - editType: 'ticks', + editType: 'ticks+margins', description: 'Sets the tick font.' }), tickangle: { valType: 'angle', dflt: 'auto', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Sets the angle of the tick labels with respect to the horizontal.', 'For example, a `tickangle` of -90 draws the tick labels', @@ -424,7 +424,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: 'Sets a tick label prefix.' }, showtickprefix: { @@ -432,7 +432,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'If *all*, all tick labels are displayed with a prefix.', 'If *first*, only the first tick is displayed with a prefix.', @@ -444,7 +444,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: 'Sets a tick label suffix.' }, showticksuffix: { @@ -452,7 +452,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: 'Same as `showtickprefix` but for tick suffixes.' }, showexponent: { @@ -460,7 +460,7 @@ module.exports = { values: ['all', 'first', 'last', 'none'], dflt: 'all', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'If *all*, all exponents are shown besides their significands.', 'If *first*, only the exponent of the first tick is shown.', @@ -473,7 +473,7 @@ module.exports = { values: ['none', 'e', 'E', 'power', 'SI', 'B'], dflt: 'B', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Determines a formatting rule for the tick exponents.', 'For example, consider the number 1,000,000,000.', @@ -489,7 +489,7 @@ module.exports = { valType: 'boolean', dflt: false, role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'If "true", even 4-digit integers are separated' ].join(' ') @@ -498,7 +498,7 @@ module.exports = { valType: 'string', dflt: '', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Sets the tick label formatting rule using d3 formatting mini-languages', 'which are very similar to those in Python. For numbers, see:', @@ -517,10 +517,10 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'any', editType: 'ticks'}, - {valType: 'any', editType: 'ticks'} + {valType: 'any', editType: 'ticks+margins'}, + {valType: 'any', editType: 'ticks+margins'} ], - editType: 'ticks', + editType: 'ticks+margins', description: [ 'range [*min*, *max*], where *min*, *max* - dtick values', 'which describe some zoom level, it is possible to omit *min*', @@ -531,12 +531,12 @@ module.exports = { valType: 'string', dflt: '', role: 'style', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'string - dtickformat for described zoom level, the same as *tickformat*' ].join(' ') }, - editType: 'ticks' + editType: 'ticks+margins' }, hoverformat: { valType: 'string', @@ -638,7 +638,7 @@ module.exports = { constants.idRegex.y.toString() ], role: 'info', - editType: 'plot', + editType: 'plot+margins', description: [ 'If set to an opposite-letter axis id (e.g. `x2`, `y`), this axis is bound to', 'the corresponding opposite-letter axis.', @@ -651,7 +651,7 @@ module.exports = { valType: 'enumerated', values: ['top', 'bottom', 'left', 'right'], role: 'info', - editType: 'plot', + editType: 'plot+margins', description: [ 'Determines whether a x (y) axis is positioned', 'at the *bottom* (*left*) or *top* (*right*)', @@ -695,11 +695,11 @@ module.exports = { valType: 'info_array', role: 'info', items: [ - {valType: 'number', min: 0, max: 1, editType: 'plot'}, - {valType: 'number', min: 0, max: 1, editType: 'plot'} + {valType: 'number', min: 0, max: 1, editType: 'plot+margins'}, + {valType: 'number', min: 0, max: 1, editType: 'plot+margins'} ], dflt: [0, 1], - editType: 'plot', + editType: 'plot+margins', description: [ 'Sets the domain of this axis (in plot fraction).' ].join(' ') @@ -710,7 +710,7 @@ module.exports = { max: 1, dflt: 0, role: 'style', - editType: 'plot', + editType: 'plot+margins', description: [ 'Sets the position of this axis in the plotting space', '(in normalized coordinates).', @@ -754,7 +754,7 @@ module.exports = { autotick: { valType: 'boolean', role: 'info', - editType: 'ticks', + editType: 'ticks+margins', description: [ 'Obsolete.', 'Set `tickmode` to *auto* for old `autotick` *true* behavior.', diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index dfd5826a707..77006d1a1fe 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -2672,6 +2672,39 @@ describe('Test axes', function() { expect(size.b).toBe(previousSize.b); expect(size.t).toBe(previousSize.t); }) + .then(function() { + previousSize = Lib.extendDeep({}, gd._fullLayout._size); + return Plotly.relayout(gd, { 'yaxis.anchor': 'free' }); + }) + .then(function() { + var size = gd._fullLayout._size; + expect(size.l).toBe(previousSize.l); + expect(size.r).toBe(previousSize.r); + expect(size.b).toBe(previousSize.b); + expect(size.t).toBe(previousSize.t); + }) + .then(function() { + previousSize = Lib.extendDeep({}, gd._fullLayout._size); + return Plotly.relayout(gd, { 'yaxis.position': 0.1}); + }) + .then(function() { + var size = gd._fullLayout._size; + expect(size.l).toBeLessThan(previousSize.l, 'axis moved right'); + expect(size.r).toBe(previousSize.r); + expect(size.b).toBe(previousSize.b); + expect(size.t).toBe(previousSize.t); + }) + .then(function() { + previousSize = Lib.extendDeep({}, gd._fullLayout._size); + return Plotly.relayout(gd, { 'yaxis.anchor': 'x' }); + }) + .then(function() { + var size = gd._fullLayout._size; + expect(size.l).toBeGreaterThan(previousSize.l, 'axis snapped back'); + expect(size.r).toBe(previousSize.r); + expect(size.b).toBe(previousSize.b); + expect(size.t).toBe(previousSize.t); + }) .then(function() { previousSize = Lib.extendDeep({}, gd._fullLayout._size); return Plotly.relayout(gd, {