Search code examples
javafxgraphstream

Show a graphstream graph in a javafx panel


I want to show a graphstream graph in a javafx StackPane panel. I know there are some similar questions in the net and in this page, but I'm getting a different output and error.

My code is in the following function:

    void cargaGrafico() {
        SingleGraph graph = aula.getSociogramas().get(currentSocio).getRelaPos().getGraph();
        FxViewer v = new FxViewer(graph,FxViewer.ThreadingModel.GRAPH_IN_GUI_THREAD);
        v.enableAutoLayout();
        FxViewPanel panel = (FxViewPanel)v.addDefaultView(false, new FxGraphRenderer());
        spGraficos.getChildren().add(panel);
    }

where spGraficos is a javafx StackPane. When I run the code I get a very small graph in the panel, not interactive, and it seems the graph is running in a different thread, because I'm getting the following exception:

Exception in thread "Thread-2" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-2
    at javafx.graphics@22-ea/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:294)
    at javafx.graphics@22-ea/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:475)
    at javafx.graphics@22-ea/javafx.animation.Animation.play(Animation.java:990)
    at [email protected]/org.graphstream.ui.fx_viewer.FxViewer.lambda$init$1(FxViewer.java:220)
    at java.base/java.lang.Thread.run(Thread.java:1570)e here

I was also looking for the graphstream maillist, but it seems it is not alive anymore. Thanks in advance!

This is a minimal reproducible example: In Intellij Idea I start a fresh new javafx project, only with a button and a anchorpane. When I push the button, a graphstream graph is populated and it should be put in the anchorpane. The result is the same, a small graph and the same exception. Code: HelloApplication.java

package com.probags.probags;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 820, 950);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        System.setProperty("org.graphstream.ui", "javafx");
        launch();
    }
}

HelloController.java

package com.probags.probags;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import org.graphstream.graph.Graph;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.ui.fx_viewer.FxViewPanel;
import org.graphstream.ui.fx_viewer.FxViewer;
import org.graphstream.ui.javafx.FxGraphRenderer;

public class HelloController {

    @FXML
    private AnchorPane apGraph;
    @FXML
    private Label welcomeText;
    private Graph graph = new SingleGraph("Test");
    @FXML
    void onHelloButtonClick(ActionEvent event) {
        populateGraph();
        FxViewer v = new FxViewer(graph,FxViewer.ThreadingModel.GRAPH_IN_GUI_THREAD);
        v.enableAutoLayout();
        FxViewPanel panel = (FxViewPanel)v.addDefaultView(false, new FxGraphRenderer());
        apGraph.getChildren().add(panel);
    }

    void populateGraph() {
        graph.addNode("A" );
        graph.addNode("B" );
        graph.addNode("C" );
        graph.addEdge("AB", "A", "B");
        graph.addEdge("BC", "B", "C");
        graph.addEdge("CA", "C", "A");
    }

}

module-info.java

module com.probags.probags {
    requires javafx.controls;
    requires javafx.fxml;
    requires gs.core;
    requires gs.ui.javafx;


    opens com.probags.probags to javafx.fxml;
    exports com.probags.probags;
}

I'm using gs.core and gs.ui.javafx 2.0 included in the project structure in Intellij Idea from Maven.


