ForceLayout.java
/**
* Java transcription of Springy v2.8.0
*
* Copyright (c) 2010-2018 Dennis Hotson
* Copyright (c) 2021-2025 RTE (https://www.rte-france.com)
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package com.powsybl.diagram.util.forcelayout;
import com.powsybl.diagram.util.layout.Layout;
import com.powsybl.diagram.util.layout.geometry.LayoutContext;
import com.powsybl.diagram.util.layout.geometry.Point;
import com.powsybl.diagram.util.layout.geometry.Vector2D;
import com.powsybl.diagram.util.layout.algorithms.BasicForceLayoutAlgorithm;
import com.powsybl.diagram.util.layout.algorithms.parameters.BasicForceLayoutParameters;
import com.powsybl.diagram.util.layout.setup.SquareRandomBarycenterSetup;
import org.jgrapht.Graph;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
/**
* The following algorithm is a force layout algorithm.
* It seeks to place the nodes of a graph in such a way that the nodes are well spaced and that there are no unnecessary crossings.
* The algorithm uses an analogy with physics where the nodes of the graph are particles with mass and the edges are springs.
* Force calculations are used to place the nodes.
*
* The algorithm is inspired from: https://github.com/dhotson/springy
* @deprecated Use {@link Layout} instead <br>
* The equivalent of: <br>
* {@code new ForceLayout(graph).execute();}<br>
* would be <br>
* {@code Layout.createBasicForceLayout().run(new LayoutContext<>(graph));}
*
* @author Mathilde Grapin {@literal <mathilde.grapin at rte-france.com>}
* @author Nathan Dissoubray {@literal <nathan.dissoubray at rte-france.com>}
*/
@Deprecated(since = "4.10.0", forRemoval = true)
public class ForceLayout<V, E> {
private final LayoutContext<V, E> layoutContext;
private final BasicForceLayoutParameters.Builder basicParametersBuilder = new BasicForceLayoutParameters.Builder();
@java.lang.SuppressWarnings("java:S2245")
private final Random random = new Random();
public ForceLayout(Graph<V, E> graph) {
this.layoutContext = new LayoutContext<>(Objects.requireNonNull(graph));
this.random.setSeed(3L);
}
public ForceLayout(LayoutContext<V, E> layoutContext) {
this.layoutContext = Objects.requireNonNull(layoutContext);
this.random.setSeed(3L);
}
public ForceLayout<V, E> setAttractToCenterForce(boolean attractToCenterForce) {
this.basicParametersBuilder.withAttractToCenterForce(attractToCenterForce);
return this;
}
public ForceLayout<V, E> setRepulsionForceFromFixedPoints(boolean repulsionForceFromFixedPoints) {
this.basicParametersBuilder.withRepulsionForceFromFixedPoints(repulsionForceFromFixedPoints);
return this;
}
public ForceLayout<V, E> setMaxSteps(int maxSteps) {
this.basicParametersBuilder.withMaxSteps(maxSteps);
return this;
}
public ForceLayout<V, E> setMinEnergyThreshold(double minEnergyThreshold) {
this.basicParametersBuilder.withMinEnergyThreshold(minEnergyThreshold);
return this;
}
public ForceLayout<V, E> setDeltaTime(double deltaTime) {
this.basicParametersBuilder.withDeltaTime(deltaTime);
return this;
}
public ForceLayout<V, E> setRepulsion(double repulsion) {
this.basicParametersBuilder.withRepulsion(repulsion);
return this;
}
public ForceLayout<V, E> setFriction(double friction) {
this.basicParametersBuilder.withFriction(friction);
return this;
}
public ForceLayout<V, E> setMaxSpeed(double maxSpeed) {
this.basicParametersBuilder.withMaxSpeed(maxSpeed);
return this;
}
public ForceLayout<V, E> setInitialPoints(Map<V, Point> initialPoints) {
this.layoutContext.setInitialPoints(initialPoints);
return this;
}
public ForceLayout<V, E> setFixedPoints(Map<V, Point> fixedPoints) {
this.layoutContext.setFixedPoints(fixedPoints);
return this;
}
public ForceLayout<V, E> setFixedNodes(Set<V> fixedNodes) {
this.layoutContext.setFixedNodes(fixedNodes);
return this;
}
public void execute() {
Layout<V, E> algorithmRunner = new Layout<>(
new SquareRandomBarycenterSetup<>(random),
new BasicForceLayoutAlgorithm<>(basicParametersBuilder.build())
);
algorithmRunner.run(layoutContext);
}
public Vector2D getStablePosition(V vertex) {
return layoutContext.getStablePosition(vertex);
}
/**
* @deprecated
* This method now returns an empty Set until its removal
*/
@Deprecated(since = "4.9.0", forRemoval = true)
public Set<Spring> getSprings() {
return Collections.emptySet();
}
public void toSVG(Function<V, String> tooltip, Path path) throws IOException {
try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
toSVG(tooltip, writer);
}
}
public void toSVG(Function<V, String> tooltip, Writer writer) {
if (layoutContext != null) {
layoutContext.toSVG(tooltip, writer);
}
}
public void setCenter(Vector2D center) {
layoutContext.setCenter(center);
}
public Vector2D getCenter() {
return layoutContext.getCenter();
}
}