tomnelson / jungrapht-visualization

visualization and sample code from Java Universal Network Graph ported to use JGraphT models and algorithms
Other
47 stars 7 forks source link

Sugiyama layout algorithm only seems to work when the application is running with headlessmode = false #23

Closed kristofsmits closed 1 year ago

kristofsmits commented 1 year ago

Hi,

First off all : great library, love it!

If I apply the TidierTree algorithm (layoutAlgorithm = TidierTreeLayoutAlgorithm), following piece of code works fine, and all locations are calculated

      LayoutModel<AbstractPlaceholder> layoutModel = LayoutModel.<AbstractPlaceholder>builder().size(1590, 1590).graph(placeholderGraph).build();
      layoutAlgorithm.visit(layoutModel);
      Map<AbstractPlaceholder, Point> locations = layoutModel.getLocations();

But if I use the same code for Sugiyama ((layoutAlgorithm = SugiyamaLayoutAlgorithm) or some other layout algorithms, then the locations list is empty, no locations are calculated

Based on the sample code, I managed to fix this by changing the code into

      VisualizationViewer<AbstractPlaceholder, DefaultEdge> vv = VisualizationViewer.builder(placeholderComputationGraph).viewSize(new Dimension(900, 900)).build();
      LayoutAlgorithmTransition.apply(vv, layoutAlgorithm, after);
      locations = vv.getVisualizationModel().getLayoutModel().getLocations();

So in theory this works fine now, but the annoying thing is that VisualizationViewer requires to run with headless mode = false (even if there is no interaction with UI, keyboard, etc)

The reason why the VisualizationViewer cannot run in headless mode, is that

Maybe I overlook something, but it would be great if the Sugiyama algorithm would just work without using VisualizationViewer, or if VisualizationViewer or DefaultGraphMouse would work in a non-headless mode.

Thanks for your reaction and keep up the good work!

tomnelson commented 1 year ago

Thanks for your interest! Are you mainly interested in the layout algorithms and not necessarily rendering them in the visualization? Is there another reason you want headless mode? Let me know.

Some of the layout algorithms spawn a new thread (by default) to do the work. If you access locations before the thread has completed its work, you will get an empty set. You can run the layout algorithm with threaded=false so that it will not spawn a thread. It will block until the layout is done. Here is an example of just getting locations. I also included the call to get the edge articulations (where they bend) from the layout algorithm. You asked about Sugiyama layout, but you may also want to consider the EiglspergerLayoutAlgorithm. It will give you fewer edge articulations. To use it instead, just do this below:

EiglspergerLayoutAlgorithm<Integer, Integer> layoutAlgorithm =
        EiglspergerLayoutAlgorithm.<Integer, Integer>edgeAwareBuilder()
        .threaded(false)
        .layering(Layering.TOP_DOWN)
        .build();`

package org.jungrapht.samples.sugiyama;

import org.jgrapht.Graph; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.jungrapht.visualization.layout.algorithms.SugiyamaLayoutAlgorithm; import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering; import org.jungrapht.visualization.layout.model.LayoutModel;

import java.util.stream.IntStream; public class SugiyamaLayoutPoints {

public SugiyamaLayoutPoints() {

Graph<Integer, Integer> graph = createInitialGraph();

SugiyamaLayoutAlgorithm<Integer, Integer> layoutAlgorithm =
    SugiyamaLayoutAlgorithm.<Integer, Integer>edgeAwareBuilder()
        .threaded(false).   // don't spawn a thread
        .layering(Layering.TOP_DOWN)
        .build();

LayoutModel<Integer> layoutModel = LayoutModel.<Integer>builder().size(100, 100).graph(graph).build();
layoutAlgorithm.visit(layoutModel);

System.err.println("locations of vertices: "+layoutModel.getLocations());

graph.edgeSet().stream().map(e -> "e:" + e + ", articulations: " + layoutAlgorithm.getEdgeArticulationFunction().apply(e)).forEach(System.err::println);

}

Graph<Integer, Integer> createInitialGraph() {

Graph<Integer, Integer> graph =
    GraphTypeBuilder.<Integer, Integer>directed()
        .edgeSupplier(SupplierUtil.createIntegerSupplier())
        .vertexSupplier(SupplierUtil.createIntegerSupplier())
        .buildGraph();

IntStream.rangeClosed(1, 23).forEach(graph::addVertex);
graph.addEdge(1, 3);
graph.addEdge(1, 4);
graph.addEdge(1, 13);
graph.addEdge(1, 21);
graph.addEdge(2, 3);
graph.addEdge(2, 20);
graph.addEdge(3, 4);
graph.addEdge(3, 5);
graph.addEdge(3, 23);
graph.addEdge(4, 6);
graph.addEdge(5, 7);
graph.addEdge(6, 8);
graph.addEdge(6, 16);
graph.addEdge(6, 23);
graph.addEdge(7, 9);
graph.addEdge(8, 10);
graph.addEdge(8, 11);
graph.addEdge(9, 12);
graph.addEdge(10, 13);
graph.addEdge(10, 14);
graph.addEdge(10, 15);
graph.addEdge(11, 15);
graph.addEdge(11, 16);
graph.addEdge(12, 20);
graph.addEdge(13, 17);
graph.addEdge(14, 17);
graph.addEdge(14, 18);
// no 15 targets
graph.addEdge(16, 18);
graph.addEdge(16, 19);
graph.addEdge(16, 20);
graph.addEdge(18, 21);
graph.addEdge(19, 22);
graph.addEdge(21, 23);
graph.addEdge(22, 23);
return graph;

}

public static void main(String[] args) { new SugiyamaLayoutPoints(); } }

kristofsmits commented 1 year ago

Thanks for the quick reply! I'm indeed only interested in the layout, and not in the rendering. I completely overlooked the thread option. When I set it to false, it now works without the use of the VisualisationViewer, and in headless mode, great. So not a bug, but just a feature I didn't notice.

Thanks for the EiglspergerLayoutAlgorithm tip, keep up the good work!