plotly.js 1.58.0 → 1.58.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/dist/README.md +25 -25
  3. package/dist/plot-schema.json +15 -15
  4. package/dist/plotly-basic.js +608 -444
  5. package/dist/plotly-basic.min.js +4 -4
  6. package/dist/plotly-cartesian.js +725 -561
  7. package/dist/plotly-cartesian.min.js +4 -4
  8. package/dist/plotly-finance.js +689 -525
  9. package/dist/plotly-finance.min.js +4 -4
  10. package/dist/plotly-geo-assets.js +2 -2
  11. package/dist/plotly-geo.js +612 -448
  12. package/dist/plotly-geo.min.js +4 -4
  13. package/dist/plotly-gl2d.js +790 -626
  14. package/dist/plotly-gl2d.min.js +10 -10
  15. package/dist/plotly-gl3d.js +514 -389
  16. package/dist/plotly-gl3d.min.js +3 -3
  17. package/dist/plotly-mapbox.js +622 -458
  18. package/dist/plotly-mapbox.min.js +4 -4
  19. package/dist/plotly-with-meta.js +988 -856
  20. package/dist/plotly.js +980 -851
  21. package/dist/plotly.min.js +3 -3
  22. package/dist/translation-keys.txt +1 -1
  23. package/package.json +1 -1
  24. package/src/components/colorscale/index.js +1 -1
  25. package/src/components/errorbars/attributes.js +1 -1
  26. package/src/components/fx/hover.js +1 -1
  27. package/src/components/sliders/draw.js +1 -1
  28. package/src/lib/index.js +2 -1
  29. package/src/lib/preserve_drawing_buffer.js +68 -0
  30. package/src/plot_api/plot_api.js +19 -15
  31. package/src/plots/cartesian/autorange.js +33 -64
  32. package/src/plots/cartesian/axes.js +44 -16
  33. package/src/plots/cartesian/axis_ids.js +16 -0
  34. package/src/plots/cartesian/constraints.js +2 -2
  35. package/src/plots/cartesian/layout_attributes.js +4 -1
  36. package/src/plots/gl3d/scene.js +16 -18
  37. package/src/plots/plots.js +61 -22
  38. package/src/plots/polar/polar.js +2 -0
  39. package/src/traces/carpet/attributes.js +2 -2
  40. package/src/traces/carpet/create_i_derivative_evaluator.js +1 -1
  41. package/src/traces/carpet/plot.js +1 -1
  42. package/src/traces/carpet/set_convert.js +2 -2
  43. package/src/traces/carpet/smooth_fill_2d_array.js +1 -1
  44. package/src/traces/parcats/attributes.js +1 -1
  45. package/src/traces/scatter/plot.js +1 -1
  46. package/src/traces/scattercarpet/plot.js +1 -1
  47. package/src/traces/sunburst/calc.js +3 -1
  48. package/src/version.js +1 -1
@@ -7,7 +7,7 @@ Click to enter Component C title // plots/ternary/ternary.
7
7
  Click to enter Plot title // plot_api/plot_api.js:604
8
8
  Click to enter X axis title // plots/plots.js:333
9
9
  Click to enter Y axis title // plots/plots.js:334
10
- Click to enter radial axis title // plots/polar/polar.js:494
10
+ Click to enter radial axis title // plots/polar/polar.js:496
11
11
  Compare data on hover // components/modebar/buttons.js:237
12
12
  Double-click on legend to isolate one trace // components/legend/handle_click.js:27
13
13
  Double-click to zoom back out // plots/cartesian/dragbox.js:1172
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plotly.js",
3
- "version": "1.58.0",
3
+ "version": "1.58.4",
4
4
  "description": "The open source javascript graphing library that powers plotly",
5
5
  "license": "MIT",
6
6
  "main": "./lib/index.js",
