public class InteractiveOrganicLayout extends Object implements ILayoutAlgorithm
Besides the organic graph arrangement, this algorithm enables to immediately visualize changes made to a graph. Changes can be committed and the layout will be locally updated. That way, live interactions with an adapting graph layout are possible. Another advantage is that it is not necessary to compute a completely new layout if only small changes were made.
This algorithm supports the organic layout style which is characterized by a natural distribution of nodes. It is well suited to exhibit clusters and symmetric properties of a graph. Nodes are placed in a space-saving manner and distances between neighbors are highly uniform. Edges maintain uniform lengths too and are routed with straight-line segments without bends.
Organic diagrams are well suited for visualizing relations in large networks, for example, in bioinformatics, enterprise networking, social networks visualization, mesh visualization or system management.
Organic layout obtained using this algorithm
The internal basis for computing actual layouts is a force-directed approach placing the nodes of the input graph. The graph is modeled as a physical system with appropriate forces acting on it. Nodes are considered as electrically charged particles with mutually repulsive forces. Edges are modeled as forces that attract adjacent nodes. A good diagram is obtained from an equilibrium state of the system, i.e., the nodes are rearranged based on the physical forces until the system reaches a (local) minimum of the sum of the forces.
InteractiveOrganicLayout
is designed to run in a thread on its own. This allows for creating interactive user
interfaces where the layout algorithm runs while users interact with the graph. The algorithm can then react to changes
by immediately adapting the layout.
However, the algorithm can also be executed in a single-threaded mode using
startLayoutSingleThreaded(LayoutGraph)
. A Context
instance provides the necessary methods to control the layout calculation.
Several update methods allow to indicate lightweight changes on the graph, for example, setCenter(Node, double, double)
for node location updates. These updates will be scheduled and executed at a specific point within the life-cycle of the
layout algorithm. They are ideally suited for usage in interactive scenarios, where users, for example, change node
positions via mouse-dragging.
If a CopiedLayoutGraph
is being laid out, structural changes (e.g. creation or removal of nodes and edges) in
the original graph can be automatically scheduled and applied to the CopiedLayoutGraph
instance. Enable automatic structure updates
for this feature.
Method addStructureUpdate(IEventHandler, IEventArgs)
allows to schedule a custom IEventHandler
instance that is executed in a synchronized context and can safely update the structure of the graph.
It is easiest to start the algorithm in its own thread using startLayout(LayoutGraph)
. Furthermore, it is
strongly recommended to start it by passing a copy of the original graph (i.e. a CopiedLayoutGraph
instance) as
parameter to the mentioned method.
Importantly, changes will not be automatically applied to the input graph. Updates can be scheduled via various
methods (e.g. setCenter(Node, double, double)
). The whole layout will then internally be adjusted accordingly
and changes are stored as intermediate results. Such intermediate results can be committed
in
order to apply them to the actual graph.
OrganicLayout
instead of this class.Modifier and Type | Class and Description |
---|---|
static interface |
InteractiveOrganicLayout.ISingleThreadContext
A
InteractiveOrganicLayout.ISingleThreadContext provides control over the layout calculation in the case of single-threaded algorithm
execution. |
Constructor and Description |
---|
InteractiveOrganicLayout()
Creates a new instance of the
InteractiveOrganicLayout with default settings. |
Modifier and Type | Method and Description |
---|---|
void |
addStructureUpdate(IEventHandler handler,
IEventArgs args)
Schedules an update for the structure of the graph, which will automatically be executed at a later point in the
life-cycle of this algorithm.
|
void |
applyLayout(LayoutGraph graph)
Calculates an organic layout for the given input graph, however, the layout is not automatically applied to the
graph.
|
void |
commitPositions()
Writes calculated intermediate locations of nodes and edges to the actual graph.
|
double |
commitPositionsSmoothly(double maxMovement,
double factor)
Writes calculated intermediate locations of nodes and edges to the actual graph and returns the largest movement value.
|
void |
disableAllStages()
Disables all predefined
ILayoutStage s so that upon applyLayout(LayoutGraph)
only the internal organic layout algorithm will be executed. |
YPoint |
getCenter(Node node)
Polls the current coordinates of the center of the given node.
|
double |
getCenterX(Node node)
Polls the current x-coordinate of the center location of the given node.
|
double |
getCenterY(Node node)
Polls the current y-coordinate of the center location of the given node.
|
long |
getLastWakeupTime()
Gets the time when the last wake-up, that is, call to
wakeUp() , occurred. |
long |
getMaximumDuration()
Gets the maximum duration in milliseconds that this algorithm is allowed to run.
|
OutputRestriction |
getOutputRestriction()
Gets an
OutputRestriction which restricts the area for the layout result of this algorithm. |
double |
getPreferredEdgeLength()
Gets the default preferred edge length.
|
double |
getPreferredNodeDistance()
Gets the preferred distance between nodes.
|
double |
getQualityTimeRatio()
Gets the ratio of layout quality versus running time.
|
double |
getStress(Node node)
Polls the current stress value of a given node.
|
double |
getWorkingRatio()
Gets the working ratio which defines the amount of processor time this algorithm tries to get.
|
boolean |
isAutomaticStructureUpdate()
Gets whether or not this algorithm performs automatic structure updates on the graph copy if the original graph
changes.
|
boolean |
isRunning()
Gets whether or not this layout algorithm is currently running.
|
boolean |
isSleeping()
Gets whether or not this layout algorithm is currently sleeping.
|
boolean |
isStopped()
Gets whether or not this layout algorithm has stopped.
|
void |
setAutomaticStructureUpdate(boolean value)
Sets whether or not this algorithm performs automatic structure updates on the graph copy if the original graph
changes.
|
void |
setCenter(Node node,
double x,
double y)
Schedules an update for the center location of the given node.
|
void |
setCenterX(Node node,
double x)
Schedules an update for the center location's x-coordinate of the given node.
|
void |
setCenterY(Node node,
double y)
Schedules an update for the center location's y-coordinate of the given node.
|
void |
setInertia(Node node,
double inertia)
Schedules an update for the inertia of the given node.
|
void |
setMaximumDuration(long value)
Sets the maximum duration in milliseconds that this algorithm is allowed to run.
|
void |
setOutputRestriction(OutputRestriction value)
Sets an
OutputRestriction which restricts the area for the layout result of this algorithm. |
void |
setPreferredEdgeLength(double value)
Sets the default preferred edge length.
|
void |
setPreferredEdgeLength(Edge edge,
double newEdgeLength)
Schedules an update for the preferred length of the given edge.
|
void |
setPreferredNodeDistance(double value)
Sets the preferred distance between nodes.
|
void |
setQualityTimeRatio(double value)
Sets the ratio of layout quality versus running time.
|
void |
setRadius(Node node,
double radius)
Schedules an update for the radius of the given node.
|
void |
setStress(Node node,
double stress)
Schedules an update for the stress value of the given node.
|
void |
setWorkingRatio(double value)
Sets the working ratio which defines the amount of processor time this algorithm tries to get.
|
Thread |
startLayout(LayoutGraph graph)
Starts the
layout process calculating an organic layout for the input graph in a new,
separate Thread . |
InteractiveOrganicLayout.ISingleThreadContext |
startLayoutSingleThreaded(LayoutGraph graph)
Creates a
context object that provides methods to continue
and stop the layout calculation for running this layout algorithm in a
single-threaded environment. |
void |
stop()
Stops the layout algorithm.
|
void |
stopAndWait()
Stops a previously
started algorithm and then blocks until the current layout
calculation is completed. |
void |
syncStructure()
Synchronizes the structure of the graph copy with the original graph.
|
void |
syncStructure(boolean block)
Synchronizes the structure of the graph copy with the original graph.
|
void |
wakeUp()
Wakes up the algorithm with the effect that it will restart/continue the layout calculation.
|
public InteractiveOrganicLayout()
InteractiveOrganicLayout
with default settings.public void addStructureUpdate(IEventHandler handler, IEventArgs args)
The given IEventHandler
will be queued and executed at a specific time. The IEventHandler
can make
structural changes (e.g. removal/creation of edges or nodes). They will be synchronized with the rest of the layout
algorithm.
setCenter(Node, double, double)
.handler
- The handler delegate that will be invoked using null
as the sender and args
as the event argumentsargs
- The event argument that will be piped to the handler
invocation.public void applyLayout(LayoutGraph graph)
Changes have to be committed
to update the graph with the actual calculated positions.
applyLayout
in interface ILayoutAlgorithm
CopiedLayoutGraph
is given as input, the setters and getters with a Node
or Edge
as parameter may be used with instances from both, the original graph or the copied graph.CopiedLayoutGraph
to buffer the original graph. Then, automatic structure updates
can be enabled to enforce that changes to the original graph are automatically applied to the copied graph.graph
- the input graphstartLayout(LayoutGraph)
,
startLayoutSingleThreaded(LayoutGraph)
public void commitPositions()
Update methods like setCenter(Node, double, double)
schedule changes which cause that the whole layout will
internally be adjusted. All adjustments are stored as intermediate results. This method immediately transfers all these
intermediate results to the actual input graph.
is running
.commitPositionsSmoothly(double, double)
public double commitPositionsSmoothly(double maxMovement, double factor)
Update methods like setCenter(Node, double, double)
schedule changes which cause that the whole layout will
internally be adjusted. All adjustments are stored as intermediate results. This method smoothly transfers all these
intermediate results to the actual input graph.
Positions are, however, not transferred directly (use commitPositions()
if that is intended). Instead,
the nodes are moved towards the calculated position. The movement is restricted to the given maximum distance.
The movement will be calculated as (movement) = (factor) * (distance between calculated and actual location)
The returned largest movement can be used for estimating the difference between calculated layout and actual positions.
If the return value is 0
, the calculated layout was completely transferred.
25
times a second, suitable parameter values are 50
for the
maximum movement and 0.15
for the factor.is running
.maxMovement
- the maximum distance a node will be movedfactor
- a factor that determines the node movement0
, if the calculated layout has been transferred completelycommitPositions()
public void disableAllStages()
ILayoutStage
s so that upon applyLayout(LayoutGraph)
only the internal organic layout algorithm will be executed.
This method is called upon construction of this class so that by default additional
ILayoutStage
s are deactivated. This method may be overridden if the additional
stages should stay active (e.g. override the method and return silently).
MultiStageLayout
to compute the organic layout and
this method delegates to MultiStageLayout.disableAllStages()
.MultiStageLayout.disableAllStages()
public YPoint getCenter(Node node)
The returned coordinates do not necessarily correspond to the actual location of the node in the input graph. They may
only be intermediate results stored in the algorithm. This will be the case if scheduled updates were not yet completely
committed
to the actual graph.
set
earlier, it will not necessarily be the
result of this method. Reasons can be that changes need to be committed first, or the algorithm is
sleeping
.node
- the node for which the center should be polledYPoint
representing the center location of the given node, or null
if nothing about the node is
knownsetCenter(Node, double, double)
public double getCenterX(Node node)
The returned coordinate is not necessarily the actual x-coordinate of the node in the input graph but only an
intermediate result stored in the algorithm. This will be the case if scheduled updates were not yet completely committed
to the actual graph.
set
earlier, it will not necessarily be the result of
this method. Reasons can be that changes need to be committed first, or the algorithm is
sleeping
.node
- the node for which the x-coordinate should be polledsetCenterX(Node, double)
public double getCenterY(Node node)
The returned coordinate is not necessarily the actual y-coordinate of the node in the input graph but only an
intermediate result stored in the algorithm. This will be the case if scheduled updates were not yet completely committed
to the actual graph.
set
earlier, it will not necessarily be the result of
this method. Reasons can be that changes need to be committed first, or the algorithm is
sleeping
.node
- the node for which the y-coordinate should be polledsetCenterY(Node, double)
public long getLastWakeupTime()
wakeUp()
, occurred.
The time is defined in terms of the difference between the current time and midnight, January 1, 1970 UTC, measured in milliseconds.
wakeUp()
occurredpublic long getMaximumDuration()
The duration needs to be non-negative.
IllegalArgumentException
- if the specified maximum duration has a negative valuesleep
.Long.MAX_VALUE
. No time restriction is imposed.setMaximumDuration(long)
public OutputRestriction getOutputRestriction()
OutputRestriction
which restricts the area for the layout result of this algorithm.OutputRestriction.NONE
. No area restriction is imposed.OutputRestriction
instance for restricting the area of the layoutOutputRestriction
,
setOutputRestriction(OutputRestriction)
public double getPreferredEdgeLength()
This length does not define the actual absolute length of edges, but the layout algorithm considers the specified preference where possible.
The preferred edge length needs to be non-negative.
IllegalArgumentException
- if the specified edge length is negativesetPreferredEdgeLength(Edge, double)
instead to schedule updates.setPreferredEdgeLength(Edge, double)
,
setPreferredEdgeLength(double)
public double getPreferredNodeDistance()
The minimum node distance needs to be non-negative.
IllegalArgumentException
- if the specified minimum node distance is negativesetPreferredNodeDistance(double)
public double getQualityTimeRatio()
The larger the ratio, the better the quality of the resulting layout but the longer it may take to perform the layout calculation.
The value needs to lie within [0,1]
.
IllegalArgumentException
- if the specified ratio is outside the interval [0,1]
0.0
(low quality, fast) and 1.0
(high quality, slow)setQualityTimeRatio(double)
public double getStress(Node node)
The stress value indicates how far a node will possibly move. The higher the stress of a node is, the farther it may move.
The stress value is defined to be a value from the interval [0,1]
.
setStress(Node, double)
, this method will not
necessarily return that same value for the same node. The reason is that the algorithm may be sleeping
and did therefore not notice the change yet.node
- the node for which the stress value should be polledsetStress(Node, double)
public double getWorkingRatio()
A working ratio value of 1
means that the algorithm will try to run as fast as possible. Lower values will lead
to small breaks after each internal round.
The ratio needs to be a value in (0,1]
.
IllegalArgumentException
- if the given ratio is not in (0,1]
.setWorkingRatio(double)
public boolean isAutomaticStructureUpdate()
If this feature is enabled, a listener will be registered with the original input graph. This listener will automatically transfer structural changes on the original graph to the graph copy and update its internal data structures.
false
. Structure updates need to be scheduled manually.true
if automatic structure updates on the graph copy are enabled, false
otherwisesetAutomaticStructureUpdate(boolean)
public boolean isRunning()
The algorithm is running if the layout process is still active, the algorithm has not stopped
yet
and is not sleeping
.
true
if this algorithm is currently running, false
otherwisepublic boolean isSleeping()
Sleeping indicates that the algorithm has not stopped
yet but is waiting (i.e. doing nothing). It
can be notified to continue its work using wakeUp()
.
maximum runtime
was exceeded.true
if this algorithm is currently sleeping, false
otherwisewakeUp()
public boolean isStopped()
If the algorithm has stopped, it terminated all its layout calculations. It is not running
anymore
and can not be restarted/continued by calling wakeUp()
.
true
if the layout algorithm has stopped, false
otherwisepublic void setAutomaticStructureUpdate(boolean value)
If this feature is enabled, a listener will be registered with the original input graph. This listener will automatically transfer structural changes on the original graph to the graph copy and update its internal data structures.
false
. Structure updates need to be scheduled manually.value
- true
if automatic structure updates on the graph copy are enabled, false
otherwiseisAutomaticStructureUpdate()
public void setCenter(Node node, double x, double y)
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
woken up
after scheduling this update.node
- the node that should be updatedx
- the desired x-coordinate of the given nodey
- the desired y-coordinate of the given nodepublic void setCenterX(Node node, double x)
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
woken up
after scheduling this update.node
- the node that should be updatedx
- the desired x-coordinate of the given nodesetCenterY(Node, double)
,
setCenter(Node, double, double)
public void setCenterY(Node node, double y)
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
woken up
after scheduling this update.node
- the node that should be updatedy
- the desired y-coordinate of the given nodesetCenterX(Node, double)
,
setCenter(Node, double, double)
public void setInertia(Node node, double inertia)
The inertia is defined to be a value from the interval [0,1]
.
1.0
: The node will not move.0.5
: The node will only move half as far as it would with an inertia of 0.0
.0.0
: The node will move as fast as possible.
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
IllegalArgumentException
- if the given inertia value is negative or greater than 1
woken up
after scheduling this update.node
- the node whose inertia to setinertia
- an inertia value between 0
and 1
public void setMaximumDuration(long value)
The duration needs to be non-negative.
IllegalArgumentException
- if the specified maximum duration has a negative valuesleep
.Long.MAX_VALUE
. No time restriction is imposed.value
- the maximum duration in millisecondsgetMaximumDuration()
public void setOutputRestriction(OutputRestriction value)
OutputRestriction
which restricts the area for the layout result of this algorithm.OutputRestriction.NONE
. No area restriction is imposed.value
- the OutputRestriction
instance for restricting the area of the layoutOutputRestriction
,
getOutputRestriction()
public void setPreferredEdgeLength(double value)
This length does not define the actual absolute length of edges, but the layout algorithm considers the specified preference where possible.
The preferred edge length needs to be non-negative.
IllegalArgumentException
- if the specified edge length is negativesetPreferredEdgeLength(Edge, double)
instead to schedule updates.value
- the preferred edge lengthsetPreferredEdgeLength(Edge, double)
,
getPreferredEdgeLength()
public void setPreferredEdgeLength(Edge edge, double newEdgeLength)
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
woken up
after scheduling this update.edge
- the edge whose preferred length should be updatednewEdgeLength
- the new preferred edge lengthpublic void setPreferredNodeDistance(double value)
The minimum node distance needs to be non-negative.
IllegalArgumentException
- if the specified minimum node distance is negativevalue
- the non-negative preferred distance between nodesgetPreferredNodeDistance()
public void setQualityTimeRatio(double value)
The larger the ratio, the better the quality of the resulting layout but the longer it may take to perform the layout calculation.
The value needs to lie within [0,1]
.
IllegalArgumentException
- if the specified ratio is outside the interval [0,1]
value
- a value between 0.0
(low quality, fast) and 1.0
(high quality, slow)getQualityTimeRatio()
public void setRadius(Node node, double radius)
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
woken up
after scheduling this update.node
- the node whose radius should be updatedradius
- the desired radius for the given nodepublic void setStress(Node node, double stress)
The stress value indicates how far a node will possibly move. The higher the stress of a node is, the farther it may move.
This method can be used while layout calculation is in progress (e.g. for interactive layout scenarios). However, the
change will not directly be applied to the graph itself but only stored internally as an intermediate result.
Scheduled updates can be committed to the graph while the algorithm is running using methods commitPositions()
or commitPositionsSmoothly(double, double)
.
The stress value is defined to be a value from the interval [0,1]
.
IllegalArgumentException
- if the given stress value is negative or greater than 1
woken up
after scheduling this update.node
- the node whose stress value should be updatedstress
- a stress value from the interval [0,1]
public void setWorkingRatio(double value)
A working ratio value of 1
means that the algorithm will try to run as fast as possible. Lower values will lead
to small breaks after each internal round.
The ratio needs to be a value in (0,1]
.
IllegalArgumentException
- if the given ratio is not in (0,1]
.value
- the working ratio valuegetWorkingRatio()
public Thread startLayout(LayoutGraph graph)
layout process
calculating an organic layout for the input graph in a new,
separate Thread
.graph
- the input graphThread
instance that has been created and started and in which the layout algorithm is runningapplyLayout(LayoutGraph)
,
startLayoutSingleThreaded(LayoutGraph)
public InteractiveOrganicLayout.ISingleThreadContext startLayoutSingleThreaded(LayoutGraph graph)
context object
that provides methods to continue
and stop
the layout calculation for running this layout algorithm in a
single-threaded environment.
Usage: Call doLayout(long)
on the created instance to run the
actual layout calculation for some specified period of time, whenever the layout should be recalculated. To actually
transfer the changes, commitPositions()
should be called subsequently.
InteractiveOrganicLayout.ISingleThreadContext.startLayout(long)
needs
to be made to do so.graph
- the input graphcontext instance
to control layout calculationInteractiveOrganicLayout.ISingleThreadContext
,
applyLayout(LayoutGraph)
,
startLayout(LayoutGraph)
public void stop()
In contrast to
stopAndWait()
, the algorithm terminates immediately and will not wait until the ongoing layout calculation
is finished.
wakeUp()
.isStopped()
,
stopAndWait()
public void stopAndWait()
started
algorithm and then blocks until the current layout
calculation is completed.isStopped()
,
stop()
public void syncStructure()
IllegalStateException
- if the currently handled graph is not of type CopiedLayoutGraph
running
.CopiedLayoutGraph
instance as
parameter to methods startLayout(LayoutGraph)
, applyLayout(LayoutGraph)
or
startLayoutSingleThreaded(LayoutGraph)
.public void syncStructure(boolean block)
IllegalStateException
- if the currently handled graph is not of type CopiedLayoutGraph
running
.CopiedLayoutGraph
instance as
parameter to methods startLayout(LayoutGraph)
, applyLayout(LayoutGraph)
or
startLayoutSingleThreaded(LayoutGraph)
.public void wakeUp()
This method is useful if the layouter is sleeping
but should be notified of changes (e.g. due to
user interaction).
setCenter(Node, double, double)
or setStress(Node, double)
.stopped
), it can not be awakened again.isSleeping()