| 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 | |
| 9 /// Collection of scales for use by charts. The role of scales is to map the | |
| 10 /// input domain to an output range. | |
| 11 /// | |
| 12 /// Charted supports two types of scales: | |
| 13 /// - Quantitative, where the scales use a mathematical function for mapping | |
| 14 /// the input domain to output range. | |
| 15 /// - Ordinal, where the input domain is discrete (i.e set of values) | |
| 16 /// | |
| 17 library charted.core.scales; | |
| 18 | |
| 19 import 'dart:math' as math; | |
| 20 import 'package:charted/core/utils.dart'; | |
| 21 import 'package:charted/core/interpolators.dart'; | |
| 22 import 'package:charted/core/time_interval.dart'; | |
| 23 import 'package:charted/locale/locale.dart'; | |
| 24 import 'package:charted/locale/format.dart'; | |
| 25 | |
| 26 part 'scales/ordinal_scale.dart'; | |
| 27 part 'scales/linear_scale.dart'; | |
| 28 part 'scales/log_scale.dart'; | |
| 29 part 'scales/time_scale.dart'; | |
| 30 | |
| 31 typedef num RoundFunction(num value); | |
| 32 | |
| 33 /// Minimum common interface supported by all scales. [QuantitativeScale] and | |
| 34 /// [OrdinalScale] contain the interface for their respective types. | |
| 35 abstract class Scale { | |
| 36 /// Given a [value] in the input domain, map it to the range. | |
| 37 /// On [QuantitativeScale]s both parameter and return values are numbers. | |
| 38 dynamic scale(value); | |
| 39 | |
| 40 /// Given a [value] in the output range, return value in the input domain | |
| 41 /// that maps to the value. | |
| 42 /// On [QuantitativeScale]s both parameter and return values are numbers. | |
| 43 dynamic invert(value); | |
| 44 | |
| 45 /// Input domain used by this scale. | |
| 46 Iterable domain; | |
| 47 | |
| 48 /// Output range used by this scale. | |
| 49 Iterable range; | |
| 50 | |
| 51 /// Maximum and minimum values of the scale's output range. | |
| 52 Extent get rangeExtent; | |
| 53 | |
| 54 /// Creates tick values over the input domain. | |
| 55 Iterable get ticks; | |
| 56 | |
| 57 /// Creates a formatter that is suitable for formatting the ticks. | |
| 58 /// For ordinal scale, the returned function is an identity function. | |
| 59 FormatFunction createTickFormatter([String format]); | |
| 60 | |
| 61 /// Creates a clone of this scale. | |
| 62 Scale clone(); | |
| 63 | |
| 64 /// Suggested number of ticks on this scale. | |
| 65 /// Note: This property is only valid on quantitative scales. | |
| 66 int ticksCount; | |
| 67 | |
| 68 /// Indicates if the current scale is using niced values for ticks. | |
| 69 /// Note: This property is only valid on quantitative scales. | |
| 70 bool nice; | |
| 71 | |
| 72 /// Indicates if output range is clamped. When clamp is not true, any input | |
| 73 /// value that is not within the input domain may result in a value that is | |
| 74 /// outside the output range. | |
| 75 /// Note: This property is only valid on quantitative scales. | |
| 76 bool clamp; | |
| 77 | |
| 78 /// Indicates that the scaled values must be rounded to the nearest | |
| 79 /// integer. Helps avoid anti-aliasing artifacts in the visualizations. | |
| 80 /// Note: This property is only valid on quantitative scales. | |
| 81 bool rounded; | |
| 82 } | |
| 83 | |
| 84 /// Minimum common interface supported by scales whose input domain | |
| 85 /// contains discreet values (Ordinal scales). | |
| 86 abstract class OrdinalScale extends Scale { | |
| 87 factory OrdinalScale() = _OrdinalScale; | |
| 88 | |
| 89 /// Amount of space that each value in the domain gets from the range. A band | |
| 90 /// is available only after [rangeBands] or [rangeRoundBands] is called by | |
| 91 /// the user. A bar-chart could use this space as width of a bar. | |
| 92 num get rangeBand; | |
| 93 | |
| 94 /// Maps each value on the domain to a single point on output range. When a | |
| 95 /// non-zero value is specified, [padding] space is left unused on both ends | |
| 96 /// of the range. | |
| 97 void rangePoints(Iterable range, [double padding]); | |
| 98 | |
| 99 /// Maps each value on the domain to a band in the output range. When a | |
| 100 /// non-zero value is specified, [padding] space is left between each bands | |
| 101 /// and [outerPadding] space is left unused at both ends of the range. | |
| 102 void rangeBands(Iterable range, [double padding, double outerPadding]); | |
| 103 | |
| 104 /// Similar to [rangeBands] but ensures that each band starts and ends on a | |
| 105 /// pixel boundary - helps avoid anti-aliasing artifacts. | |
| 106 void rangeRoundBands(Iterable range, [double padding, double outerPadding]); | |
| 107 } | |
| 108 | |
| 109 class RoundingFunctions extends Pair<RoundFunction,RoundFunction> { | |
| 110 RoundingFunctions(RoundFunction floor, RoundFunction ceil) | |
| 111 : super(floor, ceil); | |
| 112 | |
| 113 factory RoundingFunctions.defaults() => | |
| 114 new RoundingFunctions((x) => x.floor(), (x) => x.ceil()); | |
| 115 | |
| 116 factory RoundingFunctions.identity() => | |
| 117 new RoundingFunctions(identityFunction, identityFunction); | |
| 118 | |
| 119 RoundFunction get floor => super.first; | |
| 120 RoundFunction get ceil => super.last; | |
| 121 } | |
| 122 | |
| 123 /// Namespacing container for utilities used by scales. | |
| 124 abstract class ScaleUtils { | |
| 125 /// Utility to return extent of sorted [values]. | |
| 126 static Extent extent(Iterable values) => | |
| 127 values.first < values.last | |
| 128 ? new Extent(values.first, values.last) | |
| 129 : new Extent(values.last, values.first); | |
| 130 | |
| 131 /// Extends [values] to round numbers based on the given pair of | |
| 132 /// floor and ceil functions. [functions] is a pair of rounding function | |
| 133 /// among which the first is used to compute floor of a number and the | |
| 134 /// second for ceil of the number. | |
| 135 static List nice(List values, RoundingFunctions functions) { | |
| 136 if (values.last >= values.first) { | |
| 137 values[0] = functions.floor(values.first); | |
| 138 values[values.length - 1] = functions.ceil(values.last); | |
| 139 } else { | |
| 140 values[values.length - 1] = functions.floor(values.last); | |
| 141 values[0] = functions.ceil(values.first); | |
| 142 } | |
| 143 return values; | |
| 144 } | |
| 145 | |
| 146 static RoundingFunctions niceStep(num step) => (step > 0) | |
| 147 ? new RoundingFunctions( | |
| 148 (x) => (x < step) ? x.floor() : (x / step).floor() * step, | |
| 149 (x) => (x < step) ? x.ceil() : (x / step).ceil() * step) | |
| 150 : new RoundingFunctions.identity(); | |
| 151 | |
| 152 /// Returns a Function that given a value x on the domain, returns the | |
| 153 /// corresponding value on the range on a bilinear scale. | |
| 154 /// | |
| 155 /// @param domain The domain of the scale. | |
| 156 /// @param range The range of the scale. | |
| 157 /// @param uninterpolator The uninterpolator for domain values. | |
| 158 /// @param interpolator The interpolator for range values. | |
| 159 static Function bilinearScale(List domain, List range, | |
| 160 Function uninterpolator, Function interpolator) { | |
| 161 var u = uninterpolator(domain[0], domain[1]), | |
| 162 i = interpolator(range[0], range[1]); | |
| 163 return (x) => i(u(x)); | |
| 164 } | |
| 165 | |
| 166 /// Returns a Function that given a value x on the domain, returns the | |
| 167 /// corresponding value on the range on a polylinear scale. | |
| 168 /// | |
| 169 /// @param domain The domain of the scale. | |
| 170 /// @param range The range of the scale. | |
| 171 /// @param uninterpolator The uninterpolator for domain values. | |
| 172 /// @param interpolator The interpolator for range values. | |
| 173 static Function polylinearScale(List domain, List range, | |
| 174 Function uninterpolator, Function interpolator) { | |
| 175 var u = [], | |
| 176 i = [], | |
| 177 j = 0, | |
| 178 k = math.min(domain.length, range.length) - 1; | |
| 179 | |
| 180 // Handle descending domains. | |
| 181 if (domain[k] < domain[0]) { | |
| 182 domain = domain.reversed.toList(); | |
| 183 range = range.reversed.toList(); | |
| 184 } | |
| 185 | |
| 186 while (++j <= k) { | |
| 187 u.add(uninterpolator(domain[j - 1], domain[j])); | |
| 188 i.add(interpolator(range[j - 1], range[j])); | |
| 189 } | |
| 190 | |
| 191 return (x) { | |
| 192 int index = bisect(domain, x, 1, k) - 1; | |
| 193 return i[index](u[index](x)); | |
| 194 }; | |
| 195 } | |
| 196 | |
| 197 /// Returns the insertion point i for value x such that all values in a[lo:i] | |
| 198 /// will be less than x and all values in a[i:hi] will be equal to or greater | |
| 199 /// than x. | |
| 200 static int bisectLeft(List a, num x, [int lo = 0, int hi = -1]) { | |
| 201 if (hi == -1) { | |
| 202 hi = a.length; | |
| 203 } | |
| 204 while (lo < hi) { | |
| 205 int mid = ((lo + hi) / 2).floor(); | |
| 206 if (a[mid] < x) { | |
| 207 lo = mid + 1; | |
| 208 } else { | |
| 209 hi = mid; | |
| 210 } | |
| 211 } | |
| 212 return lo; | |
| 213 } | |
| 214 | |
| 215 /// Returns the insertion point i for value x such that all values in a[lo:i] | |
| 216 /// will be less than or equalto x and all values in a[i:hi] will be greater | |
| 217 /// than x. | |
| 218 static int bisectRight(List a, num x, [int lo = 0, int hi = -1]) { | |
| 219 if (hi == -1) { | |
| 220 hi = a.length; | |
| 221 } | |
| 222 while (lo < hi) { | |
| 223 int mid = ((lo + hi) / 2).floor(); | |
| 224 if (x < a[mid]) { | |
| 225 hi = mid; | |
| 226 } else { | |
| 227 lo = mid + 1; | |
| 228 } | |
| 229 } | |
| 230 return lo; | |
| 231 } | |
| 232 | |
| 233 static Function bisect = bisectRight; | |
| 234 } | |
| OLD | NEW |