Skip to content

Commit

Permalink
common code for force layout algorithms
Browse files Browse the repository at this point in the history
Signed-off-by: Luma <[email protected]>
  • Loading branch information
zamarrenolm committed Jan 10, 2024
1 parent 54de632 commit 9ad9de0
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,20 @@ public abstract class AbstractForceLayout<V, E> {

private boolean hasBeenExecuted = false;

public abstract void execute();

protected AbstractForceLayout(Graph<V, E> graph) {
this.maxSteps = DEFAULT_MAX_STEPS;
this.minEnergyThreshold = DEFAULT_MIN_ENERGY_THRESHOLD;
this.deltaTime = DEFAULT_DELTA_TIME;
this.graph = Objects.requireNonNull(graph);
}

public final void computePositions() {
execute();
hasBeenExecuted = true;
}

protected abstract void execute();

public AbstractForceLayout<V, E> setMaxSteps(int maxSteps) {
this.maxSteps = maxSteps;
return this;
Expand Down Expand Up @@ -96,52 +101,6 @@ public AbstractForceLayout<V, E> setFixedNodes(Set<V> fixedNodes) {
return this;
}

public void setHasBeenExecuted(boolean hasBeenExecuted) {
this.hasBeenExecuted = hasBeenExecuted;
}

void initializePoints() {
for (V vertex : graph.vertexSet()) {
Point p;
if (initialPoints.containsKey(vertex)) {
Point pInitial = initialPoints.get(vertex);
p = new Point(pInitial.getPosition().getX(), pInitial.getPosition().getY(), graph.degreeOf(vertex));
} else {
p = new Point(random.nextDouble(), random.nextDouble(), graph.degreeOf(vertex));
}
points.put(vertex, p);
}
}

void initializeSprings() {
for (E e : graph.edgeSet()) {
Point pointSource = points.get(graph.getEdgeSource(e));
Point pointTarget = points.get(graph.getEdgeTarget(e));
if (pointSource != pointTarget) { // no use in force layout to add loops
springs.add(new Spring(pointSource, pointTarget, graph.getEdgeWeight(e)));
}
}
}

void updatePosition() {
// Optimisation hint: do not compute forces or update velocities for fixed nodes
// We have computed forces and velocities for all nodes, even for the fixed ones
// We can optimize calculations by ignoring fixed nodes in those calculations
// Here we only update the position for the nodes that do not have fixed positions
for (Map.Entry<V, Point> vertexPoint : points.entrySet()) {
if (fixedNodes.contains(vertexPoint.getKey())) {
continue;
}
Point point = vertexPoint.getValue();
Vector position = point.getPosition().add(point.getVelocity().multiply(deltaTime));
point.setPosition(position);
}
}

boolean isStable() {
return points.values().stream().allMatch(p -> p.getEnergy() < minEnergyThreshold);
}

public Vector getStablePosition(V vertex) {
if (!hasBeenExecuted) {
LOGGER.warn("Force layout has not been executed yet");
Expand Down Expand Up @@ -197,4 +156,46 @@ public void toSVG(Function<V, String> tooltip, Writer writer) {

printWriter.close();
}

protected void initializePoints() {
for (V vertex : graph.vertexSet()) {
Point p;
if (initialPoints.containsKey(vertex)) {
Point pInitial = initialPoints.get(vertex);
p = new Point(pInitial.getPosition().getX(), pInitial.getPosition().getY(), graph.degreeOf(vertex));
} else {
p = new Point(random.nextDouble(), random.nextDouble(), graph.degreeOf(vertex));
}
points.put(vertex, p);
}
}

protected void initializeSprings() {
for (E e : graph.edgeSet()) {
Point pointSource = points.get(graph.getEdgeSource(e));
Point pointTarget = points.get(graph.getEdgeTarget(e));
if (pointSource != pointTarget) { // no use in force layout to add loops
springs.add(new Spring(pointSource, pointTarget, graph.getEdgeWeight(e)));
}
}
}

protected void updatePosition() {
// Optimisation hint: do not compute forces or update velocities for fixed nodes
// We have computed forces and velocities for all nodes, even for the fixed ones
// We can optimize calculations by ignoring fixed nodes in those calculations
// Here we only update the position for the nodes that do not have fixed positions
for (Map.Entry<V, Point> vertexPoint : points.entrySet()) {
if (fixedNodes.contains(vertexPoint.getKey())) {
continue;
}
Point point = vertexPoint.getValue();
Vector position = point.getPosition().add(point.getVelocity().multiply(deltaTime));
point.setPosition(position);
}
}

protected boolean isStable() {
return points.values().stream().allMatch(p -> p.getEnergy() < minEnergyThreshold);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ public void execute() {
}
}

setHasBeenExecuted(true);

long elapsedTime = System.nanoTime() - start;

LOGGER.info("Number of steps: {}", i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ public void execute() {
}
}

setHasBeenExecuted(true);

long elapsedTime = System.nanoTime() - start;

LOGGER.info("Number of steps: {}", i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import com.powsybl.iidm.network.Network;
import com.powsybl.nad.build.iidm.IntIdProvider;
import com.powsybl.nad.layout.BasicForceLayoutFactory;
import com.powsybl.nad.layout.BasicForceLayoutSpringyFactory;
import com.powsybl.nad.layout.LayoutFactory;
import com.powsybl.nad.layout.LayoutParameters;
import com.powsybl.nad.svg.LabelProvider;
Expand All @@ -28,7 +28,7 @@ public class NadParameters {
private LayoutParameters layoutParameters = new LayoutParameters();
private StyleProviderFactory styleProviderFactory = TopologicalStyleProvider::new;
private LabelProviderFactory labelProviderFactory = DefaultLabelProvider::new;
private LayoutFactory layoutFactory = new BasicForceLayoutFactory();
private LayoutFactory layoutFactory = new BasicForceLayoutSpringyFactory();
private IdProviderFactory idProviderFactory = IntIdProvider::new;

public SvgParameters getSvgParameters() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
package com.powsybl.nad.layout;

import com.powsybl.diagram.util.forcelayout.ForceLayoutSpringy;
import com.powsybl.diagram.util.forcelayout.AbstractForceLayout;
import com.powsybl.diagram.util.forcelayout.Vector;
import com.powsybl.nad.model.Edge;
import com.powsybl.nad.model.Graph;
Expand All @@ -19,16 +19,18 @@

/**
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
* @author Luma Zamarreño {@literal <zamarrenolm at aia.es>}
*/
public class BasicForceLayout extends AbstractLayout {
public abstract class AbstractBasicForceLayout extends AbstractLayout {

private static final int SCALE = 100;
protected abstract int getScale();

protected abstract AbstractForceLayout<Node, Edge> getForceLayoutAlgorithm(org.jgrapht.Graph<Node, Edge> jgraphtGraph, LayoutParameters layoutParameters);

@Override
protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) {
org.jgrapht.Graph<Node, Edge> jgraphtGraph = graph.getJgraphtGraph(layoutParameters.isTextNodesForceLayout());
ForceLayoutSpringy<Node, Edge> forceLayout = new ForceLayoutSpringy<>(jgraphtGraph);
forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout());
AbstractForceLayout<Node, Edge> forceLayout = getForceLayoutAlgorithm(jgraphtGraph, layoutParameters);
forceLayout.setMaxSteps(layoutParameters.getMaxSteps());

setInitialPositions(forceLayout, graph);
Expand All @@ -38,27 +40,29 @@ protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) {
.collect(Collectors.toSet());
forceLayout.setFixedNodes(fixedNodes);

forceLayout.execute();
forceLayout.computePositions();

int scale = getScale();
jgraphtGraph.vertexSet().forEach(node -> {
Vector p = forceLayout.getStablePosition(node);
node.setPosition(SCALE * p.getX(), SCALE * p.getY());
node.setPosition(scale * p.getX(), scale * p.getY());
});

if (!layoutParameters.isTextNodesForceLayout()) {
graph.getTextEdgesMap().values().forEach(nodePair -> fixedTextNodeLayout(nodePair, layoutParameters));
}
}

private void setInitialPositions(ForceLayoutSpringy<Node, Edge> forceLayout, Graph graph) {
private void setInitialPositions(AbstractForceLayout<Node, Edge> forceLayout, Graph graph) {
int scale = getScale();
Map<Node, com.powsybl.diagram.util.forcelayout.Point> initialPoints = getInitialNodePositions().entrySet().stream()
// Only accept positions for nodes in the graph
.filter(nodePosition -> graph.getNode(nodePosition.getKey()).isPresent())
.collect(Collectors.toMap(
nodePosition -> graph.getNode(nodePosition.getKey()).orElseThrow(),
nodePosition -> new com.powsybl.diagram.util.forcelayout.Point(
nodePosition.getValue().getX() / SCALE,
nodePosition.getValue().getY() / SCALE)
nodePosition.getValue().getX() / scale,
nodePosition.getValue().getY() / scale)
));
forceLayout.setInitialPoints(initialPoints);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,28 @@
*/
package com.powsybl.nad.layout;

import com.powsybl.diagram.util.forcelayout.AbstractForceLayout;
import com.powsybl.diagram.util.forcelayout.ForceAtlas2Layout;
import com.powsybl.diagram.util.forcelayout.Vector;
import com.powsybl.nad.model.Edge;
import com.powsybl.nad.model.Graph;
import com.powsybl.nad.model.Node;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
* @author Luma Zamarreño {@literal <zamarrenolm at aia.es>}
* @author José Antonio Marqués {@literal <marquesja at aia.es>}
*/
public class BasicForceAtlas2Layout extends AbstractLayout {
public class BasicForceAtlas2Layout extends AbstractBasicForceLayout {

private static final int SCALE = 20;

@Override
protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) {
org.jgrapht.Graph<Node, Edge> jgraphtGraph = graph.getJgraphtGraph(layoutParameters.isTextNodesForceLayout());
ForceAtlas2Layout<Node, Edge> forceLayout = new ForceAtlas2Layout<>(jgraphtGraph);
forceLayout.setMaxSteps(layoutParameters.getMaxSteps());

setInitialPositions(forceLayout, graph);
Set<Node> fixedNodes = getNodesWithFixedPosition().stream()
.map(graph::getNode)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
forceLayout.setFixedNodes(fixedNodes);

forceLayout.execute();

jgraphtGraph.vertexSet().forEach(node -> {
Vector p = forceLayout.getStablePosition(node);
node.setPosition(SCALE * p.getX(), SCALE * p.getY());
});

if (!layoutParameters.isTextNodesForceLayout()) {
graph.getTextEdgesMap().values().forEach(nodePair -> fixedTextNodeLayout(nodePair, layoutParameters));
}
protected int getScale() {
return SCALE;
}

private void setInitialPositions(ForceAtlas2Layout<Node, Edge> forceLayout, Graph graph) {
Map<Node, com.powsybl.diagram.util.forcelayout.Point> initialPoints = getInitialNodePositions().entrySet().stream()
// Only accept positions for nodes in the graph
.filter(nodePosition -> graph.getNode(nodePosition.getKey()).isPresent())
.collect(Collectors.toMap(
nodePosition -> graph.getNode(nodePosition.getKey()).orElseThrow(),
nodePosition -> new com.powsybl.diagram.util.forcelayout.Point(
nodePosition.getValue().getX() / SCALE,
nodePosition.getValue().getY() / SCALE)
));
forceLayout.setInitialPoints(initialPoints);
@Override
protected AbstractForceLayout<Node, Edge> getForceLayoutAlgorithm(org.jgrapht.Graph<Node, Edge> jgraphtGraph, LayoutParameters layoutParameters) {
// We could complete the ForceAtlas2 object created here
// setting additional parameters received through the layout parameters
return new ForceAtlas2Layout<>(jgraphtGraph);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.nad.layout;

import com.powsybl.diagram.util.forcelayout.AbstractForceLayout;
import com.powsybl.diagram.util.forcelayout.ForceLayoutSpringy;
import com.powsybl.nad.model.Edge;
import com.powsybl.nad.model.Node;

/**
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
*/
public class BasicForceLayoutSpringy extends AbstractBasicForceLayout {

private static final int SCALE = 100;

@Override
protected int getScale() {
return SCALE;
}

@Override
protected AbstractForceLayout<Node, Edge> getForceLayoutAlgorithm(org.jgrapht.Graph<Node, Edge> jgraphtGraph, LayoutParameters layoutParameters) {
ForceLayoutSpringy<Node, Edge> forceLayout = new ForceLayoutSpringy<>(jgraphtGraph);
forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout());
return forceLayout;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
/**
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
*/
public class BasicForceLayoutFactory implements LayoutFactory {
public class BasicForceLayoutSpringyFactory implements LayoutFactory {
@Override
public Layout create() {
return new BasicForceLayout();
return new BasicForceLayoutSpringy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.nad.build.iidm.NetworkGraphBuilder;
import com.powsybl.nad.build.iidm.VoltageLevelFilter;
import com.powsybl.nad.layout.BasicForceLayoutFactory;
import com.powsybl.nad.layout.BasicForceLayoutSpringyFactory;
import com.powsybl.nad.layout.LayoutFactory;
import com.powsybl.nad.layout.LayoutParameters;
import com.powsybl.nad.model.Graph;
Expand All @@ -35,7 +35,7 @@ public abstract class AbstractTest {

protected boolean debugSvg = false;
protected boolean overrideTestReferences = false;
protected LayoutFactory defaultLayoutFactory = new BasicForceLayoutFactory();
protected LayoutFactory defaultLayoutFactory = new BasicForceLayoutSpringyFactory();

private SvgParameters svgParameters;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private Map<String, Point> layoutResult(Network network,
Map<String, Point> fixedNodePositions,
Predicate<VoltageLevel> voltageLevelFilter
) {
LayoutFactory delegateLayoutFactory = new BasicForceLayoutFactory();
LayoutFactory delegateLayoutFactory = new BasicForceLayoutSpringyFactory();
PositionsLayoutFactory positionsLayoutFactory = new PositionsLayoutFactory(
delegateLayoutFactory,
initialNodePositions,
Expand Down

0 comments on commit 9ad9de0

Please sign in to comment.