@@ -25,7 +25,7 @@ module.exports = {
25
25
  calc: require('./calc'),
26
26
 
27
27
  // ./scales.js is required in lib/coerce.js ;
28
- // it needs to be a seperate module to avoid circular a dependency
28
+ // it needs to be a separate module to avoid circular a dependency
29
29
  scales: scales.scales,
30
30
  defaultScale: scales.defaultScale,
31
31
  getScale: scales.get,
@@ -32,7 +32,7 @@ module.exports = {
32
32
  'If *percent*, the bar lengths correspond to a percentage of',
33
33
  'underlying data. Set this percentage in `value`.',
34
34
 
35
- 'If *sqrt*, the bar lengths correspond to the sqaure of the',
35
+ 'If *sqrt*, the bar lengths correspond to the square of the',
36
36
  'underlying data.',
37
37
 
38
38
  'If *data*, the bar lengths are set with data set `array`.'
@@ -1306,7 +1306,7 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) {
1306
1306
 
1307
1307
  // Make groups of touching points, and within each group
1308
1308
  // move each point so that no labels overlap, but the average
1309
- // label position is the same as it was before moving. Indicentally,
1309
+ // label position is the same as it was before moving. Incidentally,
1310
1310
  // this is equivalent to saying all the labels are on equal linear
1311
1311
  // springs about their initial position. Initially, each point is
1312
1312
  // its own group, but as we find overlaps we will clump the points.
@@ -571,7 +571,7 @@ function setGripPosition(sliderGroup, sliderOpts, doTransition) {
571
571
  .ease(sliderOpts.transition.easing);
572
572
  }
573
573
 
574
- // Drawing.setTranslate doesn't work here becasue of the transition duck-typing.
574
+ // Drawing.setTranslate doesn't work here because of the transition duck-typing.
575
575
  // It's also not necessary because there are no other transitions to preserve.
576
576
  el.attr('transform', strTranslate(x - constants.gripWidth * 0.5, sliderOpts._dims.currentValueTotalHeight));
577
577
  }
package/src/lib/index.js CHANGED
@@ -154,6 +154,7 @@ lib.getElementAndAncestors = domModule.getElementAndAncestors;
154
154
  lib.equalDomRects = domModule.equalDomRects;
155
155
 
156
156
  lib.clearResponsive = require('./clear_responsive');
157
+ lib.preserveDrawingBuffer = require('./preserve_drawing_buffer');
157
158
 
158
159
  lib.makeTraceGroups = require('./make_trace_groups');
159
160
 
@@ -864,7 +865,7 @@ lib.objectFromPath = function(path, value) {
864
865
  * lib.expandObjectPaths({'foo[1].bar': 10, 'foo[0].bar': 20});
865
866
  * => { foo: [{bar: 10}, {bar: 20}] }
866
867
  *
867
- * It does NOT, however, merge mulitple mutliply-nested arrays::
868
+ * It does NOT, however, merge multiple multiply-nested arrays::
868
869
  *
869
870
  * lib.expandObjectPaths({'marker[1].range[1]': 5, 'marker[1].range[0]': 4})
870
871
  * => { marker: [null, {range: 4}] }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Copyright 2012-2020, Plotly, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ var isNumeric = require('fast-isnumeric');
12
+ var isMobileOrTablet = require('is-mobile');
13
+
14
+ module.exports = function preserveDrawingBuffer(opts) {
15
+ var ua;
16
+
17
+ if(opts && opts.hasOwnProperty('userAgent')) {
18
+ ua = opts.userAgent;
19
+ } else {
20
+ ua = getUserAgent();
21
+ }
22
+
23
+ if(typeof ua !== 'string') return true;
24
+
25
+ var enable = isMobileOrTablet({
26
+ ua: { headers: {'user-agent': ua }},
27
+ tablet: true,
28
+ featureDetect: false
29
+ });
30
+
31
+ if(!enable) {
32
+ var allParts = ua.split(' ');
33
+ for(var i = 1; i < allParts.length; i++) {
34
+ var part = allParts[i];
35
+ if(part.indexOf('Safari') !== -1) {
36
+ // find Safari version
37
+ for(var k = i - 1; k > -1; k--) {
38
+ var prevPart = allParts[k];
39
+ if(prevPart.substr(0, 8) === 'Version/') {
40
+ var v = prevPart.substr(8).split('.')[0];
41
+ if(isNumeric(v)) v = +v;
42
+ if(v >= 13) return true;
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ return enable;
50
+ };
51
+
52
+ function getUserAgent() {
53
+ // similar to https://github.com/juliangruber/is-mobile/blob/91ca39ccdd4cfc5edfb5391e2515b923a730fbea/index.js#L14-L17
54
+ var ua;
55
+ if(typeof navigator !== 'undefined') {
56
+ ua = navigator.userAgent;
57
+ }
58
+
59
+ if(
60
+ ua &&
61
+ ua.headers &&
62
+ typeof ua.headers['user-agent'] === 'string'
63
+ ) {
64
+ ua = ua.headers['user-agent'];
65
+ }
66
+
67
+ return ua;
68
+ }
@@ -1948,7 +1948,9 @@ function axRangeSupplyDefaultsByPass(gd, flags, specs) {
1948
1948
  var axIn = gd.layout[axName];
1949
1949
  var axOut = fullLayout[axName];
1950
1950
  axOut.autorange = axIn.autorange;
1951
- axOut.range = axIn.range.slice();
1951
+ if(axIn.range) {
1952
+ axOut.range = axIn.range.slice();
1953
+ }
1952
1954
  axOut.cleanRange();
1953
1955
 
1954
1956
  if(axOut._matchGroup) {
@@ -2735,16 +2737,6 @@ function react(gd, data, layout, config) {
2735
2737
 
2736
2738
  applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout);
2737
2739
 
2738
- var allNames = Object.getOwnPropertyNames(oldFullLayout);
2739
- for(var q = 0; q < allNames.length; q++) {
2740
- var name = allNames[q];
2741
- var start = name.substring(0, 5);
2742
- if(start === 'xaxis' || start === 'yaxis') {
2743
- var emptyCategories = oldFullLayout[name]._emptyCategories;
2744
- if(emptyCategories) emptyCategories();
2745
- }
2746
- }
2747
-
2748
2740
  // "true" skips updating calcdata and remapping arrays from calcTransforms,
2749
2741
  // which supplyDefaults usually does at the end, but we may need to NOT do
2750
2742
  // if the diff (which we haven't determined yet) says we'll recalc
@@ -2770,10 +2762,22 @@ function react(gd, data, layout, config) {
2770
2762
 
2771
2763
  if(updateAutosize(gd)) relayoutFlags.layoutReplot = true;
2772
2764
 
2773
- // clear calcdata if required
2774
- if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined;
2765
+ // clear calcdata and empty categories if required
2766
+ if(restyleFlags.calc || relayoutFlags.calc) {
2767
+ gd.calcdata = undefined;
2768
+ var allNames = Object.getOwnPropertyNames(newFullLayout);
2769
+ for(var q = 0; q < allNames.length; q++) {
2770
+ var name = allNames[q];
2771
+ var start = name.substring(0, 5);
2772
+ if(start === 'xaxis' || start === 'yaxis') {
2773
+ var emptyCategories = newFullLayout[name]._emptyCategories;
2774
+ if(emptyCategories) emptyCategories();
2775
+ }
2776
+ }
2775
2777
  // otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier
2776
- else Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
2778
+ } else {
2779
+ Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
2780
+ }
2777
2781
 
2778
2782
  // Note: what restyle/relayout use impliedEdits and clearAxisTypes for
2779
2783
  // must be handled by the user when using Plotly.react.
@@ -3227,7 +3231,7 @@ function animate(gd, frameOrGroupNameOrFrameList, animationOpts) {
3227
3231
  }
3228
3232
 
3229
3233
  // Execute a callback after the wrapper function has been called n times.
3230
- // This is used to defer the resolution until a transition has resovled *and*
3234
+ // This is used to defer the resolution until a transition has resolved *and*
3231
3235
  // the frame has completed. If it's not done this way, then we get a race
3232
3236
  // condition in which the animation might resolve before a transition is complete
3233
3237
  // or vice versa.
@@ -14,7 +14,9 @@ var Lib = require('../../lib');
14
14
  var FP_SAFE = require('../../constants/numerical').FP_SAFE;
15
15
  var Registry = require('../../registry');
16
16
 
17
- var getFromId = require('./axis_ids').getFromId;
17
+ var axIds = require('./axis_ids');
18
+ var getFromId = axIds.getFromId;
19
+ var isLinked = axIds.isLinked;
18
20
 
19
21
  module.exports = {
20
22
  getAutoRange: getAutoRange,
@@ -56,8 +58,9 @@ function getAutoRange(gd, ax) {
56
58
  var i, j;
57
59
  var newRange = [];
58
60
 
59
- var getPadMin = makePadFn(ax, 0);
60
- var getPadMax = makePadFn(ax, 1);
61
+ var fullLayout = gd._fullLayout;
62
+ var getPadMin = makePadFn(fullLayout, ax, 0);
63
+ var getPadMax = makePadFn(fullLayout, ax, 1);
61
64
  var extremes = concatExtremes(gd, ax);
62
65
  var minArray = extremes.min;
63
66
  var maxArray = extremes.max;
@@ -117,8 +120,8 @@ function getAutoRange(gd, ax) {
117
120
  } else if(dv / axLen > mbest) {
118
121
  // in case of padding longer than the axis
119
122
  // at least include the unpadded data values.
120
- minbest = {val: minpt.val, pad: 0};
121
- maxbest = {val: maxpt.val, pad: 0};
123
+ minbest = {val: minpt.val, nopad: 1};
124
+ maxbest = {val: maxpt.val, nopad: 1};
122
125
  mbest = dv / axLen;
123
126
  }
124
127
  }
@@ -155,17 +158,17 @@ function getAutoRange(gd, ax) {
155
158
  } else {
156
159
  if(toZero) {
157
160
  if(minbest.val >= 0) {
158
- minbest = {val: 0, pad: 0};
161
+ minbest = {val: 0, nopad: 1};
159
162
  }
160
163
  if(maxbest.val <= 0) {
161
- maxbest = {val: 0, pad: 0};
164
+ maxbest = {val: 0, nopad: 1};
162
165
  }
163
166
  } else if(nonNegative) {
164
167
  if(minbest.val - mbest * getPadMin(minbest) < 0) {
165
- minbest = {val: 0, pad: 0};
168
+ minbest = {val: 0, nopad: 1};
166
169
  }
167
170
  if(maxbest.val <= 0) {
168
- maxbest = {val: 1, pad: 0};
171
+ maxbest = {val: 1, nopad: 1};
169
172
  }
170
173
  }
171
174
 
@@ -202,13 +205,15 @@ function calcBreaksLength(ax, v0, v1) {
202
205
  * calculate the pixel padding for ax._min and ax._max entries with
203
206
  * optional extrapad as 5% of the total axis length
204
207
  */
205
- function makePadFn(ax, max) {
208
+ function makePadFn(fullLayout, ax, max) {
206
209
  // 5% padding for points that specify extrapad: true
207
210
  var extrappad = 0.05 * ax._length;
208
211
 
212
+ var anchorAxis = ax._anchorAxis || {};
213
+
209
214
  if(
210
215
  (ax.ticklabelposition || '').indexOf('inside') !== -1 ||
211
- ((ax._anchorAxis || {}).ticklabelposition || '').indexOf('inside') !== -1
216
+ (anchorAxis.ticklabelposition || '').indexOf('inside') !== -1
212
217
  ) {
213
218
  var axReverse = ax.autorange === 'reversed';
214
219
  if(!axReverse) {
@@ -218,8 +223,11 @@ function makePadFn(ax, max) {
218
223
  if(axReverse) max = !max;
219
224
  }
220
225
 
221
- extrappad = adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max);
222
- extrappad = adjustPadForInsideLabelsOnThisAxis(extrappad, ax, max);
226
+ var zero = 0;
227
+ if(!isLinked(fullLayout, ax._id)) {
228
+ zero = padInsideLabelsOnAnchorAxis(ax, max);
229
+ }
230
+ extrappad = Math.max(zero, extrappad);
223
231
 
224
232
  // domain-constrained axes: base extrappad on the unconstrained
225
233
  // domain so it's consistent as the domain changes
@@ -228,50 +236,17 @@ function makePadFn(ax, max) {
228
236
  (ax.domain[1] - ax.domain[0]);
229
237
  }
230
238
 
231
- return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); };
239
+ return function getPad(pt) {
240
+ if(pt.nopad) return 0;
241
+ return pt.pad + (pt.extrapad ? extrappad : zero);
242
+ };
232
243
  }
233
244
 
234
245
  var TEXTPAD = 3;
235
246
 
236
- function adjustPadForInsideLabelsOnThisAxis(extrappad, ax, max) {
237
- var ticklabelposition = ax.ticklabelposition || '';
238
- var has = function(str) {
239
- return ticklabelposition.indexOf(str) !== -1;
240
- };
241
-
242
- if(!has('inside')) return extrappad;
243
- var isTop = has('top');
244
- var isLeft = has('left');
245
- var isRight = has('right');
246
- var isBottom = has('bottom');
247
- var isAligned = isBottom || isLeft || isTop || isRight;
248
-
249
- if(
250
- (max && (isLeft || isBottom)) ||
251
- (!max && (isRight || isTop))
252
- ) {
253
- return extrappad;
254
- }
255
-
256
- // increase padding to make more room for inside tick labels of the axis
257
- var fontSize = ax.tickfont ? ax.tickfont.size : 12;
258
- var isX = ax._id.charAt(0) === 'x';
259
- var morePad = (isX ? 1.2 : 0.6) * fontSize;
260
-
261
- if(isAligned) {
262
- morePad *= 2;
263
- morePad += (ax.tickwidth || 0) / 2;
264
- }
265
-
266
- morePad += TEXTPAD;
267
-
268
- extrappad = Math.max(extrappad, morePad);
269
-
270
- return extrappad;
271
- }
272
-
273
- function adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max) {
274
- var anchorAxis = (ax._anchorAxis || {});
247
+ function padInsideLabelsOnAnchorAxis(ax, max) {
248
+ var pad = 0;
249
+ var anchorAxis = ax._anchorAxis || {};
275
250
  if((anchorAxis.ticklabelposition || '').indexOf('inside') !== -1) {
276
251
  // increase padding to make more room for inside tick labels of the counter axis
277
252
  if((
@@ -287,7 +262,6 @@ function adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max) {
287
262
  )) {
288
263
  var isX = ax._id.charAt(0) === 'x';
289
264
 
290
- var morePad = 0;
291
265
  if(anchorAxis._vals) {
292
266
  var rad = Lib.deg2rad(anchorAxis._tickAngles[anchorAxis._id + 'tick'] || 0);
293
267
  var cosA = Math.abs(Math.cos(rad));
@@ -296,29 +270,24 @@ function adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max) {
296
270
  // use bounding boxes
297
271
  anchorAxis._vals.forEach(function(t) {
298
272
  if(t.bb) {
299
- var w = t.bb.width;
300
- var h = t.bb.height;
273
+ var w = 2 * TEXTPAD + t.bb.width;
274
+ var h = 2 * TEXTPAD + t.bb.height;
301
275
 
302
- morePad = Math.max(morePad, isX ?
276
+ pad = Math.max(pad, isX ?
303
277
  Math.max(w * cosA, h * sinA) :
304
278
  Math.max(h * cosA, w * sinA)
305
279
  );
306
-
307
- // add extra pad around label
308
- morePad += 3;
309
280
  }
310
281
  });
311
282
  }
312
283
 
313
284
  if(anchorAxis.ticks === 'inside' && anchorAxis.ticklabelposition === 'inside') {
314
- morePad += anchorAxis.ticklen || 0;
285
+ pad += anchorAxis.ticklen || 0;
315
286
  }
316
-
317
- extrappad = Math.max(extrappad, morePad);
318
287
  }
319
288
  }
320
289
 
321
- return extrappad;
290
+ return pad;
322
291
  }
323
292
 
324
293
  function concatExtremes(gd, ax, noMatch) {
@@ -56,6 +56,10 @@ axes.setConvert = require('./set_convert');
56
56
  var autoType = require('./axis_autotype');
57
57
 
58
58
  var axisIds = require('./axis_ids');
59
+ var idSort = axisIds.idSort;
60
+ var isLinked = axisIds.isLinked;
61
+
62
+ // tight coupling to chart studio
59
63
  axes.id2name = axisIds.id2name;
60
64
  axes.name2id = axisIds.name2id;
61
65
  axes.cleanId = axisIds.cleanId;
@@ -1445,9 +1449,23 @@ function formatDate(ax, out, hover, extraPrecision) {
1445
1449
  // except for year headPart: turn this into "Jan 1, 2000" etc.
1446
1450
  if(tr === 'd') dateStr += ', ' + headStr;
1447
1451
  else dateStr = headStr + (dateStr ? ', ' + dateStr : '');
1448
- } else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) {
1449
- dateStr += '<br>' + headStr;
1450
- ax._prevDateHead = headStr;
1452
+ } else {
1453
+ if(
1454
+ !ax._inCalcTicks ||
1455
+ ax._prevDateHead !== headStr
1456
+ ) {
1457
+ ax._prevDateHead = headStr;
1458
+ dateStr += '<br>' + headStr;
1459
+ } else {
1460
+ var isInside = (ax.ticklabelposition || '').indexOf('inside') !== -1;
1461
+ var side = ax._realSide || ax.side; // polar mocks the side of the radial axis
1462
+ if(
1463
+ (!isInside && side === 'top') ||
1464
+ (isInside && side === 'bottom')
1465
+ ) {
1466
+ dateStr += '<br> ';
1467
+ }
1468
+ }
1451
1469
  }
1452
1470
  }
1453
1471
 
@@ -2893,7 +2911,7 @@ axes.drawZeroLine = function(gd, ax, opts) {
2893
2911
  // If several zerolines enter at the same time we will sort once per,
2894
2912
  // but generally this should be a minimal overhead.
2895
2913
  opts.layer.selectAll('path').sort(function(da, db) {
2896
- return axisIds.idSort(da.id, db.id);
2914
+ return idSort(da.id, db.id);
2897
2915
  });
2898
2916
  });
2899
2917
 
@@ -3191,7 +3209,8 @@ axes.drawLabels = function(gd, ax, opts) {
3191
3209
  var anchorAx = ax._anchorAxis;
3192
3210
  if(
3193
3211
  anchorAx && anchorAx.autorange &&
3194
- (ax.ticklabelposition || '').indexOf('inside') !== -1
3212
+ (ax.ticklabelposition || '').indexOf('inside') !== -1 &&
3213
+ !isLinked(fullLayout, ax._id)
3195
3214
  ) {
3196
3215
  if(!fullLayout._insideTickLabelsAutorange) {
3197
3216
  fullLayout._insideTickLabelsAutorange = {};
@@ -3334,27 +3353,36 @@ function drawTitle(gd, ax) {
3334
3353
  var axId = ax._id;
3335
3354
  var axLetter = axId.charAt(0);
3336
3355
  var fontSize = ax.title.font.size;
3337
-
3338
3356
  var titleStandoff;
3339
3357
 
3340
3358
  if(ax.title.hasOwnProperty('standoff')) {
3341
3359
  titleStandoff = ax._depth + ax.title.standoff + approxTitleDepth(ax);
3342
3360
  } else {
3361
+ var isInside = (ax.ticklabelposition || '').indexOf('inside') !== -1;
3362
+
3343
3363
  if(ax.type === 'multicategory') {
3344
3364
  titleStandoff = ax._depth;
3345
3365
  } else {
3346
- var offsetBase = 1.5;
3347
- titleStandoff = 10 + fontSize * offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
3366
+ var offsetBase = 1.5 * fontSize;
3367
+ if(isInside) {
3368
+ offsetBase = 0.5 * fontSize;
3369
+ if(ax.ticks === 'outside') {
3370
+ offsetBase += ax.ticklen;
3371
+ }
3372
+ }
3373
+ titleStandoff = 10 + offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
3348
3374
  }
3349
3375
 
3350
- if(axLetter === 'x') {
3351
- titleStandoff += ax.side === 'top' ?
3352
- fontSize * (ax.showticklabels ? 1 : 0) :
3353
- fontSize * (ax.showticklabels ? 1.5 : 0.5);
3354
- } else {
3355
- titleStandoff += ax.side === 'right' ?
3356
- fontSize * (ax.showticklabels ? 1 : 0.5) :
3357
- fontSize * (ax.showticklabels ? 0.5 : 0);
3376
+ if(!isInside) {
3377
+ if(axLetter === 'x') {
3378
+ titleStandoff += ax.side === 'top' ?
3379
+ fontSize * (ax.showticklabels ? 1 : 0) :
3380
+ fontSize * (ax.showticklabels ? 1.5 : 0.5);
3381
+ } else {
3382
+ titleStandoff += ax.side === 'right' ?
3383
+ fontSize * (ax.showticklabels ? 1 : 0.5) :
3384
+ fontSize * (ax.showticklabels ? 0.5 : 0);
3385
+ }
3358
3386
  }
3359
3387
  }
3360
3388
 
@@ -136,3 +136,19 @@ exports.ref2id = function(ar) {
136
136
  // return the axis ID. Otherwise it returns false.
137
137
  return (/^[xyz]/.test(ar)) ? ar.split(' ')[0] : false;
138
138
  };
139
+
140
+ function isFound(axId, list) {
141
+ if(list && list.length) {
142
+ for(var i = 0; i < list.length; i++) {
143
+ if(list[i][axId]) return true;
144
+ }
145
+ }
146
+ return false;
147
+ }
148
+
149
+ exports.isLinked = function(fullLayout, axId) {
150
+ return (
151
+ isFound(axId, fullLayout._axisMatchGroups) ||
152
+ isFound(axId, fullLayout._axisConstraintGroups)
153
+ );
154
+ };
@@ -565,8 +565,8 @@ exports.enforce = function enforce(gd) {
565
565
  // *are* expanding to the full domain
566
566
  var outerMin = rangeCenter - halfRange * factor * 1.0001;
567
567
  var outerMax = rangeCenter + halfRange * factor * 1.0001;
568
- var getPadMin = autorange.makePadFn(ax, 0);
569
- var getPadMax = autorange.makePadFn(ax, 1);
568
+ var getPadMin = autorange.makePadFn(fullLayout, ax, 0);
569
+ var getPadMax = autorange.makePadFn(fullLayout, ax, 1);
570
570
 
571
571
  updateDomain(ax, factor);
572
572
  var m = Math.abs(ax._m);
@@ -525,7 +525,10 @@ module.exports = {
525
525
  'top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*.',
526
526
  'Similarly',
527
527
  'left or right has no effect on y axes or when `ticklabelmode` is set to *period*.',
528
- 'Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*.'
528
+ 'Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*.',
529
+ 'When used on axes linked by `matches` or `scaleanchor`,',
530
+ 'no extra padding for inside labels would be added by autorange,',
531
+ 'so that the scales could match.'
529
532
  ].join(' ')
530
533
  },
531
534
  mirror: {
@@ -18,6 +18,7 @@ var passiveSupported = require('has-passive-events');
18
18
 
19
19
  var Registry = require('../../registry');
20
20
  var Lib = require('../../lib');
21
+ var preserveDrawingBuffer = Lib.preserveDrawingBuffer();
21
22
 
22
23
  var Axes = require('../../plots/cartesian/axes');
23
24
  var Fx = require('../../components/fx');
@@ -30,9 +31,6 @@ var createAxesOptions = require('./layout/convert');
30
31
  var createSpikeOptions = require('./layout/spikes');
31
32
  var computeTickMarks = require('./layout/tick_marks');
32
33
 
33
- var isMobile = require('is-mobile')({ tablet: true, featureDetect: true });
34
-
35
-
36
34
  var STATIC_CANVAS, STATIC_CONTEXT;
37
35
 
38
36
  function Scene(options, fullLayout) {
@@ -98,7 +96,7 @@ proto.prepareOptions = function() {
98
96
  canvas: scene.canvas,
99
97
  gl: scene.gl,
100
98
  glOptions: {
101
- preserveDrawingBuffer: isMobile,
99
+ preserveDrawingBuffer: preserveDrawingBuffer,
102
100
  premultipliedAlpha: true,
103
101
  antialias: true
104
102
  },
@@ -148,26 +146,26 @@ proto.tryCreatePlot = function() {
148
146
  try {
149
147
  scene.glplot = createPlot(opts);
150
148
  } catch(e) {
151
- if(scene.staticMode || !firstInit) {
149
+ if(scene.staticMode || !firstInit || preserveDrawingBuffer) {
152
150
  success = false;
153
151
  } else { // try second time
152
+ // enable preserveDrawingBuffer setup
153
+ // in case is-mobile not detecting the right device
154
+ Lib.warn([
155
+ 'webgl setup failed possibly due to',
156
+ 'false preserveDrawingBuffer config.',
157
+ 'The mobile/tablet device may not be detected by is-mobile module.',
158
+ 'Enabling preserveDrawingBuffer in second attempt to create webgl scene...'
159
+ ].join(' '));
160
+
154
161
  try {
155
- // invert preserveDrawingBuffer setup which could be resulted from is-mobile not detecting the right device
156
- Lib.warn([
157
- 'webgl setup failed possibly due to',
158
- isMobile ? 'disabling' : 'enabling',
159
- 'preserveDrawingBuffer config.',
160
- 'The device may not be supported by is-mobile module!',
161
- 'Inverting preserveDrawingBuffer option in second attempt to create webgl scene.'
162
- ].join(' '));
163
-
164
- // invert is-mobile
165
- isMobile = opts.glOptions.preserveDrawingBuffer = !opts.glOptions.preserveDrawingBuffer;
162
+ // invert preserveDrawingBuffer
163
+ preserveDrawingBuffer = opts.glOptions.preserveDrawingBuffer = true;
166
164
 
167
165
  scene.glplot = createPlot(opts);
168
166
  } catch(e) {
169
- // revert changes to is-mobile
170
- isMobile = opts.glOptions.preserveDrawingBuffer = !opts.glOptions.preserveDrawingBuffer;
167
+ // revert changes to preserveDrawingBuffer
168
+ preserveDrawingBuffer = opts.glOptions.preserveDrawingBuffer = false;
171
169
 
172
170
  success = false;
173
171
  }