babushka-viz 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,697 @@
1
+ /**
2
+ * Springy v2.0.1
3
+ *
4
+ * Copyright (c) 2010 Dennis Hotson
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person
7
+ * obtaining a copy of this software and associated documentation
8
+ * files (the "Software"), to deal in the Software without
9
+ * restriction, including without limitation the rights to use,
10
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the
12
+ * Software is furnished to do so, subject to the following
13
+ * conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be
16
+ * included in all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ * OTHER DEALINGS IN THE SOFTWARE.
26
+ */
27
+
28
+ (function() {
29
+ // Enable strict mode for EC5 compatible browsers
30
+ "use strict";
31
+
32
+ // Establish the root object, `window` in the browser, or `global` on the server.
33
+ var root = this;
34
+
35
+ // The top-level namespace. All public Springy classes and modules will
36
+ // be attached to this. Exported for both CommonJS and the browser.
37
+ var Springy;
38
+ if (typeof exports !== 'undefined') {
39
+ Springy = exports;
40
+ } else {
41
+ Springy = root.Springy = {};
42
+ }
43
+
44
+ var Graph = Springy.Graph = function() {
45
+ this.nodeSet = {};
46
+ this.nodes = [];
47
+ this.edges = [];
48
+ this.adjacency = {};
49
+
50
+ this.nextNodeId = 0;
51
+ this.nextEdgeId = 0;
52
+ this.eventListeners = [];
53
+ };
54
+
55
+ var Node = Springy.Node = function(id, data) {
56
+ this.id = id;
57
+ this.data = (data !== undefined) ? data : {};
58
+
59
+ // Data fields used by layout algorithm in this file:
60
+ // this.data.mass
61
+ // Data used by default renderer in springyui.js
62
+ // this.data.label
63
+ };
64
+
65
+ var Edge = Springy.Edge = function(id, source, target, data) {
66
+ this.id = id;
67
+ this.source = source;
68
+ this.target = target;
69
+ this.data = (data !== undefined) ? data : {};
70
+
71
+ // Edge data field used by layout alorithm
72
+ // this.data.length
73
+ // this.data.type
74
+ };
75
+
76
+ Graph.prototype.addNode = function(node) {
77
+ if (!(node.id in this.nodeSet)) {
78
+ this.nodes.push(node);
79
+ }
80
+
81
+ this.nodeSet[node.id] = node;
82
+
83
+ this.notify();
84
+ return node;
85
+ };
86
+
87
+ Graph.prototype.addNodes = function() {
88
+ // accepts variable number of arguments, where each argument
89
+ // is a string that becomes both node identifier and label
90
+ for (var i = 0; i < arguments.length; i++) {
91
+ var name = arguments[i];
92
+ var node = new Node(name, {label:name});
93
+ this.addNode(node);
94
+ }
95
+ };
96
+
97
+ Graph.prototype.addEdge = function(edge) {
98
+ var exists = false;
99
+ this.edges.forEach(function(e) {
100
+ if (edge.id === e.id) { exists = true; }
101
+ });
102
+
103
+ if (!exists) {
104
+ this.edges.push(edge);
105
+ }
106
+
107
+ if (!(edge.source.id in this.adjacency)) {
108
+ this.adjacency[edge.source.id] = {};
109
+ }
110
+ if (!(edge.target.id in this.adjacency[edge.source.id])) {
111
+ this.adjacency[edge.source.id][edge.target.id] = [];
112
+ }
113
+
114
+ exists = false;
115
+ this.adjacency[edge.source.id][edge.target.id].forEach(function(e) {
116
+ if (edge.id === e.id) { exists = true; }
117
+ });
118
+
119
+ if (!exists) {
120
+ this.adjacency[edge.source.id][edge.target.id].push(edge);
121
+ }
122
+
123
+ this.notify();
124
+ return edge;
125
+ };
126
+
127
+ Graph.prototype.addEdges = function() {
128
+ // accepts variable number of arguments, where each argument
129
+ // is a triple [nodeid1, nodeid2, attributes]
130
+ for (var i = 0; i < arguments.length; i++) {
131
+ var e = arguments[i];
132
+ var node1 = this.nodeSet[e[0]];
133
+ if (node1 == undefined) {
134
+ throw new TypeError("invalid node name: " + e[0]);
135
+ }
136
+ var node2 = this.nodeSet[e[1]];
137
+ if (node2 == undefined) {
138
+ throw new TypeError("invalid node name: " + e[1]);
139
+ }
140
+ var attr = e[2];
141
+
142
+ this.newEdge(node1, node2, attr);
143
+ }
144
+ };
145
+
146
+ Graph.prototype.newNode = function(data) {
147
+ var node = new Node(this.nextNodeId++, data);
148
+ this.addNode(node);
149
+ return node;
150
+ };
151
+
152
+ Graph.prototype.newEdge = function(source, target, data) {
153
+ var edge = new Edge(this.nextEdgeId++, source, target, data);
154
+ this.addEdge(edge);
155
+ return edge;
156
+ };
157
+
158
+
159
+ // add nodes and edges from JSON object
160
+ Graph.prototype.loadJSON = function(json) {
161
+ /**
162
+ Springy's simple JSON format for graphs.
163
+
164
+ historically, Springy uses separate lists
165
+ of nodes and edges:
166
+
167
+ {
168
+ "nodes": [
169
+ "center",
170
+ "left",
171
+ "right",
172
+ "up",
173
+ "satellite"
174
+ ],
175
+ "edges": [
176
+ ["center", "left"],
177
+ ["center", "right"],
178
+ ["center", "up"]
179
+ ]
180
+ }
181
+
182
+ **/
183
+ // parse if a string is passed (EC5+ browsers)
184
+ if (typeof json == 'string' || json instanceof String) {
185
+ json = JSON.parse( json );
186
+ }
187
+
188
+ if ('nodes' in json || 'edges' in json) {
189
+ this.addNodes.apply(this, json['nodes']);
190
+ this.addEdges.apply(this, json['edges']);
191
+ }
192
+ }
193
+
194
+
195
+ // find the edges from node1 to node2
196
+ Graph.prototype.getEdges = function(node1, node2) {
197
+ if (node1.id in this.adjacency
198
+ && node2.id in this.adjacency[node1.id]) {
199
+ return this.adjacency[node1.id][node2.id];
200
+ }
201
+
202
+ return [];
203
+ };
204
+
205
+ // remove a node and it's associated edges from the graph
206
+ Graph.prototype.removeNode = function(node) {
207
+ if (node.id in this.nodeSet) {
208
+ delete this.nodeSet[node.id];
209
+ }
210
+
211
+ for (var i = this.nodes.length - 1; i >= 0; i--) {
212
+ if (this.nodes[i].id === node.id) {
213
+ this.nodes.splice(i, 1);
214
+ }
215
+ }
216
+
217
+ this.detachNode(node);
218
+ };
219
+
220
+ // removes edges associated with a given node
221
+ Graph.prototype.detachNode = function(node) {
222
+ var tmpEdges = this.edges.slice();
223
+ tmpEdges.forEach(function(e) {
224
+ if (e.source.id === node.id || e.target.id === node.id) {
225
+ this.removeEdge(e);
226
+ }
227
+ }, this);
228
+
229
+ this.notify();
230
+ };
231
+
232
+ // remove a node and it's associated edges from the graph
233
+ Graph.prototype.removeEdge = function(edge) {
234
+ for (var i = this.edges.length - 1; i >= 0; i--) {
235
+ if (this.edges[i].id === edge.id) {
236
+ this.edges.splice(i, 1);
237
+ }
238
+ }
239
+
240
+ for (var x in this.adjacency) {
241
+ for (var y in this.adjacency[x]) {
242
+ var edges = this.adjacency[x][y];
243
+
244
+ for (var j=edges.length - 1; j>=0; j--) {
245
+ if (this.adjacency[x][y][j].id === edge.id) {
246
+ this.adjacency[x][y].splice(j, 1);
247
+ }
248
+ }
249
+
250
+ // Clean up empty edge arrays
251
+ if (this.adjacency[x][y].length == 0) {
252
+ delete this.adjacency[x][y];
253
+ }
254
+ }
255
+
256
+ // Clean up empty objects
257
+ if (isEmpty(this.adjacency[x])) {
258
+ delete this.adjacency[x];
259
+ }
260
+ }
261
+
262
+ this.notify();
263
+ };
264
+
265
+ /* Merge a list of nodes and edges into the current graph. eg.
266
+ var o = {
267
+ nodes: [
268
+ {id: 123, data: {type: 'user', userid: 123, displayname: 'aaa'}},
269
+ {id: 234, data: {type: 'user', userid: 234, displayname: 'bbb'}}
270
+ ],
271
+ edges: [
272
+ {from: 0, to: 1, type: 'submitted_design', directed: true, data: {weight: }}
273
+ ]
274
+ }
275
+ */
276
+ Graph.prototype.merge = function(data) {
277
+ var nodes = [];
278
+ data.nodes.forEach(function(n) {
279
+ nodes.push(this.addNode(new Node(n.id, n.data)));
280
+ }, this);
281
+
282
+ data.edges.forEach(function(e) {
283
+ var from = nodes[e.from];
284
+ var to = nodes[e.to];
285
+
286
+ var id = (e.directed)
287
+ ? (id = e.type + "-" + from.id + "-" + to.id)
288
+ : (from.id < to.id) // normalise id for non-directed edges
289
+ ? e.type + "-" + from.id + "-" + to.id
290
+ : e.type + "-" + to.id + "-" + from.id;
291
+
292
+ var edge = this.addEdge(new Edge(id, from, to, e.data));
293
+ edge.data.type = e.type;
294
+ }, this);
295
+ };
296
+
297
+ Graph.prototype.filterNodes = function(fn) {
298
+ var tmpNodes = this.nodes.slice();
299
+ tmpNodes.forEach(function(n) {
300
+ if (!fn(n)) {
301
+ this.removeNode(n);
302
+ }
303
+ }, this);
304
+ };
305
+
306
+ Graph.prototype.filterEdges = function(fn) {
307
+ var tmpEdges = this.edges.slice();
308
+ tmpEdges.forEach(function(e) {
309
+ if (!fn(e)) {
310
+ this.removeEdge(e);
311
+ }
312
+ }, this);
313
+ };
314
+
315
+
316
+ Graph.prototype.addGraphListener = function(obj) {
317
+ this.eventListeners.push(obj);
318
+ };
319
+
320
+ Graph.prototype.notify = function() {
321
+ this.eventListeners.forEach(function(obj){
322
+ obj.graphChanged();
323
+ });
324
+ };
325
+
326
+ // -----------
327
+ var Layout = Springy.Layout = {};
328
+ Layout.ForceDirected = function(graph, stiffness, repulsion, damping) {
329
+ this.graph = graph;
330
+ this.stiffness = stiffness; // spring stiffness constant
331
+ this.repulsion = repulsion; // repulsion constant
332
+ this.damping = damping; // velocity damping factor
333
+
334
+ this.nodePoints = {}; // keep track of points associated with nodes
335
+ this.edgeSprings = {}; // keep track of springs associated with edges
336
+ };
337
+
338
+ Layout.ForceDirected.prototype.point = function(node) {
339
+ if (!(node.id in this.nodePoints)) {
340
+ var mass = (node.data.mass !== undefined) ? node.data.mass : 1.0;
341
+ this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass);
342
+ }
343
+
344
+ return this.nodePoints[node.id];
345
+ };
346
+
347
+ Layout.ForceDirected.prototype.spring = function(edge) {
348
+ if (!(edge.id in this.edgeSprings)) {
349
+ var length = (edge.data.length !== undefined) ? edge.data.length : 1.0;
350
+
351
+ var existingSpring = false;
352
+
353
+ var from = this.graph.getEdges(edge.source, edge.target);
354
+ from.forEach(function(e) {
355
+ if (existingSpring === false && e.id in this.edgeSprings) {
356
+ existingSpring = this.edgeSprings[e.id];
357
+ }
358
+ }, this);
359
+
360
+ if (existingSpring !== false) {
361
+ return new Layout.ForceDirected.Spring(existingSpring.point1, existingSpring.point2, 0.0, 0.0);
362
+ }
363
+
364
+ var to = this.graph.getEdges(edge.target, edge.source);
365
+ from.forEach(function(e){
366
+ if (existingSpring === false && e.id in this.edgeSprings) {
367
+ existingSpring = this.edgeSprings[e.id];
368
+ }
369
+ }, this);
370
+
371
+ if (existingSpring !== false) {
372
+ return new Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0, 0.0);
373
+ }
374
+
375
+ this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring(
376
+ this.point(edge.source), this.point(edge.target), length, this.stiffness
377
+ );
378
+ }
379
+
380
+ return this.edgeSprings[edge.id];
381
+ };
382
+
383
+ // callback should accept two arguments: Node, Point
384
+ Layout.ForceDirected.prototype.eachNode = function(callback) {
385
+ var t = this;
386
+ this.graph.nodes.forEach(function(n){
387
+ callback.call(t, n, t.point(n));
388
+ });
389
+ };
390
+
391
+ // callback should accept two arguments: Edge, Spring
392
+ Layout.ForceDirected.prototype.eachEdge = function(callback) {
393
+ var t = this;
394
+ this.graph.edges.forEach(function(e){
395
+ callback.call(t, e, t.spring(e));
396
+ });
397
+ };
398
+
399
+ // callback should accept one argument: Spring
400
+ Layout.ForceDirected.prototype.eachSpring = function(callback) {
401
+ var t = this;
402
+ this.graph.edges.forEach(function(e){
403
+ callback.call(t, t.spring(e));
404
+ });
405
+ };
406
+
407
+
408
+ // Physics stuff
409
+ Layout.ForceDirected.prototype.applyCoulombsLaw = function() {
410
+ this.eachNode(function(n1, point1) {
411
+ this.eachNode(function(n2, point2) {
412
+ if (point1 !== point2)
413
+ {
414
+ var d = point1.p.subtract(point2.p);
415
+ var distance = d.magnitude() + 0.1; // avoid massive forces at small distances (and divide by zero)
416
+ var direction = d.normalise();
417
+
418
+ // apply force to each end point
419
+ point1.applyForce(direction.multiply(this.repulsion).divide(distance * distance * 0.5));
420
+ point2.applyForce(direction.multiply(this.repulsion).divide(distance * distance * -0.5));
421
+ }
422
+ });
423
+ });
424
+ };
425
+
426
+ Layout.ForceDirected.prototype.applyHookesLaw = function() {
427
+ this.eachSpring(function(spring){
428
+ var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring
429
+ var displacement = spring.length - d.magnitude();
430
+ var direction = d.normalise();
431
+
432
+ // apply force to each end point
433
+ spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5));
434
+ spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5));
435
+ });
436
+ };
437
+
438
+ Layout.ForceDirected.prototype.attractToCentre = function() {
439
+ this.eachNode(function(node, point) {
440
+ var direction = point.p.multiply(-1.0);
441
+ point.applyForce(direction.multiply(this.repulsion / 50.0));
442
+ });
443
+ };
444
+
445
+
446
+ Layout.ForceDirected.prototype.updateVelocity = function(timestep) {
447
+ this.eachNode(function(node, point) {
448
+ // Is this, along with updatePosition below, the only places that your
449
+ // integration code exist?
450
+ point.v = point.v.add(point.a.multiply(timestep)).multiply(this.damping);
451
+ point.a = new Vector(0,0);
452
+ });
453
+ };
454
+
455
+ Layout.ForceDirected.prototype.updatePosition = function(timestep) {
456
+ this.eachNode(function(node, point) {
457
+ // Same question as above; along with updateVelocity, is this all of
458
+ // your integration code?
459
+ point.p = point.p.add(point.v.multiply(timestep));
460
+ });
461
+ };
462
+
463
+ // Calculate the total kinetic energy of the system
464
+ Layout.ForceDirected.prototype.totalEnergy = function(timestep) {
465
+ var energy = 0.0;
466
+ this.eachNode(function(node, point) {
467
+ var speed = point.v.magnitude();
468
+ energy += 0.5 * point.m * speed * speed;
469
+ });
470
+
471
+ return energy;
472
+ };
473
+
474
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // stolen from coffeescript, thanks jashkenas! ;-)
475
+
476
+ Springy.requestAnimationFrame = __bind(root.requestAnimationFrame ||
477
+ root.webkitRequestAnimationFrame ||
478
+ root.mozRequestAnimationFrame ||
479
+ root.oRequestAnimationFrame ||
480
+ root.msRequestAnimationFrame ||
481
+ (function(callback, element) {
482
+ root.setTimeout(callback, 10);
483
+ }), root);
484
+
485
+
486
+ // start simulation
487
+ Layout.ForceDirected.prototype.start = function(render, done) {
488
+ var t = this;
489
+
490
+ if (this._started) return;
491
+ this._started = true;
492
+ this._stop = false;
493
+
494
+ Springy.requestAnimationFrame(function step() {
495
+ t.applyCoulombsLaw();
496
+ t.applyHookesLaw();
497
+ t.attractToCentre();
498
+ t.updateVelocity(0.03);
499
+ t.updatePosition(0.03);
500
+
501
+ if (render !== undefined) {
502
+ render();
503
+ }
504
+
505
+ // stop simulation when energy of the system goes below a threshold
506
+ if (t._stop || t.totalEnergy() < 0.01) {
507
+ t._started = false;
508
+ if (done !== undefined) { done(); }
509
+ } else {
510
+ Springy.requestAnimationFrame(step);
511
+ }
512
+ });
513
+ };
514
+
515
+ Layout.ForceDirected.prototype.stop = function() {
516
+ this._stop = true;
517
+ }
518
+
519
+ // Find the nearest point to a particular position
520
+ Layout.ForceDirected.prototype.nearest = function(pos) {
521
+ var min = {node: null, point: null, distance: null};
522
+ var t = this;
523
+ this.graph.nodes.forEach(function(n){
524
+ var point = t.point(n);
525
+ var distance = point.p.subtract(pos).magnitude();
526
+
527
+ if (min.distance === null || distance < min.distance) {
528
+ min = {node: n, point: point, distance: distance};
529
+ }
530
+ });
531
+
532
+ return min;
533
+ };
534
+
535
+ // returns [bottomleft, topright]
536
+ Layout.ForceDirected.prototype.getBoundingBox = function() {
537
+ var bottomleft = new Vector(-2,-2);
538
+ var topright = new Vector(2,2);
539
+
540
+ this.eachNode(function(n, point) {
541
+ if (point.p.x < bottomleft.x) {
542
+ bottomleft.x = point.p.x;
543
+ }
544
+ if (point.p.y < bottomleft.y) {
545
+ bottomleft.y = point.p.y;
546
+ }
547
+ if (point.p.x > topright.x) {
548
+ topright.x = point.p.x;
549
+ }
550
+ if (point.p.y > topright.y) {
551
+ topright.y = point.p.y;
552
+ }
553
+ });
554
+
555
+ var padding = topright.subtract(bottomleft).multiply(0.07); // ~5% padding
556
+
557
+ return {bottomleft: bottomleft.subtract(padding), topright: topright.add(padding)};
558
+ };
559
+
560
+
561
+ // Vector
562
+ var Vector = Springy.Vector = function(x, y) {
563
+ this.x = x;
564
+ this.y = y;
565
+ };
566
+
567
+ Vector.random = function() {
568
+ return new Vector(10.0 * (Math.random() - 0.5), 10.0 * (Math.random() - 0.5));
569
+ };
570
+
571
+ Vector.prototype.add = function(v2) {
572
+ return new Vector(this.x + v2.x, this.y + v2.y);
573
+ };
574
+
575
+ Vector.prototype.subtract = function(v2) {
576
+ return new Vector(this.x - v2.x, this.y - v2.y);
577
+ };
578
+
579
+ Vector.prototype.multiply = function(n) {
580
+ return new Vector(this.x * n, this.y * n);
581
+ };
582
+
583
+ Vector.prototype.divide = function(n) {
584
+ return new Vector((this.x / n) || 0, (this.y / n) || 0); // Avoid divide by zero errors..
585
+ };
586
+
587
+ Vector.prototype.magnitude = function() {
588
+ return Math.sqrt(this.x*this.x + this.y*this.y);
589
+ };
590
+
591
+ Vector.prototype.normal = function() {
592
+ return new Vector(-this.y, this.x);
593
+ };
594
+
595
+ Vector.prototype.normalise = function() {
596
+ return this.divide(this.magnitude());
597
+ };
598
+
599
+ // Point
600
+ Layout.ForceDirected.Point = function(position, mass) {
601
+ this.p = position; // position
602
+ this.m = mass; // mass
603
+ this.v = new Vector(0, 0); // velocity
604
+ this.a = new Vector(0, 0); // acceleration
605
+ };
606
+
607
+ Layout.ForceDirected.Point.prototype.applyForce = function(force) {
608
+ this.a = this.a.add(force.divide(this.m));
609
+ };
610
+
611
+ // Spring
612
+ Layout.ForceDirected.Spring = function(point1, point2, length, k) {
613
+ this.point1 = point1;
614
+ this.point2 = point2;
615
+ this.length = length; // spring length at rest
616
+ this.k = k; // spring constant (See Hooke's law) .. how stiff the spring is
617
+ };
618
+
619
+ // Layout.ForceDirected.Spring.prototype.distanceToPoint = function(point)
620
+ // {
621
+ // // hardcore vector arithmetic.. ohh yeah!
622
+ // // .. see http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/865080#865080
623
+ // var n = this.point2.p.subtract(this.point1.p).normalise().normal();
624
+ // var ac = point.p.subtract(this.point1.p);
625
+ // return Math.abs(ac.x * n.x + ac.y * n.y);
626
+ // };
627
+
628
+ // Renderer handles the layout rendering loop
629
+ var Renderer = Springy.Renderer = function(layout, clear, drawEdge, drawNode) {
630
+ this.layout = layout;
631
+ this.clear = clear;
632
+ this.drawEdge = drawEdge;
633
+ this.drawNode = drawNode;
634
+
635
+ this.layout.graph.addGraphListener(this);
636
+ }
637
+
638
+ Renderer.prototype.graphChanged = function(e) {
639
+ this.start();
640
+ };
641
+
642
+ Renderer.prototype.start = function() {
643
+ var t = this;
644
+ this.layout.start(function render() {
645
+ t.clear();
646
+
647
+ t.layout.eachEdge(function(edge, spring) {
648
+ t.drawEdge(edge, spring.point1.p, spring.point2.p);
649
+ });
650
+
651
+ t.layout.eachNode(function(node, point) {
652
+ t.drawNode(node, point.p);
653
+ });
654
+ });
655
+ };
656
+
657
+ Renderer.prototype.stop = function() {
658
+ this.layout.stop();
659
+ };
660
+
661
+ // Array.forEach implementation for IE support..
662
+ //https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
663
+ if ( !Array.prototype.forEach ) {
664
+ Array.prototype.forEach = function( callback, thisArg ) {
665
+ var T, k;
666
+ if ( this == null ) {
667
+ throw new TypeError( " this is null or not defined" );
668
+ }
669
+ var O = Object(this);
670
+ var len = O.length >>> 0; // Hack to convert O.length to a UInt32
671
+ if ( {}.toString.call(callback) != "[object Function]" ) {
672
+ throw new TypeError( callback + " is not a function" );
673
+ }
674
+ if ( thisArg ) {
675
+ T = thisArg;
676
+ }
677
+ k = 0;
678
+ while( k < len ) {
679
+ var kValue;
680
+ if ( k in O ) {
681
+ kValue = O[ k ];
682
+ callback.call( T, kValue, k, O );
683
+ }
684
+ k++;
685
+ }
686
+ };
687
+ }
688
+
689
+ var isEmpty = function(obj) {
690
+ for (var k in obj) {
691
+ if (obj.hasOwnProperty(k)) {
692
+ return false;
693
+ }
694
+ }
695
+ return true;
696
+ };
697
+ }).call(this);