| 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 part of charted.core.interpolators; | |
| 10 | |
| 11 /// [Interpolator] accepts [t], such that 0.0 < t < 1.0 and returns | |
| 12 /// a value in a pre-defined range. | |
| 13 typedef Interpolator(num t); | |
| 14 | |
| 15 /// [InterpolatorGenerator] accepts two parameters [a], [b] and returns an | |
| 16 /// [Interpolator] for transitioning from [a] to [b] | |
| 17 typedef Interpolator InterpolatorGenerator(a, b); | |
| 18 | |
| 19 /// List of registered interpolators - [createInterpolatorFromRegistry] | |
| 20 /// iterates through this list from backwards and the first non-null | |
| 21 /// interpolate function is returned to the caller. | |
| 22 List<InterpolatorGenerator> _interpolators = [ createInterpolatorByType ]; | |
| 23 | |
| 24 /// Returns a default interpolator between values [a] and [b]. Unless | |
| 25 /// more interpolators are added, one of the internal implementations are | |
| 26 /// selected by the type of [a] and [b]. | |
| 27 Interpolator createInterpolatorFromRegistry(a, b) { | |
| 28 var fn, i = _interpolators.length; | |
| 29 while (--i >= 0 && fn == null) { | |
| 30 fn = _interpolators[i](a, b); | |
| 31 } | |
| 32 return fn; | |
| 33 } | |
| 34 | |
| 35 /// Creates an interpolator based on the type of [a] and [b]. | |
| 36 /// | |
| 37 /// Usage note: Use this method only when type of [a] and [b] are not known. | |
| 38 /// When used, this function will prevent tree shaking of all built-in | |
| 39 /// interpolators. | |
| 40 Interpolator createInterpolatorByType(a, b) => | |
| 41 (a is List && b is List) ? createListInterpolator(a, b) : | |
| 42 (a is Map && b is Map) ? createMapInterpolator(a, b) : | |
| 43 (a is String && b is String) ? createStringInterpolator(a, b) : | |
| 44 (a is num && b is num) ? createNumberInterpolator(a, b) : | |
| 45 (a is Color && b is Color) ? createRgbColorInterpolator(a, b) : | |
| 46 (t) => (t <= 0.5) ? a : b; | |
| 47 | |
| 48 | |
| 49 // | |
| 50 // Implementations of InterpolatorGenerator | |
| 51 // | |
| 52 | |
| 53 /// Generate a numeric interpolator between numbers [a] and [b] | |
| 54 Interpolator createNumberInterpolator(num a, num b) { | |
| 55 b -= a; | |
| 56 return (t) => a + b * t; | |
| 57 } | |
| 58 | |
| 59 /// Generate a rounded number interpolator between numbers [a] and [b] | |
| 60 Interpolator createRoundedNumberInterpolator(num a, num b) { | |
| 61 b -= a; | |
| 62 return (t) => (a + b * t).round(); | |
| 63 } | |
| 64 | |
| 65 | |
| 66 /// Generate an interpolator between two strings [a] and [b]. | |
| 67 /// | |
| 68 /// The interpolator will interpolate all the number pairs in both strings | |
| 69 /// that have same number of numeric parts. The function assumes the non | |
| 70 /// number part of the string to be identical and would use string [b] for | |
| 71 /// merging the non numeric part of the strings. | |
| 72 /// | |
| 73 /// Eg: Interpolate between $100.0 and $150.0 | |
| 74 Interpolator createStringInterpolator(String a, String b) { | |
| 75 if (a == null || b == null) return (t) => b; | |
| 76 | |
| 77 // See if both A and B represent colors as RGB or HEX strings. | |
| 78 // If yes, use color interpolators | |
| 79 if (Color.isRgbColorString(a) && Color.isRgbColorString(b)) { | |
| 80 return createRgbColorInterpolator( | |
| 81 new Color.fromRgbString(a), new Color.fromRgbString(b)); | |
| 82 } | |
| 83 | |
| 84 // See if both A and B represent colors as HSL strings. | |
| 85 // If yes, use color interpolators. | |
| 86 if (Color.isHslColorString(a) && Color.isHslColorString(b)) { | |
| 87 return createHslColorInterpolator( | |
| 88 new Color.fromHslString(a), new Color.fromHslString(b)); | |
| 89 } | |
| 90 | |
| 91 var numberRegEx = | |
| 92 new RegExp(r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?'), | |
| 93 numMatchesInA = numberRegEx.allMatches(a), | |
| 94 numMatchesInB = numberRegEx.allMatches(b), | |
| 95 stringParts = [], | |
| 96 numberPartsInA = [], | |
| 97 numberPartsInB = [], | |
| 98 interpolators = [], | |
| 99 s0 = 0; | |
| 100 | |
| 101 numberPartsInA.addAll(numMatchesInA.map((m) => m.group(0))); | |
| 102 | |
| 103 for (Match m in numMatchesInB) { | |
| 104 stringParts.add(b.substring(s0, m.start)); | |
| 105 numberPartsInB.add(m.group(0)); | |
| 106 s0 = m.end; | |
| 107 } | |
| 108 | |
| 109 if (s0 < b.length) stringParts.add(b.substring(s0)); | |
| 110 | |
| 111 int numberLength = math.min(numberPartsInA.length, numberPartsInB.length); | |
| 112 int maxLength = math.max(numberPartsInA.length, numberPartsInB.length); | |
| 113 for (var i = 0; i < numberLength; i++) { | |
| 114 interpolators.add(createNumberInterpolator(num.parse(numberPartsInA[i]), | |
| 115 num.parse(numberPartsInB[i]))); | |
| 116 } | |
| 117 if (numberPartsInA.length < numberPartsInB.length) { | |
| 118 for (var i = numberLength; i < maxLength; i++) { | |
| 119 interpolators.add(createNumberInterpolator(num.parse(numberPartsInB[i]), | |
| 120 num.parse(numberPartsInB[i]))); | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 return (t) { | |
| 125 StringBuffer sb = new StringBuffer(); | |
| 126 for (var i = 0; i < stringParts.length; i++) { | |
| 127 sb.write(stringParts[i]); | |
| 128 if (interpolators.length > i) { | |
| 129 sb.write(interpolators[i](t)); | |
| 130 } | |
| 131 } | |
| 132 return sb.toString(); | |
| 133 }; | |
| 134 } | |
| 135 | |
| 136 /// Generate an interpolator for RGB values. | |
| 137 Interpolator createRgbColorInterpolator(Color a, Color b) { | |
| 138 if (a == null || b == null) return (t) => b; | |
| 139 var ar = a.r, | |
| 140 ag = a.g, | |
| 141 ab = a.b, | |
| 142 br = b.r - ar, | |
| 143 bg = b.g - ag, | |
| 144 bb = b.b - ab; | |
| 145 | |
| 146 return (t) => new Color.fromRgba((ar + br * t).round(), | |
| 147 (ag + bg * t).round(), (ab + bb * t).round(), 1.0).toRgbaString(); | |
| 148 } | |
| 149 | |
| 150 /// Generate an interpolator using HSL color system converted to Hex string. | |
| 151 Interpolator createHslColorInterpolator(Color a, Color b) { | |
| 152 if (a == null || b == null) return (t) => b; | |
| 153 var ah = a.h, | |
| 154 as = a.s, | |
| 155 al = a.l, | |
| 156 bh = b.h - ah, | |
| 157 bs = b.s - as, | |
| 158 bl = b.l - al; | |
| 159 | |
| 160 return (t) => new Color.fromHsla((ah + bh * t).round(), | |
| 161 (as + bs * t).round(), (al + bl * t).round(), 1.0).toHslaString(); | |
| 162 } | |
| 163 | |
| 164 /// Generates an interpolator to interpolate each element between lists | |
| 165 /// [a] and [b] using registered interpolators. | |
| 166 Interpolator createListInterpolator(List a, List b) { | |
| 167 if (a == null || b == null) return (t) => b; | |
| 168 var x = [], | |
| 169 aLength = a.length, | |
| 170 numInterpolated = b.length, | |
| 171 n0 = math.min(aLength, numInterpolated), | |
| 172 output = new List.filled(math.max(aLength, numInterpolated), null), | |
| 173 i; | |
| 174 | |
| 175 for (i = 0; i < n0; i++) x.add(createInterpolatorFromRegistry(a[i], b[i])); | |
| 176 for (; i < aLength; ++i) output[i] = a[i]; | |
| 177 for (; i < numInterpolated; ++i) output[i] = b[i]; | |
| 178 | |
| 179 return (t) { | |
| 180 for (i = 0; i < n0; ++i) output[i] = x[i](t); | |
| 181 return output; | |
| 182 }; | |
| 183 } | |
| 184 | |
| 185 /// Generates an interpolator to interpolate each value on [a] to [b] using | |
| 186 /// registered interpolators. | |
| 187 Interpolator createMapInterpolator(Map a, Map b) { | |
| 188 if (a == null || b == null) return (t) => b; | |
| 189 var interpolatorsMap = new Map(), | |
| 190 output = new Map(), | |
| 191 aKeys = a.keys.toList(), | |
| 192 bKeys = b.keys.toList(); | |
| 193 | |
| 194 aKeys.forEach((k) { | |
| 195 if (b[k] != null) { | |
| 196 interpolatorsMap[k] = (createInterpolatorFromRegistry(a[k], b[k])); | |
| 197 } else { | |
| 198 output[k] = a[k]; | |
| 199 } | |
| 200 }); | |
| 201 | |
| 202 bKeys.forEach((k) { | |
| 203 if (output[k] == null) { | |
| 204 output[k] = b[k]; | |
| 205 } | |
| 206 }); | |
| 207 | |
| 208 return (t) { | |
| 209 interpolatorsMap.forEach((k, v) => output[k] = v(t)); | |
| 210 return output; | |
| 211 }; | |
| 212 } | |
| 213 | |
| 214 /// Returns the interpolator that interpolators two transform strings | |
| 215 /// [a] and [b] by their translate, rotate, scale and skewX parts. | |
| 216 Interpolator createTransformInterpolator(String a, String b) { | |
| 217 if (a == null || b == null) return (t) => b; | |
| 218 var numRegExStr = r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?', | |
| 219 numberRegEx = new RegExp(numRegExStr), | |
| 220 translateRegEx = | |
| 221 new RegExp(r'translate\(' + '$numRegExStr,$numRegExStr' + r'\)'), | |
| 222 scaleRegEx = | |
| 223 new RegExp(r'scale\(' + numRegExStr + r',' + numRegExStr + r'\)'), | |
| 224 rotateRegEx = new RegExp(r'rotate\(' + numRegExStr + r'(deg)?\)'), | |
| 225 skewRegEx = new RegExp(r'skewX\(' + numRegExStr + r'(deg)?\)'), | |
| 226 | |
| 227 translateA = translateRegEx.firstMatch(a), | |
| 228 scaleA = scaleRegEx.firstMatch(a), | |
| 229 rotateA = rotateRegEx.firstMatch(a), | |
| 230 skewA = skewRegEx.firstMatch(a), | |
| 231 | |
| 232 translateB = translateRegEx.firstMatch(b), | |
| 233 scaleB = scaleRegEx.firstMatch(b), | |
| 234 rotateB = rotateRegEx.firstMatch(b), | |
| 235 skewB = skewRegEx.firstMatch(b); | |
| 236 | |
| 237 var numSetA = [], | |
| 238 numSetB = [], | |
| 239 tempStr, match; | |
| 240 | |
| 241 // translate | |
| 242 if (translateA != null) { | |
| 243 tempStr = a.substring(translateA.start, translateA.end); | |
| 244 match = numberRegEx.allMatches(tempStr); | |
| 245 for (Match m in match) { | |
| 246 numSetA.add(num.parse(m.group(0))); | |
| 247 } | |
| 248 } else { | |
| 249 numSetA.addAll(const[0, 0]); | |
| 250 } | |
| 251 | |
| 252 if (translateB != null) { | |
| 253 tempStr = b.substring(translateB.start, translateB.end); | |
| 254 match = numberRegEx.allMatches(tempStr); | |
| 255 for (Match m in match) { | |
| 256 numSetB.add(num.parse(m.group(0))); | |
| 257 } | |
| 258 } else { | |
| 259 numSetB.addAll(const[0, 0]); | |
| 260 } | |
| 261 | |
| 262 // scale | |
| 263 if (scaleA != null) { | |
| 264 tempStr = a.substring(scaleA.start, scaleA.end); | |
| 265 match = numberRegEx.allMatches(tempStr); | |
| 266 for (Match m in match) { | |
| 267 numSetA.add(num.parse(m.group(0))); | |
| 268 } | |
| 269 } else { | |
| 270 numSetA.addAll(const[1, 1]); | |
| 271 } | |
| 272 | |
| 273 if (scaleB != null) { | |
| 274 tempStr = b.substring(scaleB.start, scaleB.end); | |
| 275 match = numberRegEx.allMatches(tempStr); | |
| 276 for (Match m in match) { | |
| 277 numSetB.add(num.parse(m.group(0))); | |
| 278 } | |
| 279 } else { | |
| 280 numSetB.addAll(const[1, 1]); | |
| 281 } | |
| 282 | |
| 283 // rotate | |
| 284 if (rotateA != null) { | |
| 285 tempStr = a.substring(rotateA.start, rotateA.end); | |
| 286 match = numberRegEx.firstMatch(tempStr); | |
| 287 numSetA.add(num.parse(match.group(0))); | |
| 288 } else { | |
| 289 numSetA.add(0); | |
| 290 } | |
| 291 | |
| 292 if (rotateB != null) { | |
| 293 tempStr = b.substring(rotateB.start, rotateB.end); | |
| 294 match = numberRegEx.firstMatch(tempStr); | |
| 295 numSetB.add(num.parse(match.group(0))); | |
| 296 } else { | |
| 297 numSetB.add(0); | |
| 298 } | |
| 299 | |
| 300 // rotate < 180 degree | |
| 301 if (numSetA[4] != numSetB[4]) { | |
| 302 if (numSetA[4] - numSetB[4] > 180) { | |
| 303 numSetB[4] += 360; | |
| 304 } else if (numSetB[4] - numSetA[4] > 180) { | |
| 305 numSetA[4] += 360; | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 // skew | |
| 310 if (skewA != null) { | |
| 311 tempStr = a.substring(skewA.start, skewA.end); | |
| 312 match = numberRegEx.firstMatch(tempStr); | |
| 313 numSetA.add(num.parse(match.group(0))); | |
| 314 } else { | |
| 315 numSetA.add(0); | |
| 316 } | |
| 317 | |
| 318 if (skewB != null) { | |
| 319 tempStr = b.substring(skewB.start, skewB.end); | |
| 320 match = numberRegEx.firstMatch(tempStr); | |
| 321 numSetB.add(num.parse(match.group(0))); | |
| 322 } else { | |
| 323 numSetB.add(0); | |
| 324 } | |
| 325 | |
| 326 return (t) { | |
| 327 return | |
| 328 'translate(${createNumberInterpolator(numSetA[0], numSetB[0])(t)},' | |
| 329 '${createNumberInterpolator(numSetA[1], numSetB[1])(t)})' | |
| 330 'scale(${createNumberInterpolator(numSetA[2], numSetB[2])(t)},' | |
| 331 '${createNumberInterpolator(numSetA[3], numSetB[3])(t)})' | |
| 332 'rotate(${createNumberInterpolator(numSetA[4], numSetB[4])(t)})' | |
| 333 'skewX(${createNumberInterpolator(numSetA[5], numSetB[5])(t)})'; | |
| 334 }; | |
| 335 } | |
| 336 | |
| 337 /// Returns the interpolator that interpolators zoom list [a] to [b]. Zoom | |
| 338 /// lists are described by triple elements [ux0, uy0, w0] and [ux1, uy1, w1]. | |
| 339 Interpolator createZoomInterpolator(List a, List b) { | |
| 340 if (a == null || b == null) return (t) => b; | |
| 341 assert(a.length == b.length && a.length == 3); | |
| 342 | |
| 343 var sqrt2 = math.SQRT2, | |
| 344 param2 = 2, | |
| 345 param4 = 4; | |
| 346 | |
| 347 var ux0 = a[0], uy0 = a[1], w0 = a[2], | |
| 348 ux1 = b[0], uy1 = b[1], w1 = b[2]; | |
| 349 | |
| 350 var dx = ux1 - ux0, | |
| 351 dy = uy1 - uy0, | |
| 352 d2 = dx * dx + dy * dy, | |
| 353 d1 = math.sqrt(d2), | |
| 354 b0 = (w1 * w1 - w0 * w0 + param4 * d2) / (2 * w0 * param2 * d1), | |
| 355 b1 = (w1 * w1 - w0 * w0 - param4 * d2) / (2 * w1 * param2 * d1), | |
| 356 r0 = math.log(math.sqrt(b0 * b0 + 1) - b0), | |
| 357 r1 = math.log(math.sqrt(b1 * b1 + 1) - b1), | |
| 358 dr = r1 - r0, | |
| 359 S = ((!dr.isNaN) ? dr : math.log(w1 / w0)) / sqrt2; | |
| 360 | |
| 361 return (t) { | |
| 362 var s = t * S; | |
| 363 if (!dr.isNaN) { | |
| 364 // General case. | |
| 365 var coshr0 = cosh(r0), | |
| 366 u = w0 / (param2 * d1) * (coshr0 * tanh(sqrt2 * s + r0) - sinh(r0)); | |
| 367 return [ | |
| 368 ux0 + u * dx, | |
| 369 uy0 + u * dy, | |
| 370 w0 * coshr0 / cosh(sqrt2 * s + r0) | |
| 371 ]; | |
| 372 } | |
| 373 // Special case for u0 ~= u1. | |
| 374 return [ | |
| 375 ux0 + t * dx, | |
| 376 uy0 + t * dy, | |
| 377 w0 * math.exp(sqrt2 * s) | |
| 378 ]; | |
| 379 }; | |
| 380 } | |
| 381 | |
| 382 /// Reverse interpolator for a number. | |
| 383 Interpolator uninterpolateNumber(num a, num b) { | |
| 384 b = 1 / (b - a); | |
| 385 return (x) => (x - a) * b; | |
| 386 } | |
| 387 | |
| 388 /// Reverse interpolator for a clamped number. | |
| 389 Interpolator uninterpolateClamp(num a, num b) { | |
| 390 b = 1 / (b - a); | |
| 391 return (x) => math.max(0, math.min(1, (x - a) * b)); | |
| 392 } | |
| OLD | NEW |