Solution

  • Issues with your code

    I don’t know graph stream, but you have ThreadingModel.GRAPH_IN_GUI_THREAD. And the stack trace shows you are not on the GUI thread. To run code on the (JavaFX) GUI thread, use Platform.runLater. As graphstream seems to rely on some Swing code for some operations, I don't know if the context for this means the GUI thread should be the JavaFX thread or the AWT thread. If you continue to have issues, provide a minimal reproducible example.

    I tried using the GraphStream JavaFX sample tutorial application and it worked fine for me (JavaFX 22.0.2, Java 21, OS X 14.5 - Intel). I used the default threading model from the sample code FxViewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD.

    Working Example

    I tried creating a project based on the instructions at https://github.com/graphstream/gs-ui-javafx/tree/master.

    The icon used was sourced from here:

    The diagram code was sourced and modified from here:

    Modifications were minor, to do with resource loading for the icon so it will work in a modular environment, cleanup to use lamdas, removing unused code and setting the documented system property to route graphstream through JavaFX.

    See the original link for copyright info.

    Output

    The sample code outputs 4 graphs, I just show one here. It is a pretty ugly output. I am not sure if that is what it is supposed to look like, but that is what it generated.

    image

    Source

    package org.example.graphstream;
    
    import org.graphstream.graph.Edge;
    import org.graphstream.graph.Graph;
    import org.graphstream.graph.Node;
    import org.graphstream.graph.implementations.MultiGraph;
    import org.graphstream.ui.fx_viewer.FxDefaultView;
    import org.graphstream.ui.fx_viewer.FxViewer;
    import org.graphstream.ui.view.Viewer;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.scene.SceneAntialiasing;
    import javafx.stage.Stage;
    
    import java.util.Objects;
    
    public class TutorialDiagrams extends Application {
        public static final String URL_IMAGE = Objects.requireNonNull(TutorialDiagrams.class.getResource("/org/graphstream/ui/viewer_fx/test/data/icon.png")).toString();
        
        public static void main(String[] args) {
            System.setProperty("org.graphstream.ui", "javafx");
            launch(args);
        }
    
        public void start(Stage primaryStage) {
            Scene scene1 = diagram1("diagram1", styleSheet1);
            Scene scene2 = diagram1b("diagram1b", styleSheet1);
            Scene scene3 = diagram2("diagram2", styleSheet1);
            Scene scene4 = diagram3("diagram3", styleSheet2);
            
            primaryStage.setScene(scene1);
            primaryStage.setOnCloseRequest(t -> Platform.exit());
            primaryStage.show();
            
            Stage stage2 = new Stage();
            stage2.setX(primaryStage.getX() + primaryStage.getWidth());
            stage2.setY(primaryStage.getY());
            stage2.setScene(scene2);
            stage2.setOnCloseRequest(t -> Platform.exit());
            stage2.show();
            
            Stage stage3 = new Stage();
            stage3.setX(primaryStage.getX());
            stage3.setY(primaryStage.getY() + primaryStage.getY());
            stage3.setScene(scene3);
            stage3.setOnCloseRequest(t -> Platform.exit());
            stage3.show();
            
            Stage stage4 = new Stage();
            stage4.setX(primaryStage.getX() + primaryStage.getWidth());
            stage4.setY(primaryStage.getY() + primaryStage.getY());
            stage4.setScene(scene4);
            stage4.setOnCloseRequest(t -> Platform.exit());
            stage4.show();
        }
        
        public Scene diagram(Graph graph, String style, String title, int width, int height) {
             graph.setAttribute("ui.quality");
             graph.setAttribute("ui.antialias");
             graph.setAttribute("ui.stylesheet", style);
             
             graph.setAttribute("ui.screenshot", title+".png");
             
             Viewer viewer = new FxViewer( graph, FxViewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD );
             FxDefaultView view = (FxDefaultView) viewer.addDefaultView(true);
             view.resize(width, height);
    
            return new Scene(view, width, height, true, SceneAntialiasing.DISABLED);
        }
        
        public Scene diagram1(String title, String styleSheet) {
            MultiGraph graph = new MultiGraph(title);
            Scene s = diagram(graph, styleSheet, title, 500, 250);
            
            Node G = graph.addNode("Graph");
            Node V = graph.addNode("Viewer");
            Edge E = graph.addEdge("G->V", "Graph", "Viewer", true);
            
            G.setAttribute("xyz", new double[] {0, 0, 0});
            V.setAttribute("xyz", new double[] {1, 0, 0});
            G.setAttribute("ui.label", "Graph");
            V.setAttribute("ui.label", "Viewer");
            
            return s ;
        }
        
        public Scene diagram1b(String title, String styleSheet) {
            MultiGraph graph = new MultiGraph(title);
            Scene s = diagram(graph, styleSheet, title, 500, 370);
            
            Node G = graph.addNode("Graph");
            Node V = graph.addNode("Viewer");
            Node B1 = graph.addNode("bidon1");
            Node B2 = graph.addNode("bidon2");
            
            graph.addEdge("G->bidon1", "Graph", "bidon1", true);
            graph.addEdge("bidon1->V", "bidon1", "Viewer", true);
            graph.addEdge("V->bidon2", "Viewer", "bidon2", true);
            graph.addEdge("bidon2->G", "bidon2", "Graph", true);
            
            G.setAttribute("xyz", new double[]{0, 0, 0});
            B1.setAttribute("xyz", new double[]{0, 0.5, 0});
            V.setAttribute("xyz", new double[]{1, 0.5, 0});
            B2.setAttribute("xyz", new double[]{1, 0, 0});
            G.setAttribute("ui.label", "Graph");
            V.setAttribute("ui.label", "Viewer");
            B1.setAttribute("ui.class", "invisible");
            B2.setAttribute("ui.class", "invisible");
            
            return s;
        }
        
        public Scene diagram2(String title, String styleSheet) {
            MultiGraph graph = new MultiGraph(title);
            Scene s = diagram(graph, styleSheet, title, 500, 250);
            
            Node G = graph.addNode("Graph");
            Node P = graph.addNode("Pipe");
            Node V = graph.addNode("Viewer");
                    
            graph.addEdge("G->P", "Graph", "Pipe", true);
            graph.addEdge("P->V", "Pipe", "Viewer", true);
             
            G.setAttribute("xyz", new double[] {0, 0, 0});
            P.setAttribute("xyz", new double[] {1, 0, 0});
            V.setAttribute("xyz", new double[] {2, 0, 0});
            G.setAttribute("ui.label", "Graph");
            P.setAttribute("ui.label", "Pipe");
            V.setAttribute("ui.label", "Viewer");
            
            return s;
        }
        
        public Scene diagram3(String title, String styleSheet) {
            MultiGraph graph = new MultiGraph(title);
            Scene s = diagram(graph, styleSheet, title, 800, 500);
            
            Node G = graph.addNode("Graph");
            Node V = graph.addNode("Viewer");
            Node P1 = graph.addNode("GtoV");
            Node P2 = graph.addNode("VtoG");
            graph.addEdge("G->GtoV", "Graph", "GtoV", true);
            graph.addEdge("GtoV->V", "GtoV", "Viewer", true);
            graph.addEdge("VtoG<-V", "Viewer", "VtoG", true);
            graph.addEdge("G<-VtoG", "VtoG", "Graph", true);
                    
            G.setAttribute("ui.label", "Graph");
            P1.setAttribute("ui.label", "Pipe");
            P2.setAttribute("ui.label", "ViewerPipe");
            V.setAttribute("ui.label", "Viewer");
                
            G.setAttribute("xyz", new double[] {-2,  0, 0});
            P1.setAttribute("xyz", new double[] {-1,  1.4, 0});
            P2.setAttribute("xyz", new double[] { 1, -1.4, 0});
            V.setAttribute("xyz", new double[] { 2,  0, 0});
            
            return s;
        }
        
        public String styleSheet1 = 
                "graph {"+
                "   padding: 90px;"+
                "}"+
                "node {"+
                "   size: 128px;"+
                "   shape: box;"+
                "   fill-mode: image-scaled;"+
                "   fill-image: url('"+URL_IMAGE+"');"+
                "   text-alignment: under;"+
                "   text-color: #DDD;"+
                "   text-background-mode: rounded-box;"+
                "   text-background-color: #333;"+
                "   text-padding: 4px;"+
                "}"+
                "node#Pipe {"+
                "   fill-image: url('"+URL_IMAGE+"');"+
                "}"+
                "node#Viewer {"+
                "   fill-image: url('"+URL_IMAGE+"');"+
                "}"+
                "node.invisible {"+
                "   fill-mode: plain;"+
                "   fill-color: #0000;"+
                "}"+
                "edge {"+
                "   size: 4px;"+
                "   fill-color: #979797;"+
                "   arrow-shape: none;"+
                "}";
        
        public String styleSheet2 = 
            "graph {"+
            "   padding: 90px;"+
            "}"+
            "node {"+
            "   size: 128px;"+
            "   shape: box;"+
            "   fill-mode: image-scaled;"+
            "   fill-image: url('"+URL_IMAGE+"');"+
            "   text-alignment: under;"+
            "   text-color: #DDD;"+
            "   text-background-mode: rounded-box;"+
            "   text-background-color: #333;"+
            "   text-padding: 4px;"+
            "}"+
            "node#Graph {"+
            "   fill-image: url('"+URL_IMAGE+"');"+
            "}"+
            "node#Viewer {"+
            "   fill-image: url('"+URL_IMAGE+"');"+
            "}"+
            "node#VtoG {"+
            "   fill-image: url('"+URL_IMAGE+"');"+
            "}"+
            "edge {"+
            "   size: 4px;"+
            "   fill-color: #979797;"+
            "   shape: L-square-line;"+
            "   arrow-size: 25px, 10px;"+
            "   arrow-shape: none;"+
            "}";
    }
    

    module-info.java

    module org.example.graphstream {
        requires javafx.controls;
        requires javafx.swing;
    
        requires gs.core;
        requires gs.ui.javafx;
    
        exports org.example.graphstream;
    }
    

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>graph-stream</artifactId>
        <version>1.0-SNAPSHOT</version>
        <name>graph-stream</name>
    
        <dependencies>
            <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-controls</artifactId>
                <version>22.0.2</version>
            </dependency>
            <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-swing</artifactId>
                <version>22.0.2</version>
            </dependency>
            <dependency>
                <groupId>org.graphstream</groupId>
                <artifactId>gs-core</artifactId>
                <version>2.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.graphstream</groupId>
                <artifactId>gs-ui-javafx</artifactId>
                <version>2.0</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.11.0</version>
                    <configuration>
                        <source>21</source>
                        <target>21</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    

    FXML style example

    output

    pom.xml

    Same as above, just add the following dependency.

    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>22.0.2</version>
    </dependency>
    

    hello-view.fxml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.layout.VBox?>
    
    <VBox fx:id="apGraph" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1"
          fx:controller="org.example.graphstream.HelloController">
        <Button mnemonicParsing="false" text="Show Graph" onAction="#onHelloButtonClick"/>
    </VBox>
    

    module.info

    module org.example.graphstream {
        requires javafx.controls;
        requires javafx.swing;
        requires javafx.fxml;
    
        requires gs.core;
        requires gs.ui.javafx;
    
        opens org.example.graphstream to javafx.fxml;
        exports org.example.graphstream;
    }
    

    HelloApplication.java

    package org.example.graphstream;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) throws IOException {
            FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
            stage.setScene(
                    new Scene(fxmlLoader.load(), 820, 950)
            );
            stage.show();
        }
    
        public static void main(String[] args) {
            System.setProperty("org.graphstream.ui", "javafx");
            launch();
        }
    }
    

    HelloController.java

    package org.example.graphstream;
    
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.scene.layout.VBox;
    import org.graphstream.graph.Graph;
    import org.graphstream.graph.implementations.SingleGraph;
    import org.graphstream.ui.fx_viewer.FxViewPanel;
    import org.graphstream.ui.fx_viewer.FxViewer;
    import org.graphstream.ui.javafx.FxGraphRenderer;
    
    public class HelloController {
    
        @FXML
        private VBox apGraph;
    
        private Graph graph = new SingleGraph("Test");
    
        @FXML
        void onHelloButtonClick(ActionEvent event) {
            populateGraph();
            FxViewer v = new FxViewer(graph,FxViewer.ThreadingModel.GRAPH_IN_GUI_THREAD);
            v.enableAutoLayout();
            FxViewPanel panel = (FxViewPanel)v.addDefaultView(false, new FxGraphRenderer());
            apGraph.getChildren().add(panel);
        }
    
        void populateGraph() {
            graph.addNode("A" );
            graph.addNode("B" );
            graph.addNode("C" );
            graph.addEdge("AB", "A", "B");
            graph.addEdge("BC", "B", "C");
            graph.addEdge("CA", "C", "A");
        }
    }