Skip to content

Commit 270726c

Browse files
committed
Merge pull request #547 from plotly/pie-separators
Pie: number separators formatting
2 parents 796c49e + eeb10f7 commit 270726c

File tree

9 files changed

+158
-41
lines changed

9 files changed

+158
-41
lines changed

src/lib/index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,47 @@ lib.objectFromPath = function(path, value) {
541541

542542
return obj;
543543
};
544+
545+
/**
546+
* Converts value to string separated by the provided separators.
547+
*
548+
* @example
549+
* lib.numSeparate(2016, '.,');
550+
* // returns '2016'
551+
*
552+
* @example
553+
* lib.numSeparate(1234.56, '|,')
554+
* // returns '1,234|56'
555+
*
556+
* @param {string|number} value the value to be converted
557+
* @param {string} separators string of decimal, then thousands separators
558+
*
559+
* @return {string} the value that has been separated
560+
*/
561+
lib.numSeparate = function(value, separators) {
562+
563+
if(typeof separators !== 'string' || separators.length === 0) {
564+
throw new Error('Separator string required for formatting!');
565+
}
566+
567+
if(typeof value === 'number') {
568+
value = String(value);
569+
}
570+
571+
var thousandsRe = /(\d+)(\d{3})/,
572+
decimalSep = separators.charAt(0),
573+
thouSep = separators.charAt(1);
574+
575+
var x = value.split('.'),
576+
x1 = x[0],
577+
x2 = x.length > 1 ? decimalSep + x[1] : '';
578+
579+
// Years are ignored for thousands separators
580+
if(thouSep && (x.length > 1 || x1.length>4)) {
581+
while(thousandsRe.test(x1)) {
582+
x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2');
583+
}
584+
}
585+
586+
return x1 + x2;
587+
};

src/plots/cartesian/axes.js

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,7 @@ function numFormat(v, ax, fmtoverride, hover) {
11051105
if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, '');
11061106
}
11071107
// insert appropriate decimal point and thousands separator
1108-
v = numSeparate(v, ax._gd._fullLayout.separators);
1108+
v = Lib.numSeparate(v, ax._gd._fullLayout.separators);
11091109
}
11101110

11111111
// add exponent
@@ -1141,27 +1141,6 @@ function numFormat(v, ax, fmtoverride, hover) {
11411141
return v;
11421142
}
11431143

1144-
// add arbitrary decimal point and thousands separator
1145-
var findThousands = /(\d+)(\d{3})/;
1146-
function numSeparate(nStr, separators) {
1147-
// separators - first char is decimal point,
1148-
// next char is thousands separator if there is one
1149-
1150-
var dp = separators.charAt(0),
1151-
thou = separators.charAt(1),
1152-
x = nStr.split('.'),
1153-
x1 = x[0],
1154-
x2 = x.length > 1 ? dp + x[1] : '';
1155-
// even if there is a thousands separator, don't use it on
1156-
// 4-digit integers (like years)
1157-
if(thou && (x.length > 1 || x1.length>4)) {
1158-
while(findThousands.test(x1)) {
1159-
x1 = x1.replace(findThousands, '$1' + thou + '$2');
1160-
}
1161-
}
1162-
return x1 + x2;
1163-
}
1164-
11651144

11661145
axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/;
11671146

src/traces/pie/calc.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@ module.exports = function calc(gd, trace) {
107107
hasText = trace.textinfo.indexOf('text') !== -1,
108108
hasValue = trace.textinfo.indexOf('value') !== -1,
109109
hasPercent = trace.textinfo.indexOf('percent') !== -1,
110+
separators = fullLayout.separators,
110111
thisText;
111112

112113
for(i = 0; i < cd.length; i++) {
113114
pt = cd[i];
114115
thisText = hasLabel ? [pt.label] : [];
115116
if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]);
116-
if(hasValue) thisText.push(helpers.formatPieValue(pt.v));
117-
if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal));
117+
if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators));
118+
if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators));
118119
pt.text = thisText.join('<br>');
119120
}
120121
}

src/traces/pie/helpers.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@
88

99
'use strict';
1010

11-
exports.formatPiePercent = function formatPiePercent(v) {
11+
var Lib = require('../../lib');
12+
13+
exports.formatPiePercent = function formatPiePercent(v, separators) {
1214
var vRounded = (v * 100).toPrecision(3);
13-
if(vRounded.indexOf('.') !== -1) return vRounded.replace(/[.]?0+$/,'') + '%';
14-
return vRounded + '%';
15+
if(vRounded.lastIndexOf('.') !== -1) {
16+
vRounded = vRounded.replace(/[.]?0+$/, '');
17+
}
18+
return Lib.numSeparate(vRounded, separators) + '%';
1519
};
1620

17-
exports.formatPieValue = function formatPieValue(v) {
21+
exports.formatPieValue = function formatPieValue(v, separators) {
1822
var vRounded = v.toPrecision(10);
19-
if(vRounded.indexOf('.') !== -1) return vRounded.replace(/[.]?0+$/,'');
20-
return vRounded;
23+
if(vRounded.lastIndexOf('.') !== -1) {
24+
vRounded = vRounded.replace(/[.]?0+$/, '');
25+
}
26+
return Lib.numSeparate(vRounded, separators);
2127
};

src/traces/pie/plot.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,15 @@ module.exports = function plot(gd, cdpie) {
103103
var rInscribed = getInscribedRadiusFraction(pt, cd0),
104104
hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed),
105105
hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed),
106+
separators = fullLayout.separators,
106107
thisText = [];
108+
107109
if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label);
108110
if(trace2.text && trace2.text[pt.i] && hoverinfo.indexOf('text') !== -1) {
109111
thisText.push(trace2.text[pt.i]);
110112
}
111-
if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v));
112-
if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal));
113+
if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators));
114+
if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
113115

