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 |