Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ forceatlas2 = ForceAtlas2(
# Behavior alternatives
outboundAttractionDistribution=True, # Dissuade hubs
linLogMode=False, # NOT IMPLEMENTED
adjustSizes=False, # Prevent overlap (NOT IMPLEMENTED)
adjustSizes=False, # Prevent overlap
edgeWeightInfluence=1.0,

# Performance
Expand All @@ -101,6 +101,7 @@ forceatlas2 = ForceAtlas2(
scalingRatio=2.0,
strongGravityMode=False,
gravity=1.0,
nodeSize=10, # For overlapping

# Log
verbose=True)
Expand Down
30 changes: 22 additions & 8 deletions fa2_modified/fa2util.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import cython
# This will substitute for the nLayout object
cdef class Node:
cdef public double mass
cdef public double size
cdef public double old_dx, old_dy
cdef public double dx, dy
cdef public double x, y
Expand All @@ -34,9 +35,15 @@ cdef class Edge:

@cython.locals(xDist = cython.double,
yDist = cython.double,
distance2 = cython.double,
distance = cython.double,
factor = cython.double)
cpdef void linRepulsion(Node n1, Node n2, double coefficient=*)
cpdef void linRepulsion(Node n1, Node n2, double coefficient, bint adjustSizes=*)

@cython.locals(xDist = cython.double,
yDist = cython.double,
distance = cython.double,
factor = cython.double)
cpdef void linRepulsion_antiCollision(Node n1, Node n2, double coefficient)

@cython.locals(xDist = cython.double,
yDist = cython.double,
Expand All @@ -60,19 +67,25 @@ cpdef void strongGravity(Node n, double g, double coefficient=*)
@cython.locals(xDist = cython.double,
yDist = cython.double,
factor = cython.double)
cpdef void linAttraction(Node n1, Node n2, double e, bint distributedAttraction, double coefficient=*)
cpdef void linAttraction(Node n1, Node n2, double e, bint distributedAttraction, double coefficient, bint adjustSizes=*)

@cython.locals(xDist = cython.double,
yDist = cython.double,
distance = cython.double,
factor = cython.double)
cpdef void linAttraction_antiCollision(Node n1, Node n2, double e, bint distributedAttraction, double coefficient)

@cython.locals(i = cython.int,
j = cython.int,
n1 = Node,
n2 = Node)
cpdef void apply_repulsion(list nodes, double coefficient)
cpdef void apply_repulsion(list nodes, double coefficient, bint adjustSizes=*)

@cython.locals(n = Node)
cpdef void apply_gravity(list nodes, double gravity, double scalingRatio, bint useStrongGravity=*)

@cython.locals(edge = Edge)
cpdef void apply_attraction(list nodes, list edges, bint distributedAttraction, double coefficient, double edgeWeightInfluence)
cpdef void apply_attraction(list nodes, list edges, bint distributedAttraction, double coefficient, double edgeWeightInfluence, bint adjustSizes=*)

cdef class Region:
cdef public double mass
Expand All @@ -98,10 +111,10 @@ cdef class Region:

@cython.locals(distance = cython.double,
subregion = Region)
cpdef void applyForce(self, Node n, double theta, double coefficient=*)
cpdef void applyForce(self, Node n, double theta, double coefficient, bint adjustSizes=*)

@cython.locals(n = Node)
cpdef applyForceOnNodes(self, list nodes, double theta, double coefficient=*)
cpdef applyForceOnNodes(self, list nodes, double theta, double coefficient, bint adjustSizes=*)

@cython.locals(totalSwinging = cython.double,
totalEffectiveTraction = cython.double,
Expand All @@ -117,5 +130,6 @@ cdef class Region:
targetSpeed = cython.double,
maxRise = cython.double,
factor = cython.double,
df = cython.double,
values = dict)
cpdef dict adjustSpeedAndApplyForces(list nodes, double speed, double speedEfficiency, double jitterTolerance)
cpdef dict adjustSpeedAndApplyForces(list nodes, double speed, double speedEfficiency, double jitterTolerance, bint adjustSizes=*)
130 changes: 102 additions & 28 deletions fa2_modified/fa2util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# fa2util.pxd TO REFLECT ANY CHANGES IN FUNCTION DEFINITIONS!
#
# Copyright (C) 2017 Bhargav Chippada <bhargavchippada19@gmail.com>
# Copyright (C) 2017 Amin Alam <ma.alamalhoda@gmail.com>
#
# Available under the GPLv3

Expand All @@ -17,6 +18,7 @@
class Node:
def __init__(self):
self.mass = 0.0
self.size = 1.0
self.old_dx = 0.0
self.old_dy = 0.0
self.dx = 0.0
Expand All @@ -39,13 +41,40 @@ def __init__(self):

# Repulsion function. `n1` and `n2` should be nodes. This will
# adjust the dx and dy values of `n1` `n2`
def linRepulsion(n1, n2, coefficient=0):
def linRepulsion(n1, n2, coefficient, adjustSizes=False):
# Not the ideal solution, just to avoid repeating code in apply_repulsion.
if adjustSizes:
linRepulsion_antiCollision(n1, n2, coefficient)
return

xDist = n1.x - n2.x
yDist = n1.y - n2.y
distance2 = xDist * xDist + yDist * yDist # Distance squared
distance = sqrt(xDist * xDist + yDist * yDist)

if distance > 0:
factor = coefficient * n1.mass * n2.mass / distance / distance
n1.dx += xDist * factor
n1.dy += yDist * factor
n2.dx -= xDist * factor
n2.dy -= yDist * factor


def linRepulsion_antiCollision(n1, n2, coefficient):
xDist = n1.x - n2.x
yDist = n1.y - n2.y
distance = sqrt(xDist * xDist + yDist * yDist) - n1.size - n2.size

if distance > 0:
factor = coefficient * n1.mass * n2.mass / distance / distance

n1.dx += xDist * factor
n1.dy += yDist * factor
n2.dx -= xDist * factor
n2.dy -= yDist * factor

elif distance < 0:
factor = 100 * coefficient * n1.mass * n2.mass

if distance2 > 0:
factor = coefficient * n1.mass * n2.mass / distance2
n1.dx += xDist * factor
n1.dy += yDist * factor
n2.dx -= xDist * factor
Expand All @@ -56,10 +85,10 @@ def linRepulsion(n1, n2, coefficient=0):
def linRepulsion_region(n, r, coefficient=0):
xDist = n.x - r.massCenterX
yDist = n.y - r.massCenterY
distance2 = xDist * xDist + yDist * yDist
distance = sqrt(xDist * xDist + yDist * yDist)

if distance2 > 0:
factor = coefficient * n.mass * r.mass / distance2
if distance > 0:
factor = coefficient * n.mass * r.mass / distance / distance
n.dx += xDist * factor
n.dy += yDist * factor

Expand Down Expand Up @@ -95,30 +124,54 @@ def strongGravity(n, g, coefficient=0):
# Attraction function. `n1` and `n2` should be nodes. This will
# adjust the dx and dy values of `n1` and `n2`. It does
# not return anything.
def linAttraction(n1, n2, e, distributedAttraction, coefficient=0):
def linAttraction(n1, n2, e, distributedAttraction, coefficient, adjustSizes=False):
# Not the ideal solution, just to avoid repeating code in apply_attraction.
if adjustSizes:
linAttraction_antiCollision(n1, n2, e, distributedAttraction, coefficient)
return

xDist = n1.x - n2.x
yDist = n1.y - n2.y
if not distributedAttraction:
factor = -coefficient * e
else:
factor = -coefficient * e / n1.mass

n1.dx += xDist * factor
n1.dy += yDist * factor
n2.dx -= xDist * factor
n2.dy -= yDist * factor


def linAttraction_antiCollision(n1, n2, e, distributedAttraction, coefficient):
xDist = n1.x - n2.x
yDist = n1.y - n2.y
distance = sqrt(xDist * xDist + yDist * yDist) - n1.size - n2.size

if distance > 0:
if not distributedAttraction:
factor = -coefficient * e
else:
factor = -coefficient * e / n1.mass

n1.dx += xDist * factor
n1.dy += yDist * factor

n2.dx -= xDist * factor
n2.dy -= yDist * factor


# The following functions iterate through the nodes or edges and apply
# the forces directly to the node objects. These iterations are here
# instead of the main file because Python is slow with loops.
def apply_repulsion(nodes, coefficient):
def apply_repulsion(nodes, coefficient, adjustSizes=False):
i = 0
for n1 in nodes:
j = i
for n2 in nodes:
if j == 0:
break
linRepulsion(n1, n2, coefficient)
linRepulsion(n1, n2, coefficient, adjustSizes)
j -= 1
i += 1

Expand All @@ -133,7 +186,12 @@ def apply_gravity(nodes, gravity, scalingRatio, useStrongGravity=False):


def apply_attraction(
nodes, edges, distributedAttraction, coefficient, edgeWeightInfluence
nodes,
edges,
distributedAttraction,
coefficient,
edgeWeightInfluence,
adjustSizes=False,
):
# Optimization, since usually edgeWeightInfluence is 0 or 1, and pow is slow
if edgeWeightInfluence == 0:
Expand All @@ -144,6 +202,7 @@ def apply_attraction(
1,
distributedAttraction,
coefficient,
adjustSizes,
)
elif edgeWeightInfluence == 1:
for edge in edges:
Expand All @@ -153,6 +212,7 @@ def apply_attraction(
edge.weight,
distributedAttraction,
coefficient,
adjustSizes,
)
else:
for edge in edges:
Expand All @@ -162,6 +222,7 @@ def apply_attraction(
pow(edge.weight, edgeWeightInfluence),
distributedAttraction,
coefficient,
adjustSizes,
)


Expand Down Expand Up @@ -255,9 +316,9 @@ def buildSubRegions(self):
for subregion in self.subregions:
subregion.buildSubRegions()

def applyForce(self, n, theta, coefficient=0):
def applyForce(self, n, theta, coefficient, adjustSizes=False):
if len(self.nodes) < 2:
linRepulsion(n, self.nodes[0], coefficient)
linRepulsion(n, self.nodes[0], coefficient, adjustSizes)
else:
distance = sqrt(
(n.x - self.massCenterX) ** 2 + (n.y - self.massCenterY) ** 2
Expand All @@ -266,15 +327,17 @@ def applyForce(self, n, theta, coefficient=0):
linRepulsion_region(n, self, coefficient)
else:
for subregion in self.subregions:
subregion.applyForce(n, theta, coefficient)
subregion.applyForce(n, theta, coefficient, adjustSizes)

def applyForceOnNodes(self, nodes, theta, coefficient=0):
def applyForceOnNodes(self, nodes, theta, coefficient, adjustSizes=False):
for n in nodes:
self.applyForce(n, theta, coefficient)
self.applyForce(n, theta, coefficient, adjustSizes)


# Adjust speed and apply forces step
def adjustSpeedAndApplyForces(nodes, speed, speedEfficiency, jitterTolerance):
def adjustSpeedAndApplyForces(
nodes, speed, speedEfficiency, jitterTolerance, adjustSizes=False
):
# Auto adjust speed.
totalSwinging = 0.0 # How much irregular movement
totalEffectiveTraction = 0.0 # How much useful movement
Expand Down Expand Up @@ -334,17 +397,28 @@ def adjustSpeedAndApplyForces(nodes, speed, speedEfficiency, jitterTolerance):
speed = speed + min(targetSpeed - speed, maxRise * speed)

# Apply forces.
#
# Need to add a case if adjustSizes ("prevent overlap") is
# implemented.
for n in nodes:
swinging = n.mass * sqrt(
(n.old_dx - n.dx) * (n.old_dx - n.dx)
+ (n.old_dy - n.dy) * (n.old_dy - n.dy)
)
factor = speed / (1.0 + sqrt(speed * swinging))
n.x = n.x + (n.dx * factor)
n.y = n.y + (n.dy * factor)
if adjustSizes:
for n in nodes:
swinging = n.mass * sqrt(
(n.old_dx - n.dx) * (n.old_dx - n.dx)
+ (n.old_dy - n.dy) * (n.old_dy - n.dy)
)
factor = 0.1 * speed / (1.0 + sqrt(speed * swinging))

df = sqrt(pow(n.dx, 2) + pow(n.dy, 2))
factor = min(factor * df, 10.0) / df

n.x = n.x + (n.dx * factor)
n.y = n.y + (n.dy * factor)
else:
for n in nodes:
swinging = n.mass * sqrt(
(n.old_dx - n.dx) * (n.old_dx - n.dx)
+ (n.old_dy - n.dy) * (n.old_dy - n.dy)
)
factor = speed / (1.0 + sqrt(speed * swinging))
n.x = n.x + (n.dx * factor)
n.y = n.y + (n.dy * factor)

values = {}
values["speed"] = speed
Expand Down
Loading
Loading