Skip to content

Commit

Permalink
Merge pull request #1930 from plotly/big-exponents
Browse files Browse the repository at this point in the history
Better exponent treatment beyond SI prefixes
  • Loading branch information
alexcjohnson authored Aug 5, 2017
2 parents e78ed04 + 52ac7f4 commit 6b54152
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 13 deletions.
8 changes: 7 additions & 1 deletion src/constants/numerical.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
};
40 changes: 28 additions & 12 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<sup>' + p + '</sup>';
else out.text = '10<sup>\u2212' + -p + '</sup>';
else out.text = '10<sup>' + MINUS_SIGN + -p + '</sup>';

out.fontSize *= 1.25;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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') {
Expand All @@ -1455,19 +1472,18 @@ 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];
}
}

// 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
Expand Down
106 changes: 106 additions & 0 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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×10<sup>15</sup>',
'1×10<sup>15</sup>',
'1.1×10<sup>15</sup>'
]);

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<sup>\u221216</sup>',
'1×10<sup>\u221216</sup>',
'1.1×10<sup>\u221216</sup>'
]);
});

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<sup>\u221218</sup>',
'10<sup>\u221217</sup>',
'10<sup>\u221216</sup>',
'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',
'10<sup>15</sup>',
'10<sup>16</sup>',
'10<sup>17</sup>',
'10<sup>18</sup>'
]);

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',
Expand Down

0 comments on commit 6b54152

Please sign in to comment.