diff --git a/src/constants/numerical.js b/src/constants/numerical.js
index 6b0d6b55ed2..ac18cf60ee3 100644
--- a/src/constants/numerical.js
+++ b/src/constants/numerical.js
@@ -47,5 +47,11 @@ module.exports = {
/*
* Are two values nearly equal? Compare to 1PPM
*/
- ALMOST_EQUAL: 1 - 1e-6
+ ALMOST_EQUAL: 1 - 1e-6,
+
+ /*
+ * not a number, but for displaying numbers: the "minus sign" symbol is
+ * wider than the regular ascii dash "-"
+ */
+ MINUS_SIGN: '\u2212'
};
diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js
index 8a25ad031c4..c2009959aa3 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -27,6 +27,7 @@ var ONEDAY = constants.ONEDAY;
var ONEHOUR = constants.ONEHOUR;
var ONEMIN = constants.ONEMIN;
var ONESEC = constants.ONESEC;
+var MINUS_SIGN = constants.MINUS_SIGN;
var MID_SHIFT = require('../../constants/alignment').MID_SHIFT;
@@ -1055,7 +1056,7 @@ function autoTickRound(ax) {
var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01);
if(Math.abs(rangeexp) > 3) {
- if(ax.exponentformat === 'SI' || ax.exponentformat === 'B') {
+ if(isSIFormat(ax.exponentformat) && !beyondSI(rangeexp)) {
ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3);
}
else ax._tickexponent = rangeexp;
@@ -1299,12 +1300,13 @@ function formatLog(ax, out, hover, extraPrecision, hideexp) {
out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision);
}
else if(isNumeric(dtick) || ((dtick.charAt(0) === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) {
- if(['e', 'E', 'power'].indexOf(ax.exponentformat) !== -1) {
- var p = Math.round(x);
+ var p = Math.round(x);
+ if(['e', 'E', 'power'].indexOf(ax.exponentformat) !== -1 ||
+ (isSIFormat(ax.exponentformat) && beyondSI(p))) {
if(p === 0) out.text = 1;
else if(p === 1) out.text = '10';
else if(p > 1) out.text = '10' + p + '';
- else out.text = '10\u2212' + -p + '';
+ else out.text = '10' + MINUS_SIGN + -p + '';
out.fontSize *= 1.25;
}
@@ -1359,6 +1361,21 @@ function formatLinear(ax, out, hover, extraPrecision, hideexp) {
// also automatically switch to sci. notation
var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T'];
+function isSIFormat(exponentFormat) {
+ return exponentFormat === 'SI' || exponentFormat === 'B';
+}
+
+// are we beyond the range of common SI prefixes?
+// 10^-16 -> 1x10^-16
+// 10^-15 -> 1f
+// ...
+// 10^14 -> 100T
+// 10^15 -> 1x10^15
+// 10^16 -> 1x10^16
+function beyondSI(exponent) {
+ return exponent > 14 || exponent < -15;
+}
+
function numFormat(v, ax, fmtoverride, hover) {
// negative?
var isNeg = v < 0,
@@ -1387,7 +1404,7 @@ function numFormat(v, ax, fmtoverride, hover) {
if(ax.hoverformat) tickformat = ax.hoverformat;
}
- if(tickformat) return d3.format(tickformat)(v).replace(/-/g, '\u2212');
+ if(tickformat) return d3.format(tickformat)(v).replace(/-/g, MINUS_SIGN);
// 'epsilon' - rounding increment
var e = Math.pow(10, -tickRound) / 2;
@@ -1436,14 +1453,14 @@ function numFormat(v, ax, fmtoverride, hover) {
// add exponent
if(exponent && exponentFormat !== 'hide') {
+ if(isSIFormat(exponentFormat) && beyondSI(exponent)) exponentFormat = 'power';
+
var signedExponent;
- if(exponent < 0) signedExponent = '\u2212' + -exponent;
+ if(exponent < 0) signedExponent = MINUS_SIGN + -exponent;
else if(exponentFormat !== 'power') signedExponent = '+' + exponent;
else signedExponent = String(exponent);
- if(exponentFormat === 'e' ||
- ((exponentFormat === 'SI' || exponentFormat === 'B') &&
- (exponent > 12 || exponent < -15))) {
+ if(exponentFormat === 'e') {
v += 'e' + signedExponent;
}
else if(exponentFormat === 'E') {
@@ -1455,7 +1472,7 @@ function numFormat(v, ax, fmtoverride, hover) {
else if(exponentFormat === 'B' && exponent === 9) {
v += 'B';
}
- else if(exponentFormat === 'SI' || exponentFormat === 'B') {
+ else if(isSIFormat(exponentFormat)) {
v += SIPREFIXES[exponent / 3 + 5];
}
}
@@ -1463,11 +1480,10 @@ function numFormat(v, ax, fmtoverride, hover) {
// put sign back in and return
// replace standard minus character (which is technically a hyphen)
// with a true minus sign
- if(isNeg) return '\u2212' + v;
+ if(isNeg) return MINUS_SIGN + v;
return v;
}
-
axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/;
// getSubplots - extract all combinations of axes we need to make plots for
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index b76259e1e48..0367649a9e1 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -1929,6 +1929,112 @@ describe('Test axes', function() {
});
}
+ it('reverts to "power" for SI/B exponentformat beyond the prefix range (linear case)', function() {
+ var textOut = mockCalc({
+ type: 'linear',
+ tickmode: 'linear',
+ exponentformat: 'B',
+ showexponent: 'all',
+ tick0: 0,
+ dtick: 1e13,
+ range: [8.5e13, 11.5e13]
+ });
+
+ expect(textOut).toEqual([
+ '90T', '100T', '110T'
+ ]);
+
+ textOut = mockCalc({
+ type: 'linear',
+ tickmode: 'linear',
+ exponentformat: 'B',
+ showexponent: 'all',
+ tick0: 0,
+ dtick: 1e14,
+ range: [8.5e14, 11.5e14]
+ });
+
+ expect(textOut).toEqual([
+ '0.9×1015',
+ '1×1015',
+ '1.1×1015'
+ ]);
+
+ textOut = mockCalc({
+ type: 'linear',
+ tickmode: 'linear',
+ exponentformat: 'SI',
+ showexponent: 'all',
+ tick0: 0,
+ dtick: 1e-16,
+ range: [8.5e-16, 11.5e-16]
+ });
+
+ expect(textOut).toEqual([
+ '0.9f', '1f', '1.1f'
+ ]);
+
+ textOut = mockCalc({
+ type: 'linear',
+ tickmode: 'linear',
+ exponentformat: 'SI',
+ showexponent: 'all',
+ tick0: 0,
+ dtick: 1e-17,
+ range: [8.5e-17, 11.5e-17]
+ });
+
+ expect(textOut).toEqual([
+ '0.9×10\u221216',
+ '1×10\u221216',
+ '1.1×10\u221216'
+ ]);
+ });
+
+ it('reverts to "power" for SI/B exponentformat beyond the prefix range (log case)', function() {
+ var textOut = mockCalc({
+ type: 'log',
+ tickmode: 'linear',
+ exponentformat: 'B',
+ showexponent: 'all',
+ tick0: 0,
+ dtick: 1,
+ range: [-18.5, 18.5]
+ });
+
+ expect(textOut).toEqual([
+ '10\u221218',
+ '10\u221217',
+ '10\u221216',
+ '1f', '10f', '100f', '1p', '10p', '100p', '1n', '10n', '100n',
+ '1μ', '10μ', '100μ', '0.001', '0.01', '0.1', '1', '10', '100',
+ '1000', '10k', '100k', '1M', '10M', '100M', '1B', '10B', '100B',
+ '1T', '10T', '100T',
+ '1015',
+ '1016',
+ '1017',
+ '1018'
+ ]);
+
+ textOut = mockCalc({
+ type: 'log',
+ tickmode: 'linear',
+ exponentformat: 'SI',
+ showexponent: 'all',
+ tick0: 0,
+ dtick: 'D2',
+ range: [7.9, 12.1]
+ });
+
+ expect(textOut).toEqual([
+ '100M', '2', '5',
+ '1G', '2', '5',
+ '10G', '2', '5',
+ '100G', '2', '5',
+ '1T'
+ ]);
+ });
+
it('provides a new date suffix whenever the suffix changes', function() {
var ax = {
type: 'date',