OLD | NEW |
1 // | 1 // |
2 // Copyright 2014 Google Inc. All rights reserved. | 2 // Copyright 2014 Google Inc. All rights reserved. |
3 // | 3 // |
4 // Use of this source code is governed by a BSD-style | 4 // Use of this source code is governed by a BSD-style |
5 // license that can be found in the LICENSE file or at | 5 // license that can be found in the LICENSE file or at |
6 // https://developers.google.com/open-source/licenses/bsd | 6 // https://developers.google.com/open-source/licenses/bsd |
7 // | 7 // |
8 part of charted.core.scales; | 8 part of charted.core.scales; |
9 | 9 |
10 class LinearScale implements Scale { | 10 class LinearScale implements Scale { |
11 static const defaultDomain = const [0, 1]; | 11 static const defaultDomain = const [0, 1]; |
12 static const defaultRange = const [0, 1]; | 12 static const defaultRange = const [0, 1]; |
13 | 13 |
14 bool _rounded = false; | 14 bool _rounded = false; |
15 Iterable _domain = defaultDomain; | 15 Iterable _domain = defaultDomain; |
16 Iterable _range = defaultRange; | 16 Iterable _range = defaultRange; |
17 | 17 |
18 int _ticksCount = 5; | 18 int _ticksCount = 5; |
| 19 int _forcedTicksCount = -1; |
| 20 |
19 bool _clamp = false; | 21 bool _clamp = false; |
20 bool _nice = false; | 22 bool _nice = false; |
21 | |
22 Function _invert; | 23 Function _invert; |
23 Function _scale; | 24 Function _scale; |
24 | 25 |
25 LinearScale(); | 26 LinearScale(); |
26 | 27 |
27 LinearScale._clone(LinearScale source) | 28 LinearScale._clone(LinearScale source) |
28 : _domain = source._domain.toList(), | 29 : _domain = source._domain.toList(), |
29 _range = source._range.toList(), | 30 _range = source._range.toList(), |
30 _ticksCount = source._ticksCount, | 31 _ticksCount = source._ticksCount, |
31 _clamp = source._clamp, | 32 _clamp = source._clamp, |
32 _nice = source._nice, | 33 _nice = source._nice, |
33 _rounded = source._rounded { | 34 _rounded = source._rounded { |
34 _reset(); | 35 _reset(); |
35 } | 36 } |
36 | 37 |
37 void _reset({bool nice: false}) { | 38 void _reset({bool nice: false}) { |
38 if (nice) { | 39 if (nice) { |
39 _domain = ScaleUtils.nice( | 40 _domain = ScaleUtils.nice( |
40 _domain, ScaleUtils.niceStep(_linearTickRange().step)); | 41 _domain, ScaleUtils.niceStep(_linearTickRange().step)); |
| 42 } else { |
| 43 if (_forcedTicksCount > 0) { |
| 44 var tickRange = _linearTickRange(); |
| 45 _domain = [tickRange.first, tickRange.last]; |
| 46 } |
41 } | 47 } |
42 | 48 |
43 Function linear = math.min(_domain.length, _range.length) > 2 | 49 Function linear = math.min(_domain.length, _range.length) > 2 |
44 ? ScaleUtils.polylinearScale | 50 ? ScaleUtils.polylinearScale |
45 : ScaleUtils.bilinearScale; | 51 : ScaleUtils.bilinearScale; |
46 | 52 |
47 Function uninterpolator = clamp ? uninterpolateClamp : uninterpolateNumber; | 53 Function uninterpolator = clamp ? uninterpolateClamp : uninterpolateNumber; |
48 InterpolatorGenerator interpolator; | 54 InterpolatorGenerator interpolator; |
49 if (rounded) { | 55 if (rounded) { |
50 interpolator = createRoundedNumberInterpolator; | 56 interpolator = createRoundedNumberInterpolator; |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 assert(value != null); | 98 assert(value != null); |
93 if (value != null && _ticksCount != value) { | 99 if (value != null && _ticksCount != value) { |
94 _ticksCount = value; | 100 _ticksCount = value; |
95 _reset(); | 101 _reset(); |
96 } | 102 } |
97 } | 103 } |
98 | 104 |
99 @override | 105 @override |
100 int get ticksCount => _ticksCount; | 106 int get ticksCount => _ticksCount; |
101 | 107 |
| 108 set forcedTicksCount(int value) { |
| 109 _forcedTicksCount = value; |
| 110 _reset(nice: false); |
| 111 } |
| 112 |
| 113 get forcedTicksCount => _forcedTicksCount; |
| 114 |
102 @override | 115 @override |
103 Iterable get ticks => _linearTickRange(); | 116 Iterable get ticks => _linearTickRange(); |
104 | 117 |
105 @override | 118 @override |
106 set clamp(bool value) { | 119 set clamp(bool value) { |
107 assert(value != null); | 120 assert(value != null); |
108 if (value != null && _clamp != value) { | 121 if (value != null && _clamp != value) { |
109 _clamp = value; | 122 _clamp = value; |
110 _reset(); | 123 _reset(); |
111 } | 124 } |
(...skipping 24 matching lines...) Expand all Loading... |
136 invert(value) => _invert(value); | 149 invert(value) => _invert(value); |
137 | 150 |
138 Range _linearTickRange([Extent extent]) { | 151 Range _linearTickRange([Extent extent]) { |
139 if (extent == null) { | 152 if (extent == null) { |
140 extent = ScaleUtils.extent(_domain); | 153 extent = ScaleUtils.extent(_domain); |
141 } | 154 } |
142 var span = extent.max - extent.min; | 155 var span = extent.max - extent.min; |
143 if (span == 0) { | 156 if (span == 0) { |
144 span = 1.0; // [span / _ticksCount] should never be equal zero. | 157 span = 1.0; // [span / _ticksCount] should never be equal zero. |
145 } | 158 } |
146 var step = math.pow(10, (math.log(span / _ticksCount) / math.LN10).floor()); | |
147 var err = _ticksCount / span * step; | |
148 | 159 |
149 // Filter ticks to get closer to the desired count. | 160 var step; |
150 if (err <= .15) { | 161 if (_forcedTicksCount > 0) { |
151 step *= 10; | 162 // Find the factor (in power of 10) for the max and min of the extent and |
152 } else if (err <= .35) { | 163 // round the max up and min down to make sure the domain of the scale is |
153 step *= 5; | 164 // of nicely rounded number and it contains the original domain. This way |
154 } else if (err <= .75) { | 165 // when forcing the ticks count at least the two ends of the scale would |
155 step *= 2; | 166 // look nice and has a high chance of having the intermediate tick values |
| 167 // to be nice. |
| 168 var maxFactor = extent.max == 0 ? 1 |
| 169 : math.pow(10, (math.log((extent.max as num).abs() / forcedTicksCount) |
| 170 / math.LN10).floor()); |
| 171 var max = (extent.max / maxFactor).ceil() * maxFactor; |
| 172 var minFactor = extent.min == 0 ? 1 |
| 173 : math.pow(10, (math.log((extent.min as num).abs() / forcedTicksCount) |
| 174 / math.LN10).floor()); |
| 175 var min = (extent.min / minFactor).floor() * minFactor; |
| 176 step = (max - min) / forcedTicksCount; |
| 177 return new Range(min, max + step * 0.5, step); |
| 178 } else { |
| 179 |
| 180 step = math.pow(10, (math.log(span / _ticksCount) / math.LN10).floor()); |
| 181 var err = _ticksCount / span * step; |
| 182 |
| 183 // Filter ticks to get closer to the desired count. |
| 184 if (err <= .15) { |
| 185 step *= 10; |
| 186 } else if (err <= .35) { |
| 187 step *= 5; |
| 188 } else if (err <= .75) { |
| 189 step *= 2; |
| 190 } |
156 } | 191 } |
157 | 192 |
158 return new Range((extent.min / step).ceil() * step, | 193 return new Range((extent.min / step).ceil() * step, |
159 (extent.max / step).floor() * step + step * 0.5, step); | 194 (extent.max / step).floor() * step + step * 0.5, step); |
160 } | 195 } |
161 | 196 |
162 @override | 197 @override |
163 FormatFunction createTickFormatter([String formatStr]) { | 198 FormatFunction createTickFormatter([String formatStr]) { |
164 int precision(value) { | 199 int precision(value) { |
165 return -(math.log(value) / math.LN10 + .01).floor(); | 200 return -(math.log(value) / math.LN10 + .01).floor(); |
166 } | 201 } |
167 Range tickRange = _linearTickRange(); | 202 Range tickRange = _linearTickRange(); |
168 if (formatStr == null) { | 203 if (formatStr == null) { |
169 formatStr = ".${precision(tickRange.step)}f"; | 204 formatStr = ".${precision(tickRange.step)}f"; |
170 } | 205 } |
171 NumberFormat formatter = new NumberFormat(new EnUsLocale()); | 206 NumberFormat formatter = new NumberFormat(new EnUsLocale()); |
172 return formatter.format(formatStr); | 207 return formatter.format(formatStr); |
173 } | 208 } |
174 | 209 |
175 @override | 210 @override |
176 LinearScale clone() => new LinearScale._clone(this); | 211 LinearScale clone() => new LinearScale._clone(this); |
177 } | 212 } |
OLD | NEW |