From c68799c80da44ca2d03a8f7a4d6b2fb69cb4b295 Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Tue, 27 Aug 2024 10:31:46 +0200
Subject: [PATCH 1/6] first try with v2 (upgrade d3js >= 4)

---
 .../dataView/ItineraryOverviewController.java |   2 +-
 .../po2vocabmanager/graph/graph-creator2.css  | 192 ++++++
 .../po2vocabmanager/graph/graph-creator2.js   | 648 ++++++++++++++++++
 .../fr/inra/po2vocabmanager/graph/graph2.html |  32 +
 4 files changed, 873 insertions(+), 1 deletion(-)
 create mode 100644 src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css
 create mode 100644 src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
 create mode 100644 src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html

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 33d496e2..92dd67e9 100644
--- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
+++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
@@ -311,7 +311,7 @@ public class ItineraryOverviewController {
 
 
         Platform.runLater(() -> {
-            webEngine.load(MainApp.class.getResource("graph/graph.html").toExternalForm());
+            webEngine.load(MainApp.class.getResource("graph/graph2.html").toExternalForm());
         });
     }
 
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css
new file mode 100644
index 00000000..3e29acbe
--- /dev/null
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2023. INRAE
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+body{
+    margin: 0;
+    padding: 0;
+    overflow:hidden;
+}
+
+p{
+    text-align: center;
+    overflow: overlay;
+    position: relative;
+}
+
+body{
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    background-color: rgb(248, 248, 248)
+}
+
+#toolbox{
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    margin-bottom: 0.5em;
+    margin-left: 1em;
+    border: 2px solid #EEEEEE;
+    border-radius: 5px;
+    padding: 1em;
+    z-index: 5;
+}
+
+#toolbox input{
+    width: 30px;
+    opacity: 0.4;
+}
+#toolbox input:hover{
+    opacity: 1;
+    cursor: pointer;
+}
+
+#hidden-file-upload{
+    display: none;
+}
+
+#download-input{
+    margin: 0 0.5em;
+}
+
+.conceptG text{
+    pointer-events: none;
+}
+
+marker{
+    fill: #333;
+}
+
+g.conceptG[nodeType=cluster] circle {
+    fill: rgba(255, 255, 255, 0);
+    stroke-width: 0px;
+}
+g.conceptG[nodeType=hat][obs=no] circle{
+    fill: #fff;
+    stroke: url(#hat);
+    stroke-width: 6px;
+}
+g.conceptG[nodeType=hat][obs=yes] circle{
+    fill: #fff;
+    stroke: url(#hatobs);
+    stroke-width: 6px;
+}
+
+g.conceptG[nodeType=step][obs=no] circle{
+    fill: #fff;
+    stroke: #000;
+    stroke-width: 6px;
+}
+g.conceptG[nodeType=step][obs=yes] circle{
+    fill: #fff;
+    stroke: url(#stepobs);
+    stroke-width: 6px;
+}
+
+g.conceptG[nodeType=composition] circle {
+    fill: #fff;
+    stroke: #ff4747;
+    stroke-width: 6px
+}
+
+g.conceptG:hover[nodeType][obs] circle{
+    fill: #53aab1;
+}
+
+g.selected[nodeType][obs] circle{
+    fill: #a0a0a0;
+}
+g.selected:hover circle{
+    fill: #ff6464;
+}
+
+
+
+path.link[linkType=hc]{
+    fill: none;
+    stroke: #ffb941;
+    stroke-width: 6px;
+    cursor: default;
+}
+path.link[linkType=ch]{
+    fill: none;
+    stroke: #ffb941;
+    stroke-width: 6px;
+    cursor: default;
+}
+
+path.link[linkType=sh]{
+    fill: none;
+    stroke: #ffb941;
+    stroke-width: 6px;
+    cursor: default;
+}
+
+path.link[linkType=ss]{
+    fill: none;
+    stroke: #000000;
+    stroke-width: 6px;
+    cursor: default;
+}
+path.link[linkType=hs]{
+    fill: none;
+    stroke: #5593ff;
+    /*stroke: url(#linksc);*/
+    stroke-width: 6px;
+    cursor: default;
+}
+path.link[linkType=sc]{
+    fill: none;
+    stroke: #ff6464;
+    /*stroke: url(#linksc);*/
+    stroke-width: 6px;
+    cursor: default;
+}
+path.link[linkType=cs]{
+    fill: none;
+    stroke: #ff6464;
+    /*stroke: url(#linkcs);*/
+    stroke-width: 6px;
+    cursor: default;
+}
+
+path.link[linkType=drag]{
+    fill: none;
+    stroke: #000000;
+    stroke-width: 6px;
+    cursor: default;
+}
+
+path.link:hover{
+    stroke: #53aab1;
+}
+
+g.connect-node circle{
+    fill: #BEFFFF;
+}
+
+path.link.hidden{
+    stroke-width: 0;
+}
+
+path.link.selected {
+    stroke: #a0a0a0;
+}
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
new file mode 100644
index 00000000..3cbe8b32
--- /dev/null
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2023. INRAE
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+// Copied from https://bl.ocks.org/cjrd/6863459
+
+ // define graphcreator object
+  var GraphCreator = function(svg) {
+    var thisGraph = this;
+
+    thisGraph.nodes = [];
+    thisGraph.edges = [];
+
+    thisGraph.state = {
+      selectedNode: null,
+      selectedEdge: null,
+      mouseDownNode: null,
+      mouseOverNode: null,
+      mouseDownLink: null,
+      justDragged: false,
+      justScaleTransGraph: false,
+      lastKeyDown: -1,
+      shiftNodeDrag: false,
+      selectedText: null
+    };
+
+    // define arrow markers for graph links
+    var defs = svg.append('svg:defs');
+    defs.append('svg:marker')
+      .attr('id', 'end-arrow')
+      .attr('viewBox', '0 -5 10 10')
+      .attr('refX', "32")
+      .attr('markerWidth', 3.5)
+      .attr('markerHeight', 3.5)
+      .attr('orient', 'auto')
+      .append('svg:path')
+      .attr('d', 'M0,-5L10,0L0,5');
+
+    // define arrow markers for leading arrow
+    defs.append('svg:marker')
+      .attr('id', 'mark-end-arrow')
+      .attr('viewBox', '0 -5 10 10')
+      .attr('refX', 7)
+      .attr('markerWidth', 3.5)
+      .attr('markerHeight', 3.5)
+      .attr('orient', 'auto')
+      .append('svg:path')
+      .attr('d', 'M0,-5L10,0L0,5');
+
+    var grad = defs.append('svg:linearGradient')
+        .attr('id', 'hat')
+    grad.append("svg:stop")
+        .attr("offset","0%")
+        .attr("stop-color","#000000");
+    grad.append("svg:stop")
+        .attr("offset","100%")
+        .attr("stop-color","#5593ff");
+
+
+    var grad = defs.append('svg:linearGradient')
+        .attr('id', 'hatobs')
+    grad.append("svg:stop")
+        .attr("offset","0%")
+        .attr("stop-color","#000000");
+    grad.append("svg:stop")
+        .attr("offset","30%")
+        .attr("stop-color","#5593ff");
+    grad.append("svg:stop")
+        .attr("offset","100%")
+        .attr("stop-color","#51d13a");
+
+    var grad = defs.append('svg:linearGradient')
+        .attr('id', 'stepobs')
+    grad.append("svg:stop")
+          .attr("offset","0%")
+          .attr("stop-color","#000000");
+    grad.append("svg:stop")
+          .attr("offset","100%")
+          .attr("stop-color","#51d13a");
+
+    var gradsc = defs.append('svg:linearGradient')
+        .attr('id', 'linksc')
+    gradsc.append("svg:stop")
+        .attr("offset","0%")
+        .attr("stop-color","#000000");
+    gradsc.append("svg:stop")
+        .attr("offset","100%")
+        .attr("stop-color","#ff6464");
+
+    var gradcs = defs.append('svg:linearGradient')
+        .attr('id', 'linkcs')
+    gradcs.append("svg:stop")
+        .attr("offset","0%")
+        .attr("stop-color","#ff6464");
+    gradcs.append("svg:stop")
+        .attr("offset","100%")
+        .attr("stop-color","#000000");
+
+    thisGraph.svg = svg;
+    thisGraph.svgG = svg.append("g")
+      .classed(thisGraph.consts.graphClass, true);
+    var svgG = thisGraph.svgG;
+	svgG.attr("width", width)
+        .attr("height", height);
+    // displayed when dragging between nodes
+    thisGraph.dragLine = svgG.append('svg:path')
+      .attr('class', 'link')
+      .attr('linkType', 'drag')
+      .style('marker-end', 'url(#mark-end-arrow)');
+
+    // svg nodes and edges 
+    thisGraph.paths = svgG.append("g").selectAll("g");
+    thisGraph.circles = svgG.append("g").selectAll("g");
+
+    thisGraph.drag = d3.drag()
+      .subject(function(d) {
+        return {
+          x: d.x,
+          y: d.y
+        };
+      })
+      .on("drag", function(args) {
+        thisGraph.state.justDragged = true;
+        thisGraph.dragmove.call(thisGraph, args);
+      })
+      .on("end", function(d) {
+        thisGraph.MouseUp.call(thisGraph, d3.select(this), d);
+      });
+
+    // listen for key events
+    d3.select(window).on("keydown", function() {
+        thisGraph.svgKeyDown.call(thisGraph);
+      })
+      .on("keyup", function() {
+        thisGraph.svgKeyUp.call(thisGraph);
+      });
+    svg.on("mousedown", function(d) {
+      thisGraph.svgMouseDown.call(thisGraph, d);
+    });
+    svg.on("mouseup", function(d) {
+      console.log("mouse up on svg");
+      thisGraph.svgMouseUp.call(thisGraph, d);
+    });
+
+
+    // listen for resize
+    window.onresize = function() {
+      thisGraph.updateWindow(svg);
+    };
+  };
+
+  GraphCreator.prototype.consts = {
+    selectedClass: "selected",
+    connectClass: "connect-node",
+    circleGClass: "conceptG",
+    graphClass: "graph",
+    activeEditId: "active-editing",
+    BACKSPACE_KEY: 8,
+    DELETE_KEY: 46,
+    ENTER_KEY: 13,
+    nodeRadius: 50
+  };
+
+  /* PROTOTYPE FUNCTIONS */
+
+  GraphCreator.prototype.dragmove = function(d) {
+    var thisGraph = this;
+    if (thisGraph.state.shiftNodeDrag) {
+      thisGraph.dragLine.attr('d', 'M' + d.x + ',' + d.y + 'L' + d3.mouse(thisGraph.svgG.node())[0] + ',' + d3.mouse(this.svgG.node())[1]);
+    } else {
+      d.x += d3.event.dx;
+      d.y += d3.event.dy;
+      thisGraph.updateGraph();
+    }
+  };
+
+  /* select all text in element: taken from http://stackoverflow.com/questions/6139107/programatically-select-text-in-a-contenteditable-html-element */
+  GraphCreator.prototype.selectElementContents = function(el) {
+    var range = document.createRange();
+    range.selectNodeContents(el);
+    var sel = window.getSelection();
+    sel.removeAllRanges();
+    sel.addRange(range);
+  };
+
+  /* insert svg line breaks: taken from http://stackoverflow.com/questions/13241475/how-do-i-include-newlines-in-labels-in-d3-charts */
+  GraphCreator.prototype.insertTitleLinebreaks = function(gEl, title) {
+    var newtext = title.replace(/([^\n]{1,11})\s/g, '$1!:!');
+    var words = newtext.split(/!:!/g),
+      nwords = words.length;
+    var el = gEl.append("text")
+      .attr("text-anchor", "middle")
+      .attr("dy", "-" + (nwords - 1) * 7.5);
+
+    for (var i = 0; i < words.length; i++) {
+      var tspan = el.append('tspan').text(words[i]);
+      if (i > 0)
+        tspan.attr('x', 0).attr('dy', '15');
+    }
+  };
+
+  // remove edges associated with a node
+  GraphCreator.prototype.spliceLinksForNode = function(node) {
+    var thisGraph = this,
+      toSplice = thisGraph.edges.filter(function(l) {
+        return (l.source === node || l.target === node);
+      });
+    toSplice.map(function(l) {
+      // remove edge only if target && source = step.
+      if(l.source.type === "step" && l.target === "step") {
+        thisGraph.javaFXDelEdge(l);
+        thisGraph.edges.splice(thisGraph.edges.indexOf(l), 1);
+      }
+    });
+  };
+
+  GraphCreator.prototype.replaceSelectEdge = function(d3Path, edgeData) {
+    var thisGraph = this;
+    d3Path.classed(thisGraph.consts.selectedClass, true);
+    if (thisGraph.state.selectedEdge) {
+      thisGraph.removeSelectFromEdge();
+    }
+    thisGraph.state.selectedEdge = edgeData;
+  };
+
+  GraphCreator.prototype.replaceSelectNode = function(d3Node, nodeData) {
+    var thisGraph = this;
+    d3Node.classed(this.consts.selectedClass, true);
+    if (thisGraph.state.selectedNode) {
+      thisGraph.removeSelectFromNode();
+    }
+    thisGraph.state.selectedNode = nodeData;
+    thisGraph.javaFXHighLightNode(nodeData);
+  };
+
+  GraphCreator.prototype.removeSelectFromNode = function() {
+    var thisGraph = this;
+    thisGraph.circles.filter(function(cd) {
+      return cd.id === thisGraph.state.selectedNode.id;
+    }).classed(thisGraph.consts.selectedClass, false);
+    thisGraph.state.selectedNode = null;
+  };
+
+  GraphCreator.prototype.removeSelectFromEdge = function() {
+    var thisGraph = this;
+    thisGraph.paths.filter(function(cd) {
+      return cd === thisGraph.state.selectedEdge;
+    }).classed(thisGraph.consts.selectedClass, false);
+    thisGraph.state.selectedEdge = null;
+  };
+
+  GraphCreator.prototype.pathMouseDown = function(d3path, d) {
+    var thisGraph = this,
+      state = thisGraph.state;
+    d3.event.stopPropagation();
+    state.mouseDownLink = d;
+
+    if (state.selectedNode) {
+      thisGraph.removeSelectFromNode();
+    }
+
+    var prevEdge = state.selectedEdge;
+    if (!prevEdge || prevEdge !== d) {
+      thisGraph.replaceSelectEdge(d3path, d);
+    } else {
+      thisGraph.removeSelectFromEdge();
+    }
+  };
+
+  // mousedown on node
+  GraphCreator.prototype.circleMouseDown = function(d3node, d) {
+    var thisGraph = this,
+      state = thisGraph.state;
+    d3.event.stopPropagation();
+    state.mouseDownNode = d;
+    if (d3.event.shiftKey) {
+      state.shiftNodeDrag = d3.event.shiftKey;
+      // reposition dragged directed edge
+      thisGraph.dragLine.classed('hidden', false)
+        .attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
+      return;
+    }
+  };
+
+  /* place editable text on node in place of svg text */
+
+  // mouseup on nodes
+  GraphCreator.prototype.MouseUp = function(d3node, d) {
+    var thisGraph = this,
+      state = thisGraph.state,
+      consts = thisGraph.consts;
+    // reset the states
+    state.shiftNodeDrag = false;
+    state.mouseDownLink = null;
+
+    d3node.classed(consts.connectClass, false);
+
+    var mouseOverNode = state.mouseOverNode;
+
+    thisGraph.dragLine.classed("hidden", true);
+
+    if (!mouseOverNode) return;
+
+
+    if (mouseOverNode !== d && ((mouseOverNode.type === "step" || d.type === "step") && (mouseOverNode.type !== "hat" && d.type !== "hat"))) {
+      // we're in a different node: create new edge for mousedown edge and add to graph
+      var newEdge = {
+        source: d,
+        target: mouseOverNode
+      };
+      var filtRes = thisGraph.paths.filter(function(d) {
+        if (mouseOverNode.source === newEdge.target && mouseOverNode.target === newEdge.source && thisGraph.javaFXEditable()) {
+          // inversion de sens de l'arc.
+          thisGraph.javaFXDelEdge(mouseOverNode);
+          thisGraph.edges.splice(thisGraph.edges.indexOf(mouseOverNode), 1);
+        }
+        return mouseOverNode.source === newEdge.source && mouseOverNode.target === newEdge.target;
+      });
+      if (!filtRes[0].length && thisGraph.javaFXEditable()) {
+        thisGraph.edges.push(newEdge);
+        thisGraph.updateGraph(false);
+        thisGraph.javaFXAddEdge(newEdge.source, newEdge.target);
+      }
+    } else {
+      // we're in the same node
+      if (state.justDragged) {
+        // dragged, not clicked
+        state.justDragged = false;
+      } else {
+        // clicked, not dragged
+        if (d3.event.shiftKey) {
+        } else {
+          if (state.selectedEdge) {
+            thisGraph.removeSelectFromEdge();
+          }
+          var prevNode = state.selectedNode;
+
+          if (!prevNode || prevNode.id !== d.id) {
+            thisGraph.replaceSelectNode(d3node, mouseOverNode);
+          } else {
+            thisGraph.removeSelectFromNode();
+          }
+        }
+      }
+    }
+    state.mouseDownNode = null;
+    return;
+
+  }; // end of circles mouseup
+
+  // mousedown on main svg
+  GraphCreator.prototype.svgMouseDown = function() {
+    this.state.graphMouseDown = true;
+  };
+
+  // mouseup on main svg
+  GraphCreator.prototype.svgMouseUp = function() {
+    var thisGraph = this,
+      state = thisGraph.state;
+    if (state.justScaleTransGraph) {
+      // dragged not clicked
+      state.justScaleTransGraph = false;
+    } else if (state.graphMouseDown && d3.event.shiftKey) {
+
+    } else if (state.shiftNodeDrag) {
+      // dragged from node
+      state.shiftNodeDrag = false;
+      thisGraph.dragLine.classed("hidden", true);
+    }
+    state.graphMouseDown = false;
+  };
+
+  // keydown on main svg
+  GraphCreator.prototype.svgKeyDown = function() {
+
+    var thisGraph = this,
+      state = thisGraph.state,
+      consts = thisGraph.consts;
+    // make sure repeated key presses don't register for each keydown
+    if (state.lastKeyDown !== -1) return;
+
+    state.lastKeyDown = d3.event.keyCode;
+    var selectedNode = state.selectedNode,
+      selectedEdge = state.selectedEdge;
+
+    switch (d3.event.keyCode) {
+      case consts.BACKSPACE_KEY:
+      case consts.DELETE_KEY:
+
+        d3.event.preventDefault();
+        if (selectedNode && selectedNode.type === "composition" && thisGraph.javaFXEditable()) {
+ 	  alert("Composition can't be removed here");
+	  thisGraph.svgKeyUp(); // force keyup because event is blocked by alert !
+	} else if(selectedNode && selectedNode.type === "hat" && thisGraph.javaFXEditable()) {
+          alert("Box step can't be removed here");
+          thisGraph.svgKeyUp(); // force keyup because event is blocked by alert !
+        } else if (selectedNode && selectedNode.type === "step" && thisGraph.javaFXEditable()) {
+          thisGraph.javaFXDelNode(selectedNode);
+          thisGraph.nodes.splice(thisGraph.nodes.indexOf(selectedNode), 1);
+          thisGraph.spliceLinksForNode(selectedNode);
+          state.selectedNode = null;
+          thisGraph.updateGraph(false);
+        } else if(selectedEdge && selectedEdge.source.type === "hat" && selectedEdge.target.type === "step") {
+          alert("Substep link can't be removed here");
+          thisGraph.svgKeyUp();
+        } else if (selectedEdge && thisGraph.javaFXEditable()) {
+        thisGraph.javaFXDelEdge(selectedEdge);
+        thisGraph.edges.splice(thisGraph.edges.indexOf(selectedEdge), 1);
+        state.selectedEdge = null;
+        thisGraph.updateGraph(false);
+      }
+        break;
+    }
+  };
+
+  GraphCreator.prototype.svgKeyUp = function() {
+    this.state.lastKeyDown = -1;
+  };
+
+  // call to propagate changes to graph
+  GraphCreator.prototype.updateGraph = function() {
+
+    var thisGraph = this,
+      consts = thisGraph.consts,
+      state = thisGraph.state;
+
+
+    thisGraph.paths = thisGraph.paths.data(thisGraph.edges, d => String(d.source.id) + "+" + String(d.target.id)).join(
+        enter => enter.append("path")
+            .style('marker-end', 'url(#end-arrow)')
+            .classed("link", true)
+            .attr("linkType", function(d) {
+              return d.source.type[0]+d.target.type[0];
+            })
+            .attr("d", function(d) {
+              return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
+            })
+            .on("mousedown", function(d) {
+              thisGraph.pathMouseDown.call(thisGraph, d3.select(this), d);
+            }),
+        update => update.style('marker-end', 'url(#end-arrow)')
+            .classed(consts.selectedClass, function(d) {
+              return d === state.selectedEdge;
+            })
+            .attr("d", function(d) {
+              return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
+            }),
+        exit => exit.remove()
+    );
+
+    // update nodes data
+    thisGraph.circles = thisGraph.circles.data(thisGraph.nodes, d => d.id).join(
+        enter => {
+          let gg = enter.append("g")
+            .classed(consts.circleGClass, true)
+              .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
+                .attr("nodeType", d => d.type)
+                  .attr("obs", d => d.obs)
+                  .on("mouseover", function(d) {
+                    state.mouseOverNode = d;
+                    if (state.shiftNodeDrag) {
+                      d3.select(this).classed(consts.connectClass, true);
+                    }
+                  })
+                  .on("mouseout", function(d) {
+                    state.mouseOverNode = null;
+                    d3.select(this).classed(consts.connectClass, false);
+                  })
+                  .on("mousedown", function(d) {
+                    thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
+                  })
+                  .call(thisGraph.drag)
+
+          gg.append("circle")
+            .attr("r", String(consts.nodeRadius));
+          gg.each(function(d) {
+            thisGraph.insertTitleLinebreaks(d3.select(this), d.title);
+          });
+          return gg;
+        },
+        update => update.attr("transform", d => "translate(" + d.x + "," + d.y + ")"),
+        exit => exit.remove()
+    );
+
+    var bbox = thisGraph.svgG.node().getBBox();
+    thisGraph.svg.attr("viewBox", [bbox.x-3, bbox.y-3, bbox.width+6, bbox.height+6]);
+  };
+
+  GraphCreator.prototype.updateWindow = function(svg) {
+    var x = window.innerWidth ;
+    var y = window.innerHeight;
+    svg.attr("width", x).attr("height", y);
+  };
+
+
+
+  // initial node data
+
+
+GraphCreator.prototype.addNodeNoUpdate = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
+  var thisGraph = this;
+  var txt = nodeName;
+  // if (obsNb > 0) {
+  //   txt  = txt + "\n "+obsNb + " obs";
+  // }
+  var obs = 'no';
+  if(obsNb > 0) {
+    obs = 'yes';
+  }
+  var d = {
+    id: nodeId,
+    title: txt,
+    obs: obs,
+    x: posX,
+    y: posY,
+    type: nodeType
+  };
+  thisGraph.nodes.push(d);
+};
+
+GraphCreator.prototype.clear = function() {
+  var thisGraph = this;
+  thisGraph.nodes.splice(0,thisGraph.nodes.length)
+  thisGraph.edges.splice(0,thisGraph.edges.length)
+  thisGraph.updateGraph();
+};
+
+GraphCreator.prototype.addNode = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
+    var thisGraph = this;
+    var txt = nodeName;
+    // if (obsNb > 0) {
+    //   txt  = txt + "\n "+obsNb + " obs";
+    // }
+    var obs = 'no';
+    if(obsNb > 0) {
+      obs = 'yes';
+    }
+    var d = {
+      id: nodeId,
+      title: txt,
+      obs: obs,
+      x: posX,
+      y: posY,
+      type: nodeType
+    };
+    thisGraph.nodes.push(d);
+    thisGraph.updateGraph();
+    return "node added " + nodeId;
+  };
+
+GraphCreator.prototype.addEdgeNoUpdate = function(nodeSrc, nodeTgt) {
+  var thisGraph = this;
+  var d = {
+    source: thisGraph.nodes[nodeSrc],
+    target: thisGraph.nodes[nodeTgt]
+  };
+  thisGraph.edges.push(d);
+};
+
+GraphCreator.prototype.addEdge = function(nodeSrc, nodeTgt) {
+    var thisGraph = this; 
+    var d = {
+      source: thisGraph.nodes[nodeSrc],
+      target: thisGraph.nodes[nodeTgt]
+    };
+    thisGraph.edges.push(d);
+    thisGraph.updateGraph(false);
+  };
+
+GraphCreator.prototype.javaFXEditable = function() {
+    	var edit = javaConnector.checkEdition();
+	if(!edit) {
+		alert("Not in edition mode");
+		graph.svgKeyUp(); // force keyup because event is blocked by alert !
+	}
+	return edit;
+  }
+
+GraphCreator.prototype.javaFXAddEdge = function(source, target) {
+    javaConnector.addEdge(JSON.stringify(source), JSON.stringify(target));
+  }
+
+GraphCreator.prototype.javaFXDelEdge = function(edge) {
+    javaConnector.delEdge(JSON.stringify(edge.source), JSON.stringify(edge.target));
+  }
+
+GraphCreator.prototype.javaFXDelNode = function(node){
+    javaConnector.delNode(JSON.stringify(node));
+  }
+
+GraphCreator.prototype.javaFXHighLightNode = function(node) {
+    javaConnector.highlightStep(JSON.stringify(node));
+  }
+
+var width = window.innerWidth,
+    height = window.innerHeight;
+
+
+var svg = d3.select("#graph-div").append("svg")
+    .attr("width", width)
+    .attr("height", height)
+    .attr("viewBox", [0, 0, width, height]);
+
+var graph = new GraphCreator(svg);
+graph.addNodeNoUpdate('kneading (fabrication pate)',444,'step', 1,1066.9,244.0);
+graph.addNodeNoUpdate('assembling (préparation pizza)',445,'step',1, 477.92999999999995,532.0);
+graph.addNodeNoUpdate('cooking (cuisson pizza)',447,'step', 1,477.92999999999995,820.0);
+graph.addNodeNoUpdate('pizza cuite',454,'composition',0, 477.92999999999995,964.0);
+graph.addNodeNoUpdate('pizza crue',453,'composition',0, 600.93,676.0);
+graph.addNodeNoUpdate('pate 1',449,'composition', 0,1066.9,388.0);
+graph.addNodeNoUpdate('pate 2',450,'composition', 0,1265.9,388.0);
+graph.addNodeNoUpdate('mixing (fabrication sauce)',446,'step',0, 12.930000000000007,244.0);
+graph.addNodeNoUpdate('sauce tomate',452,'composition',0, 140.93,388.0);
+graph.addNodeNoUpdate('mix pate',448,'composition', 0,1066.9,100.0);
+graph.addNodeNoUpdate('mix sauce',451,'composition',0, 12.930000000000007,100.0);
+graph.addNodeNoUpdate('basilic',455,'composition',0, 367.93,388.0);
+graph.addNodeNoUpdate('mozzarella',456,'composition', 0,587.93,388.0);
+graph.addEdgeNoUpdate(0,1);
+graph.addEdgeNoUpdate(0,5);
+graph.addEdgeNoUpdate(0,6);
+graph.addEdgeNoUpdate(1,2);
+graph.addEdgeNoUpdate(1,4);
+graph.addEdgeNoUpdate(2,3);
+graph.addEdgeNoUpdate(4,2);
+graph.addEdgeNoUpdate(5,1);
+graph.addEdgeNoUpdate(7,1);
+graph.addEdgeNoUpdate(7,8);
+graph.addEdgeNoUpdate(8,1);
+graph.addEdgeNoUpdate(9,0);
+graph.addEdgeNoUpdate(10,7);
+graph.addEdgeNoUpdate(11,1);
+graph.addEdgeNoUpdate(12,1);
+
+graph.updateGraph();
\ No newline at end of file
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html b/src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html
new file mode 100644
index 00000000..7ad6be4a
--- /dev/null
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (c) 2023. INRAE
+  ~ 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.
+  ~
+  ~ SPDX-License-Identifier: MIT
+  -->
+
+<html>
+    <head>
+        <link href="graph-creator2.css" rel="stylesheet" />
+
+        <script charset="utf-8" src="http://d3js.org/d3.v5.min.js"></script>
+
+    </head>
+    <body>
+        <div id="graph-div">
+
+        </div>
+        <script src="graph-creator2.js"></script>
+    </body>
+</html>
-- 
GitLab


From 8f6d044827cf0776409c788a7eda9849926a95fa Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Tue, 27 Aug 2024 11:35:41 +0200
Subject: [PATCH 2/6] bug fix on upgrade d3js version

---
 .../po2vocabmanager/graph/graph-creator2.js   | 74 +++++++++----------
 1 file changed, 37 insertions(+), 37 deletions(-)

diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
index 3cbe8b32..997162a9 100644
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
@@ -323,14 +323,14 @@
         target: mouseOverNode
       };
       var filtRes = thisGraph.paths.filter(function(d) {
-        if (mouseOverNode.source === newEdge.target && mouseOverNode.target === newEdge.source && thisGraph.javaFXEditable()) {
+        if (d.source === newEdge.target && d.target === newEdge.source && thisGraph.javaFXEditable()) {
           // inversion de sens de l'arc.
-          thisGraph.javaFXDelEdge(mouseOverNode);
-          thisGraph.edges.splice(thisGraph.edges.indexOf(mouseOverNode), 1);
+          thisGraph.javaFXDelEdge(d);
+          thisGraph.edges.splice(thisGraph.edges.indexOf(d), 1);
         }
-        return mouseOverNode.source === newEdge.source && mouseOverNode.target === newEdge.target;
+        return d.source === newEdge.source && d.target === newEdge.target;
       });
-      if (!filtRes[0].length && thisGraph.javaFXEditable()) {
+      if (!filtRes.size() && thisGraph.javaFXEditable()) {
         thisGraph.edges.push(newEdge);
         thisGraph.updateGraph(false);
         thisGraph.javaFXAddEdge(newEdge.source, newEdge.target);
@@ -349,7 +349,7 @@
           }
           var prevNode = state.selectedNode;
 
-          if (!prevNode || prevNode.id !== d.id) {
+          if (!prevNode || prevNode.id !== mouseOverNode.id) {
             thisGraph.replaceSelectNode(d3node, mouseOverNode);
           } else {
             thisGraph.removeSelectFromNode();
@@ -360,7 +360,7 @@
     state.mouseDownNode = null;
     return;
 
-  }; // end of circles mouseup
+  }; // end of mouseup
 
   // mousedown on main svg
   GraphCreator.prototype.svgMouseDown = function() {
@@ -616,33 +616,33 @@ var svg = d3.select("#graph-div").append("svg")
     .attr("viewBox", [0, 0, width, height]);
 
 var graph = new GraphCreator(svg);
-graph.addNodeNoUpdate('kneading (fabrication pate)',444,'step', 1,1066.9,244.0);
-graph.addNodeNoUpdate('assembling (préparation pizza)',445,'step',1, 477.92999999999995,532.0);
-graph.addNodeNoUpdate('cooking (cuisson pizza)',447,'step', 1,477.92999999999995,820.0);
-graph.addNodeNoUpdate('pizza cuite',454,'composition',0, 477.92999999999995,964.0);
-graph.addNodeNoUpdate('pizza crue',453,'composition',0, 600.93,676.0);
-graph.addNodeNoUpdate('pate 1',449,'composition', 0,1066.9,388.0);
-graph.addNodeNoUpdate('pate 2',450,'composition', 0,1265.9,388.0);
-graph.addNodeNoUpdate('mixing (fabrication sauce)',446,'step',0, 12.930000000000007,244.0);
-graph.addNodeNoUpdate('sauce tomate',452,'composition',0, 140.93,388.0);
-graph.addNodeNoUpdate('mix pate',448,'composition', 0,1066.9,100.0);
-graph.addNodeNoUpdate('mix sauce',451,'composition',0, 12.930000000000007,100.0);
-graph.addNodeNoUpdate('basilic',455,'composition',0, 367.93,388.0);
-graph.addNodeNoUpdate('mozzarella',456,'composition', 0,587.93,388.0);
-graph.addEdgeNoUpdate(0,1);
-graph.addEdgeNoUpdate(0,5);
-graph.addEdgeNoUpdate(0,6);
-graph.addEdgeNoUpdate(1,2);
-graph.addEdgeNoUpdate(1,4);
-graph.addEdgeNoUpdate(2,3);
-graph.addEdgeNoUpdate(4,2);
-graph.addEdgeNoUpdate(5,1);
-graph.addEdgeNoUpdate(7,1);
-graph.addEdgeNoUpdate(7,8);
-graph.addEdgeNoUpdate(8,1);
-graph.addEdgeNoUpdate(9,0);
-graph.addEdgeNoUpdate(10,7);
-graph.addEdgeNoUpdate(11,1);
-graph.addEdgeNoUpdate(12,1);
-
-graph.updateGraph();
\ No newline at end of file
+// graph.addNodeNoUpdate('kneading (fabrication pate)',444,'step', 1,1066.9,244.0);
+// graph.addNodeNoUpdate('assembling (préparation pizza)',445,'step',1, 477.92999999999995,532.0);
+// graph.addNodeNoUpdate('cooking (cuisson pizza)',447,'step', 1,477.92999999999995,820.0);
+// graph.addNodeNoUpdate('pizza cuite',454,'composition',0, 477.92999999999995,964.0);
+// graph.addNodeNoUpdate('pizza crue',453,'composition',0, 600.93,676.0);
+// graph.addNodeNoUpdate('pate 1',449,'composition', 0,1066.9,388.0);
+// graph.addNodeNoUpdate('pate 2',450,'composition', 0,1265.9,388.0);
+// graph.addNodeNoUpdate('mixing (fabrication sauce)',446,'step',0, 12.930000000000007,244.0);
+// graph.addNodeNoUpdate('sauce tomate',452,'composition',0, 140.93,388.0);
+// graph.addNodeNoUpdate('mix pate',448,'composition', 0,1066.9,100.0);
+// graph.addNodeNoUpdate('mix sauce',451,'composition',0, 12.930000000000007,100.0);
+// graph.addNodeNoUpdate('basilic',455,'composition',0, 367.93,388.0);
+// graph.addNodeNoUpdate('mozzarella',456,'composition', 0,587.93,388.0);
+// graph.addEdgeNoUpdate(0,1);
+// graph.addEdgeNoUpdate(0,5);
+// graph.addEdgeNoUpdate(0,6);
+// graph.addEdgeNoUpdate(1,2);
+// graph.addEdgeNoUpdate(1,4);
+// graph.addEdgeNoUpdate(2,3);
+// graph.addEdgeNoUpdate(4,2);
+// graph.addEdgeNoUpdate(5,1);
+// graph.addEdgeNoUpdate(7,1);
+// graph.addEdgeNoUpdate(7,8);
+// graph.addEdgeNoUpdate(8,1);
+// graph.addEdgeNoUpdate(9,0);
+// graph.addEdgeNoUpdate(10,7);
+// graph.addEdgeNoUpdate(11,1);
+// graph.addEdgeNoUpdate(12,1);
+//
+// graph.updateGraph();
\ No newline at end of file
-- 
GitLab


From 152bb5f0837e5dcd0dd336d73e5ff61bdae2a9ce Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Thu, 29 Aug 2024 11:21:36 +0200
Subject: [PATCH 3/6] allow multiple node with the same name

---
 .../po2vocabmanager/utils/JavaConnector.java  | 42 ++++++++---------
 .../dataView/ItineraryOverviewController.java | 45 ++++++++++---------
 2 files changed, 44 insertions(+), 43 deletions(-)

diff --git a/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java b/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
index b7072356..6e2bfb04 100644
--- a/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
+++ b/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
@@ -29,10 +29,10 @@ import java.util.HashMap;
 public class JavaConnector {
 
     ItineraryFile itiFile;
-    HashMap<Integer, StepFile> listStep;
-    HashMap<Integer, CompositionFile> listComposition;
+    HashMap<String, StepFile> listStep;
+    HashMap<String, CompositionFile> listComposition;
 
-    public JavaConnector(ItineraryFile file, HashMap<Integer, StepFile> listStep,  HashMap<Integer, CompositionFile> listComposition) {
+    public JavaConnector(ItineraryFile file, HashMap<String, StepFile> listStep,  HashMap<String, CompositionFile> listComposition) {
         this.itiFile = file;
         this.listStep = listStep;
         this.listComposition = listComposition;
@@ -53,20 +53,20 @@ public class JavaConnector {
 
         JSONObject oSource = new JSONObject(source);
         JSONObject oTarget = new JSONObject(target);
-        if(listStep.containsKey(oSource.getInt("id"))) { // stepFile ->
-            if(listStep.containsKey(oTarget.getInt("id"))) { // -> stepFile  => enchainement step
-                itiFile.addLinkItinerary(listStep.get(oSource.getInt("id")), listStep.get(oTarget.getInt("id")));
+        if(listStep.containsKey(oSource.getString("id"))) { // stepFile ->
+            if(listStep.containsKey(oTarget.getString("id"))) { // -> stepFile  => enchainement step
+                itiFile.addLinkItinerary(listStep.get(oSource.getString("id")), listStep.get(oTarget.getString("id")));
             } else {
-                if(listComposition.containsKey(oTarget.getInt("id"))) {  // -> compoFile  => compo output
-                    listStep.get(oSource.getInt("id")).addCompositionFile(listComposition.get(oTarget.getInt("id")), false);
+                if(listComposition.containsKey(oTarget.getString("id"))) {  // -> compoFile  => compo output
+                    listStep.get(oSource.getString("id")).addCompositionFile(listComposition.get(oTarget.getString("id")), false);
                 } else {
                     // error
                 }
             }
         } else {
-            if(listComposition.containsKey(oSource.getInt("id"))) { // compoFile ->
-                if(listStep.containsKey(oTarget.getInt("id"))) { // -> stepFile  => compo input
-                    listStep.get(oTarget.getInt("id")).addCompositionFile(listComposition.get(oSource.getInt("id")), true);
+            if(listComposition.containsKey(oSource.getString("id"))) { // compoFile ->
+                if(listStep.containsKey(oTarget.getString("id"))) { // -> stepFile  => compo input
+                    listStep.get(oTarget.getString("id")).addCompositionFile(listComposition.get(oSource.getString("id")), true);
                 } else {
                     // error. Cas impossible
                 }
@@ -80,20 +80,20 @@ public class JavaConnector {
         }
         JSONObject oSource = new JSONObject(source);
         JSONObject oTarget = new JSONObject(target);
-        if(listStep.containsKey(oSource.getInt("id"))) { // stepFile ->
-            if(listStep.containsKey(oTarget.getInt("id"))) { // -> stepFile  => enchainement step
-                itiFile.removeLinkItinerary(listStep.get(oSource.getInt("id")),listStep.get(oTarget.getInt("id")) );
+        if(listStep.containsKey(oSource.getString("id"))) { // stepFile ->
+            if(listStep.containsKey(oTarget.getString("id"))) { // -> stepFile  => enchainement step
+                itiFile.removeLinkItinerary(listStep.get(oSource.getString("id")),listStep.get(oTarget.getString("id")) );
             } else {
-                if(listComposition.containsKey(oTarget.getInt("id"))) {  // -> compoFile
-                    listStep.get(oSource.getInt("id")).removeCompositionFile(listComposition.get(oTarget.getInt("id")));
+                if(listComposition.containsKey(oTarget.getString("id"))) {  // -> compoFile
+                    listStep.get(oSource.getString("id")).removeCompositionFile(listComposition.get(oTarget.getString("id")));
                 } else {
                     // error
                 }
             }
         } else {
-            if(listComposition.containsKey(oSource.getInt("id"))) { // compoFile ->
-                if(listStep.containsKey(oTarget.getInt("id"))) { // -> stepFile  => compo input
-                    listStep.get(oTarget.getInt("id")).removeCompositionFile(listComposition.get(oSource.getInt("id")));
+            if(listComposition.containsKey(oSource.getString("id"))) { // compoFile ->
+                if(listStep.containsKey(oTarget.getString("id"))) { // -> stepFile  => compo input
+                    listStep.get(oTarget.getString("id")).removeCompositionFile(listComposition.get(oSource.getString("id")));
                 } else {
                     // error. Cas impossible
                 }
@@ -106,7 +106,7 @@ public class JavaConnector {
             return;
         }
         JSONObject oStep = new JSONObject(node);
-        StepFile s = listStep.get(oStep.getInt("id"));
+        StepFile s = listStep.get(oStep.getString("id"));
         if(s != null) {
             itiFile.removeStepFromItinerary(s);
             MainApp.getDataControler().buildItineraryTree(itiFile);
@@ -116,7 +116,7 @@ public class JavaConnector {
 
     public void highlightStep(String node) {
         JSONObject oStep = new JSONObject(node);
-        StepFile s = listStep.get(oStep.getInt("id"));
+        StepFile s = listStep.get(oStep.getString("id"));
         if(s != null) {
             MainApp.getDataControler().highlightStep(s, itiFile);
         }
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 92dd67e9..4932cb77 100644
--- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
+++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
@@ -86,8 +86,8 @@ public class ItineraryOverviewController {
     BooleanProperty showAllCompo =  new SimpleBooleanProperty(true);
     BooleanProperty showISCompo =  new SimpleBooleanProperty(false);
     BooleanProperty showNoneCompo = new SimpleBooleanProperty(false);
-    private HashMap<Integer, StepFile> listIdStep;
-    private HashMap<Integer, CompositionFile> listIdComposition;
+    private HashMap<String, StepFile> listIdStep;
+    private HashMap<String, CompositionFile> listIdComposition;
     private JavaConnector jc;
     private ListView<TextField> listProduct;
     private WebView browser;
@@ -315,7 +315,7 @@ public class ItineraryOverviewController {
         });
     }
 
-    private void generateGraph2(WebView webView, ItineraryFile itiFile, String title, HashMap<Integer, StepFile> listIdStep, HashMap<Integer, CompositionFile> listIdComposition) {
+    private void generateGraph2(WebView webView, ItineraryFile itiFile, String title, HashMap<String, StepFile> listIdStep, HashMap<String, CompositionFile> listIdComposition) {
 
 
         ObservableList<Pair<Pair<ComplexField, ObjectProperty<StepFile>>, Pair<ComplexField, ObjectProperty<StepFile>>>> itinerary = itiFile.getItinerary();
@@ -333,17 +333,19 @@ public class ItineraryOverviewController {
                 HashMap<CompositionFile, MutableNode> listCompo = new HashMap<>();
                 HashMap<StepFile, MutableGraph> listSubGraph = new HashMap<>();
 
-                final Integer[] nbNode = {444};
+//                final Integer[] nbNode = {444};
                 itiFile.getListStep().filtered(s -> s.getFather() != null).forEach(s -> {
                     StepFile father = s.getFather();
                     if(!listStepNode.containsKey(father)) {
-                        MutableNode ms = Factory.mutNode(father.getOntoType() + " (" + father.getId() + ")");
-                        ms.add("id", nbNode[0]);
+                        MutableNode ms = Factory.mutNode(father.getUuid());
+                        ms.add("id", father.getUuid());
+                        ms.add(guru.nidi.graphviz.attribute.Label.of(father.getId()));
                         ms.add("observation", father.getObservationFiles().size());
                         ms.add("hat", "yes");
-                        listIdStep.put(nbNode[0], father);
+                        ms.add("type", "step");
+                        listIdStep.put(father.getUuid(), father);
 
-                        MutableGraph cluster = Factory.mutGraph(nbNode[0].toString());
+                        MutableGraph cluster = Factory.mutGraph(father.getUuid());
                         cluster.setDirected(true);
                         cluster.graphAttrs().add(Rank.dir(LEFT_TO_RIGHT));
                         cluster.setCluster(false);
@@ -351,18 +353,18 @@ public class ItineraryOverviewController {
                         cluster.addTo(g);
                         listSubGraph.put(father, cluster);
                         listStepNode.put(father, ms);
-                        nbNode[0]++;
                     }
                 });
 
                 for(StepFile s : itiFile.getListStep()) {
                     if(!listIdStep.containsValue(s)) {
-                        MutableNode ms = Factory.mutNode(s.getOntoType() + " (" + s.getId() + ")");
-                        ms.add("id", nbNode[0]);
+                        MutableNode ms = Factory.mutNode(s.getUuid());
+                        ms.add("id", s.getUuid());
+                        ms.add(guru.nidi.graphviz.attribute.Label.of(s.getId()));
                         ms.add("observation", s.getObservationFiles().size());
                         ms.add("hat", "no");
-                        listIdStep.put(nbNode[0],s);
-                        nbNode[0]++;
+                        ms.add("type", "step");
+                        listIdStep.put(s.getUuid(),s);
 
                         listStepNode.put( s, ms);
 
@@ -414,12 +416,12 @@ public class ItineraryOverviewController {
                             MutableNode pc = null;
                             pc = listCompo.get(en.getKey());
                             if (pc == null) {
-                                pc = Factory.mutNode( en.getKey().getFileName());
+                                pc = Factory.mutNode( en.getKey().getUuid());
                                 pc.add(guru.nidi.graphviz.attribute.Label.of(en.getKey().getCompositionID().getValue().get()));
                                 pc.add(Color.named("red"));
-                                pc.add("id", nbNode[0]);
-                                listIdComposition.put(nbNode[0], en.getKey());
-                                nbNode[0]++;
+                                pc.add("id", en.getKey().getUuid());
+                                pc.add("type", "composition");
+                                listIdComposition.put(en.getKey().getUuid(), en.getKey());
                                 g.add(pc);
                                 listCompo.put(en.getKey(), pc);
                             }
@@ -459,15 +461,14 @@ public class ItineraryOverviewController {
                     Double posX = Double.valueOf(pos[0]) - 100.0;
                     Double posY = maxHeaght - (Double.valueOf(pos[1]) - 100.0);
 
-                    if (label.isEmpty()) {
+                    if (node.getString("type").equalsIgnoreCase("step")) {
                         Platform.runLater(() -> {
                             String type = node.getString("hat").equalsIgnoreCase("yes") ? "hat" : "step";
-                            webEngine.executeScript("graph.addNodeNoUpdate('" + node.getString("name").replaceAll("'", "\\\\'") + "'," + node.getInt("id") + ",'" + type + "', " + node.getInt("observation") + "," + posX + "," + posY + ");");
+                            webEngine.executeScript("graph.addNodeNoUpdate('" + label.replaceAll("'", "\\\\'") + "','" + node.getString("id") + "','" + type + "', " + node.getInt("observation") + "," + posX + "," + posY + ");");
                         });
                     } else {
                         Platform.runLater(() -> {
-
-                            webEngine.executeScript("graph.addNodeNoUpdate('" + label.replaceAll("'", "\\\\'") + "'," + node.getInt("id") + ",'composition', 0 ," + posX + "," + posY + ");");
+                            webEngine.executeScript("graph.addNodeNoUpdate('" + label.replaceAll("'", "\\\\'") + "','" + node.getString("id") + "','composition', 0 ," + posX + "," + posY + ");");
                         });
                     }
                 } else {
@@ -476,7 +477,7 @@ public class ItineraryOverviewController {
                     Double posY = 0.0;
 
                     Platform.runLater(() -> {
-                        webEngine.executeScript("graph.addNodeNoUpdate(''," + -1 + ",'" + "cluster" + "', " + "0" + "," + posX + "," + posY + ");");
+                        webEngine.executeScript("graph.addNodeNoUpdate('','" + -1 + "','" + "cluster" + "', " + "0" + "," + posX + "," + posY + ");");
                     });
                 }
             }
-- 
GitLab


From f1d89166c516a7ad270c22a6ca13db95ab26f6fa Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Tue, 3 Sep 2024 10:55:36 +0200
Subject: [PATCH 4/6] prevent bug on highligh node when selecting node in graph

---
 .../view/dataView/DataOverviewController.java             | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java b/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java
index 645a2f30..9e07c2b7 100644
--- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java
+++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java
@@ -802,10 +802,14 @@ public class DataOverviewController {
                 Platform.runLater(() -> {
                     Timeline timeline = new Timeline(
                             new KeyFrame(Duration.seconds(0.2), evt -> {
-                                treeItem.getGraphic().getParent().setVisible(false);
+                                if(treeItem.getGraphic().getParent() != null) {
+                                    treeItem.getGraphic().getParent().setVisible(false);
+                                }
                             }),
                             new KeyFrame(Duration.seconds( 0.4), evt -> {
-                                treeItem.getGraphic().getParent().setVisible(true);
+                                if(treeItem.getGraphic().getParent() != null) {
+                                    treeItem.getGraphic().getParent().setVisible(true);
+                                }
                             }));
                     timeline.setCycleCount(10);
                     timeline.play();
-- 
GitLab


From 5b023a2aa86551f7749478a541f6b1d1ece51d38 Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Tue, 3 Sep 2024 10:57:37 +0200
Subject: [PATCH 5/6] rebuild data node itinerary without remove it.

---
 .../po2vocabmanager/utils/JavaConnector.java  |  6 ++++-
 .../view/dataView/DataOverviewController.java | 22 +++++++++++++------
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java b/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
index 6e2bfb04..c42f32ba 100644
--- a/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
+++ b/src/main/java/fr/inra/po2vocabmanager/utils/JavaConnector.java
@@ -19,6 +19,7 @@
 package fr.inra.po2vocabmanager.utils;
 
 import fr.inra.po2vocabmanager.MainApp;
+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;
@@ -110,7 +111,10 @@ public class JavaConnector {
         if(s != null) {
             itiFile.removeStepFromItinerary(s);
             MainApp.getDataControler().buildItineraryTree(itiFile);
-            MainApp.getDataControler().selectNode(itiFile);
+            DataNode itineraryNode = MainApp.getDataControler().getDataNode(itiFile);
+            if(itineraryNode != null) {
+                MainApp.getDataControler().getItineraryControler().showNodeDetails(itineraryNode);
+            }
         }
     }
 
diff --git a/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java b/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java
index 9e07c2b7..8f86f5dc 100644
--- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java
+++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/DataOverviewController.java
@@ -331,19 +331,27 @@ public class DataOverviewController {
 
     }
 
+    public ItineraryOverviewController getItineraryControler() {
+        return this.itineraryControler;
+    }
+
     public void buildItineraryTree(ItineraryFile itiF) {
         DataNode processNode = getDataNode(itiF.getProcessFile());
-        DataNode oldIti = getDataNode(itiF);
-        if(oldIti != null) {
-            MainApp.getDataControler().deleteDataNode(oldIti);
+        DataNode itineraryNode = getDataNode(itiF);
+        if(itineraryNode != null) {
+            List<DataNode> tempList = FXCollections.observableArrayList(itineraryNode.getSubNodeNoSort());
+            for(DataNode allNode : tempList) {
+                MainApp.getDataControler().deleteDataNode(allNode);
+            }
+        } else {
+            itineraryNode = new DataNode(DataNodeType.ITINERARY);
+            addNode(itiF, itineraryNode, processNode);
         }
-        DataNode dataNodeIti = new DataNode(DataNodeType.ITINERARY);
-        addNode(itiF, dataNodeIti, processNode);
+
         // node observation.
         for(ObservationFile obf : itiF.getListObservation()) {
             DataNode dataNodeObs = new DataNode(DataNodeType.OBSERVATION);
-//            dataNodeObs.setItineraryFile(itiF);
-            addNode(obf, dataNodeObs ,dataNodeIti);
+            addNode(obf, dataNodeObs ,itineraryNode);
         }
         // node step (and step obs)
         for(StepFile stepF : itiF.getListStep()) {
-- 
GitLab


From 62e65b53f8d489099f03e72070e7813099282f7c Mon Sep 17 00:00:00 2001
From: stephane <stephane.dervaux@inrae.fr>
Date: Tue, 3 Sep 2024 11:08:24 +0200
Subject: [PATCH 6/6] upgrade graph to d3js version v5 add a minimap Remove
 subgraph management for hat node because link in subgraph were duplicated

---
 .../dataView/ItineraryOverviewController.java |  32 +-
 .../po2vocabmanager/graph/graph-creator.css   |  26 +
 .../po2vocabmanager/graph/graph-creator.js    | 553 +++++----------
 .../po2vocabmanager/graph/graph-creator2.css  | 192 ------
 .../po2vocabmanager/graph/graph-creator2.js   | 648 ------------------
 .../fr/inra/po2vocabmanager/graph/graph.html  |  38 +-
 .../fr/inra/po2vocabmanager/graph/graph2.html |  32 -
 7 files changed, 231 insertions(+), 1290 deletions(-)
 delete mode 100644 src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css
 delete mode 100644 src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
 delete mode 100644 src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html

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 4932cb77..a1e9ed97 100644
--- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
+++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java
@@ -30,7 +30,6 @@ import fr.inrae.po2engine.model.dataModel.StepFile;
 import fr.inrae.po2engine.utils.ProgressPO2;
 import fr.inrae.po2engine.utils.Tools;
 import guru.nidi.graphviz.attribute.Color;
-import guru.nidi.graphviz.attribute.Rank;
 import guru.nidi.graphviz.engine.*;
 import guru.nidi.graphviz.model.Factory;
 import guru.nidi.graphviz.model.MutableGraph;
@@ -70,8 +69,6 @@ import java.io.IOException;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static guru.nidi.graphviz.attribute.Rank.RankDir.LEFT_TO_RIGHT;
-
 public class ItineraryOverviewController {
 
    @FXML
@@ -311,13 +308,12 @@ public class ItineraryOverviewController {
 
 
         Platform.runLater(() -> {
-            webEngine.load(MainApp.class.getResource("graph/graph2.html").toExternalForm());
+            webEngine.load(MainApp.class.getResource("graph/graph.html").toExternalForm());
         });
     }
 
     private void generateGraph2(WebView webView, ItineraryFile itiFile, String title, HashMap<String, StepFile> listIdStep, HashMap<String, CompositionFile> listIdComposition) {
 
-
         ObservableList<Pair<Pair<ComplexField, ObjectProperty<StepFile>>, Pair<ComplexField, ObjectProperty<StepFile>>>> itinerary = itiFile.getItinerary();
             WebEngine webEngine = webView.getEngine();
 
@@ -331,9 +327,7 @@ public class ItineraryOverviewController {
 
                 HashMap<StepFile, MutableNode> listStepNode = new HashMap<>();
                 HashMap<CompositionFile, MutableNode> listCompo = new HashMap<>();
-                HashMap<StepFile, MutableGraph> listSubGraph = new HashMap<>();
 
-//                final Integer[] nbNode = {444};
                 itiFile.getListStep().filtered(s -> s.getFather() != null).forEach(s -> {
                     StepFile father = s.getFather();
                     if(!listStepNode.containsKey(father)) {
@@ -345,13 +339,7 @@ public class ItineraryOverviewController {
                         ms.add("type", "step");
                         listIdStep.put(father.getUuid(), father);
 
-                        MutableGraph cluster = Factory.mutGraph(father.getUuid());
-                        cluster.setDirected(true);
-                        cluster.graphAttrs().add(Rank.dir(LEFT_TO_RIGHT));
-                        cluster.setCluster(false);
-                        cluster.add(ms);
-                        cluster.addTo(g);
-                        listSubGraph.put(father, cluster);
+                        g.add(ms);
                         listStepNode.put(father, ms);
                     }
                 });
@@ -365,14 +353,8 @@ public class ItineraryOverviewController {
                         ms.add("hat", "no");
                         ms.add("type", "step");
                         listIdStep.put(s.getUuid(),s);
-
                         listStepNode.put( s, ms);
-
-                        if(s.getFather() != null) {
-                            listSubGraph.get(s.getFather()).add(ms);
-                        } else {
-                            g.add(ms);
-                        }
+                        g.add(ms);
                     }
                 }
 
@@ -439,6 +421,11 @@ public class ItineraryOverviewController {
         Graphviz.useEngine(listEngine);
         Graphviz viz = Graphviz.fromGraph(g);
         String json = viz.render(Format.JSON).toString();
+//        try {
+//            viz.render(Format.PNG).toFile(new File("resources/graph.png"));
+//        } catch (IOException e) {
+//            throw new RuntimeException(e);
+//        }
 
         JSONObject jsonGraph = new JSONObject(json);
         JSONArray listGraphNode = jsonGraph.optJSONArray("objects");
@@ -493,7 +480,8 @@ public class ItineraryOverviewController {
             }
         }
         Platform.runLater(() -> {
-            webEngine.executeScript("graph.updateGraph(true)");
+            webEngine.executeScript("graph.updateGraph()");
+            webEngine.executeScript("graph.fitWindow()");
         });
     }
 
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.css b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.css
index 1ec8edb7..c7c1a2ec 100644
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.css
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.css
@@ -38,6 +38,32 @@ body{
     background-color: rgb(248, 248, 248)
 }
 
+#viewport {
+    border: 1px solid black;
+
+    /*margin-top: 5px;*/
+    /*margin-left: 5px;*/
+    /*margin-bottom: 5px;*/
+    /*margin-right: 5px;*/
+}
+
+#minimap {
+    position: absolute;
+    right: 10px;
+    bottom: 10px;
+
+    border: 1px solid black;
+    background-color: white;
+}
+
+html, body {
+    height: 100%;
+    width: 100%;
+
+    padding: 0;
+    margin: 0;
+}
+
 #toolbox{
     position: absolute;
     bottom: 0;
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 af469b86..b155d110 100644
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js
@@ -19,17 +19,17 @@
 // Copied from https://bl.ocks.org/cjrd/6863459
 
  // define graphcreator object
-  var GraphCreator = function(svg, nodes, edges) {
+  var GraphCreator = function(svg) {
     var thisGraph = this;
-    thisGraph.idct = 0;
 
-    thisGraph.nodes = nodes || [];
-    thisGraph.edges = edges || [];
+    thisGraph.nodes = [];
+    thisGraph.edges = [];
 
     thisGraph.state = {
       selectedNode: null,
       selectedEdge: null,
       mouseDownNode: null,
+      mouseOverNode: null,
       mouseDownLink: null,
       justDragged: false,
       justScaleTransGraph: false,
@@ -124,10 +124,10 @@
 
     // svg nodes and edges 
     thisGraph.paths = svgG.append("g").selectAll("g");
-    thisGraph.circles = svgG.append("g").selectAll("g");
+    thisGraph.circles = svgG.append("g").attr("id","nodes").selectAll("g");
 
-    thisGraph.drag = d3.behavior.drag()
-      .origin(function(d) {
+    thisGraph.drag = d3.drag()
+      .subject(function(d) {
         return {
           x: d.x,
           y: d.y
@@ -137,8 +137,8 @@
         thisGraph.state.justDragged = true;
         thisGraph.dragmove.call(thisGraph, args);
       })
-      .on("dragend", function() {
-        // todo check if edge-mode is selected
+      .on("end", function(d) {
+        thisGraph.MouseUp.call(thisGraph, d3.select(this), d);
       });
 
     // listen for key events
@@ -152,98 +152,41 @@
       thisGraph.svgMouseDown.call(thisGraph, d);
     });
     svg.on("mouseup", function(d) {
+      console.log("mouse up on svg");
       thisGraph.svgMouseUp.call(thisGraph, d);
     });
 
-    // listen for dragging
-    thisGraph.dragSvg = d3.behavior.zoom()
-      .on("zoom", function() {
-        if (d3.event.sourceEvent.shiftKey) {
-          // TODO  the internal d3 state is still changing
-          return false;
-        } else {
-          thisGraph.zoomed.call(thisGraph);
-        }
-        return true;
-      })
-      .on("zoomstart", function() {
-        var ael = d3.select("#" + thisGraph.consts.activeEditId).node();
-        if (ael) {
-          ael.blur();
-        }
-        if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
-      })
-      .on("zoomend", function() {
-        d3.select('body').style("cursor", "auto");
-      });
-
-    svg.call(thisGraph.dragSvg).on("dblclick.zoom", null);
-
-    // listen for resize
-    window.onresize = function() {
-      thisGraph.updateWindow(svg);
-    };
 
-    // handle download data
-    d3.select("#download-input").on("click", function() {
-      var svgString = getSVGString(graph.svg.node());
-      svgString2Image( svgString, width, height, 'png', save );
-      function save( dataBlob, filesize ){
-        saveAs( dataBlob, 'D3 vis exported to PNG.png' ); // FileSaver.js function
-      }
-    });
+    let transform = d3.zoomIdentity.translate(0, 0).scale(1);
+    thisGraph.zoom = d3.zoom()
+        // .scaleExtent([1, 100])
+        .on('zoom', zoomed);
 
-    // handle uploaded data
-    d3.select("#upload-input").on("click", function() {
-      document.getElementById("hidden-file-upload").click();
-    });
-    d3.select("#hidden-file-upload").on("change", function() {
-      if (window.File && window.FileReader && window.FileList && window.Blob) {
-        var uploadFile = this.files[0];
-        var filereader = new window.FileReader();
-
-        filereader.onload = function() {
-          var txtRes = filereader.result;
-          // TODO better error handling
-          try {
-            var jsonObj = JSON.parse(txtRes);
-            thisGraph.deleteGraph(true);
-            thisGraph.nodes = jsonObj.nodes;
-            thisGraph.setIdCt(jsonObj.nodes.length + 1);
-            var newEdges = jsonObj.edges;
-            newEdges.forEach(function(e, i) {
-              newEdges[i] = {
-                source: thisGraph.nodes.filter(function(n) {
-                  return n.id == e.source;
-                })[0],
-                target: thisGraph.nodes.filter(function(n) {
-                  return n.id == e.target;
-                })[0]
-              };
-            });
-            thisGraph.edges = newEdges;
-            thisGraph.updateGraph(false);
-          } catch (err) {
-            window.alert("Error parsing uploaded file\nerror message: " + err.message);
-            return;
-          }
-        };
-        filereader.readAsText(uploadFile);
+    thisGraph.svg.call(thisGraph.zoom).call(thisGraph.zoom.transform, transform);
+    function zoomed() {
+      let transform = d3.event.transform;
+      let modifiedTransform = d3.zoomIdentity.scale( 1/transform.k ).translate( -transform.x - 50, -transform.y  + 50);
 
-      } else {
-        alert("Your browser won't let you save this graph -- try upgrading your browser to IE 10+ or Chrome or Firefox.");
-      }
+      let mapMainContainer = thisGraph.svgG
+          .attr('transform', transform);
 
-    });
+      console.log(mapMainContainer.node().getBBox().width)
+      d3.select('#minimapRect')
+          .attr('width', mapMainContainer.node().getBBox().width )
+          .attr('height', mapMainContainer.node().getBBox().height)
+          .attr('stroke', 'red')
+          .attr('stroke-width', 10/modifiedTransform.k )
+          .attr('stroke-dasharray', 10/modifiedTransform.k )
+          .attr('fill', 'none')
+          .attr('transform', modifiedTransform)
 
-    // handle delete graph
-    d3.select("#delete-graph").on("click", function() {
-      thisGraph.deleteGraph(false);
-    });
-  };
+      thisGraph.updateGraph();
+    }
 
-  GraphCreator.prototype.setIdCt = function(idct) {
-    this.idct = idct;
+    // listen for resize
+    window.onresize = function() {
+      thisGraph.updateWindow(svg);
+    };
   };
 
   GraphCreator.prototype.consts = {
@@ -267,20 +210,7 @@
     } else {
       d.x += d3.event.dx;
       d.y += d3.event.dy;
-      thisGraph.updateGraph(false);
-    }
-  };
-
-  GraphCreator.prototype.deleteGraph = function(skipPrompt) {
-    var thisGraph = this,
-      doDelete = true;
-    if (!skipPrompt) {
-      doDelete = window.confirm("Press OK to delete this graph");
-    }
-    if (doDelete) {
-      thisGraph.nodes = [];
-      thisGraph.edges = [];
-      thisGraph.updateGraph(false);
+      thisGraph.updateGraph();
     }
   };
 
@@ -393,65 +323,30 @@
   };
 
   /* place editable text on node in place of svg text */
-  GraphCreator.prototype.changeTextOfNode = function(d3node, d) {
-    var thisGraph = this,
-      consts = thisGraph.consts,
-      htmlEl = d3node.node();
-    d3node.selectAll("text").remove();
-    var nodeBCR = htmlEl.getBoundingClientRect(),
-      curScale = nodeBCR.width / consts.nodeRadius,
-      placePad = 5 * curScale,
-      useHW = curScale > 1 ? nodeBCR.width * 0.71 : consts.nodeRadius * 1.42;
-    // replace with editableconent text
-    var d3txt = thisGraph.svg.selectAll("foreignObject")
-      .data([d])
-      .enter()
-      .append("foreignObject")
-      .attr("x", nodeBCR.left + placePad)
-      .attr("y", nodeBCR.top + placePad)
-      .attr("height", 2 * useHW)
-      .attr("width", useHW)
-      .append("xhtml:p")
-      .attr("id", consts.activeEditId)
-      .attr("contentEditable", "true")
-      .text(d.title)
-      .on("mousedown", function(d) {
-        d3.event.stopPropagation();
-      })
-      .on("keydown", function(d) {
-        d3.event.stopPropagation();
-        if (d3.event.keyCode == consts.ENTER_KEY && !d3.event.shiftKey) {
-          this.blur();
-        }
-      })
-      .on("blur", function(d) {
-        d.title = this.textContent;
-        thisGraph.insertTitleLinebreaks(d3node, d.title);
-        d3.select(this.parentElement).remove();
-      });
-    return d3txt;
-  };
 
   // mouseup on nodes
-  GraphCreator.prototype.circleMouseUp = function(d3node, d) {
+  GraphCreator.prototype.MouseUp = function(d3node, d) {
     var thisGraph = this,
       state = thisGraph.state,
       consts = thisGraph.consts;
     // reset the states
     state.shiftNodeDrag = false;
-    d3node.classed(consts.connectClass, false);
+    state.mouseDownLink = null;
 
-    var mouseDownNode = state.mouseDownNode;
+    d3node.classed(consts.connectClass, false);
 
-    if (!mouseDownNode) return;
+    var mouseOverNode = state.mouseOverNode;
 
     thisGraph.dragLine.classed("hidden", true);
 
-    if (mouseDownNode !== d && ((mouseDownNode.type === "step" || d.type === "step") && (mouseDownNode.type !== "hat" && d.type !== "hat"))) {
+    if (!mouseOverNode) return;
+
+
+    if (mouseOverNode !== d && ((mouseOverNode.type === "step" || d.type === "step") && (mouseOverNode.type !== "hat" && d.type !== "hat"))) {
       // we're in a different node: create new edge for mousedown edge and add to graph
       var newEdge = {
-        source: mouseDownNode,
-        target: d
+        source: d,
+        target: mouseOverNode
       };
       var filtRes = thisGraph.paths.filter(function(d) {
         if (d.source === newEdge.target && d.target === newEdge.source && thisGraph.javaFXEditable()) {
@@ -461,7 +356,7 @@
         }
         return d.source === newEdge.source && d.target === newEdge.target;
       });
-      if (!filtRes[0].length && thisGraph.javaFXEditable()) {
+      if (!filtRes.size() && thisGraph.javaFXEditable()) {
         thisGraph.edges.push(newEdge);
         thisGraph.updateGraph(false);
         thisGraph.javaFXAddEdge(newEdge.source, newEdge.target);
@@ -474,19 +369,14 @@
       } else {
         // clicked, not dragged
         if (d3.event.shiftKey) {
-          // shift-clicked node: edit text content
-          // var d3txt = thisGraph.changeTextOfNode(d3node, d);
-          // var txtNode = d3txt.node();
-          // thisGraph.selectElementContents(txtNode);
-          // txtNode.focus();
         } else {
           if (state.selectedEdge) {
             thisGraph.removeSelectFromEdge();
           }
           var prevNode = state.selectedNode;
 
-          if (!prevNode || prevNode.id !== d.id) {
-            thisGraph.replaceSelectNode(d3node, d);
+          if (!prevNode || prevNode.id !== mouseOverNode.id) {
+            thisGraph.replaceSelectNode(d3node, mouseOverNode);
           } else {
             thisGraph.removeSelectFromNode();
           }
@@ -496,7 +386,7 @@
     state.mouseDownNode = null;
     return;
 
-  }; // end of circles mouseup
+  }; // end of mouseup
 
   // mousedown on main svg
   GraphCreator.prototype.svgMouseDown = function() {
@@ -511,23 +401,7 @@
       // dragged not clicked
       state.justScaleTransGraph = false;
     } else if (state.graphMouseDown && d3.event.shiftKey) {
-      // clicked not dragged from svg
-      //var xycoords = d3.mouse(thisGraph.svgG.node()),
-      //  d = {
-      //    id: thisGraph.idct++,
-      //    title: "new concept",
-      //    x: xycoords[0],
-      //    y: xycoords[1]
-      //  };
-      //thisGraph.nodes.push(d);
-      //thisGraph.updateGraph();
-      // make title of text immediently editable
-      // var d3txt = thisGraph.changeTextOfNode(thisGraph.circles.filter(function(dval) {
-      //     return dval.id === d.id;
-      //   }), d),
-      //  txtNode = d3txt.node();
-      // thisGraph.selectElementContents(txtNode);
-      // txtNode.focus();
+
     } else if (state.shiftNodeDrag) {
       // dragged from node
       state.shiftNodeDrag = false;
@@ -584,178 +458,110 @@
   };
 
   // call to propagate changes to graph
-  GraphCreator.prototype.updateGraph = function(reinit) {
-
+  GraphCreator.prototype.updateGraph = function() {
     var thisGraph = this,
       consts = thisGraph.consts,
       state = thisGraph.state;
 
-    thisGraph.paths = thisGraph.paths.data(thisGraph.edges, function(d) {
-      return String(d.source.id) + "+" + String(d.target.id);
-    });
-    var paths = thisGraph.paths;
-    // update existing paths
-    paths.style('marker-end', 'url(#end-arrow)')
-      .classed(consts.selectedClass, function(d) {
-        return d === state.selectedEdge;
-      })
-      .attr("d", function(d) {
-        return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
-      });
-
-    // add new paths
-    paths.enter()
-      .append("path")
-      .style('marker-end', 'url(#end-arrow)')
-      .classed("link", true)
-      .attr("linkType", function(d) {
-        return d.source.type[0]+d.target.type[0];
-      })
-      .attr("d", function(d) {
-        return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
-      })
-      .on("mousedown", function(d) {
-        thisGraph.pathMouseDown.call(thisGraph, d3.select(this), d);
-      })
-      .on("mouseup", function(d) {
-        state.mouseDownLink = null;
-      });
-
-    // remove old links
-    paths.exit().remove();
-
-    // update existing nodes
-    thisGraph.circles = thisGraph.circles.data(thisGraph.nodes, function(d) {
-      return d.id;
-    });
-    thisGraph.circles.attr("transform", function(d) {
-      return "translate(" + d.x + "," + d.y + ")";
-    });
-
-    // add new nodes
-    var newGs = thisGraph.circles.enter()
-      .append("g");
-
-    newGs.classed(consts.circleGClass, true)
-      .attr("transform", function(d) {
-        return "translate(" + d.x + "," + d.y + ")";
-      })
-      .attr("nodeType", function(d) {
-	return d.type;
-      })
-      .attr("obs", function(d) {
-        return d.obs;
-      })
-      .on("mouseover", function(d) {
-        if (state.shiftNodeDrag) {
-          d3.select(this).classed(consts.connectClass, true);
-        }
-      })
-      .on("mouseout", function(d) {
-        d3.select(this).classed(consts.connectClass, false);
-      })
-      .on("mousedown", function(d) {
-        thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
-      })
-      .on("mouseup", function(d) {
-        thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
-      })
-      .call(thisGraph.drag);
-
-    newGs.append("circle")
-      .attr("r", String(consts.nodeRadius));
-
-    newGs.each(function(d) {
-      thisGraph.insertTitleLinebreaks(d3.select(this), d.title);
-    });
 
-    // remove old nodes
-    thisGraph.circles.exit().remove();
-    if(reinit) {
-      thisGraph.reinit();
+    thisGraph.paths = thisGraph.paths.data(thisGraph.edges, d => String(d.source.id) + "+" + String(d.target.id)).join(
+        enter => enter.append("path")
+            .style('marker-end', 'url(#end-arrow)')
+            .classed("link", true)
+            .attr("linkType", function(d) {
+              return d.source.type[0]+d.target.type[0];
+            })
+            .attr("d", function(d) {
+              return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
+            })
+            .on("mousedown", function(d) {
+              thisGraph.pathMouseDown.call(thisGraph, d3.select(this), d);
+            }),
+        update => update.style('marker-end', 'url(#end-arrow)')
+            .classed(consts.selectedClass, function(d) {
+              return d === state.selectedEdge;
+            })
+            .attr("d", function(d) {
+              return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
+            }),
+        exit => exit.remove()
+    );
+
+    // update nodes data
+    thisGraph.circles = thisGraph.circles.data(thisGraph.nodes, d => d.id).join(
+        enter => {
+          let gg = enter.append("g")
+            .classed(consts.circleGClass, true)
+              .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
+                .attr("nodeType", d => d.type)
+                  .attr("obs", d => d.obs)
+                  .on("mouseover", function(d) {
+                    state.mouseOverNode = d;
+                    if (state.shiftNodeDrag) {
+                      d3.select(this).classed(consts.connectClass, true);
+                    }
+                  })
+                  .on("mouseout", function(d) {
+                    state.mouseOverNode = null;
+                    d3.select(this).classed(consts.connectClass, false);
+                  })
+                  .on("mousedown", function(d) {
+                    thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
+                  })
+                  .call(thisGraph.drag)
+
+          gg.append("circle")
+            .attr("r", String(consts.nodeRadius));
+          gg.each(function(d) {
+            thisGraph.insertTitleLinebreaks(d3.select(this), d.title);
+          });
+          return gg;
+        },
+        update => update.attr("transform", d => "translate(" + d.x + "," + d.y + ")"),
+        exit => exit.remove()
+    );
+
+    // mini map management
+    var oldMiniMap = document.querySelector('#minimap>svg');
+    var cloneRect;
+    if (oldMiniMap != null) { // remove the old minimap if exist
+      cloneRect = d3.select("#minimap>svg>g>rect").node().cloneNode(true);
+      document.querySelector('#minimap').removeChild(oldMiniMap);
     }
-  };
+    // creating a new minimap
+    var newMiniMap = thisGraph.svg.node().cloneNode(true);
+
+    document.querySelector('#minimap').append(newMiniMap);
+    let minimap = d3.select('#minimap').select('svg')
+        .attr('width', window.innerWidth * 0.25).attr("height", window.innerHeight * 0.25);
+    if (cloneRect) {
+      document.querySelector("#minimap>svg>g").append(cloneRect)
+    } else {
+      minimap.select("g").append('rect')
+          .attr('id', 'minimapRect');
+    }
+    // fiting in the box
 
-  GraphCreator.prototype.zoomed = function() {
-    this.state.justScaleTransGraph = true;
-    d3.select("." + this.consts.graphClass)
-      .attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
+    var bboxMiniMap = minimap.node().getBBox();
+    console.log("new bbox " + bboxMiniMap.width)
+    minimap.attr("viewBox", [bboxMiniMap.x-3, bboxMiniMap.y-3, bboxMiniMap.width+6, bboxMiniMap.height+6]);
   };
 
-  GraphCreator.prototype.reinit = function() {
-    this.state.justScaleTransGraph = true;
-
-    var curSc = this.dragSvg.scale();
-    // alert(this.svgG + " - " + this.svgG[0] + " - " + this.svgG[0][0]);
-    var curWidth = this.svgG[0][0].getBoundingClientRect().width / curSc ;
-    var curHeight = this.svgG[0][0].getBoundingClientRect().height / curSc;
-    var availWidth = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
-    var availHeight = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
-
-    var scW = availWidth / curWidth;
-    var scH = availHeight / curHeight;
-
-    var newScale = Math.min(scW, scH);
-
-    this.dragSvg.scale(newScale);
-
-
-    var trW = (availWidth / 2) - ((curWidth * newScale) / 2);
-    trW += 40.0 * newScale ;
-    var trH = (availHeight / 2) - ((curHeight * newScale) / 2);
-    trH -= 25.0 * newScale;
-
-    this.dragSvg.translate([trW,trH]);
-    d3.select("." + this.consts.graphClass)
-      .attr("transform", "translate("+trW+","+trH+") scale("+newScale+")");
+  GraphCreator.prototype.fitWindow = function() {
+    var bbox = this.svgG.node().getBBox();
+    this.svg.attr("viewBox", [bbox.x-3, bbox.y-3, bbox.width+6, bbox.height+6]);
+    this.updateGraph();
   };
 
   GraphCreator.prototype.updateWindow = function(svg) {
-    var docEl = document.documentElement,
-      bodyEl = document.getElementsByTagName('body')[0];
-    var x = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
-    var y = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
+    console.log("update");
+    var x = window.innerWidth ;
+    var y = window.innerHeight;
     svg.attr("width", x).attr("height", y);
+    this.updateGraph();
   };
 
-  /**** MAIN ****/
 
-  // warn the user when leaving
-  window.onbeforeunload = function() {
-    return "Make sure to save your graph locally before leaving :-)";
-  };
-
-  var docEl = document.documentElement,
-    bodyEl = document.getElementsByTagName('body')[0];
-
-  var width = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth,
-    height = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
-
-  var xLoc = width / 2 - 25,
-    yLoc = 100;
-
-  // initial node data
-  var nodes = [
-//  {
-//    title: "new concept",
-//    type: "step",
-//    id: 0,
-//    x: 100,
-//    y: 100
-//  }, {
-//    title: "new concept",
-//    type: "step",
-//    id: 1,
-//    x: 2500,
-//    y: 200
-//  }
-  ];
-  var edges = [
-//  {
-//    source: nodes[1],
-//    target: nodes[0]
-//  }
-  ];
 
 GraphCreator.prototype.addNodeNoUpdate = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
   var thisGraph = this;
@@ -782,10 +588,10 @@ GraphCreator.prototype.clear = function() {
   var thisGraph = this;
   thisGraph.nodes.splice(0,thisGraph.nodes.length)
   thisGraph.edges.splice(0,thisGraph.edges.length)
-  thisGraph.updateGraph(true);
+  thisGraph.updateGraph();
 };
 
-  GraphCreator.prototype.addNode = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
+GraphCreator.prototype.addNode = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
     var thisGraph = this;
     var txt = nodeName;
     // if (obsNb > 0) {
@@ -804,7 +610,7 @@ GraphCreator.prototype.clear = function() {
       type: nodeType
     };
     thisGraph.nodes.push(d);
-    thisGraph.updateGraph(false);
+    thisGraph.updateGraph();
     return "node added " + nodeId;
   };
 
@@ -817,7 +623,7 @@ GraphCreator.prototype.addEdgeNoUpdate = function(nodeSrc, nodeTgt) {
   thisGraph.edges.push(d);
 };
 
-  GraphCreator.prototype.addEdge = function(nodeSrc, nodeTgt) {
+GraphCreator.prototype.addEdge = function(nodeSrc, nodeTgt) {
     var thisGraph = this; 
     var d = {
       source: thisGraph.nodes[nodeSrc],
@@ -827,7 +633,7 @@ GraphCreator.prototype.addEdgeNoUpdate = function(nodeSrc, nodeTgt) {
     thisGraph.updateGraph(false);
   };
 
-  GraphCreator.prototype.javaFXEditable = function() {
+GraphCreator.prototype.javaFXEditable = function() {
     	var edit = javaConnector.checkEdition();
 	if(!edit) {
 		alert("Not in edition mode");
@@ -836,58 +642,63 @@ GraphCreator.prototype.addEdgeNoUpdate = function(nodeSrc, nodeTgt) {
 	return edit;
   }
 
-
-  GraphCreator.prototype.javaFXAddEdge = function(source, target) {
+GraphCreator.prototype.javaFXAddEdge = function(source, target) {
     javaConnector.addEdge(JSON.stringify(source), JSON.stringify(target));
   }
 
-  GraphCreator.prototype.javaFXDelEdge = function(edge) {
+GraphCreator.prototype.javaFXDelEdge = function(edge) {
     javaConnector.delEdge(JSON.stringify(edge.source), JSON.stringify(edge.target));
   }
 
-  GraphCreator.prototype.javaFXDelNode = function(node){
+GraphCreator.prototype.javaFXDelNode = function(node){
     javaConnector.delNode(JSON.stringify(node));
   }
 
-  GraphCreator.prototype.javaFXHighLightNode = function(node) {
+GraphCreator.prototype.javaFXHighLightNode = function(node) {
     javaConnector.highlightStep(JSON.stringify(node));
   }
 
-  /** MAIN SVG **/
-  var svg = d3.select("body").append("svg")
+var width = window.innerWidth,
+    height = window.innerHeight;
+
+
+var viewport = d3.select("#viewport").attr("width", width).attr("height", height);
+
+var svg = d3.select("#graph-div").append("svg")
     .attr("width", width)
     .attr("height", height);
-  var graph = new GraphCreator(svg, nodes, edges);
-
-
-// graph.addNodeNoUpdate('kneading (fabrication pate)',444,'step', 1,1066.9,244.0);
-// graph.addNodeNoUpdate('assembling (préparation pizza)',445,'step',1, 477.92999999999995,532.0);
-// graph.addNodeNoUpdate('cooking (cuisson pizza)',447,'step', 1,477.92999999999995,820.0);
-// graph.addNodeNoUpdate('pizza cuite',454,'composition',0, 477.92999999999995,964.0);
-// graph.addNodeNoUpdate('pizza crue',453,'composition',0, 600.93,676.0);
-// graph.addNodeNoUpdate('pate 1',449,'composition', 0,1066.9,388.0);
-// graph.addNodeNoUpdate('pate 2',450,'composition', 0,1265.9,388.0);
-// graph.addNodeNoUpdate('mixing (fabrication sauce)',446,'step',0, 12.930000000000007,244.0);
-// graph.addNodeNoUpdate('sauce tomate',452,'composition',0, 140.93,388.0);
-// graph.addNodeNoUpdate('mix pate',448,'composition', 0,1066.9,100.0);
-// graph.addNodeNoUpdate('mix sauce',451,'composition',0, 12.930000000000007,100.0);
-// graph.addNodeNoUpdate('basilic',455,'composition',0, 367.93,388.0);
-// graph.addNodeNoUpdate('mozzarella',456,'composition', 0,587.93,388.0);
+    // .attr("viewBox", [0, 0, width, height]);
+
+var graph = new GraphCreator(svg);
+// graph.addNodeNoUpdate('PHBV Characterization','96b0f6b3-a06a-400b-bb75-dbab9d83fa28','step', 0,620.84,532.0);
+// graph.addNodeNoUpdate('Extrusion PHBV-B00(10%)','9b5bf2b9-9908-4c39-a781-0e4227868fc0','step', 2,390.84,820.0);
+// graph.addNodeNoUpdate('Pelletizing+Drying PHBV-B00(10%)','6f9de507-84a6-43df-9281-50dde279317e','step', 2,390.84,1108.0);
+// graph.addNodeNoUpdate('Thermopressin PHBV-B00(10%)','a122f3de-d326-46ef-8976-ab02c3167b51','step', 6,390.84,1396.0);
+// graph.addNodeNoUpdate('PHBV-B00(10%) film','eada0aed-c2c4-4d10-adea-469212a3b109','composition', 0 ,390.84,1540.0);
+// graph.addNodeNoUpdate('PHBV-B00(10%) dried','5fce487c-fd69-49fc-8ebd-426257dff10f','composition', 0 ,545.84,1200.0);
+// graph.addNodeNoUpdate('PHBV-B00(10%)','6a1772e9-9ad7-4db0-a767-480f66ccb2ec','composition', 0 ,531.84,964.0);
+// graph.addNodeNoUpdate('PHBV','316db778-19c5-4cf7-b095-9874d57a92d9','composition', 0 ,737.84,676.0);
+// graph.addNodeNoUpdate('B00 Characterization','3d2e525f-41a7-43ce-859a-da5755bc0b61','step', 2,9.840000000000003,676.0);
+// graph.addNodeNoUpdate('Pelletizing + Drying PHBV-0.5wt%BN 2','3496953d-6f93-45d7-bbdb-e911d37d0b7c','step', 0,620.84,244.0);
+// graph.addNodeNoUpdate('PHBV 2 dried','909ddee4-8972-4896-9cb2-68515e3b50e2','composition', 0 ,620.84,388.0);
+// graph.addNodeNoUpdate('B00','2356e14d-b789-4bc3-b21b-4b8edcc659d3','composition', 0 ,278.84,676.0);
+// graph.addNodeNoUpdate('PHBV 2','29c3c2e6-8be9-4afc-a1cc-6636911b30c0','composition', 0 ,620.84,100.0);
 // graph.addEdgeNoUpdate(0,1);
-// graph.addEdgeNoUpdate(0,5);
-// graph.addEdgeNoUpdate(0,6);
+// graph.addEdgeNoUpdate(0,7);
 // graph.addEdgeNoUpdate(1,2);
-// graph.addEdgeNoUpdate(1,4);
+// graph.addEdgeNoUpdate(1,6);
 // graph.addEdgeNoUpdate(2,3);
-// graph.addEdgeNoUpdate(4,2);
-// graph.addEdgeNoUpdate(5,1);
+// graph.addEdgeNoUpdate(2,5);
+// graph.addEdgeNoUpdate(3,4);
+// graph.addEdgeNoUpdate(5,3);
+// graph.addEdgeNoUpdate(6,2);
 // graph.addEdgeNoUpdate(7,1);
-// graph.addEdgeNoUpdate(7,8);
 // graph.addEdgeNoUpdate(8,1);
-// graph.addEdgeNoUpdate(9,0);
-// graph.addEdgeNoUpdate(10,7);
+// graph.addEdgeNoUpdate(9,10);
+// graph.addEdgeNoUpdate(10,0);
 // graph.addEdgeNoUpdate(11,1);
-// graph.addEdgeNoUpdate(12,1);
+// graph.addEdgeNoUpdate(12,9);
+//
 //
 // graph.updateGraph();
-// graph.reinit();
\ No newline at end of file
+// graph.fitWindow();
\ No newline at end of file
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css
deleted file mode 100644
index 3e29acbe..00000000
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.css
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (c) 2023. INRAE
- * 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.
- *
- * SPDX-License-Identifier: MIT
- */
-
-body{
-    margin: 0;
-    padding: 0;
-    overflow:hidden;
-}
-
-p{
-    text-align: center;
-    overflow: overlay;
-    position: relative;
-}
-
-body{
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-    background-color: rgb(248, 248, 248)
-}
-
-#toolbox{
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    margin-bottom: 0.5em;
-    margin-left: 1em;
-    border: 2px solid #EEEEEE;
-    border-radius: 5px;
-    padding: 1em;
-    z-index: 5;
-}
-
-#toolbox input{
-    width: 30px;
-    opacity: 0.4;
-}
-#toolbox input:hover{
-    opacity: 1;
-    cursor: pointer;
-}
-
-#hidden-file-upload{
-    display: none;
-}
-
-#download-input{
-    margin: 0 0.5em;
-}
-
-.conceptG text{
-    pointer-events: none;
-}
-
-marker{
-    fill: #333;
-}
-
-g.conceptG[nodeType=cluster] circle {
-    fill: rgba(255, 255, 255, 0);
-    stroke-width: 0px;
-}
-g.conceptG[nodeType=hat][obs=no] circle{
-    fill: #fff;
-    stroke: url(#hat);
-    stroke-width: 6px;
-}
-g.conceptG[nodeType=hat][obs=yes] circle{
-    fill: #fff;
-    stroke: url(#hatobs);
-    stroke-width: 6px;
-}
-
-g.conceptG[nodeType=step][obs=no] circle{
-    fill: #fff;
-    stroke: #000;
-    stroke-width: 6px;
-}
-g.conceptG[nodeType=step][obs=yes] circle{
-    fill: #fff;
-    stroke: url(#stepobs);
-    stroke-width: 6px;
-}
-
-g.conceptG[nodeType=composition] circle {
-    fill: #fff;
-    stroke: #ff4747;
-    stroke-width: 6px
-}
-
-g.conceptG:hover[nodeType][obs] circle{
-    fill: #53aab1;
-}
-
-g.selected[nodeType][obs] circle{
-    fill: #a0a0a0;
-}
-g.selected:hover circle{
-    fill: #ff6464;
-}
-
-
-
-path.link[linkType=hc]{
-    fill: none;
-    stroke: #ffb941;
-    stroke-width: 6px;
-    cursor: default;
-}
-path.link[linkType=ch]{
-    fill: none;
-    stroke: #ffb941;
-    stroke-width: 6px;
-    cursor: default;
-}
-
-path.link[linkType=sh]{
-    fill: none;
-    stroke: #ffb941;
-    stroke-width: 6px;
-    cursor: default;
-}
-
-path.link[linkType=ss]{
-    fill: none;
-    stroke: #000000;
-    stroke-width: 6px;
-    cursor: default;
-}
-path.link[linkType=hs]{
-    fill: none;
-    stroke: #5593ff;
-    /*stroke: url(#linksc);*/
-    stroke-width: 6px;
-    cursor: default;
-}
-path.link[linkType=sc]{
-    fill: none;
-    stroke: #ff6464;
-    /*stroke: url(#linksc);*/
-    stroke-width: 6px;
-    cursor: default;
-}
-path.link[linkType=cs]{
-    fill: none;
-    stroke: #ff6464;
-    /*stroke: url(#linkcs);*/
-    stroke-width: 6px;
-    cursor: default;
-}
-
-path.link[linkType=drag]{
-    fill: none;
-    stroke: #000000;
-    stroke-width: 6px;
-    cursor: default;
-}
-
-path.link:hover{
-    stroke: #53aab1;
-}
-
-g.connect-node circle{
-    fill: #BEFFFF;
-}
-
-path.link.hidden{
-    stroke-width: 0;
-}
-
-path.link.selected {
-    stroke: #a0a0a0;
-}
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
deleted file mode 100644
index 997162a9..00000000
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator2.js
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Copyright (c) 2023. INRAE
- * 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.
- *
- * SPDX-License-Identifier: MIT
- */
-
-// Copied from https://bl.ocks.org/cjrd/6863459
-
- // define graphcreator object
-  var GraphCreator = function(svg) {
-    var thisGraph = this;
-
-    thisGraph.nodes = [];
-    thisGraph.edges = [];
-
-    thisGraph.state = {
-      selectedNode: null,
-      selectedEdge: null,
-      mouseDownNode: null,
-      mouseOverNode: null,
-      mouseDownLink: null,
-      justDragged: false,
-      justScaleTransGraph: false,
-      lastKeyDown: -1,
-      shiftNodeDrag: false,
-      selectedText: null
-    };
-
-    // define arrow markers for graph links
-    var defs = svg.append('svg:defs');
-    defs.append('svg:marker')
-      .attr('id', 'end-arrow')
-      .attr('viewBox', '0 -5 10 10')
-      .attr('refX', "32")
-      .attr('markerWidth', 3.5)
-      .attr('markerHeight', 3.5)
-      .attr('orient', 'auto')
-      .append('svg:path')
-      .attr('d', 'M0,-5L10,0L0,5');
-
-    // define arrow markers for leading arrow
-    defs.append('svg:marker')
-      .attr('id', 'mark-end-arrow')
-      .attr('viewBox', '0 -5 10 10')
-      .attr('refX', 7)
-      .attr('markerWidth', 3.5)
-      .attr('markerHeight', 3.5)
-      .attr('orient', 'auto')
-      .append('svg:path')
-      .attr('d', 'M0,-5L10,0L0,5');
-
-    var grad = defs.append('svg:linearGradient')
-        .attr('id', 'hat')
-    grad.append("svg:stop")
-        .attr("offset","0%")
-        .attr("stop-color","#000000");
-    grad.append("svg:stop")
-        .attr("offset","100%")
-        .attr("stop-color","#5593ff");
-
-
-    var grad = defs.append('svg:linearGradient')
-        .attr('id', 'hatobs')
-    grad.append("svg:stop")
-        .attr("offset","0%")
-        .attr("stop-color","#000000");
-    grad.append("svg:stop")
-        .attr("offset","30%")
-        .attr("stop-color","#5593ff");
-    grad.append("svg:stop")
-        .attr("offset","100%")
-        .attr("stop-color","#51d13a");
-
-    var grad = defs.append('svg:linearGradient')
-        .attr('id', 'stepobs')
-    grad.append("svg:stop")
-          .attr("offset","0%")
-          .attr("stop-color","#000000");
-    grad.append("svg:stop")
-          .attr("offset","100%")
-          .attr("stop-color","#51d13a");
-
-    var gradsc = defs.append('svg:linearGradient')
-        .attr('id', 'linksc')
-    gradsc.append("svg:stop")
-        .attr("offset","0%")
-        .attr("stop-color","#000000");
-    gradsc.append("svg:stop")
-        .attr("offset","100%")
-        .attr("stop-color","#ff6464");
-
-    var gradcs = defs.append('svg:linearGradient')
-        .attr('id', 'linkcs')
-    gradcs.append("svg:stop")
-        .attr("offset","0%")
-        .attr("stop-color","#ff6464");
-    gradcs.append("svg:stop")
-        .attr("offset","100%")
-        .attr("stop-color","#000000");
-
-    thisGraph.svg = svg;
-    thisGraph.svgG = svg.append("g")
-      .classed(thisGraph.consts.graphClass, true);
-    var svgG = thisGraph.svgG;
-	svgG.attr("width", width)
-        .attr("height", height);
-    // displayed when dragging between nodes
-    thisGraph.dragLine = svgG.append('svg:path')
-      .attr('class', 'link')
-      .attr('linkType', 'drag')
-      .style('marker-end', 'url(#mark-end-arrow)');
-
-    // svg nodes and edges 
-    thisGraph.paths = svgG.append("g").selectAll("g");
-    thisGraph.circles = svgG.append("g").selectAll("g");
-
-    thisGraph.drag = d3.drag()
-      .subject(function(d) {
-        return {
-          x: d.x,
-          y: d.y
-        };
-      })
-      .on("drag", function(args) {
-        thisGraph.state.justDragged = true;
-        thisGraph.dragmove.call(thisGraph, args);
-      })
-      .on("end", function(d) {
-        thisGraph.MouseUp.call(thisGraph, d3.select(this), d);
-      });
-
-    // listen for key events
-    d3.select(window).on("keydown", function() {
-        thisGraph.svgKeyDown.call(thisGraph);
-      })
-      .on("keyup", function() {
-        thisGraph.svgKeyUp.call(thisGraph);
-      });
-    svg.on("mousedown", function(d) {
-      thisGraph.svgMouseDown.call(thisGraph, d);
-    });
-    svg.on("mouseup", function(d) {
-      console.log("mouse up on svg");
-      thisGraph.svgMouseUp.call(thisGraph, d);
-    });
-
-
-    // listen for resize
-    window.onresize = function() {
-      thisGraph.updateWindow(svg);
-    };
-  };
-
-  GraphCreator.prototype.consts = {
-    selectedClass: "selected",
-    connectClass: "connect-node",
-    circleGClass: "conceptG",
-    graphClass: "graph",
-    activeEditId: "active-editing",
-    BACKSPACE_KEY: 8,
-    DELETE_KEY: 46,
-    ENTER_KEY: 13,
-    nodeRadius: 50
-  };
-
-  /* PROTOTYPE FUNCTIONS */
-
-  GraphCreator.prototype.dragmove = function(d) {
-    var thisGraph = this;
-    if (thisGraph.state.shiftNodeDrag) {
-      thisGraph.dragLine.attr('d', 'M' + d.x + ',' + d.y + 'L' + d3.mouse(thisGraph.svgG.node())[0] + ',' + d3.mouse(this.svgG.node())[1]);
-    } else {
-      d.x += d3.event.dx;
-      d.y += d3.event.dy;
-      thisGraph.updateGraph();
-    }
-  };
-
-  /* select all text in element: taken from http://stackoverflow.com/questions/6139107/programatically-select-text-in-a-contenteditable-html-element */
-  GraphCreator.prototype.selectElementContents = function(el) {
-    var range = document.createRange();
-    range.selectNodeContents(el);
-    var sel = window.getSelection();
-    sel.removeAllRanges();
-    sel.addRange(range);
-  };
-
-  /* insert svg line breaks: taken from http://stackoverflow.com/questions/13241475/how-do-i-include-newlines-in-labels-in-d3-charts */
-  GraphCreator.prototype.insertTitleLinebreaks = function(gEl, title) {
-    var newtext = title.replace(/([^\n]{1,11})\s/g, '$1!:!');
-    var words = newtext.split(/!:!/g),
-      nwords = words.length;
-    var el = gEl.append("text")
-      .attr("text-anchor", "middle")
-      .attr("dy", "-" + (nwords - 1) * 7.5);
-
-    for (var i = 0; i < words.length; i++) {
-      var tspan = el.append('tspan').text(words[i]);
-      if (i > 0)
-        tspan.attr('x', 0).attr('dy', '15');
-    }
-  };
-
-  // remove edges associated with a node
-  GraphCreator.prototype.spliceLinksForNode = function(node) {
-    var thisGraph = this,
-      toSplice = thisGraph.edges.filter(function(l) {
-        return (l.source === node || l.target === node);
-      });
-    toSplice.map(function(l) {
-      // remove edge only if target && source = step.
-      if(l.source.type === "step" && l.target === "step") {
-        thisGraph.javaFXDelEdge(l);
-        thisGraph.edges.splice(thisGraph.edges.indexOf(l), 1);
-      }
-    });
-  };
-
-  GraphCreator.prototype.replaceSelectEdge = function(d3Path, edgeData) {
-    var thisGraph = this;
-    d3Path.classed(thisGraph.consts.selectedClass, true);
-    if (thisGraph.state.selectedEdge) {
-      thisGraph.removeSelectFromEdge();
-    }
-    thisGraph.state.selectedEdge = edgeData;
-  };
-
-  GraphCreator.prototype.replaceSelectNode = function(d3Node, nodeData) {
-    var thisGraph = this;
-    d3Node.classed(this.consts.selectedClass, true);
-    if (thisGraph.state.selectedNode) {
-      thisGraph.removeSelectFromNode();
-    }
-    thisGraph.state.selectedNode = nodeData;
-    thisGraph.javaFXHighLightNode(nodeData);
-  };
-
-  GraphCreator.prototype.removeSelectFromNode = function() {
-    var thisGraph = this;
-    thisGraph.circles.filter(function(cd) {
-      return cd.id === thisGraph.state.selectedNode.id;
-    }).classed(thisGraph.consts.selectedClass, false);
-    thisGraph.state.selectedNode = null;
-  };
-
-  GraphCreator.prototype.removeSelectFromEdge = function() {
-    var thisGraph = this;
-    thisGraph.paths.filter(function(cd) {
-      return cd === thisGraph.state.selectedEdge;
-    }).classed(thisGraph.consts.selectedClass, false);
-    thisGraph.state.selectedEdge = null;
-  };
-
-  GraphCreator.prototype.pathMouseDown = function(d3path, d) {
-    var thisGraph = this,
-      state = thisGraph.state;
-    d3.event.stopPropagation();
-    state.mouseDownLink = d;
-
-    if (state.selectedNode) {
-      thisGraph.removeSelectFromNode();
-    }
-
-    var prevEdge = state.selectedEdge;
-    if (!prevEdge || prevEdge !== d) {
-      thisGraph.replaceSelectEdge(d3path, d);
-    } else {
-      thisGraph.removeSelectFromEdge();
-    }
-  };
-
-  // mousedown on node
-  GraphCreator.prototype.circleMouseDown = function(d3node, d) {
-    var thisGraph = this,
-      state = thisGraph.state;
-    d3.event.stopPropagation();
-    state.mouseDownNode = d;
-    if (d3.event.shiftKey) {
-      state.shiftNodeDrag = d3.event.shiftKey;
-      // reposition dragged directed edge
-      thisGraph.dragLine.classed('hidden', false)
-        .attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
-      return;
-    }
-  };
-
-  /* place editable text on node in place of svg text */
-
-  // mouseup on nodes
-  GraphCreator.prototype.MouseUp = function(d3node, d) {
-    var thisGraph = this,
-      state = thisGraph.state,
-      consts = thisGraph.consts;
-    // reset the states
-    state.shiftNodeDrag = false;
-    state.mouseDownLink = null;
-
-    d3node.classed(consts.connectClass, false);
-
-    var mouseOverNode = state.mouseOverNode;
-
-    thisGraph.dragLine.classed("hidden", true);
-
-    if (!mouseOverNode) return;
-
-
-    if (mouseOverNode !== d && ((mouseOverNode.type === "step" || d.type === "step") && (mouseOverNode.type !== "hat" && d.type !== "hat"))) {
-      // we're in a different node: create new edge for mousedown edge and add to graph
-      var newEdge = {
-        source: d,
-        target: mouseOverNode
-      };
-      var filtRes = thisGraph.paths.filter(function(d) {
-        if (d.source === newEdge.target && d.target === newEdge.source && thisGraph.javaFXEditable()) {
-          // inversion de sens de l'arc.
-          thisGraph.javaFXDelEdge(d);
-          thisGraph.edges.splice(thisGraph.edges.indexOf(d), 1);
-        }
-        return d.source === newEdge.source && d.target === newEdge.target;
-      });
-      if (!filtRes.size() && thisGraph.javaFXEditable()) {
-        thisGraph.edges.push(newEdge);
-        thisGraph.updateGraph(false);
-        thisGraph.javaFXAddEdge(newEdge.source, newEdge.target);
-      }
-    } else {
-      // we're in the same node
-      if (state.justDragged) {
-        // dragged, not clicked
-        state.justDragged = false;
-      } else {
-        // clicked, not dragged
-        if (d3.event.shiftKey) {
-        } else {
-          if (state.selectedEdge) {
-            thisGraph.removeSelectFromEdge();
-          }
-          var prevNode = state.selectedNode;
-
-          if (!prevNode || prevNode.id !== mouseOverNode.id) {
-            thisGraph.replaceSelectNode(d3node, mouseOverNode);
-          } else {
-            thisGraph.removeSelectFromNode();
-          }
-        }
-      }
-    }
-    state.mouseDownNode = null;
-    return;
-
-  }; // end of mouseup
-
-  // mousedown on main svg
-  GraphCreator.prototype.svgMouseDown = function() {
-    this.state.graphMouseDown = true;
-  };
-
-  // mouseup on main svg
-  GraphCreator.prototype.svgMouseUp = function() {
-    var thisGraph = this,
-      state = thisGraph.state;
-    if (state.justScaleTransGraph) {
-      // dragged not clicked
-      state.justScaleTransGraph = false;
-    } else if (state.graphMouseDown && d3.event.shiftKey) {
-
-    } else if (state.shiftNodeDrag) {
-      // dragged from node
-      state.shiftNodeDrag = false;
-      thisGraph.dragLine.classed("hidden", true);
-    }
-    state.graphMouseDown = false;
-  };
-
-  // keydown on main svg
-  GraphCreator.prototype.svgKeyDown = function() {
-
-    var thisGraph = this,
-      state = thisGraph.state,
-      consts = thisGraph.consts;
-    // make sure repeated key presses don't register for each keydown
-    if (state.lastKeyDown !== -1) return;
-
-    state.lastKeyDown = d3.event.keyCode;
-    var selectedNode = state.selectedNode,
-      selectedEdge = state.selectedEdge;
-
-    switch (d3.event.keyCode) {
-      case consts.BACKSPACE_KEY:
-      case consts.DELETE_KEY:
-
-        d3.event.preventDefault();
-        if (selectedNode && selectedNode.type === "composition" && thisGraph.javaFXEditable()) {
- 	  alert("Composition can't be removed here");
-	  thisGraph.svgKeyUp(); // force keyup because event is blocked by alert !
-	} else if(selectedNode && selectedNode.type === "hat" && thisGraph.javaFXEditable()) {
-          alert("Box step can't be removed here");
-          thisGraph.svgKeyUp(); // force keyup because event is blocked by alert !
-        } else if (selectedNode && selectedNode.type === "step" && thisGraph.javaFXEditable()) {
-          thisGraph.javaFXDelNode(selectedNode);
-          thisGraph.nodes.splice(thisGraph.nodes.indexOf(selectedNode), 1);
-          thisGraph.spliceLinksForNode(selectedNode);
-          state.selectedNode = null;
-          thisGraph.updateGraph(false);
-        } else if(selectedEdge && selectedEdge.source.type === "hat" && selectedEdge.target.type === "step") {
-          alert("Substep link can't be removed here");
-          thisGraph.svgKeyUp();
-        } else if (selectedEdge && thisGraph.javaFXEditable()) {
-        thisGraph.javaFXDelEdge(selectedEdge);
-        thisGraph.edges.splice(thisGraph.edges.indexOf(selectedEdge), 1);
-        state.selectedEdge = null;
-        thisGraph.updateGraph(false);
-      }
-        break;
-    }
-  };
-
-  GraphCreator.prototype.svgKeyUp = function() {
-    this.state.lastKeyDown = -1;
-  };
-
-  // call to propagate changes to graph
-  GraphCreator.prototype.updateGraph = function() {
-
-    var thisGraph = this,
-      consts = thisGraph.consts,
-      state = thisGraph.state;
-
-
-    thisGraph.paths = thisGraph.paths.data(thisGraph.edges, d => String(d.source.id) + "+" + String(d.target.id)).join(
-        enter => enter.append("path")
-            .style('marker-end', 'url(#end-arrow)')
-            .classed("link", true)
-            .attr("linkType", function(d) {
-              return d.source.type[0]+d.target.type[0];
-            })
-            .attr("d", function(d) {
-              return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
-            })
-            .on("mousedown", function(d) {
-              thisGraph.pathMouseDown.call(thisGraph, d3.select(this), d);
-            }),
-        update => update.style('marker-end', 'url(#end-arrow)')
-            .classed(consts.selectedClass, function(d) {
-              return d === state.selectedEdge;
-            })
-            .attr("d", function(d) {
-              return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
-            }),
-        exit => exit.remove()
-    );
-
-    // update nodes data
-    thisGraph.circles = thisGraph.circles.data(thisGraph.nodes, d => d.id).join(
-        enter => {
-          let gg = enter.append("g")
-            .classed(consts.circleGClass, true)
-              .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
-                .attr("nodeType", d => d.type)
-                  .attr("obs", d => d.obs)
-                  .on("mouseover", function(d) {
-                    state.mouseOverNode = d;
-                    if (state.shiftNodeDrag) {
-                      d3.select(this).classed(consts.connectClass, true);
-                    }
-                  })
-                  .on("mouseout", function(d) {
-                    state.mouseOverNode = null;
-                    d3.select(this).classed(consts.connectClass, false);
-                  })
-                  .on("mousedown", function(d) {
-                    thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
-                  })
-                  .call(thisGraph.drag)
-
-          gg.append("circle")
-            .attr("r", String(consts.nodeRadius));
-          gg.each(function(d) {
-            thisGraph.insertTitleLinebreaks(d3.select(this), d.title);
-          });
-          return gg;
-        },
-        update => update.attr("transform", d => "translate(" + d.x + "," + d.y + ")"),
-        exit => exit.remove()
-    );
-
-    var bbox = thisGraph.svgG.node().getBBox();
-    thisGraph.svg.attr("viewBox", [bbox.x-3, bbox.y-3, bbox.width+6, bbox.height+6]);
-  };
-
-  GraphCreator.prototype.updateWindow = function(svg) {
-    var x = window.innerWidth ;
-    var y = window.innerHeight;
-    svg.attr("width", x).attr("height", y);
-  };
-
-
-
-  // initial node data
-
-
-GraphCreator.prototype.addNodeNoUpdate = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
-  var thisGraph = this;
-  var txt = nodeName;
-  // if (obsNb > 0) {
-  //   txt  = txt + "\n "+obsNb + " obs";
-  // }
-  var obs = 'no';
-  if(obsNb > 0) {
-    obs = 'yes';
-  }
-  var d = {
-    id: nodeId,
-    title: txt,
-    obs: obs,
-    x: posX,
-    y: posY,
-    type: nodeType
-  };
-  thisGraph.nodes.push(d);
-};
-
-GraphCreator.prototype.clear = function() {
-  var thisGraph = this;
-  thisGraph.nodes.splice(0,thisGraph.nodes.length)
-  thisGraph.edges.splice(0,thisGraph.edges.length)
-  thisGraph.updateGraph();
-};
-
-GraphCreator.prototype.addNode = function(nodeName, nodeId, nodeType,obsNb, posX, posY) {
-    var thisGraph = this;
-    var txt = nodeName;
-    // if (obsNb > 0) {
-    //   txt  = txt + "\n "+obsNb + " obs";
-    // }
-    var obs = 'no';
-    if(obsNb > 0) {
-      obs = 'yes';
-    }
-    var d = {
-      id: nodeId,
-      title: txt,
-      obs: obs,
-      x: posX,
-      y: posY,
-      type: nodeType
-    };
-    thisGraph.nodes.push(d);
-    thisGraph.updateGraph();
-    return "node added " + nodeId;
-  };
-
-GraphCreator.prototype.addEdgeNoUpdate = function(nodeSrc, nodeTgt) {
-  var thisGraph = this;
-  var d = {
-    source: thisGraph.nodes[nodeSrc],
-    target: thisGraph.nodes[nodeTgt]
-  };
-  thisGraph.edges.push(d);
-};
-
-GraphCreator.prototype.addEdge = function(nodeSrc, nodeTgt) {
-    var thisGraph = this; 
-    var d = {
-      source: thisGraph.nodes[nodeSrc],
-      target: thisGraph.nodes[nodeTgt]
-    };
-    thisGraph.edges.push(d);
-    thisGraph.updateGraph(false);
-  };
-
-GraphCreator.prototype.javaFXEditable = function() {
-    	var edit = javaConnector.checkEdition();
-	if(!edit) {
-		alert("Not in edition mode");
-		graph.svgKeyUp(); // force keyup because event is blocked by alert !
-	}
-	return edit;
-  }
-
-GraphCreator.prototype.javaFXAddEdge = function(source, target) {
-    javaConnector.addEdge(JSON.stringify(source), JSON.stringify(target));
-  }
-
-GraphCreator.prototype.javaFXDelEdge = function(edge) {
-    javaConnector.delEdge(JSON.stringify(edge.source), JSON.stringify(edge.target));
-  }
-
-GraphCreator.prototype.javaFXDelNode = function(node){
-    javaConnector.delNode(JSON.stringify(node));
-  }
-
-GraphCreator.prototype.javaFXHighLightNode = function(node) {
-    javaConnector.highlightStep(JSON.stringify(node));
-  }
-
-var width = window.innerWidth,
-    height = window.innerHeight;
-
-
-var svg = d3.select("#graph-div").append("svg")
-    .attr("width", width)
-    .attr("height", height)
-    .attr("viewBox", [0, 0, width, height]);
-
-var graph = new GraphCreator(svg);
-// graph.addNodeNoUpdate('kneading (fabrication pate)',444,'step', 1,1066.9,244.0);
-// graph.addNodeNoUpdate('assembling (préparation pizza)',445,'step',1, 477.92999999999995,532.0);
-// graph.addNodeNoUpdate('cooking (cuisson pizza)',447,'step', 1,477.92999999999995,820.0);
-// graph.addNodeNoUpdate('pizza cuite',454,'composition',0, 477.92999999999995,964.0);
-// graph.addNodeNoUpdate('pizza crue',453,'composition',0, 600.93,676.0);
-// graph.addNodeNoUpdate('pate 1',449,'composition', 0,1066.9,388.0);
-// graph.addNodeNoUpdate('pate 2',450,'composition', 0,1265.9,388.0);
-// graph.addNodeNoUpdate('mixing (fabrication sauce)',446,'step',0, 12.930000000000007,244.0);
-// graph.addNodeNoUpdate('sauce tomate',452,'composition',0, 140.93,388.0);
-// graph.addNodeNoUpdate('mix pate',448,'composition', 0,1066.9,100.0);
-// graph.addNodeNoUpdate('mix sauce',451,'composition',0, 12.930000000000007,100.0);
-// graph.addNodeNoUpdate('basilic',455,'composition',0, 367.93,388.0);
-// graph.addNodeNoUpdate('mozzarella',456,'composition', 0,587.93,388.0);
-// graph.addEdgeNoUpdate(0,1);
-// graph.addEdgeNoUpdate(0,5);
-// graph.addEdgeNoUpdate(0,6);
-// graph.addEdgeNoUpdate(1,2);
-// graph.addEdgeNoUpdate(1,4);
-// graph.addEdgeNoUpdate(2,3);
-// graph.addEdgeNoUpdate(4,2);
-// graph.addEdgeNoUpdate(5,1);
-// graph.addEdgeNoUpdate(7,1);
-// graph.addEdgeNoUpdate(7,8);
-// graph.addEdgeNoUpdate(8,1);
-// graph.addEdgeNoUpdate(9,0);
-// graph.addEdgeNoUpdate(10,7);
-// graph.addEdgeNoUpdate(11,1);
-// graph.addEdgeNoUpdate(12,1);
-//
-// graph.updateGraph();
\ No newline at end of file
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph.html b/src/main/resources/fr/inra/po2vocabmanager/graph/graph.html
index f0be13cf..2cf6d77c 100644
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph.html
+++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph.html
@@ -17,31 +17,19 @@
   -->
 
 <html>
+    <head>
+        <link href="graph-creator.css" rel="stylesheet" />
 
-<head>
-  <link href="graph-creator.css" rel="stylesheet" />
-</head>
-
-<body>
-<!--hello-->
-<!--<script>document.write(window.location.href);</script>-->
-<!--  <div id="toolbox">-->
-<!--    <input type="file" id="hidden-file-upload">-->
-<!--    <input id="upload-input" type="image" title="upload graph" src="upload-icon.png" alt="upload graph">-->
-<!--    <input type="image" id="download-input" title="download graph" src="download-icon.png" alt="download graph">-->
-<!--    <input type="image" id="delete-graph" title="delete graph" src="trash-icon.png" alt="delete graph">-->
-<!--  </div>-->
-
-  <script charset="utf-8" src="d3.v3.min.js"></script>
-  <!-- window.D3 -->
-<!--  <script src="//cdn.jsdelivr.net/filesaver.js/0.1/FileSaver.min.js"></script>-->
-  <!-- window.saveAs window.Blob -->
-  <script src="graph-creator.js"></script>
-  <script src="saveSvgAsPng.js"></script>
-<!--<script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>-->
-
-<!--<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>-->
-
-</body>
+        <script charset="utf-8" src="http://d3js.org/d3.v5.min.js"></script>
 
+    </head>
+    <body>
+    <div id="viewport">
+        <div id="graph-div">
+        </div>
+        <div id="minimap">
+        </div>
+    </div>
+        <script src="graph-creator.js"></script>
+    </body>
 </html>
diff --git a/src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html b/src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html
deleted file mode 100644
index 7ad6be4a..00000000
--- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph2.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
-  ~ Copyright (c) 2023. INRAE
-  ~ 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.
-  ~
-  ~ SPDX-License-Identifier: MIT
-  -->
-
-<html>
-    <head>
-        <link href="graph-creator2.css" rel="stylesheet" />
-
-        <script charset="utf-8" src="http://d3js.org/d3.v5.min.js"></script>
-
-    </head>
-    <body>
-        <div id="graph-div">
-
-        </div>
-        <script src="graph-creator2.js"></script>
-    </body>
-</html>
-- 
GitLab