OLD | NEW |
| (Empty) |
1 // | |
2 // Copyright 2014 Google Inc. All rights reserved. | |
3 // | |
4 // Use of this source code is governed by a BSD-style | |
5 // license that can be found in the LICENSE file or at | |
6 // https://developers.google.com/open-source/licenses/bsd | |
7 // | |
8 part of charted.core.scales; | |
9 | |
10 /// Log scale is similar to linear scale, except there's a logarithmic | |
11 /// transform that is applied to the input domain value before the output | |
12 /// range value is computed. | |
13 /// | |
14 /// The mapping to the output range value y can be expressed as a function | |
15 /// of the input domain value x: y = m log(x) + b. | |
16 /// | |
17 /// As log(0) is negative infinity, a log scale must have either an | |
18 /// exclusively-positive or exclusively-negative domain; the domain must not | |
19 /// include or cross zero. | |
20 class LogScale implements Scale { | |
21 static const defaultBase = 10; | |
22 static const defaultDomain = const [1, 10]; | |
23 static final negativeNumbersRoundFunctionsPair = | |
24 new RoundingFunctions( | |
25 (x) => -((-x).floor()), | |
26 (x) => -((-x).ceil())); | |
27 | |
28 final LinearScale _linear; | |
29 | |
30 bool _nice = false; | |
31 int _base = defaultBase; | |
32 int _ticksCount = 10; | |
33 bool _positive = true; | |
34 List _domain = defaultDomain; | |
35 | |
36 LogScale() : _linear = new LinearScale(); | |
37 | |
38 LogScale._clone(LogScale source) | |
39 : _linear = source._linear.clone(), | |
40 _domain = source._domain.toList(), | |
41 _positive = source._positive, | |
42 _base = source._base, | |
43 _nice = source._nice, | |
44 _ticksCount = source._ticksCount; | |
45 | |
46 num _log(x) => (_positive ? | |
47 math.log(x < 0 ? 0 : x) : -math.log(x > 0 ? 0 : -x)) / math.log(base); | |
48 | |
49 num _pow(x) => _positive ? math.pow(base, x) : -math.pow(base, -x); | |
50 | |
51 set base(int value) { | |
52 if (_base != value) { | |
53 _base = value; | |
54 _reset(); | |
55 } | |
56 } | |
57 | |
58 get base => _base; | |
59 | |
60 @override | |
61 num scale(x) => _linear.scale(_log(x)); | |
62 | |
63 @override | |
64 num invert(x) => _pow(_linear.invert(x)); | |
65 | |
66 @override | |
67 set domain(Iterable values) { | |
68 _positive = values.first >= 0; | |
69 _domain = values; | |
70 _reset(); | |
71 } | |
72 | |
73 @override | |
74 Iterable get domain => _domain; | |
75 | |
76 @override | |
77 set range(Iterable newRange) { | |
78 _linear.range = newRange; | |
79 } | |
80 | |
81 @override | |
82 Iterable get range => _linear.range; | |
83 | |
84 @override | |
85 set rounded(bool value) { | |
86 _linear.rounded = value; | |
87 } | |
88 | |
89 @override | |
90 bool get rounded => _linear.rounded; | |
91 | |
92 @override | |
93 set nice(bool value) { | |
94 if (_nice != value) { | |
95 _nice = value; | |
96 _reset(); | |
97 } | |
98 } | |
99 | |
100 @override | |
101 bool get nice => _nice; | |
102 | |
103 @override | |
104 set ticksCount(int value) { | |
105 if (_ticksCount != value) { | |
106 _ticksCount = value; | |
107 _reset(); | |
108 } | |
109 } | |
110 | |
111 @override | |
112 int get ticksCount => _ticksCount; | |
113 | |
114 @override | |
115 set clamp(bool value) { | |
116 _linear.clamp = value; | |
117 } | |
118 | |
119 @override | |
120 bool get clamp => _linear.clamp; | |
121 | |
122 @override | |
123 Extent get rangeExtent => _linear.rangeExtent; | |
124 | |
125 _reset() { | |
126 if (_nice) { | |
127 var niced = _domain.map((e) => _log(e)).toList(); | |
128 var roundFunctions = _positive | |
129 ? new RoundingFunctions.defaults() | |
130 : negativeNumbersRoundFunctionsPair; | |
131 | |
132 _linear.domain = ScaleUtils.nice(niced, roundFunctions); | |
133 _domain = niced.map((e) => _pow(e)).toList(); | |
134 } else { | |
135 _linear.domain = _domain.map((e) => _log(e)).toList(); | |
136 } | |
137 } | |
138 | |
139 Iterable get ticks { | |
140 var extent = ScaleUtils.extent(_domain), | |
141 ticks = [], | |
142 u = extent.min, | |
143 v = extent.max, | |
144 i = (_log(u)).floor(), | |
145 j = (_log(v)).ceil(), | |
146 n = (_base % 1 > 0) ? 2 : _base; | |
147 | |
148 if ((j - i).isFinite) { | |
149 if (_positive) { | |
150 for (; i < j; i++) for (var k = 1; k < n; k++) ticks.add(_pow(i) * k); | |
151 ticks.add(_pow(i)); | |
152 } else { | |
153 ticks.add(_pow(i)); | |
154 for (; i++ < j;) for (var k = n - 1; k > 0; k--) ticks.add(_pow(i) * k); | |
155 } | |
156 for (i = 0; ticks[i] < u; i++) {} // strip small values | |
157 for (j = ticks.length; ticks[j - 1] > v; j--) {} // strip big values | |
158 ticks = ticks.sublist(i, j); | |
159 } | |
160 return ticks; | |
161 } | |
162 | |
163 FormatFunction createTickFormatter([String formatStr]) { | |
164 NumberFormat formatter = new NumberFormat(new EnUsLocale()); | |
165 FormatFunction logFormatFunction = | |
166 formatter.format(formatStr != null ? formatStr : ".0E"); | |
167 var k = math.max(.1, ticksCount / this.ticks.length), | |
168 e = _positive ? 1e-12 : -1e-12; | |
169 return (d) { | |
170 if (_positive) { | |
171 return d / _pow((_log(d) + e).ceil()) <= k ? logFormatFunction(d) : ''; | |
172 } else { | |
173 return d / _pow((_log(d) + e).floor()) <= k ? logFormatFunction(d) : ''; | |
174 } | |
175 }; | |
176 } | |
177 | |
178 @override | |
179 LogScale clone() => new LogScale._clone(this); | |
180 } | |
OLD | NEW |