diff --git a/src/lib/index.js b/src/lib/index.js
index eb4f17d844c..612dd2fa065 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -541,3 +541,47 @@ lib.objectFromPath = function(path, value) {
return obj;
};
+
+/**
+ * Converts value to string separated by the provided separators.
+ *
+ * @example
+ * lib.numSeparate(2016, '.,');
+ * // returns '2016'
+ *
+ * @example
+ * lib.numSeparate(1234.56, '|,')
+ * // returns '1,234|56'
+ *
+ * @param {string|number} value the value to be converted
+ * @param {string} separators string of decimal, then thousands separators
+ *
+ * @return {string} the value that has been separated
+ */
+lib.numSeparate = function(value, separators) {
+
+ if(typeof separators !== 'string' || separators.length === 0) {
+ throw new Error('Separator string required for formatting!');
+ }
+
+ if(typeof value === 'number') {
+ value = String(value);
+ }
+
+ var thousandsRe = /(\d+)(\d{3})/,
+ decimalSep = separators.charAt(0),
+ thouSep = separators.charAt(1);
+
+ var x = value.split('.'),
+ x1 = x[0],
+ x2 = x.length > 1 ? decimalSep + x[1] : '';
+
+ // Years are ignored for thousands separators
+ if(thouSep && (x.length > 1 || x1.length>4)) {
+ while(thousandsRe.test(x1)) {
+ x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2');
+ }
+ }
+
+ return x1 + x2;
+};
diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js
index 461bb077744..5c61c8b42ec 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -1105,7 +1105,7 @@ function numFormat(v, ax, fmtoverride, hover) {
if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, '');
}
// insert appropriate decimal point and thousands separator
- v = numSeparate(v, ax._gd._fullLayout.separators);
+ v = Lib.numSeparate(v, ax._gd._fullLayout.separators);
}
// add exponent
@@ -1141,27 +1141,6 @@ function numFormat(v, ax, fmtoverride, hover) {
return v;
}
-// add arbitrary decimal point and thousands separator
-var findThousands = /(\d+)(\d{3})/;
-function numSeparate(nStr, separators) {
- // separators - first char is decimal point,
- // next char is thousands separator if there is one
-
- var dp = separators.charAt(0),
- thou = separators.charAt(1),
- x = nStr.split('.'),
- x1 = x[0],
- x2 = x.length > 1 ? dp + x[1] : '';
- // even if there is a thousands separator, don't use it on
- // 4-digit integers (like years)
- if(thou && (x.length > 1 || x1.length>4)) {
- while(findThousands.test(x1)) {
- x1 = x1.replace(findThousands, '$1' + thou + '$2');
- }
- }
- return x1 + x2;
-}
-
axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/;
diff --git a/src/traces/pie/calc.js b/src/traces/pie/calc.js
index 032f89640d4..be1a2538405 100644
--- a/src/traces/pie/calc.js
+++ b/src/traces/pie/calc.js
@@ -107,14 +107,15 @@ module.exports = function calc(gd, trace) {
hasText = trace.textinfo.indexOf('text') !== -1,
hasValue = trace.textinfo.indexOf('value') !== -1,
hasPercent = trace.textinfo.indexOf('percent') !== -1,
+ separators = fullLayout.separators,
thisText;
for(i = 0; i < cd.length; i++) {
pt = cd[i];
thisText = hasLabel ? [pt.label] : [];
if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]);
- if(hasValue) thisText.push(helpers.formatPieValue(pt.v));
- if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal));
+ if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators));
+ if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators));
pt.text = thisText.join('
');
}
}
diff --git a/src/traces/pie/helpers.js b/src/traces/pie/helpers.js
index 653f6dfad8c..71323340346 100644
--- a/src/traces/pie/helpers.js
+++ b/src/traces/pie/helpers.js
@@ -8,14 +8,20 @@
'use strict';
-exports.formatPiePercent = function formatPiePercent(v) {
+var Lib = require('../../lib');
+
+exports.formatPiePercent = function formatPiePercent(v, separators) {
var vRounded = (v * 100).toPrecision(3);
- if(vRounded.indexOf('.') !== -1) return vRounded.replace(/[.]?0+$/,'') + '%';
- return vRounded + '%';
+ if(vRounded.lastIndexOf('.') !== -1) {
+ vRounded = vRounded.replace(/[.]?0+$/, '');
+ }
+ return Lib.numSeparate(vRounded, separators) + '%';
};
-exports.formatPieValue = function formatPieValue(v) {
+exports.formatPieValue = function formatPieValue(v, separators) {
var vRounded = v.toPrecision(10);
- if(vRounded.indexOf('.') !== -1) return vRounded.replace(/[.]?0+$/,'');
- return vRounded;
+ if(vRounded.lastIndexOf('.') !== -1) {
+ vRounded = vRounded.replace(/[.]?0+$/, '');
+ }
+ return Lib.numSeparate(vRounded, separators);
};
diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js
index bea712eb9fb..44e48405376 100644
--- a/src/traces/pie/plot.js
+++ b/src/traces/pie/plot.js
@@ -103,13 +103,15 @@ module.exports = function plot(gd, cdpie) {
var rInscribed = getInscribedRadiusFraction(pt, cd0),
hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed),
hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed),
+ separators = fullLayout.separators,
thisText = [];
+
if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label);
if(trace2.text && trace2.text[pt.i] && hoverinfo.indexOf('text') !== -1) {
thisText.push(trace2.text[pt.i]);
}
- if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v));
- if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal));
+ if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators));
+ if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
Fx.loneHover({
x0: hoverCenterX - rInscribed * cd0.r,
diff --git a/test/image/baselines/pie_fonts.png b/test/image/baselines/pie_fonts.png
index 1a58140d2f7..ed8b084a10b 100644
Binary files a/test/image/baselines/pie_fonts.png and b/test/image/baselines/pie_fonts.png differ
diff --git a/test/image/mocks/pie_fonts.json b/test/image/mocks/pie_fonts.json
index 6a88b272e27..2961f3ed035 100644
--- a/test/image/mocks/pie_fonts.json
+++ b/test/image/mocks/pie_fonts.json
@@ -2,8 +2,8 @@
"data": [
{
"values": [
- 1,
- 9
+ 1.151,
+ 8.134
],
"text": [
"inherit from
global...",
@@ -17,8 +17,8 @@
},
{
"values": [
- 1,
- 9
+ 1.151,
+ 8.134
],
"text": [
"a font of...",
@@ -37,8 +37,8 @@
},
{
"values": [
- 1,
- 9
+ 1.151,
+ 8.134
],
"text": [
"outside and textfont
both modify...",
@@ -58,8 +58,8 @@
},
{
"values": [
- 1,
- 9
+ 1.151,
+ 8.134
],
"text": [
"independent fonts
outside and...",
@@ -89,6 +89,7 @@
"family": "Old Standard TT, serif",
"size": 20
},
- "showlegend": true
+ "showlegend": true,
+ "separators": "∆|"
}
}
diff --git a/test/jasmine/tests/hover_pie_test.js b/test/jasmine/tests/hover_pie_test.js
index 7d96d12daf1..e7472b33951 100644
--- a/test/jasmine/tests/hover_pie_test.js
+++ b/test/jasmine/tests/hover_pie_test.js
@@ -111,4 +111,57 @@ describe('pie hovering', function() {
}, 100);
});
});
+
+ describe('labels', function() {
+
+ var gd,
+ mockCopy;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ mockCopy = Lib.extendDeep({}, mock);
+ });
+
+ afterEach(destroyGraphDiv);
+
+ it('should show the default selected values', function(done) {
+
+ var expected = ['4', '5', '33.3%'];
+
+ Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
+
+ mouseEvent('mouseover', 230, 150);
+
+ var labels = Plotly.d3.selectAll('.hovertext .nums .line');
+
+ expect(labels[0].length).toBe(3);
+
+ labels.each(function(_, i) {
+ expect(Plotly.d3.select(this).text()).toBe(expected[i]);
+ });
+ }).then(done);
+ });
+
+ it('should show the correct separators for values', function(done) {
+
+ var expected = ['0', '12|345|678@91', '99@9%'];
+
+ mockCopy.layout.separators = '@|';
+ mockCopy.data[0].values[0] = 12345678.912;
+ mockCopy.data[0].values[1] = 10000;
+
+ Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
+
+ mouseEvent('mouseover', 230, 150);
+
+ var labels = Plotly.d3.selectAll('.hovertext .nums .line');
+
+ expect(labels[0].length).toBe(3);
+
+ labels.each(function(_, i) {
+ expect(Plotly.d3.select(this).text()).toBe(expected[i]);
+ });
+ }).then(done);
+ });
+ });
});
diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js
index d91328e1dc2..b87f45de5a3 100644
--- a/test/jasmine/tests/lib_test.js
+++ b/test/jasmine/tests/lib_test.js
@@ -930,4 +930,35 @@ describe('Test lib.js:', function() {
});
});
+
+ describe('numSeparate', function() {
+
+ it('should work on numbers and strings', function() {
+ expect(Lib.numSeparate(12345.67, '.,')).toBe('12,345.67');
+ expect(Lib.numSeparate('12345.67', '.,')).toBe('12,345.67');
+ });
+
+ it('should ignore years', function() {
+ expect(Lib.numSeparate(2016, '.,')).toBe('2016');
+ });
+
+ it('should work for multiple thousands', function() {
+ expect(Lib.numSeparate(1000000000, '.,')).toBe('1,000,000,000');
+ });
+
+ it('should work when there\'s only one separator', function() {
+ expect(Lib.numSeparate(12.34, '|')).toBe('12|34');
+ expect(Lib.numSeparate(1234.56, '|')).toBe('1234|56');
+ });
+
+ it('should throw an error when no separator is provided', function() {
+ expect(function() {
+ Lib.numSeparate(1234);
+ }).toThrowError('Separator string required for formatting!');
+
+ expect(function() {
+ Lib.numSeparate(1234, '');
+ }).toThrowError('Separator string required for formatting!');
+ });
+ });
});