Skip to content

Commit d7ac688

Browse files
committed
improved rounding & percentage handling
complex rounding rules, see minicharts/d3nfs/shared.js feels natural now though.
1 parent 0fb32f3 commit d7ac688

File tree

5 files changed

+98
-75
lines changed

5 files changed

+98
-75
lines changed

scout-ui/src/minicharts/d3fns/few.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ module.exports = function(data, g, width, height, options) {
1111
var barHeight = 25;
1212
var values = _.pluck(data, 'value');
1313
var sumValues = d3.sum(values);
14+
var maxValue = d3.max(values);
15+
var percentFormat = shared.friendlyPercentFormat(maxValue / sumValues * 100);
1416

15-
// data.x is still the label, and data.y the length of the bar
1617
var x = d3.scale.linear()
1718
.domain([0, sumValues])
1819
.range([0, width]);
@@ -26,7 +27,7 @@ module.exports = function(data, g, width, height, options) {
2627
}
2728
return d.tooltip || tooltipHtml({
2829
label: d.label,
29-
value: shared.percentFormat(d.value / sumValues)[2]
30+
value: percentFormat(d.value / sumValues * 100, false)
3031
});
3132
})
3233
.direction('n')

scout-ui/src/minicharts/d3fns/many.js

Lines changed: 29 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = function(data, g, width, height, options) {
2121
var values = _.pluck(data, 'value');
2222
var maxValue = d3.max(values);
2323
var sumValues = d3.sum(values);
24+
var percentFormat = shared.friendlyPercentFormat(maxValue / sumValues * 100);
2425

2526
var y = d3.scale.linear()
2627
.domain([0, maxValue])
@@ -35,7 +36,7 @@ module.exports = function(data, g, width, height, options) {
3536
}
3637
return d.tooltip || tooltipHtml({
3738
label: d.label,
38-
value: shared.percentFormat(d.value / sumValues)[2]
39+
value: percentFormat(d.value / sumValues * 100, false)
3940
});
4041
})
4142
.direction('n')
@@ -46,70 +47,48 @@ module.exports = function(data, g, width, height, options) {
4647
g.call(tip);
4748

4849
if (options.scale) {
49-
var maxVal = d3.max(y.domain());
50-
var scaleLabels = percentFormat(maxVal);
51-
52-
// @todo use a scale and wrap both text and line in g element
53-
var legend = g.append('g')
54-
.attr('class', 'legend')
55-
.data(scaleLabes)
56-
.enter()
57-
.append('text');
50+
var triples = function(v) {
51+
return [v, v / 2, 0];
52+
};
5853

54+
var scaleLabels = _.map(triples(maxValue / sumValues * 100), function(x) {
55+
return percentFormat(x, true);
56+
});
57+
var labelScale = d3.scale.ordinal()
58+
.domain(scaleLabels)
59+
.rangePoints([0, height]);
5960

60-
g.append('g')
61+
// @todo use a scale and wrap both text and line in g element
62+
var legend = g.selectAll('.legend')
63+
.data(scaleLabels)
64+
.enter().append('g')
6165
.attr('class', 'legend');
6266

63-
legend.append('text')
64-
.attr('class', 'legend')
67+
legend
68+
.append('text')
6569
.attr('x', 0)
6670
.attr('dx', '-1em')
67-
.attr('y', 0)
71+
.attr('y', function(d) {
72+
return labelScale(d);
73+
})
6874
.attr('dy', '0.3em')
6975
.attr('text-anchor', 'end')
70-
.text(shared.percentFormat(maxValue / sumValues));
71-
72-
legend.append('text')
73-
.attr('class', 'legend')
74-
.attr('x', 0)
75-
.attr('dx', '-1em')
76-
.attr('y', height / 2)
77-
.attr('dy', '0.3em')
78-
.attr('text-anchor', 'end')
79-
.text(shared.percentFormat(maxValue / sumValues / 2));
80-
81-
legend.append('text')
82-
.attr('class', 'legend')
83-
.attr('x', 0)
84-
.attr('dx', '-1em')
85-
.attr('y', height)
86-
.attr('dy', '0.3em')
87-
.attr('text-anchor', 'end')
88-
.text('0%');
89-
90-
legend.append('line')
91-
.attr('class', 'bg legend')
92-
.attr('x1', -5)
93-
.attr('x2', width)
94-
.attr('y1', 0)
95-
.attr('y2', 0);
76+
.text(function(d) {
77+
return d;
78+
});
9679

9780
legend.append('line')
9881
.attr('class', 'bg legend')
9982
.attr('x1', -5)
10083
.attr('x2', width)
101-
.attr('y1', height / 2)
102-
.attr('y2', height / 2);
103-
104-
legend.append('line')
105-
.attr('class', 'bg legend')
106-
.attr('x1', -5)
107-
.attr('x2', width)
108-
.attr('y1', height)
109-
.attr('y2', height);
84+
.attr('y1', function(d) {
85+
return labelScale(d);
86+
})
87+
.attr('y2', function(d) {
88+
return labelScale(d);
89+
});
11090
}
11191

112-
11392
var bar = g.selectAll('.bar')
11493
.data(data)
11594
.enter().append('g')

scout-ui/src/minicharts/d3fns/number.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ var d3 = require('d3');
22
var _ = require('lodash');
33
var many = require('./many');
44
var shared = require('./shared');
5-
var tooltipHtml = require('./tooltip.jade');
65
var debug = require('debug')('scout-ui:minicharts:number');
76

87
module.exports = function(opts) {
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
var d3 = require('d3');
2+
var debug = require('debug')('scout-ui:minicharts:shared');
3+
4+
5+
// source: http://stackoverflow.com/questions/9539513/is-there-a-reliable-way-in-javascript-to-obtain-the-number-of-decimal-places-of
6+
function decimalPlaces(number) {
7+
return ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length;
8+
}
29

310
module.exports = {
411

@@ -9,17 +16,22 @@ module.exports = {
916
left: 40
1017
},
1118

12-
percentFormat: function(v) {
13-
// round max value to 1 digit precision
19+
friendlyPercentFormat: function(vmax) {
1420
var prec1Format = d3.format('.1r');
1521
var intFormat = d3.format('.0f');
22+
var format = (vmax > 1) ? intFormat : prec1Format;
23+
var maxFormatted = format(vmax);
24+
var maxDecimals = decimalPlaces(maxFormatted);
1625

17-
// multiply by 100 for percentages
18-
v *= 100;
19-
20-
var top = v > 1 ? intFormat(v) : prec1Format(v);
21-
var mid = parseFloat(top) / 2;
22-
23-
return ['0%', mid + '%', top + '%'];
26+
return function(v, incPrec) {
27+
if (v === vmax) {
28+
return maxFormatted + '%';
29+
}
30+
if (v > 1 && !incPrec) { // v > vmax || maxFormatted % 2 === 0
31+
return d3.round(v, maxDecimals) + '%';
32+
}
33+
// adjust for corrections, if increased precision required
34+
return d3.round(v / vmax * maxFormatted, maxDecimals + 1) + '%';
35+
};
2436
}
2537
};

scout-ui/test/minicharts.test.js

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,51 @@
11
var shared = require('../src/minicharts/d3fns/shared');
2+
var _ = require('lodash');
23
var assert = require('assert');
34

5+
function triples(v) {
6+
return [v, v / 2, 0];
7+
}
8+
49
describe('shared components', function() {
5-
it('should return percentages for bottom, middle and top scale correctly', function() {
6-
assert.deepEqual(shared.percentFormat(2.1), ['0%', '105%', '210%']);
7-
assert.deepEqual(shared.percentFormat(2.0), ['0%', '100%', '200%']);
8-
assert.deepEqual(shared.percentFormat(1.0), ['0%', '50%', '100%']);
9-
assert.deepEqual(shared.percentFormat(0.995), ['0%', '50%', '100%']);
10-
assert.deepEqual(shared.percentFormat(0.99), ['0%', '49.5%', '99%']);
11-
assert.deepEqual(shared.percentFormat(0.9900001), ['0%', '49.5%', '99%']);
12-
assert.deepEqual(shared.percentFormat(0.49999), ['0%', '25%', '50%']);
13-
assert.deepEqual(shared.percentFormat(0.011), ['0%', '0.5%', '1%']);
14-
assert.deepEqual(shared.percentFormat(0.009), ['0%', '0.45%', '0.9%']);
15-
assert.deepEqual(shared.percentFormat(0.004), ['0%', '0.2%', '0.4%']);
16-
assert.deepEqual(shared.percentFormat(0.0), ['0%', '0%', '0%']);
17-
assert.deepEqual(shared.percentFormat(-0.015), ['0%', '-1%', '-2%']);
10+
it('should return percentages for top, middle and bottom scale correctly', function() {
11+
assert.deepEqual(_.map(triples(209), function(x) {
12+
return shared.friendlyPercentFormat(209)(x, true);
13+
}), ['209%', '104.5%', '0%']);
14+
assert.deepEqual(_.map(triples(200), function(x) {
15+
return shared.friendlyPercentFormat(200)(x, true);
16+
}), ['200%', '100%', '0%']);
17+
assert.deepEqual(_.map(triples(100), function(x) {
18+
return shared.friendlyPercentFormat(100)(x, true);
19+
}), ['100%', '50%', '0%']);
20+
assert.deepEqual(_.map(triples(99.5), function(x) {
21+
return shared.friendlyPercentFormat(99.5)(x, true);
22+
}), ['100%', '50%', '0%']);
23+
assert.deepEqual(_.map(triples(99.0), function(x) {
24+
return shared.friendlyPercentFormat(99.0)(x, true);
25+
}), ['99%', '49.5%', '0%']);
26+
assert.deepEqual(_.map(triples(99.00001), function(x) {
27+
return shared.friendlyPercentFormat(99.00001)(x, true);
28+
}), ['99%', '49.5%', '0%']);
29+
assert.deepEqual(_.map(triples(49.936), function(x) {
30+
return shared.friendlyPercentFormat(49.936)(x, true);
31+
}), ['50%', '25%', '0%']);
32+
assert.deepEqual(_.map(triples(1.1), function(x) {
33+
return shared.friendlyPercentFormat(1.1)(x, true);
34+
}), ['1%', '0.5%', '0%']);
35+
assert.deepEqual(_.map(triples(0.9), function(x) {
36+
return shared.friendlyPercentFormat(0.9)(x, true);
37+
}), ['0.9%', '0.45%', '0%']);
38+
assert.deepEqual(_.map(triples(0.4), function(x) {
39+
return shared.friendlyPercentFormat(0.4)(x, true);
40+
}), ['0.4%', '0.2%', '0%']);
41+
assert.deepEqual(_.map(triples(0.003), function(x) {
42+
return shared.friendlyPercentFormat(0.003)(x, true);
43+
}), ['0.003%', '0.0015%', '0%']);
44+
assert.deepEqual(_.map(triples(0), function(x) {
45+
return shared.friendlyPercentFormat(0)(x, true);
46+
}), ['0%', '0%', '0%']);
47+
assert.deepEqual(_.map(triples(-1.5), function(x) {
48+
return shared.friendlyPercentFormat(-1.5)(x, true);
49+
}), ['-2%', '-1%', '0%']);
1850
});
1951
});

0 commit comments

Comments
 (0)