Newer
Older
54001
54002
54003
54004
54005
54006
54007
54008
54009
54010
54011
54012
54013
54014
54015
54016
54017
54018
54019
54020
54021
54022
54023
54024
54025
54026
54027
54028
54029
54030
54031
54032
54033
54034
54035
54036
54037
54038
54039
54040
54041
54042
54043
54044
54045
54046
54047
54048
54049
54050
54051
54052
54053
54054
54055
54056
54057
54058
54059
54060
54061
54062
54063
54064
54065
54066
54067
54068
54069
54070
54071
54072
54073
54074
54075
54076
54077
54078
54079
54080
54081
54082
54083
54084
54085
54086
54087
54088
54089
54090
54091
54092
54093
54094
54095
54096
54097
54098
54099
54100
54101
54102
54103
54104
54105
54106
54107
54108
54109
54110
54111
54112
54113
54114
54115
54116
54117
54118
54119
54120
54121
54122
54123
54124
54125
54126
54127
54128
54129
54130
54131
54132
54133
54134
54135
54136
54137
54138
54139
54140
54141
54142
54143
54144
54145
54146
54147
54148
54149
54150
54151
54152
54153
54154
54155
54156
54157
54158
54159
54160
54161
54162
54163
54164
54165
54166
54167
54168
54169
54170
54171
54172
54173
54174
54175
54176
54177
54178
54179
54180
54181
54182
54183
54184
54185
54186
54187
54188
54189
54190
54191
54192
54193
54194
54195
54196
54197
54198
54199
54200
54201
54202
54203
54204
54205
54206
54207
54208
54209
54210
54211
54212
54213
54214
54215
54216
54217
54218
54219
54220
54221
54222
54223
54224
54225
54226
54227
54228
54229
54230
54231
54232
54233
54234
54235
54236
54237
54238
54239
54240
54241
54242
54243
54244
54245
54246
54247
54248
54249
54250
54251
54252
54253
54254
54255
54256
54257
54258
54259
54260
54261
54262
54263
54264
54265
54266
54267
54268
54269
54270
54271
54272
54273
54274
54275
54276
54277
54278
54279
54280
54281
54282
54283
54284
54285
54286
54287
54288
54289
54290
54291
54292
54293
54294
54295
54296
54297
54298
54299
54300
54301
54302
54303
54304
54305
54306
54307
54308
54309
54310
54311
54312
54313
54314
54315
54316
54317
54318
54319
54320
54321
54322
54323
54324
54325
54326
54327
54328
54329
54330
54331
54332
54333
54334
54335
54336
54337
54338
54339
54340
54341
54342
54343
54344
54345
54346
54347
54348
54349
54350
54351
54352
54353
54354
54355
54356
54357
54358
54359
54360
54361
54362
54363
54364
54365
54366
54367
54368
54369
54370
54371
54372
54373
54374
54375
54376
54377
54378
54379
54380
54381
54382
54383
54384
54385
54386
54387
54388
54389
54390
54391
54392
54393
54394
54395
54396
54397
54398
54399
54400
54401
54402
54403
54404
54405
54406
54407
54408
54409
54410
54411
54412
54413
54414
54415
54416
54417
54418
54419
54420
54421
54422
54423
54424
54425
54426
54427
54428
54429
54430
54431
54432
54433
54434
54435
54436
54437
54438
54439
54440
54441
54442
54443
54444
54445
54446
54447
54448
54449
54450
54451
54452
54453
54454
54455
54456
54457
54458
54459
54460
54461
54462
54463
54464
54465
54466
54467
54468
54469
54470
54471
54472
54473
54474
54475
54476
54477
54478
54479
54480
54481
54482
54483
54484
54485
54486
54487
54488
54489
54490
54491
54492
54493
54494
54495
54496
54497
54498
54499
54500
54501
54502
54503
54504
54505
54506
54507
54508
54509
54510
54511
54512
54513
54514
54515
54516
54517
54518
54519
54520
54521
54522
54523
54524
54525
54526
54527
54528
54529
54530
54531
54532
54533
54534
54535
54536
54537
54538
54539
54540
54541
54542
54543
54544
54545
54546
54547
54548
54549
54550
54551
54552
54553
54554
54555
54556
54557
54558
54559
54560
54561
54562
54563
54564
54565
54566
54567
54568
54569
54570
54571
54572
54573
54574
54575
54576
54577
54578
54579
54580
54581
54582
54583
54584
54585
54586
54587
54588
54589
54590
54591
54592
54593
54594
54595
54596
54597
54598
54599
54600
54601
54602
54603
54604
54605
54606
54607
54608
54609
54610
54611
54612
54613
54614
54615
54616
54617
54618
54619
54620
54621
54622
54623
54624
54625
54626
54627
54628
54629
54630
54631
54632
54633
54634
54635
54636
54637
54638
54639
54640
54641
54642
54643
54644
54645
54646
54647
54648
54649
54650
54651
54652
54653
54654
54655
54656
54657
54658
54659
54660
54661
54662
54663
54664
54665
54666
54667
54668
54669
54670
54671
54672
54673
54674
54675
54676
54677
54678
54679
54680
54681
54682
54683
54684
54685
54686
54687
54688
54689
54690
54691
54692
54693
54694
54695
54696
54697
54698
54699
54700
54701
54702
54703
54704
54705
54706
54707
54708
54709
54710
54711
54712
54713
54714
54715
54716
54717
54718
54719
54720
54721
54722
54723
54724
54725
54726
54727
54728
54729
54730
54731
54732
54733
54734
54735
54736
54737
54738
54739
54740
54741
54742
54743
54744
54745
54746
54747
54748
54749
54750
54751
54752
54753
54754
54755
54756
54757
54758
54759
54760
54761
54762
54763
54764
54765
54766
54767
54768
54769
54770
54771
54772
54773
54774
54775
54776
54777
54778
54779
54780
54781
54782
54783
54784
54785
54786
54787
54788
54789
54790
54791
54792
54793
54794
54795
54796
54797
54798
54799
54800
54801
54802
54803
54804
54805
54806
54807
54808
54809
54810
54811
54812
54813
54814
54815
54816
54817
54818
54819
54820
54821
54822
54823
54824
54825
54826
54827
54828
54829
54830
54831
54832
54833
54834
54835
54836
54837
54838
54839
54840
54841
54842
54843
54844
54845
54846
54847
54848
54849
54850
54851
54852
54853
54854
54855
54856
54857
54858
54859
54860
54861
54862
54863
54864
54865
54866
54867
54868
54869
54870
54871
54872
54873
54874
54875
54876
54877
54878
54879
54880
54881
54882
54883
54884
54885
54886
54887
54888
54889
54890
54891
54892
54893
54894
54895
54896
54897
54898
54899
54900
54901
54902
54903
54904
54905
54906
54907
54908
54909
54910
54911
54912
54913
54914
54915
54916
54917
54918
54919
54920
54921
54922
54923
54924
54925
54926
54927
54928
54929
54930
54931
54932
54933
54934
54935
54936
54937
54938
54939
54940
54941
54942
54943
54944
54945
54946
54947
54948
54949
54950
54951
54952
54953
54954
54955
54956
54957
54958
54959
54960
54961
54962
54963
54964
54965
54966
54967
54968
54969
54970
54971
54972
54973
54974
54975
54976
54977
54978
54979
54980
54981
54982
54983
54984
54985
54986
54987
54988
54989
54990
54991
54992
54993
54994
54995
54996
54997
54998
54999
55000
// and the node's center-of-mass
dx = node.massX / node.mass - sourceBody.pos.x;
dy = node.massY / node.mass - sourceBody.pos.y;
r = Math.sqrt(dx * dx + dy * dy);
if (r === 0) {
// Sorry about code duplucation. I don't want to create many functions
// right away. Just want to see performance first.
dx = (random.nextDouble() - 0.5) / 50;
dy = (random.nextDouble() - 0.5) / 50;
r = Math.sqrt(dx * dx + dy * dy);
}
// If s / r < θ, treat this internal node as a single body, and calculate the
// force it exerts on sourceBody, and add this amount to sourceBody's net force.
if ((node.right - node.left) / r < theta) {
// in the if statement above we consider node's width only
// because the region was squarified during tree creation.
// Thus there is no difference between using width or height.
v = gravity * node.mass * sourceBody.mass / (r * r * r);
fx += v * dx;
fy += v * dy;
} else {
// Otherwise, run the procedure recursively on each of the current node's children.
// I intentionally unfolded this loop, to save several CPU cycles.
if (node.quad0) {
queue[pushIdx] = node.quad0;
queueLength += 1;
pushIdx += 1;
}
if (node.quad1) {
queue[pushIdx] = node.quad1;
queueLength += 1;
pushIdx += 1;
}
if (node.quad2) {
queue[pushIdx] = node.quad2;
queueLength += 1;
pushIdx += 1;
}
if (node.quad3) {
queue[pushIdx] = node.quad3;
queueLength += 1;
pushIdx += 1;
}
}
}
}
sourceBody.force.x += fx;
sourceBody.force.y += fy;
}
function insertBodies(bodies) {
var x1 = Number.MAX_VALUE,
y1 = Number.MAX_VALUE,
x2 = Number.MIN_VALUE,
y2 = Number.MIN_VALUE,
i,
max = bodies.length;
// To reduce quad tree depth we are looking for exact bounding box of all particles.
i = max;
while (i--) {
var x = bodies[i].pos.x;
var y = bodies[i].pos.y;
if (x < x1) {
x1 = x;
}
if (x > x2) {
x2 = x;
}
if (y < y1) {
y1 = y;
}
if (y > y2) {
y2 = y;
}
}
// Squarify the bounds.
var dx = x2 - x1,
dy = y2 - y1;
if (dx > dy) {
y2 = y1 + dx;
} else {
x2 = x1 + dy;
}
currentInCache = 0;
root = newNode();
root.left = x1;
root.right = x2;
root.top = y1;
root.bottom = y2;
i = max - 1;
if (i >= 0) {
root.body = bodies[i];
}
while (i--) {
insert(bodies[i]);
}
}
function insert(newBody) {
insertStack$1.reset();
insertStack$1.push(root, newBody);
while (!insertStack$1.isEmpty()) {
var stackItem = insertStack$1.pop(),
node = stackItem.node,
body = stackItem.body;
if (!node.body) {
// This is internal node. Update the total mass of the node and center-of-mass.
var x = body.pos.x;
var y = body.pos.y;
node.mass = node.mass + body.mass;
node.massX = node.massX + body.mass * x;
node.massY = node.massY + body.mass * y;
// Recursively insert the body in the appropriate quadrant.
// But first find the appropriate quadrant.
var quadIdx = 0, // Assume we are in the 0's quad.
left = node.left,
right = (node.right + left) / 2,
top = node.top,
bottom = (node.bottom + top) / 2;
if (x > right) { // somewhere in the eastern part.
quadIdx = quadIdx + 1;
left = right;
right = node.right;
}
if (y > bottom) { // and in south.
quadIdx = quadIdx + 2;
top = bottom;
bottom = node.bottom;
}
var child = getChild(node, quadIdx);
if (!child) {
// The node is internal but this quadrant is not taken. Add
// subnode to it.
child = newNode();
child.left = left;
child.top = top;
child.right = right;
child.bottom = bottom;
child.body = body;
setChild(node, quadIdx, child);
} else {
// continue searching in this quadrant.
insertStack$1.push(child, body);
}
} else {
// We are trying to add to the leaf node.
// We have to convert current leaf into internal node
// and continue adding two nodes.
var oldBody = node.body;
node.body = null; // internal nodes do not cary bodies
if (isSamePosition$1(oldBody.pos, body.pos)) {
// Prevent infinite subdivision by bumping one node
// anywhere in this quadrant
var retriesCount = 3;
do {
var offset = random.nextDouble();
var dx = (node.right - node.left) * offset;
var dy = (node.bottom - node.top) * offset;
oldBody.pos.x = node.left + dx;
oldBody.pos.y = node.top + dy;
retriesCount -= 1;
// Make sure we don't bump it out of the box. If we do, next iteration should fix it
} while (retriesCount > 0 && isSamePosition$1(oldBody.pos, body.pos));
if (retriesCount === 0 && isSamePosition$1(oldBody.pos, body.pos)) {
// This is very bad, we ran out of precision.
// if we do not return from the method we'll get into
// infinite loop here. So we sacrifice correctness of layout, and keep the app running
// Next layout iteration should get larger bounding box in the first step and fix this
return;
}
}
// Next iteration should subdivide node further.
insertStack$1.push(node, oldBody);
insertStack$1.push(node, body);
}
}
}
};
function getChild(node, idx) {
if (idx === 0) return node.quad0;
if (idx === 1) return node.quad1;
if (idx === 2) return node.quad2;
if (idx === 3) return node.quad3;
return null;
}
function setChild(node, idx, child) {
if (idx === 0) node.quad0 = child;
else if (idx === 1) node.quad1 = child;
else if (idx === 2) node.quad2 = child;
else if (idx === 3) node.quad3 = child;
}
var bounds = function (bodies, settings) {
var random = ngraph_random.random(42);
var boundingBox = { x1: 0, y1: 0, x2: 0, y2: 0 };
return {
box: boundingBox,
update: updateBoundingBox,
reset : function () {
boundingBox.x1 = boundingBox.y1 = 0;
boundingBox.x2 = boundingBox.y2 = 0;
},
getBestNewPosition: function (neighbors) {
var graphRect = boundingBox;
var baseX = 0, baseY = 0;
if (neighbors.length) {
for (var i = 0; i < neighbors.length; ++i) {
baseX += neighbors[i].pos.x;
baseY += neighbors[i].pos.y;
}
baseX /= neighbors.length;
baseY /= neighbors.length;
} else {
baseX = (graphRect.x1 + graphRect.x2) / 2;
baseY = (graphRect.y1 + graphRect.y2) / 2;
}
var springLength = settings.springLength;
return {
x: baseX + random.next(springLength) - springLength / 2,
y: baseY + random.next(springLength) - springLength / 2
};
}
};
function updateBoundingBox() {
var i = bodies.length;
if (i === 0) { return; } // don't have to wory here.
var x1 = Number.MAX_VALUE,
y1 = Number.MAX_VALUE,
x2 = Number.MIN_VALUE,
y2 = Number.MIN_VALUE;
while(i--) {
// this is O(n), could it be done faster with quadtree?
// how about pinned nodes?
var body = bodies[i];
if (body.isPinned) {
body.pos.x = body.prevPos.x;
body.pos.y = body.prevPos.y;
} else {
body.prevPos.x = body.pos.x;
body.prevPos.y = body.pos.y;
}
if (body.pos.x < x1) {
x1 = body.pos.x;
}
if (body.pos.x > x2) {
x2 = body.pos.x;
}
if (body.pos.y < y1) {
y1 = body.pos.y;
}
if (body.pos.y > y2) {
y2 = body.pos.y;
}
}
boundingBox.x1 = x1;
boundingBox.x2 = x2;
boundingBox.y1 = y1;
boundingBox.y2 = y2;
}
};
/**
* Represents drag force, which reduces force value on each step by given
* coefficient.
*
* @param {Object} options for the drag force
* @param {Number=} options.dragCoeff drag force coefficient. 0.1 by default
*/
var dragForce = function (options) {
var merge = ngraph_merge,
expose = ngraph_expose;
options = merge(options, {
dragCoeff: 0.02
});
var api = {
update : function (body) {
body.force.x -= options.dragCoeff * body.velocity.x;
body.force.y -= options.dragCoeff * body.velocity.y;
}
};
// let easy access to dragCoeff:
expose(options, api, ['dragCoeff']);
return api;
};
/**
* Represents spring force, which updates forces acting on two bodies, connected
* by a spring.
*
* @param {Object} options for the spring force
* @param {Number=} options.springCoeff spring force coefficient.
* @param {Number=} options.springLength desired length of a spring at rest.
*/
var springForce = function (options) {
var merge = ngraph_merge;
var random = ngraph_random.random(42);
var expose = ngraph_expose;
options = merge(options, {
springCoeff: 0.0002,
springLength: 80
});
var api = {
/**
* Upsates forces acting on a spring
*/
update : function (spring) {
var body1 = spring.from,
body2 = spring.to,
length = spring.length < 0 ? options.springLength : spring.length,
dx = body2.pos.x - body1.pos.x,
dy = body2.pos.y - body1.pos.y,
r = Math.sqrt(dx * dx + dy * dy);
if (r === 0) {
dx = (random.nextDouble() - 0.5) / 50;
dy = (random.nextDouble() - 0.5) / 50;
r = Math.sqrt(dx * dx + dy * dy);
}
var d = r - length;
var coeff = ((!spring.coeff || spring.coeff < 0) ? options.springCoeff : spring.coeff) * d / r;
body1.force.x += coeff * dx;
body1.force.y += coeff * dy;
body2.force.x -= coeff * dx;
body2.force.y -= coeff * dy;
}
};
expose(options, api, ['springCoeff', 'springLength']);
return api;
};
/**
* Performs forces integration, using given time step. Uses Euler method to solve
* differential equation (http://en.wikipedia.org/wiki/Euler_method ).
*
* @returns {Number} squared distance of total position updates.
*/
var eulerIntegrator = integrate;
function integrate(bodies, timeStep) {
var dx = 0, tx = 0,
dy = 0, ty = 0,
i,
max = bodies.length;
if (max === 0) {
return 0;
}
for (i = 0; i < max; ++i) {
var body = bodies[i],
coeff = timeStep / body.mass;
body.velocity.x += coeff * body.force.x;
body.velocity.y += coeff * body.force.y;
var vx = body.velocity.x,
vy = body.velocity.y,
v = Math.sqrt(vx * vx + vy * vy);
if (v > 1) {
// We normalize it so that we move within timeStep range.
// for the case when v <= 1 - we let velocity to fade out.
body.velocity.x = vx / v;
body.velocity.y = vy / v;
}
dx = timeStep * body.velocity.x;
dy = timeStep * body.velocity.y;
body.pos.x += dx;
body.pos.y += dy;
tx += Math.abs(dx); ty += Math.abs(dy);
}
return (tx * tx + ty * ty)/max;
}
var ngraph_physics_primitives = {
Body: Body,
Vector2d: Vector2d,
Body3d: Body3d,
Vector3d: Vector3d
};
function Body(x, y) {
this.pos = new Vector2d(x, y);
this.prevPos = new Vector2d(x, y);
this.force = new Vector2d();
this.velocity = new Vector2d();
this.mass = 1;
}
Body.prototype.setPosition = function (x, y) {
this.prevPos.x = this.pos.x = x;
this.prevPos.y = this.pos.y = y;
};
function Vector2d(x, y) {
if (x && typeof x !== 'number') {
// could be another vector
this.x = typeof x.x === 'number' ? x.x : 0;
this.y = typeof x.y === 'number' ? x.y : 0;
} else {
this.x = typeof x === 'number' ? x : 0;
this.y = typeof y === 'number' ? y : 0;
}
}
Vector2d.prototype.reset = function () {
this.x = this.y = 0;
};
function Body3d(x, y, z) {
this.pos = new Vector3d(x, y, z);
this.prevPos = new Vector3d(x, y, z);
this.force = new Vector3d();
this.velocity = new Vector3d();
this.mass = 1;
}
Body3d.prototype.setPosition = function (x, y, z) {
this.prevPos.x = this.pos.x = x;
this.prevPos.y = this.pos.y = y;
this.prevPos.z = this.pos.z = z;
};
function Vector3d(x, y, z) {
if (x && typeof x !== 'number') {
// could be another vector
this.x = typeof x.x === 'number' ? x.x : 0;
this.y = typeof x.y === 'number' ? x.y : 0;
this.z = typeof x.z === 'number' ? x.z : 0;
} else {
this.x = typeof x === 'number' ? x : 0;
this.y = typeof y === 'number' ? y : 0;
this.z = typeof z === 'number' ? z : 0;
}
}
Vector3d.prototype.reset = function () {
this.x = this.y = this.z = 0;
};
var createBody = function(pos) {
return new ngraph_physics_primitives.Body(pos);
};
/**
* Manages a simulation of physical forces acting on bodies and springs.
*/
var ngraph_physics_simulator = physicsSimulator;
function physicsSimulator(settings) {
var Spring = spring;
var expose = ngraph_expose;
var merge = ngraph_merge;
var eventify = ngraph_events;
settings = merge(settings, {
/**
* Ideal length for links (springs in physical model).
*/
springLength: 30,
/**
* Hook's law coefficient. 1 - solid spring.
*/
springCoeff: 0.0008,
/**
* Coulomb's law coefficient. It's used to repel nodes thus should be negative
* if you make it positive nodes start attract each other :).
*/
gravity: -1.2,
/**
* Theta coefficient from Barnes Hut simulation. Ranged between (0, 1).
* The closer it's to 1 the more nodes algorithm will have to go through.
* Setting it to one makes Barnes Hut simulation no different from
* brute-force forces calculation (each node is considered).
*/
theta: 0.8,
/**
* Drag force coefficient. Used to slow down system, thus should be less than 1.
* The closer it is to 0 the less tight system will be.
*/
dragCoeff: 0.02,
/**
* Default time step (dt) for forces integration
*/
timeStep : 20,
});
// We allow clients to override basic factory methods:
var createQuadTree = settings.createQuadTree || ngraph_quadtreebh;
var createBounds = settings.createBounds || bounds;
var createDragForce = settings.createDragForce || dragForce;
var createSpringForce = settings.createSpringForce || springForce;
var integrate = settings.integrator || eulerIntegrator;
var createBody$1 = settings.createBody || createBody;
var bodies = [], // Bodies in this simulation.
springs = [], // Springs in this simulation.
quadTree = createQuadTree(settings),
bounds$1 = createBounds(bodies, settings),
springForce$1 = createSpringForce(settings),
dragForce$1 = createDragForce(settings);
var bboxNeedsUpdate = true;
var totalMovement = 0; // how much movement we made on last step
var publicApi = {
/**
* Array of bodies, registered with current simulator
*
* Note: To add new body, use addBody() method. This property is only
* exposed for testing/performance purposes.
*/
bodies: bodies,
quadTree: quadTree,
/**
* Array of springs, registered with current simulator
*
* Note: To add new spring, use addSpring() method. This property is only
* exposed for testing/performance purposes.
*/
springs: springs,
/**
* Returns settings with which current simulator was initialized
*/
settings: settings,
/**
* Performs one step of force simulation.
*
* @returns {boolean} true if system is considered stable; False otherwise.
*/
step: function () {
accumulateForces();
var movement = integrate(bodies, settings.timeStep);
bounds$1.update();
return movement;
},
/**
* Adds body to the system
*
* @param {ngraph.physics.primitives.Body} body physical body
*
* @returns {ngraph.physics.primitives.Body} added body
*/
addBody: function (body) {
if (!body) {
throw new Error('Body is required');
}
bodies.push(body);
return body;
},
/**
* Adds body to the system at given position
*
* @param {Object} pos position of a body
*
* @returns {ngraph.physics.primitives.Body} added body
*/
addBodyAt: function (pos) {
if (!pos) {
throw new Error('Body position is required');
}
var body = createBody$1(pos);
bodies.push(body);
return body;
},
/**
* Removes body from the system
*
* @param {ngraph.physics.primitives.Body} body to remove
*
* @returns {Boolean} true if body found and removed. falsy otherwise;
*/
removeBody: function (body) {
if (!body) { return; }
var idx = bodies.indexOf(body);
if (idx < 0) { return; }
bodies.splice(idx, 1);
if (bodies.length === 0) {
bounds$1.reset();
}
return true;
},
/**
* Adds a spring to this simulation.
*
* @returns {Object} - a handle for a spring. If you want to later remove
* spring pass it to removeSpring() method.
*/
addSpring: function (body1, body2, springLength, springCoefficient) {
if (!body1 || !body2) {
throw new Error('Cannot add null spring to force simulator');
}
if (typeof springLength !== 'number') {
springLength = -1; // assume global configuration
}
var spring = new Spring(body1, body2, springLength, springCoefficient >= 0 ? springCoefficient : -1);
springs.push(spring);
// TODO: could mark simulator as dirty.
return spring;
},
/**
* Returns amount of movement performed on last step() call
*/
getTotalMovement: function () {
return totalMovement;
},
/**
* Removes spring from the system
*
* @param {Object} spring to remove. Spring is an object returned by addSpring
*
* @returns {Boolean} true if spring found and removed. falsy otherwise;
*/
removeSpring: function (spring) {
if (!spring) { return; }
var idx = springs.indexOf(spring);
if (idx > -1) {
springs.splice(idx, 1);
return true;
}
},
getBestNewBodyPosition: function (neighbors) {
return bounds$1.getBestNewPosition(neighbors);
},
/**
* Returns bounding box which covers all bodies
*/
getBBox: function () {
if (bboxNeedsUpdate) {
bounds$1.update();
bboxNeedsUpdate = false;
}
return bounds$1.box;
},
invalidateBBox: function () {
bboxNeedsUpdate = true;
},
gravity: function (value) {
if (value !== undefined) {
settings.gravity = value;
quadTree.options({gravity: value});
return this;
} else {
return settings.gravity;
}
},
theta: function (value) {
if (value !== undefined) {
settings.theta = value;
quadTree.options({theta: value});
return this;
} else {
return settings.theta;
}
}
};
// allow settings modification via public API:
expose(settings, publicApi);
eventify(publicApi);
return publicApi;
function accumulateForces() {
// Accumulate forces acting on bodies.
var body,
i = bodies.length;
if (i) {
// only add bodies if there the array is not empty:
quadTree.insertBodies(bodies); // performance: O(n * log n)
while (i--) {
body = bodies[i];
// If body is pinned there is no point updating its forces - it should
// never move:
if (!body.isPinned) {
body.force.reset();
quadTree.updateBodyForce(body);
dragForce$1.update(body);
}
}
}
i = springs.length;
while(i--) {
springForce$1.update(springs[i]);
}
}
}
var ngraph_forcelayout = createLayout;
var simulator = ngraph_physics_simulator;
/**
* Creates force based layout for a given graph.
*
* @param {ngraph.graph} graph which needs to be laid out
* @param {object} physicsSettings if you need custom settings
* for physics simulator you can pass your own settings here. If it's not passed
* a default one will be created.
*/
function createLayout(graph, physicsSettings) {
if (!graph) {
throw new Error('Graph structure cannot be undefined');
}
var createSimulator = ngraph_physics_simulator;
var physicsSimulator = createSimulator(physicsSettings);
var nodeMass = defaultNodeMass;
if (physicsSettings && typeof physicsSettings.nodeMass === 'function') {
nodeMass = physicsSettings.nodeMass;
}
var nodeBodies = new Map();
var springs = {};
var bodiesCount = 0;
var springTransform = physicsSimulator.settings.springTransform || noop$1;
// Initialize physics with what we have in the graph:
initPhysics();
listenToEvents();
var wasStable = false;
var api = {
/**
* Performs one step of iterative layout algorithm
*
* @returns {boolean} true if the system should be considered stable; False otherwise.
* The system is stable if no further call to `step()` can improve the layout.
*/
step: function() {
if (bodiesCount === 0) return true; // TODO: This will never fire 'stable'
var lastMove = physicsSimulator.step();
// Save the movement in case if someone wants to query it in the step
// callback.
api.lastMove = lastMove;
// Allow listeners to perform low-level actions after nodes are updated.
api.fire('step');
var ratio = lastMove/bodiesCount;
var isStableNow = ratio <= 0.01; // TODO: The number is somewhat arbitrary...
if (wasStable !== isStableNow) {
wasStable = isStableNow;
onStableChanged(isStableNow);
}
return isStableNow;
},
/**
* For a given `nodeId` returns position
*/
getNodePosition: function (nodeId) {
return getInitializedBody(nodeId).pos;
},
/**
* Sets position of a node to a given coordinates
* @param {string} nodeId node identifier
* @param {number} x position of a node
* @param {number} y position of a node
* @param {number=} z position of node (only if applicable to body)
*/
setNodePosition: function (nodeId) {
var body = getInitializedBody(nodeId);
body.setPosition.apply(body, Array.prototype.slice.call(arguments, 1));
physicsSimulator.invalidateBBox();
},
/**
* @returns {Object} Link position by link id
* @returns {Object.from} {x, y} coordinates of link start
* @returns {Object.to} {x, y} coordinates of link end
*/
getLinkPosition: function (linkId) {
var spring = springs[linkId];
if (spring) {
return {
from: spring.from.pos,
to: spring.to.pos
};
}
},
/**
* @returns {Object} area required to fit in the graph. Object contains
* `x1`, `y1` - top left coordinates
* `x2`, `y2` - bottom right coordinates
*/
getGraphRect: function () {
return physicsSimulator.getBBox();
},
/**
* Iterates over each body in the layout simulator and performs a callback(body, nodeId)
*/
forEachBody: forEachBody,
/*
* Requests layout algorithm to pin/unpin node to its current position
* Pinned nodes should not be affected by layout algorithm and always
* remain at their position
*/
pinNode: function (node, isPinned) {
var body = getInitializedBody(node.id);
body.isPinned = !!isPinned;
},
/**
* Checks whether given graph's node is currently pinned
*/
isNodePinned: function (node) {
return getInitializedBody(node.id).isPinned;
},
/**
* Request to release all resources
*/
dispose: function() {
graph.off('changed', onGraphChanged);
api.fire('disposed');
},
/**
* Gets physical body for a given node id. If node is not found undefined
* value is returned.
*/
getBody: getBody,
/**
* Gets spring for a given edge.
*
* @param {string} linkId link identifer. If two arguments are passed then
* this argument is treated as formNodeId
* @param {string=} toId when defined this parameter denotes head of the link
* and first argument is treated as tail of the link (fromId)
*/
getSpring: getSpring,
/**
* [Read only] Gets current physics simulator
*/
simulator: physicsSimulator,
/**
* Gets the graph that was used for layout
*/
graph: graph,
/**
* Gets amount of movement performed during last step operation
*/
lastMove: 0
};
ngraph_events(api);
return api;
function forEachBody(cb) {
nodeBodies.forEach(function(body, bodyId) {
cb(body, bodyId);
});
}
function getSpring(fromId, toId) {
var linkId;
if (toId === undefined) {
if (typeof fromId !== 'object') {
// assume fromId as a linkId:
linkId = fromId;
} else {
// assume fromId to be a link object:
linkId = fromId.id;
}
} else {
// toId is defined, should grab link:
var link = graph.hasLink(fromId, toId);
if (!link) return;
linkId = link.id;
}
return springs[linkId];
}
function getBody(nodeId) {
return nodeBodies.get(nodeId);
}
function listenToEvents() {
graph.on('changed', onGraphChanged);
}
function onStableChanged(isStable) {
api.fire('stable', isStable);
}
function onGraphChanged(changes) {
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
if (change.changeType === 'add') {
if (change.node) {
initBody(change.node.id);
}
if (change.link) {
initLink(change.link);
}
} else if (change.changeType === 'remove') {
if (change.node) {
releaseNode(change.node);
}
if (change.link) {
releaseLink(change.link);
}
}
}
bodiesCount = graph.getNodesCount();