Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maintain position history #11

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
30 changes: 24 additions & 6 deletions fa2/forceatlas2.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ def __init__(self,
strongGravityMode=False,
gravity=1.0,

# Random seed
seed=None,

# Log
verbose=True):
assert linLogMode == adjustSizes == multiThreaded == False, "You selected a feature that has not been implemented yet..."
Expand All @@ -77,11 +80,13 @@ def __init__(self,
self.strongGravityMode = strongGravityMode
self.gravity = gravity
self.verbose = verbose
self.seed = seed

def init(self,
G, # a graph in 2D numpy ndarray format (or) scipy sparse matrix format
pos=None # Array of initial positions
):
random.seed(a=self.seed)
isSparse = False
if isinstance(G, numpy.ndarray):
# Check our assumptions
Expand Down Expand Up @@ -149,7 +154,8 @@ def init(self,
def forceatlas2(self,
G, # a graph in 2D numpy ndarray format (or) scipy sparse matrix format
pos=None, # Array of initial positions
iterations=100 # Number of times to iterate the main loop
iterations=100, # Number of times to iterate the main loop
keep_history=False # Whether or not to return the historical values while fa2 is running
):
# Initializing, initAlgo()
# ================================================================
Expand Down Expand Up @@ -178,6 +184,7 @@ def forceatlas2(self,
niters = range(iterations)
if self.verbose:
niters = tqdm(niters)
history = []
for _i in niters:
for n in nodes:
n.old_dx = n.dx
Expand Down Expand Up @@ -219,6 +226,11 @@ def forceatlas2(self,
speedEfficiency = values['speedEfficiency']
applyforces_timer.stop()

# Add current positions to history
if keep_history:
positions = [(n.x, n.y) for n in nodes]
history.append(positions)

if self.verbose:
if self.barnesHutOptimize:
barneshut_timer.display()
Expand All @@ -227,23 +239,29 @@ def forceatlas2(self,
attraction_timer.display()
applyforces_timer.display()
# ================================================================
return [(n.x, n.y) for n in nodes]
if not keep_history:
return [(n.x, n.y) for n in nodes]
else:
return positions, history

# A layout for NetworkX.
#
# This function returns a NetworkX layout, which is really just a
# dictionary of node positions (2D X-Y tuples) indexed by the node name.
def forceatlas2_networkx_layout(self, G, pos=None, iterations=100):
def forceatlas2_networkx_layout(self, G, pos=None, iterations=100, keep_history=False):
import networkx
assert isinstance(G, networkx.classes.graph.Graph), "Not a networkx graph"
assert isinstance(pos, dict) or (pos is None), "pos must be specified as a dictionary, as in networkx"
M = networkx.to_scipy_sparse_matrix(G, dtype='f', format='lil')
if pos is None:
l = self.forceatlas2(M, pos=None, iterations=iterations)
l = self.forceatlas2(M, pos=None, iterations=iterations, keep_history=keep_history)
else:
poslist = numpy.asarray([pos[i] for i in G.nodes()])
l = self.forceatlas2(M, pos=poslist, iterations=iterations)
return dict(zip(G.nodes(), l))
l = self.forceatlas2(M, pos=poslist, iterations=iterations, keep_history=keep_history)
if not keep_history:
return dict(zip(G.nodes(), l))
else:
return [dict(zip(G.nodes(), i)) for i in l[1]]

# A layout for igraph.
#
Expand Down