114116
Fx.loneHover({
115117
x0: hoverCenterX - rInscribed * cd0.r,

test/image/baselines/pie_fonts.png

3.49 KB
Loading

test/image/mocks/pie_fonts.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"data": [
33
{
44
"values": [
5-
1,
6-
9
5+
1.151,
6+
8.134
77
],
88
"text": [
99
"inherit from<br><b>global...</b>",
@@ -17,8 +17,8 @@
1717
},
1818
{
1919
"values": [
20-
1,
21-
9
20+
1.151,
21+
8.134
2222
],
2323
"text": [
2424
"a font of...",
@@ -37,8 +37,8 @@
3737
},
3838
{
3939
"values": [
40-
1,
41-
9
40+
1.151,
41+
8.134
4242
],
4343
"text": [
4444
"outside and textfont<br>both modify...",
@@ -58,8 +58,8 @@
5858
},
5959
{
6060
"values": [
61-
1,
62-
9
61+
1.151,
62+
8.134
6363
],
6464
"text": [
6565
"independent fonts<br>outside and...",
@@ -89,6 +89,7 @@
8989
"family": "Old Standard TT, serif",
9090
"size": 20
9191
},
92-
"showlegend": true
92+
"showlegend": true,
93+
"separators": "∆|"
9394
}
9495
}

test/jasmine/tests/hover_pie_test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,57 @@ describe('pie hovering', function() {
111111
}, 100);
112112
});
113113
});
114+
115+
describe('labels', function() {
116+
117+
var gd,
118+
mockCopy;
119+
120+
beforeEach(function() {
121+
gd = createGraphDiv();
122+
mockCopy = Lib.extendDeep({}, mock);
123+
});
124+
125+
afterEach(destroyGraphDiv);
126+
127+
it('should show the default selected values', function(done) {
128+
129+
var expected = ['4', '5', '33.3%'];
130+
131+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
132+
133+
mouseEvent('mouseover', 230, 150);
134+
135+
var labels = Plotly.d3.selectAll('.hovertext .nums .line');
136+
137+
expect(labels[0].length).toBe(3);
138+
139+
labels.each(function(_, i) {
140+
expect(Plotly.d3.select(this).text()).toBe(expected[i]);
141+
});
142+
}).then(done);
143+
});
144+
145+
it('should show the correct separators for values', function(done) {
146+
147+
var expected = ['0', '12|345|678@91', '99@9%'];
148+
149+
mockCopy.layout.separators = '@|';
150+
mockCopy.data[0].values[0] = 12345678.912;
151+
mockCopy.data[0].values[1] = 10000;
152+
153+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
154+
155+
mouseEvent('mouseover', 230, 150);
156+
157+
var labels = Plotly.d3.selectAll('.hovertext .nums .line');
158+
159+
expect(labels[0].length).toBe(3);
160+
161+
labels.each(function(_, i) {
162+
expect(Plotly.d3.select(this).text()).toBe(expected[i]);
163+
});
164+
}).then(done);
165+
});
166+
});
114167
});

test/jasmine/tests/lib_test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,4 +930,35 @@ describe('Test lib.js:', function() {
930930

931931
});
932932
});
933+
934+
describe('numSeparate', function() {
935+
936+
it('should work on numbers and strings', function() {
937+
expect(Lib.numSeparate(12345.67, '.,')).toBe('12,345.67');
938+
expect(Lib.numSeparate('12345.67', '.,')).toBe('12,345.67');
939+
});
940+
941+
it('should ignore years', function() {
942+
expect(Lib.numSeparate(2016, '.,')).toBe('2016');
943+
});
944+
945+
it('should work for multiple thousands', function() {
946+
expect(Lib.numSeparate(1000000000, '.,')).toBe('1,000,000,000');
947+
});
948+
949+
it('should work when there\'s only one separator', function() {
950+
expect(Lib.numSeparate(12.34, '|')).toBe('12|34');
951+
expect(Lib.numSeparate(1234.56, '|')).toBe('1234|56');
952+
});
953+
954+
it('should throw an error when no separator is provided', function() {
955+
expect(function() {
956+
Lib.numSeparate(1234);
957+
}).toThrowError('Separator string required for formatting!');
958+
959+
expect(function() {
960+
Lib.numSeparate(1234, '');
961+
}).toThrowError('Separator string required for formatting!');
962+
});
963+
});
933964
});

0 commit comments

Comments
 (0)