graphstream / gs-core

Graphstream core
http://graphstream-project.org/
Other
398 stars 109 forks source link

Obtaining nice layouts programmatically #352

Closed DanySK closed 3 years ago

DanySK commented 3 years ago

Hi, I'd like to be able to layout a graph in the same nice way the default UI does when autoLayout is true. I tried to mimick what LayoutRunner does, by: Creating a Graph, adding it as a sink for a RandomEuclideanGenerator, then creating a SpringBox layout, and adding the graph as a sink for the graph. Once done, I generate 400 nodes using generator.nextEvents(). At each generation, I also run:

while (layout.stabilization < 1) {
    layout.compute()
}

(it's Kotlin code, but I can translate to Java if it's clearer for you)

This seems similar to what LayoutRunner does (besides taking a nap between multiple compute() calls, but I guess that is not to make the system uselessly slow when reshaping the displayed graph).

However, I must be doing something very wrong, as my layout appears to be: image

while the autolayout for the same graph (exactly the same, I'm seeding the rng): image

The only difference I can see from code is a call to Graph.replay() done from the UI, but the documentation says to avoid doing so. What's the suggested method to achieve a good layout manually?

hichbra commented 3 years ago

You just have to define a new instance of the layout algorithm you like and use it :

SpringBox l = new SpringBox();

Then you can define the parameter of the layout, like the force or the stabilization point :

l.setStabilizationLimit(0);

But please keep in mind that if you want to use your instance of layout algorithm, you need to create your viewer before the display. And that implies to build your own UI. Here an easy example :

SpringBox l = new SpringBox(); // The layout algorithm
l.setStabilizationLimit(0);

Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_GUI_THREAD);
viewer.enableAutoLayout(l); // Add the layout algorithm to the viewer

// Build your UI
add(viewer.addDefaultView(false), BorderLayout.CENTER); // Your class should extends JFrame
setSize(800, 600);
setVisible(true);
DanySK commented 3 years ago

I solved this way:

        SingleGraph("asda").also { graph ->
            val layout = SpringBox(false, Random(1))
            with(LobsterGenerator(2, 10)) {
                addNodeLabels(false)
                setRandomSeed(0)
                addSink(graph)
                addSink(layout)
                layout.addSink(graph)
                layout.quality = 1.0
                begin()
                (0..nodes).forEach { _ ->
                    nextEvents()
                }
                end()
            }
            while (layout.stabilization < 1) {
                layout.compute()
            }
            graph.display(false)
        }

I got a question though. Nodes have properties "xyz", "x", and "y". The first two values of "xyz" do not seem to match the values of "x" and "y". Values of "xyz" seem to be what the display system is using. I wonder what "x" and "y" mean.

hichbra commented 3 years ago

It depends on when it is used, but it is strongly advised to use xyz, as specified here in the Coordinate system chapter.