| 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 |