From 90f8b8f19a0549531029d758ce2cdf4cf00a1e93 Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Mon, 9 Sep 2024 17:16:28 +0200
Subject: [PATCH] =?UTF-8?q?add=20save=20node=20position=20in=20PO=C2=B2=20?=
 =?UTF-8?q?file=20format?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../po2vocabmanager/utils/JavaConnector.java  |   9 +
 .../dataView/ItineraryOverviewController.java | 196 ++++++++++--------
 .../po2vocabmanager/graph/graph-creator.js    |   5 +
 3 files changed, 129 insertions(+), 81 deletions(-)

diff --git a/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java b/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
index c42f32ba..9ef592dd 100644
--- a/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
+++ b/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
@@ -23,6 +23,7 @@ import fr.inra.po2vocabmanager.model.DataNode;
 import fr.inrae.po2engine.model.dataModel.CompositionFile;
 import fr.inrae.po2engine.model.dataModel.ItineraryFile;
 import fr.inrae.po2engine.model.dataModel.StepFile;
+import org.json.JSONArray;
 import org.json.JSONObject;
 
 import java.util.HashMap;
@@ -47,6 +48,14 @@ public class JavaConnector {
         System.out.println("log : " + message);
     }
 
+    public void nodeDragged(String allNodes) throws NumberFormatException{
+        JSONArray nodesArray = new JSONArray(allNodes);
+        nodesArray.forEach(node -> {
+            JSONObject obj = (JSONObject) node;
+            this.itiFile.setNodePosition(obj.optString("id", ""), obj.optDouble("x", 0.0) / 72.0, obj.optDouble("y", 0.0) / 72.0);
+        });
+    }
+
     public void addEdge(String source, String target) {
         if(!MainApp.getDataControler().canEdit()) {
             return;
diff --git a/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java b/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
index a1e9ed97..952e72e0 100644
--- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
+++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
@@ -67,6 +67,7 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 public class ItineraryOverviewController {
@@ -88,6 +89,7 @@ public class ItineraryOverviewController {
     private JavaConnector jc;
     private ListView<TextField> listProduct;
     private WebView browser;
+    private ItineraryFile itiFile;
 
     @FXML
     private void initialize() {
@@ -104,96 +106,41 @@ public class ItineraryOverviewController {
     }
 
     public void showNodeDetails(DataNode node) {
-        Tools.addProgress(ProgressPO2.GRAPH, "creating graph");
 
-        ItineraryFile file = (ItineraryFile) node.getFile();
-        file.checkValue();
-        WebView browser = new WebView();
-        ap.getChildren().clear();
-        ap.getChildren().add(browser);
-        AnchorPane.setTopAnchor(browser, 0.0);
-        AnchorPane.setLeftAnchor(browser, 0.0);
-        AnchorPane.setRightAnchor(browser, 0.0);
-        AnchorPane.setBottomAnchor(browser, 0.0);
 
-        WebEngine webEngine = browser.getEngine();
-        webEngine.setJavaScriptEnabled(true);
+        this.itiFile = (ItineraryFile) node.getFile();
 
-        listIdStep = new HashMap<>();
-        listIdComposition = new HashMap<>();
         listProduct = new ListView<>();
+        this.itiFile.checkValue();
 
-        // Important. garder une référence au javaconnector pour eviter le garbage collector ! bug openJDK
-        jc = new JavaConnector(file, listIdStep, listIdComposition);
-        webEngine.setOnAlert(event -> {
-            Alert alert = new Alert(Alert.AlertType.INFORMATION);
-            alert.initOwner(MainApp.primaryStage);
-            alert.initModality(Modality.WINDOW_MODAL);
-            alert.setContentText(event.getData());
-            alert.showAndWait();
-        });
-        webEngine.setOnError(event -> {
-            Alert alert = new Alert(Alert.AlertType.ERROR);
-            alert.initOwner(MainApp.primaryStage);
-            alert.initModality(Modality.WINDOW_MODAL);
-            alert.setContentText(event.getMessage());
-            alert.showAndWait();
-        });
-        webEngine.setConfirmHandler(event -> {
-            Alert alert = new Alert(Alert.AlertType.INFORMATION);
-            alert.initOwner(MainApp.primaryStage);
-            alert.initModality(Modality.WINDOW_MODAL);
-            alert.setContentText(event);
-            alert.showAndWait();
-            return true;
-        });
-        webEngine.getLoadWorker().stateProperty().addListener(
-                new ChangeListener() {
-                    @Override
-                    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
-                        if (newValue == Worker.State.SUCCEEDED) {
-                            JSObject jsobj = (JSObject) webEngine.executeScript("window");
-                            jsobj.setMember("javaConnector", jc);
-//                            webEngine.executeScript("var firebug=document.createElement('script');firebug.setAttribute('src','https://lupatec.eu/getfirebug/firebug-lite-compressed.js');document.body.appendChild(firebug);(function(){if(window.firebug.version){firebug.init();}else{setTimeout(arguments.callee);}})();void(firebug);");
 
-                            Platform.runLater(() -> {
-                                new Thread(() -> {
-                                    generateGraph2(browser, file, file.getItineraryName() + " (" + file.getItineraryNumber()+")", listIdStep, listIdComposition);
-                                    Tools.delProgress(ProgressPO2.GRAPH);
-                                }).start();
-                            });
-                        }
-                    }
-                }
-        );
 
 
 
         prodOfbox.getChildren().clear();
 
-        Label lnum = new Label(file.getItineraryNumber());
+        Label lnum = new Label(itiFile.getItineraryNumber());
         Label lname = new Label("  --  name : ");
-        TextField itiName = new TextField(file.getItineraryName());
+        TextField itiName = new TextField(itiFile.getItineraryName());
         itiName.disableProperty().bind(this.mainApp.getEditProperty().not());
         itiName.setStyle(" -fx-text-fill: black;  -fx-opacity: 1;");
         itiName.textProperty().addListener((observableValue, oldValue, newValue) ->  {
-            file.getProcessFile().getData().setModified(true);
-
+            itiFile.getProcessFile().getData().setModified(true);
             if(newValue != null && !newValue.isEmpty()) {
-                file.itineraryNameProperty().set(newValue);
+                itiFile.itineraryNameProperty().set(newValue);
             } else {
-                file.itineraryNameProperty().set("");
+                itiFile.itineraryNameProperty().set("");
             }
         } );
         Label separator = new Label("");
         Label connectedComponent = new Label( "");
-        connectedComponent.visibleProperty().bind(file.connectedComposant.greaterThan(1));
-        connectedComponent.textProperty().bind(new SimpleStringProperty(" -- ").concat(file.connectedComposant.asString().concat(" connected components")));
+        connectedComponent.visibleProperty().bind(itiFile.connectedComposant.greaterThan(1));
+        connectedComponent.textProperty().bind(new SimpleStringProperty(" -- ").concat(itiFile.connectedComposant.asString().concat(" connected components")));
         separator.setMaxWidth(Double.MAX_VALUE);
         HBox bb = new HBox();
         Background warn = new Background(new BackgroundFill(javafx.scene.paint.Color.DARKORANGE,null,null));
         Background norm = new Background(new BackgroundFill(null,null,null));
-        bb.backgroundProperty().bind(Bindings.when(file.connectedComposant.greaterThan(1)).then(warn).otherwise(norm));
+        bb.backgroundProperty().bind(Bindings.when(itiFile.connectedComposant.greaterThan(1)).then(warn).otherwise(norm));
 
         bb.setAlignment(Pos.CENTER_RIGHT);
         bb.setHgrow(separator, Priority.ALWAYS);
@@ -213,15 +160,14 @@ public class ItineraryOverviewController {
         VBox box = new VBox();
 
         box.getChildren().add(listProduct);
-        for (String c : file.getProductsOfInterest()) {
+        for (String c : itiFile.getProductsOfInterest()) {
             if (!c.isEmpty()) {
                 TextField comb = new TextField();
                 comb.setText(c);
                 comb.editableProperty().bind(MainApp.getDataControler().getCanEditProperty());
                 UITools.addAutoComplete(comb, Ontology.listComponentProperty());
-//                UITools.addPopOver(comb, Ontology.Component);
                 listProduct.getItems().add(comb);
-                comb.textProperty().addListener((observableValue, s, t1) -> updateListProduct(file));
+                comb.textProperty().addListener((observableValue, s, t1) -> updateListProduct(itiFile));
             }
         }
         listProduct.setCellFactory(cellData -> {
@@ -236,7 +182,7 @@ public class ItineraryOverviewController {
                         setGraphic(null);
                     } else {
                         Button delButton = new Button(null, new ImageView(UITools.getImage("resources/images/del_16.png")));
-                        delButton.setOnMouseClicked(event1 -> {listProduct.getItems().remove(item);updateListProduct(file);});
+                        delButton.setOnMouseClicked(event1 -> {listProduct.getItems().remove(item);updateListProduct(itiFile);});
                         delButton.disableProperty().bind(MainApp.getDataControler().getCanEditProperty().not());
 
                         HBox hBox = new HBox();
@@ -254,8 +200,8 @@ public class ItineraryOverviewController {
             UITools.addAutoComplete(comb, Ontology.listComponentProperty());
 //            UITools.addPopOver(comb, Ontology.Component);
             listProduct.getItems().add(comb);
-            updateListProduct(file);
-            comb.textProperty().addListener((observableValue, s, t1) -> updateListProduct(file));
+            updateListProduct(itiFile);
+            comb.textProperty().addListener((observableValue, s, t1) -> updateListProduct(itiFile));
         });
 
 
@@ -269,15 +215,17 @@ public class ItineraryOverviewController {
 
         exportImg.setGraphic(new ImageView(UITools.getImage("resources/images/file-export-16.png")));
 
-        Button reinitScreen = new Button("reinit view");
-        reinitScreen.setGraphic(new ImageView(UITools.getImage("resources/images/resize-expand_16.png")));
+        Button resetLayout = new Button("reset nodes position");
+        resetLayout.setGraphic(new ImageView(UITools.getImage("resources/images/resize-expand_16.png")));
 
 
-        HBox bbb = new HBox(exportImg, reinitScreen);
+        HBox bbb = new HBox(exportImg, resetLayout);
         prodOfbox.getChildren().addAll(prodLabel, listProduct, graphTips, bbb);
 
-        reinitScreen.setOnAction(actionEvent -> {
-            browser.getEngine().executeScript("graph.reinit()");
+
+        resetLayout.setOnAction(actionEvent -> {
+            this.itiFile.clearNodePosition();
+            drawGraph();
         });
 
         exportImg.setOnAction(actionEvent -> {
@@ -306,6 +254,69 @@ public class ItineraryOverviewController {
 //        generateGraphSmart(sp2, file.getItinerary(), file.getItineraryName() + " (" + file.getItineraryNumber()+")");
 //        generateGraph(sp2, file.getItinerary(), file.getItineraryName() + " (" + file.getItineraryNumber()+")");
 
+        drawGraph();
+
+    }
+
+    private void drawGraph() {
+        Tools.addProgress(ProgressPO2.GRAPH, "creating graph");
+        browser = new WebView();
+        AnchorPane.setTopAnchor(browser, 0.0);
+        AnchorPane.setLeftAnchor(browser, 0.0);
+        AnchorPane.setRightAnchor(browser, 0.0);
+        AnchorPane.setBottomAnchor(browser, 0.0);
+        ap.getChildren().clear();
+        ap.getChildren().add(browser);
+
+        WebEngine webEngine = browser.getEngine();
+        webEngine.setJavaScriptEnabled(true);
+
+        listIdStep = new HashMap<>();
+        listIdComposition = new HashMap<>();
+
+        // Important. garder une référence au javaconnector pour eviter le garbage collector ! bug openJDK
+        jc = new JavaConnector(this.itiFile, listIdStep, listIdComposition);
+        webEngine.setOnAlert(event -> {
+            Alert alert = new Alert(Alert.AlertType.INFORMATION);
+            alert.initOwner(MainApp.primaryStage);
+            alert.initModality(Modality.WINDOW_MODAL);
+            alert.setContentText(event.getData());
+            alert.showAndWait();
+        });
+        webEngine.setOnError(event -> {
+            Alert alert = new Alert(Alert.AlertType.ERROR);
+            alert.initOwner(MainApp.primaryStage);
+            alert.initModality(Modality.WINDOW_MODAL);
+            alert.setContentText(event.getMessage());
+            alert.showAndWait();
+        });
+        webEngine.setConfirmHandler(event -> {
+            Alert alert = new Alert(Alert.AlertType.INFORMATION);
+            alert.initOwner(MainApp.primaryStage);
+            alert.initModality(Modality.WINDOW_MODAL);
+            alert.setContentText(event);
+            alert.showAndWait();
+            return true;
+        });
+        webEngine.getLoadWorker().stateProperty().addListener(
+                new ChangeListener() {
+                    @Override
+                    public void changed(ObservableValue observable, Object oldValue, Object newValue) {
+                        if (newValue == Worker.State.SUCCEEDED) {
+                            JSObject jsobj = (JSObject) webEngine.executeScript("window");
+                            jsobj.setMember("javaConnector", jc);
+//                            webEngine.executeScript("var firebug=document.createElement('script');firebug.setAttribute('src','https://lupatec.eu/getfirebug/firebug-lite-compressed.js');document.body.appendChild(firebug);(function(){if(window.firebug.version){firebug.init();}else{setTimeout(arguments.callee);}})();void(firebug);");
+
+                            Platform.runLater(() -> {
+                                new Thread(() -> {
+                                    generateGraph2(browser, itiFile, itiFile.getItineraryName() + " (" + itiFile.getItineraryNumber()+")", listIdStep, listIdComposition);
+                                    Tools.delProgress(ProgressPO2.GRAPH);
+                                }).start();
+                            });
+                        }
+                    }
+                }
+        );
 
         Platform.runLater(() -> {
             webEngine.load(MainApp.class.getResource("graph/graph.html").toExternalForm());
@@ -327,6 +338,7 @@ public class ItineraryOverviewController {
 
                 HashMap<StepFile, MutableNode> listStepNode = new HashMap<>();
                 HashMap<CompositionFile, MutableNode> listCompo = new HashMap<>();
+                AtomicBoolean positionExist = new AtomicBoolean(false);
 
                 itiFile.getListStep().filtered(s -> s.getFather() != null).forEach(s -> {
                     StepFile father = s.getFather();
@@ -337,6 +349,11 @@ public class ItineraryOverviewController {
                         ms.add("observation", father.getObservationFiles().size());
                         ms.add("hat", "yes");
                         ms.add("type", "step");
+                        String pos = itiFile.getNodePosition(father.getUuid());
+                        if(pos != null) {
+                            ms.add("pos", pos);
+                            positionExist.set(true);
+                        }
                         listIdStep.put(father.getUuid(), father);
 
                         g.add(ms);
@@ -352,6 +369,11 @@ public class ItineraryOverviewController {
                         ms.add("observation", s.getObservationFiles().size());
                         ms.add("hat", "no");
                         ms.add("type", "step");
+                        String pos = itiFile.getNodePosition(s.getUuid());
+                        if(pos != null) {
+                            ms.add("pos", pos);
+                            positionExist.set(true);
+                        }
                         listIdStep.put(s.getUuid(),s);
                         listStepNode.put( s, ms);
                         g.add(ms);
@@ -403,6 +425,11 @@ public class ItineraryOverviewController {
                                 pc.add(Color.named("red"));
                                 pc.add("id", en.getKey().getUuid());
                                 pc.add("type", "composition");
+                                String pos = itiFile.getNodePosition(en.getKey().getUuid());
+                                if(pos != null) {
+                                    pc.add("pos", pos);
+                                    positionExist.set(true);
+                                }
                                 listIdComposition.put(en.getKey().getUuid(), en.getKey());
                                 g.add(pc);
                                 listCompo.put(en.getKey(), pc);
@@ -420,9 +447,14 @@ public class ItineraryOverviewController {
                 Arrays.asList(new GraphvizCmdLineEngine(), new GraphvizV8Engine(), new GraphvizJdkEngine()));
         Graphviz.useEngine(listEngine);
         Graphviz viz = Graphviz.fromGraph(g);
-        String json = viz.render(Format.JSON).toString();
+        String json = "";
+        if(positionExist.get()) {
+            json = viz.engine(Engine.NEATO).render(Format.JSON).toString();
+        } else {
+            json = viz.engine(Engine.DOT).yInvert(true).render(Format.JSON).toString();
+        }
 //        try {
-//            viz.render(Format.PNG).toFile(new File("resources/graph.png"));
+//            viz.engine(Engine.NEATO).render(Format.PNG).toFile(new File("resources/graph.png"));
 //        } catch (IOException e) {
 //            throw new RuntimeException(e);
 //        }
@@ -438,6 +470,9 @@ public class ItineraryOverviewController {
                 if(!node.optString("pos").isEmpty()) {
                     String[] pos = node.getString("pos").split(",");
                     maxHeaght = Math.max(maxHeaght,Double.valueOf(pos[1]) );
+                    if(!positionExist.get()) { // pos calculatedby dot algo
+                        itiFile.setNodePosition(node.getString("id"),+Double.valueOf(pos[0]) / 72.0, Double.valueOf(pos[1]) / 72.0 );
+                    }
                 }
             }
             for (Integer i = 0; i < listGraphNode.length(); i++) {
@@ -445,9 +480,8 @@ public class ItineraryOverviewController {
                 if(!node.optString("pos").isEmpty()) {
                     String[] pos = node.getString("pos").split(",");
                     String label = node.getString("label").replaceAll("\\\\N", "");
-                    Double posX = Double.valueOf(pos[0]) - 100.0;
-                    Double posY = maxHeaght - (Double.valueOf(pos[1]) - 100.0);
-
+                    Double posX = Double.valueOf(pos[0]);
+                    Double posY = Double.valueOf(pos[1]);
                     if (node.getString("type").equalsIgnoreCase("step")) {
                         Platform.runLater(() -> {
                             String type = node.getString("hat").equalsIgnoreCase("yes") ? "hat" : "step";
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js
index b155d110..d0c5284c 100644
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js
@@ -139,6 +139,7 @@
       })
       .on("end", function(d) {
         thisGraph.MouseUp.call(thisGraph, d3.select(this), d);
+        thisGraph.javaFXNodeDragged(thisGraph.nodes);
       });
 
     // listen for key events
@@ -658,6 +659,10 @@ GraphCreator.prototype.javaFXHighLightNode = function(node) {
     javaConnector.highlightStep(JSON.stringify(node));
   }
 
+GraphCreator.prototype.javaFXNodeDragged = function(nodes) {
+  javaConnector.nodeDragged(JSON.stringify(nodes));
+}
+
 var width = window.innerWidth,
     height = window.innerHeight;
 
-- 
GitLab