3 _parse: function(str, sep)
6 str.split(sep).forEach(function(part) {
7 var item = part.split("=");
8 var value = decodeURIComponent(item[1]);
10 output[item[0]] = value.substr(1, value.length - 2);
12 output[item[0]] = value;
17 parseParameters: function()
19 return this._parse(window.location.search.substr(1), "&");
22 parseArguments: function(str)
24 return this._parse(str, " ");
27 extendObject: function(obj1, obj2)
29 for (var attrname in obj2)
30 obj1[attrname] = obj2[attrname];
34 copyObject: function(obj)
36 return this.extendObject({}, obj);
39 mergeObjects: function(obj1, obj2)
41 return this.extendObject(this.copyObject(obj1), obj2);
44 createClass: function(classConstructor, classMethods)
46 classConstructor.prototype = classMethods;
47 return classConstructor;
50 createSubclass: function(superclass, classConstructor, classMethods)
52 classConstructor.prototype = Object.create(superclass.prototype);
53 classConstructor.prototype.constructor = classConstructor;
55 Utilities.extendObject(classConstructor.prototype, classMethods);
56 return classConstructor;
59 createElement: function(name, attrs, parentElement)
61 var element = document.createElement(name);
63 for (var key in attrs)
64 element.setAttribute(key, attrs[key]);
66 parentElement.appendChild(element);
70 createSVGElement: function(name, attrs, xlinkAttrs, parentElement)
72 const svgNamespace = "http://www.w3.org/2000/svg";
73 const xlinkNamespace = "http://www.w3.org/1999/xlink";
75 var element = document.createElementNS(svgNamespace, name);
77 for (var key in attrs)
78 element.setAttribute(key, attrs[key]);
80 for (var key in xlinkAttrs)
81 element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]);
83 parentElement.appendChild(element);
87 browserPrefix: function()
89 // Get the HTML element's CSSStyleDeclaration
90 var styles = window.getComputedStyle(document.documentElement, '');
92 // Convert the styles list to an array
93 var stylesArray = Array.prototype.slice.call(styles);
95 // Concatenate all the styles in one big string
96 var stylesString = stylesArray.join('');
98 // Search the styles string for a known prefix type, settle on Opera if none is found.
99 var prefixes = stylesString.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']);
101 // prefixes has two elements; e.g. for webkit it has ['-webkit-', 'webkit'];
102 var prefix = prefixes[1];
104 // Have 'O' before 'Moz' in the string so it is matched first.
105 var dom = ('WebKit|O|Moz|MS').match(new RegExp(prefix, 'i'))[0];
107 // Return all the required prefixes.
111 css: '-' + prefix + '-',
112 js: prefix[0].toUpperCase() + prefix.substr(1)
116 setElementPrefixedProperty: function(element, property, value)
118 element.style[property] = element.style[this.browserPrefix().js + property[0].toUpperCase() + property.substr(1)] = value;
121 stripNonASCIICharacters: function(inputString)
123 return inputString.replace(/[ .,]/g, '');
126 convertObjectToQueryString: function(object)
128 var queryString = [];
129 for (var property in object) {
130 if (object.hasOwnProperty(property))
131 queryString.push(encodeURIComponent(property) + "=" + encodeURIComponent(object[property]));
133 return "?" + queryString.join("&");
136 convertQueryStringToObject: function(queryString)
138 queryString = queryString.substring(1);
143 queryString.split("&").forEach(function(parameter) {
144 var components = parameter.split("=");
145 object[components[0]] = components[1];
150 progressValue: function(value, min, max)
152 return (value - min) / (max - min);
155 lerp: function(value, min, max)
157 return min + (max - min) * value;
160 toFixedNumber: function(number, precision)
163 return Number(number.toFixed(precision));
168 Array.prototype.swap = function(i, j)
176 if (!Array.prototype.fill) {
177 Array.prototype.fill = function(value) {
179 throw new TypeError('Array.prototype.fill called on null or undefined');
181 var object = Object(this);
182 var len = parseInt(object.length, 10);
183 var start = arguments[1];
184 var relativeStart = parseInt(start, 10) || 0;
185 var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
186 var end = arguments[2];
187 var relativeEnd = end === undefined ? len : (parseInt(end) || 0) ;
188 var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
190 for (; k < final; k++)
197 if (!Array.prototype.find) {
198 Array.prototype.find = function(predicate) {
200 throw new TypeError('Array.prototype.find called on null or undefined');
201 if (typeof predicate !== 'function')
202 throw new TypeError('predicate must be a function');
204 var list = Object(this);
205 var length = list.length >>> 0;
206 var thisArg = arguments[1];
209 for (var i = 0; i < length; i++) {
211 if (predicate.call(thisArg, value, i, list))
218 Array.prototype.shuffle = function()
220 for (var index = this.length - 1; index >= 0; --index) {
221 var randomIndex = Math.floor(Math.random() * (index + 1));
222 this.swap(index, randomIndex);
227 Point = Utilities.createClass(
234 // Used when the point object is used as a size object.
240 // Used when the point object is used as a size object.
246 // Used when the point object is used as a size object.
249 return new Point(this.x / 2, this.y / 2);
254 return "x = " + this.x + ", y = " + this.y;
260 return new Point(this.x + other, this.y + other);
261 return new Point(this.x + other.x, this.y + other.y);
264 subtract: function(other)
267 return new Point(this.x - other, this.y - other);
268 return new Point(this.x - other.x, this.y - other.y);
271 multiply: function(other)
274 return new Point(this.x * other, this.y * other);
275 return new Point(this.x * other.x, this.y * other.y);
278 move: function(angle, velocity, timeDelta)
280 return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000)));
284 return Math.sqrt( this.x * this.x + this.y * this.y );
287 normalize: function() {
288 var l = Math.sqrt( this.x * this.x + this.y * this.y );
295 Utilities.extendObject(Point, {
296 zero: new Point(0, 0),
298 pointOnCircle: function(angle, radius)
300 return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
303 pointOnEllipse: function(angle, radiuses)
305 return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
308 elementClientSize: function(element)
310 var rect = element.getBoundingClientRect();
311 return new Point(rect.width, rect.height);
315 Insets = Utilities.createClass(
316 function(top, right, bottom, left)
320 this.bottom = bottom;
326 return this.left + this.right;
331 return this.top + this.bottom;
336 return new Point(this.width, this.height);
340 Insets.elementPadding = function(element)
342 var styles = window.getComputedStyle(element);
344 parseFloat(styles.paddingTop),
345 parseFloat(styles.paddingRight),
346 parseFloat(styles.paddingBottom),
347 parseFloat(styles.paddingTop));
350 UnitBezier = Utilities.createClass(
351 function(point1, point2)
353 // First and last points in the Bézier curve are assumed to be (0,0) and (!,1)
354 this._c = point1.multiply(3);
355 this._b = point2.subtract(point1).multiply(3).subtract(this._c);
356 this._a = new Point(1, 1).subtract(this._c).subtract(this._b);
360 derivativeEpsilon: 1e-6,
364 return this.sampleY(this.solveForT(x));
369 return ((this._a.x * t + this._b.x) * t + this._c.x) * t;
374 return ((this._a.y * t + this._b.y) * t + this._c.y) * t;kkkj
377 sampleDerivativeX: function(t)
379 return(3 * this._a.x * t + 2 * this._b.x) * t + this._c.x;
382 solveForT: function(x)
384 var t0, t1, t2, x2, d2, i;
386 for (t2 = x, i = 0; i < 8; ++i) {
387 x2 = this.sampleX(t2) - x;
388 if (Math.abs(x2) < this.epsilon)
390 d2 = this.sampleDerivativeX(t2);
391 if (Math.abs(d2) < this.derivativeEpsilon)
406 x2 = this.sampleX(t2);
407 if (Math.abs(x2 - x) < this.epsilon)
413 t2 = (t1 - t0) * .5 + t0;
420 SimplePromise = Utilities.createClass(
423 this._chainedPromise = null;
424 this._callback = null;
427 then: function (callback)
430 throw "SimplePromise doesn't support multiple calls to then";
432 this._callback = callback;
433 this._chainedPromise = new SimplePromise;
436 this.resolve(this._resolvedValue);
438 return this._chainedPromise;
441 resolve: function (value)
443 if (!this._callback) {
444 this._resolved = true;
445 this._resolvedValue = value;
449 var result = this._callback(value);
450 if (result instanceof SimplePromise) {
451 var chainedPromise = this._chainedPromise;
452 result.then(function (result) { chainedPromise.resolve(result); });
454 this._chainedPromise.resolve(result);
458 var Heap = Utilities.createClass(
459 function(maxSize, compare)
461 this._maxSize = maxSize;
462 this._compare = compare;
464 this._values = new Array(this._maxSize);
467 // This is a binary heap represented in an array. The root element is stored
468 // in the first element in the array. The root is followed by its two children.
469 // Then its four grandchildren and so on. So every level in the binary heap is
470 // doubled in the following level. Here is an example of the node indices and
471 // how they are related to their parents and children.
472 // ===========================================================================
474 // PARENT -1 0 0 1 1 2 2
475 // LEFT 1 3 5 7 9 11 13
476 // RIGHT 2 4 6 8 10 12 14
477 // ===========================================================================
478 _parentIndex: function(i)
480 return i > 0 ? Math.floor((i - 1) / 2) : -1;
483 _leftIndex: function(i)
485 var leftIndex = i * 2 + 1;
486 return leftIndex < this._size ? leftIndex : -1;
489 _rightIndex: function(i)
491 var rightIndex = i * 2 + 2;
492 return rightIndex < this._size ? rightIndex : -1;
495 // Return the child index that may violate the heap property at index i.
496 _childIndex: function(i)
498 var left = this._leftIndex(i);
499 var right = this._rightIndex(i);
501 if (left != -1 && right != -1)
502 return this._compare(this._values[left], this._values[right]) > 0 ? left : right;
504 return left != -1 ? left : right;
514 return this._size ? this._values[0] : NaN;
517 push: function(value)
519 if (this._size == this._maxSize) {
520 // If size is bounded and the new value can be a parent of the top()
521 // if the size were unbounded, just ignore the new value.
522 if (this._compare(value, this.top()) > 0)
526 this._values[this._size++] = value;
527 this._bubble(this._size - 1);
535 this._values[0] = this._values[--this._size];
541 // Fix the heap property at index i given that parent is the only node that
542 // may violate the heap property.
543 for (var pi = this._parentIndex(i); pi != -1; i = pi, pi = this._parentIndex(pi)) {
544 if (this._compare(this._values[pi], this._values[i]) > 0)
547 this._values.swap(pi, i);
553 // Fix the heap property at index i given that each of the left and the right
554 // sub-trees satisfies the heap property.
555 for (var ci = this._childIndex(i); ci != -1; i = ci, ci = this._childIndex(ci)) {
556 if (this._compare(this._values[i], this._values[ci]) > 0)
559 this._values.swap(ci, i);
565 var out = "Heap[" + this._size + "] = [";
566 for (var i = 0; i < this._size; ++i) {
567 out += this._values[i];
568 if (i < this._size - 1)
574 values: function(size) {
575 // Return the last "size" heap elements values.
576 var values = this._values.slice(0, this._size);
577 return values.sort(this._compare).slice(0, Math.min(size, this._size));
581 Utilities.extendObject(Heap, {
582 createMinHeap: function(maxSize)
584 return new Heap(maxSize, function(a, b) { return b - a; });
587 createMaxHeap: function(maxSize) {
588 return new Heap(maxSize, function(a, b) { return a - b; });
592 var SampleData = Utilities.createClass(
593 function(fieldMap, data)
595 this.fieldMap = fieldMap || {};
596 this.data = data || [];
601 return this.data.length;
604 addField: function(name, index)
606 this.fieldMap[name] = index;
609 push: function(datum)
611 this.data.push(datum);
614 sort: function(sortFunction)
616 this.data.sort(sortFunction);
619 slice: function(begin, end)
621 return new SampleData(this.fieldMap, this.data.slice(begin, end));
624 forEach: function(iterationFunction)
626 this.data.forEach(iterationFunction);
629 createDatum: function()
634 getFieldInDatum: function(datum, fieldName)
636 if (typeof datum === 'number')
637 datum = this.data[datum];
638 return datum[this.fieldMap[fieldName]];
641 setFieldInDatum: function(datum, fieldName, value)
643 if (typeof datum === 'number')
644 datum = this.data[datum];
645 return datum[this.fieldMap[fieldName]] = value;
650 return this.data[index];
657 this.data.forEach(function(datum) {
659 array.push(newDatum);
661 for (var fieldName in this.fieldMap) {
662 var value = this.getFieldInDatum(datum, fieldName);
663 if (value !== null && value !== undefined)
664 newDatum[fieldName] = value;