Skip to content

Commit 9f745d0

Browse files
mbostockFil
andauthored
exact log ticks (#253)
* fix #234; exact log ticks * let/const (see also #212) Co-authored-by: Philippe Rivière <[email protected]>
1 parent 8fd6d25 commit 9f745d0

File tree

2 files changed

+48
-45
lines changed

2 files changed

+48
-45
lines changed

src/log.js

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,37 @@ function transformExpn(x) {
2020
return -Math.exp(-x);
2121
}
2222

23-
function pow10(x) {
24-
return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x;
23+
function pow10(x, k) {
24+
return isFinite(x) ? +(k + "e" + x) : x < 0 ? 0 : x;
25+
}
26+
27+
function exp(x, k) {
28+
return Math.exp(x) * k;
2529
}
2630

2731
function powp(base) {
2832
return base === 10 ? pow10
29-
: base === Math.E ? Math.exp
30-
: function(x) { return Math.pow(base, x); };
33+
: base === Math.E ? exp
34+
: (x, k) => Math.pow(base, x) * k;
3135
}
3236

3337
function logp(base) {
3438
return base === Math.E ? Math.log
3539
: base === 10 && Math.log10
3640
|| base === 2 && Math.log2
37-
|| (base = Math.log(base), function(x) { return Math.log(x) / base; });
41+
|| (base = Math.log(base), (x) => Math.log(x) / base);
3842
}
3943

4044
function reflect(f) {
41-
return function(x) {
42-
return -f(-x);
43-
};
45+
return (x, k) => -f(-x, k);
4446
}
4547

4648
export function loggish(transform) {
47-
var scale = transform(transformLog, transformExp),
48-
domain = scale.domain,
49-
base = 10,
50-
logs,
51-
pows;
49+
const scale = transform(transformLog, transformExp);
50+
const domain = scale.domain;
51+
let base = 10;
52+
let logs;
53+
let pows;
5254

5355
function rescale() {
5456
logs = logp(base), pows = powp(base);
@@ -69,81 +71,75 @@ export function loggish(transform) {
6971
return arguments.length ? (domain(_), rescale()) : domain();
7072
};
7173

72-
scale.ticks = function(count) {
73-
var d = domain(),
74-
u = d[0],
75-
v = d[d.length - 1],
76-
r;
74+
scale.ticks = count => {
75+
const d = domain();
76+
let u = d[0];
77+
let v = d[d.length - 1];
78+
const r = v < u;
7779

78-
if (r = v < u) i = u, u = v, v = i;
80+
if (r) ([u, v] = [v, u]);
7981

80-
var i = logs(u),
81-
j = logs(v),
82-
p,
83-
k,
84-
t,
85-
n = count == null ? 10 : +count,
86-
z = [];
82+
let i = logs(u);
83+
let j = logs(v);
84+
let k;
85+
let t;
86+
const n = count == null ? 10 : +count;
87+
let z = [];
8788

8889
if (!(base % 1) && j - i < n) {
8990
i = Math.floor(i), j = Math.ceil(j);
9091
if (u > 0) for (; i <= j; ++i) {
91-
for (k = 1, p = pows(i); k < base; ++k) {
92-
t = p * k;
92+
for (k = 1; k < base; ++k) {
93+
t = pows(i, k);
9394
if (t < u) continue;
9495
if (t > v) break;
9596
z.push(t);
9697
}
9798
} else for (; i <= j; ++i) {
98-
for (k = base - 1, p = pows(i); k >= 1; --k) {
99-
t = p * k;
99+
for (k = base - 1; k >= 1; --k) {
100+
t = pows(i, k);
100101
if (t < u) continue;
101102
if (t > v) break;
102103
z.push(t);
103104
}
104105
}
105106
if (z.length * 2 < n) z = ticks(u, v, n);
106107
} else {
107-
z = ticks(i, j, Math.min(j - i, n)).map(pows);
108+
z = ticks(i, j, Math.min(j - i, n)).map(i => pows(i, 1));
108109
}
109110

110111
return r ? z.reverse() : z;
111112
};
112113

113-
scale.tickFormat = function(count, specifier) {
114+
scale.tickFormat = (count, specifier) => {
114115
if (count == null) count = 10;
115116
if (specifier == null) specifier = base === 10 ? ".0e" : ",";
116117
if (typeof specifier !== "function") {
117118
if (!(base % 1) && (specifier = formatSpecifier(specifier)).precision == null) specifier.trim = true;
118119
specifier = format(specifier);
119120
}
120121
if (count === Infinity) return specifier;
121-
var k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate?
122-
return function(d) {
123-
var i = d / pows(Math.round(logs(d)));
122+
const k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate?
123+
return d => {
124+
let i = d / pows(Math.round(logs(d)), 1);
124125
if (i * base < base - 0.5) i *= base;
125126
return i <= k ? specifier(d) : "";
126127
};
127128
};
128129

129-
scale.nice = function() {
130+
scale.nice = () => {
130131
return domain(nice(domain(), {
131-
floor: function(x) { return pows(Math.floor(logs(x))); },
132-
ceil: function(x) { return pows(Math.ceil(logs(x))); }
132+
floor: x => pows(Math.floor(logs(x)), 1),
133+
ceil: x => pows(Math.ceil(logs(x)), 1)
133134
}));
134135
};
135136

136137
return scale;
137138
}
138139

139140
export default function log() {
140-
var scale = loggish(transformer()).domain([1, 10]);
141-
142-
scale.copy = function() {
143-
return copy(scale, log()).base(scale.base());
144-
};
145-
141+
const scale = loggish(transformer()).domain([1, 10]);
142+
scale.copy = () => copy(scale, log()).base(scale.base());
146143
initRange.apply(scale, arguments);
147-
148144
return scale;
149145
}

test/log-test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ it("log.domain(…) preserves specified domain exactly, with no floating point e
6161
assert.deepStrictEqual(x.domain(), [0.1, 1000]);
6262
});
6363

64+
it("log.ticks(…) returns exact ticks, with no floating point error", () => {
65+
assert.deepStrictEqual(scaleLog().domain([0.15, 0.68]).ticks(), [0.2, 0.3, 0.4, 0.5, 0.6]);
66+
assert.deepStrictEqual(scaleLog().domain([0.68, 0.15]).ticks(), [0.6, 0.5, 0.4, 0.3, 0.2]);
67+
assert.deepStrictEqual(scaleLog().domain([-0.15, -0.68]).ticks(), [-0.2, -0.3, -0.4, -0.5, -0.6]);
68+
assert.deepStrictEqual(scaleLog().domain([-0.68, -0.15]).ticks(), [-0.6, -0.5, -0.4, -0.3, -0.2]);
69+
});
70+
6471
it("log.range(…) does not coerce values to numbers", () => {
6572
const x = scaleLog().range(["0", "2"]);
6673
assert.strictEqual(typeof x.range()[0], "string");

0 commit comments

Comments
 (0)