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 c4f866b891cec69916dbbec20f4f5f9c8d59fe6b..a68f55f00a75326e11ddab8f9a75b038fad82592 100644 --- a/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java +++ b/src/main/java/fr/inra/po2vocabmanager/view/dataView/ItineraryOverviewController.java @@ -30,6 +30,7 @@ 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,6 +71,8 @@ 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 @@ -271,7 +274,7 @@ public class ItineraryOverviewController { prodOfbox.getChildren().addAll(prodLabel, listProduct, graphTips, bbb); reinitScreen.setOnAction(actionEvent -> { - browser.getEngine().reload(); + browser.getEngine().executeScript("graph.reinit()"); }); exportImg.setOnAction(actionEvent -> { @@ -309,7 +312,7 @@ public class ItineraryOverviewController { private void generateGraph2(WebView webView, ItineraryFile itiFile, String title, HashMap<Integer, StepFile> listIdStep, HashMap<Integer, CompositionFile> listIdComposition) { - ObservableList<Pair<Pair<ComplexField, ObjectProperty<StepFile>>, Pair<ComplexField, ObjectProperty<StepFile>>>> list = itiFile.getItinerary(); + ObservableList<Pair<Pair<ComplexField, ObjectProperty<StepFile>>, Pair<ComplexField, ObjectProperty<StepFile>>>> itinerary = itiFile.getItinerary(); WebEngine webEngine = webView.getEngine(); // Double width = webView.getWidth(); @@ -324,22 +327,67 @@ public class ItineraryOverviewController { HashMap<String, StepFile> listStep = new HashMap<>(); 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)) { + MutableNode ms = Factory.mutNode(father.getOntoType() + " (" + father.getId() + ")"); + ms.add("id", nbNode[0]); + ms.add("observation", father.getObservationFiles().size()); + ms.add("hat", "yes"); + listIdStep.put(nbNode[0], father); + + MutableGraph cluster = Factory.mutGraph(nbNode[0].toString()); + cluster.setDirected(true); + cluster.graphAttrs().add(Rank.dir(LEFT_TO_RIGHT)); + cluster.setCluster(false); + cluster.add(ms); + cluster.addTo(g); + listSubGraph.put(father, cluster); + listNode.put(father.getOntoType() + " (" + father.getId() + ")", ms); + listStep.put(father.getOntoType() + " (" + father.getId() + ")", father); + listStepNode.put(father, ms); + nbNode[0]++; + } + }); - Integer nbNode = 444; for(StepFile s : itiFile.getListStep()) { - MutableNode ms = Factory.mutNode(s.getOntoType() + " (" + s.getId() + ")"); - ms.add("id", nbNode); - ms.add("observation", s.getObservationFiles().size()); - listIdStep.put(nbNode,s); - nbNode++; - - listNode.put(s.getOntoType() + " (" + s.getId() + ")", ms); - listStep.put(s.getOntoType() + " (" + s.getId() + ")", s); - listStepNode.put( s, ms); - g.add(ms); + if(!listIdStep.containsValue(s)) { + MutableNode ms = Factory.mutNode(s.getOntoType() + " (" + s.getId() + ")"); + ms.add("id", nbNode[0]); + ms.add("observation", s.getObservationFiles().size()); + ms.add("hat", "no"); + listIdStep.put(nbNode[0],s); + nbNode[0]++; + + listNode.put(s.getOntoType() + " (" + s.getId() + ")", ms); + listStep.put(s.getOntoType() + " (" + s.getId() + ")", s); + listStepNode.put( s, ms); + + if(s.getFather() != null) { + listSubGraph.get(s.getFather()).add(ms); + } else { + g.add(ms); + } + } } - for (Pair<Pair<ComplexField, ObjectProperty<StepFile>>, Pair<ComplexField, ObjectProperty<StepFile>>> pair : list) { + // adding link substep + listStepNode.keySet().stream().filter(s -> s.getFather() != null).forEach(s -> { + MutableNode p = null; + if(s.getFather() != null) { + p = listNode.get(s.getFather().getOntoType() + " (" + s.getFather().getId() + ")"); + } + MutableNode f = null; + f = listNode.get(s.getOntoType() + " (" + s.getId() + ")"); + if(p!=null && f!= null) { + p.addLink(f); + } + }); + + for (Pair<Pair<ComplexField, ObjectProperty<StepFile>>, Pair<ComplexField, ObjectProperty<StepFile>>> pair : itinerary) { MutableNode p = null; if(pair.getKey().getValue().get() != null) { p = listNode.get(pair.getKey().getValue().getValue().getOntoType() + " (" + pair.getKey().getValue().get().getId() + ")"); @@ -349,27 +397,20 @@ public class ItineraryOverviewController { f = listNode.get(pair.getValue().getValue().getValue().getOntoType() + " (" + pair.getValue().getValue().get().getId() + ")"); } - if (p == null) { + if (p == null || f == null) { // error } - if(p!= null) { - // g.add(p); - } - if (f == null) { -// error - } - if(f!= null) { -// g.add(f); - } if(p!=null && f!= null) { - g.add(p.addLink(f)); + p.addLink(f); } - } if(showAllCompo.get()) { for (StepFile sf : listStep.values()) { + if(!sf.getSubStep().isEmpty()) { + System.out.println("break"); + } for (Map.Entry<CompositionFile, Boolean> en : sf.getCompositionFile().entrySet()) { MutableNode pc = null; pc = listCompo.get(en.getKey()); @@ -377,9 +418,9 @@ public class ItineraryOverviewController { pc = Factory.mutNode( en.getKey().getFileName()); pc.add(guru.nidi.graphviz.attribute.Label.of(en.getKey().getCompositionID().getValue().get())); pc.add(Color.named("red")); - pc.add("id", nbNode); - listIdComposition.put(nbNode, en.getKey()); - nbNode++; + pc.add("id", nbNode[0]); + listIdComposition.put(nbNode[0], en.getKey()); + nbNode[0]++; g.add(pc); listCompo.put(en.getKey(), pc); } @@ -392,63 +433,15 @@ public class ItineraryOverviewController { } } - if(showISCompo.get()) { - HashMap<MutableNode, ArrayList<StepFile>> nodeToAddInput = new HashMap<>(); - HashMap<MutableNode, ArrayList<StepFile>> nodeToAddOutput = new HashMap<>(); - - for (StepFile sf : listStep.values()) { - for (Map.Entry<CompositionFile, Boolean> en : sf.getCompositionFile().entrySet()) { - MutableNode pc = null; - pc = listCompo.get(en.getKey()); - if (pc == null) { // ok premiere fois qu'on la voie - pc = Factory.mutNode( en.getKey().getFileName()); - pc.add(guru.nidi.graphviz.attribute.Label.of(en.getKey().getCompositionID().getValue().get())); - pc.add(Color.named("red")); - listCompo.put(en.getKey(), pc); - ArrayList<StepFile> l = new ArrayList<>(); - l.add(sf); - if (en.getValue()) { - nodeToAddInput.put(pc, l); - } else { - nodeToAddOutput.put(pc, l); } - } else { - // compo déjà rencontré - if(en.getValue()) { // input - if(nodeToAddOutput.containsKey(pc)) { // output -> on supprime - nodeToAddOutput.remove(pc); - } else { // on rajoute son nouvel input - nodeToAddInput.get(pc).add(sf); - } - } else { - if(nodeToAddInput.containsKey(pc)) { - nodeToAddInput.remove(pc); - } else { // on rajoute son nouvel output - nodeToAddOutput.get(pc).add(sf); - } - } - } - - } - } - nodeToAddInput.forEach((entries, listStepFile) -> { - listStepFile.forEach(stepFile -> { - entries.addLink(listStepNode.get(stepFile)); - }); - g.add(entries); - }); - nodeToAddOutput.forEach((entries, listStepFile) -> { - listStepFile.forEach(stepFile -> { - listStepNode.get(stepFile).addLink(entries); - }); - g.add(entries); - }); - } - - List<GraphvizEngine> listEngine = new ArrayList<>( Arrays.asList(new GraphvizCmdLineEngine(), new GraphvizV8Engine(), new GraphvizJdkEngine())); Graphviz.useEngine(listEngine); Graphviz viz = Graphviz.fromGraph(g); + try { + viz.render(Format.PNG).toFile(new File("/home/stephane/test.png")); + } catch (IOException e) { + throw new RuntimeException(e); + } String json = viz.render(Format.JSON).toString(); JSONObject jsonGraph = new JSONObject(json); @@ -459,23 +452,37 @@ public class ItineraryOverviewController { Double maxHeaght = 0.0; for (Integer i = 0; i < listGraphNode.length(); i++) { JSONObject node = listGraphNode.getJSONObject(i); - String[] pos = node.getString("pos").split(","); - maxHeaght = Math.max(maxHeaght,Double.valueOf(pos[1]) ); + if(!node.optString("pos").isEmpty()) { + String[] pos = node.getString("pos").split(","); + maxHeaght = Math.max(maxHeaght,Double.valueOf(pos[1]) ); + } } for (Integer i = 0; i < listGraphNode.length(); i++) { JSONObject node = listGraphNode.getJSONObject(i); - String[] pos = node.getString("pos").split(","); - String label = node.getString("label").replaceAll("\\\\N", ""); - Double posX = Double.valueOf(pos[0]) - 100.0; - Double posY = maxHeaght - (Double.valueOf(pos[1]) - 100.0); + if(!node.optString("pos").isEmpty()) { + String[] pos = node.getString("pos").split(","); + String label = node.getString("label").replaceAll("\\\\N", ""); + Double posX = Double.valueOf(pos[0]) - 100.0; + Double posY = maxHeaght - (Double.valueOf(pos[1]) - 100.0); + + if (label.isEmpty()) { + 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 + ");"); + }); + } else { + Platform.runLater(() -> { - if (label.isEmpty()) { - Platform.runLater(() -> { - webEngine.executeScript("graph.addNodeNoUpdate('" + node.getString("name").replaceAll("'", "\\\\'") + "'," + node.getInt("id") + ",'step', "+ node.getInt("observation")+ "," + posX + "," + posY + ");"); - }); + webEngine.executeScript("graph.addNodeNoUpdate('" + label.replaceAll("'", "\\\\'") + "'," + node.getInt("id") + ",'composition', 0 ," + posX + "," + posY + ");"); + }); + } } else { + // cluster node + Double posX = 0.0; + Double posY = 0.0; + Platform.runLater(() -> { - webEngine.executeScript("graph.addNodeNoUpdate('" + label.replaceAll("'", "\\\\'") + "'," + node.getInt("id") + ",'composition', 0 ," + posX + "," + posY + ");"); + webEngine.executeScript("graph.addNodeNoUpdate(''," + -1 + ",'" + "cluster" + "', " + "0" + "," + posX + "," + posY + ");"); }); } } @@ -486,6 +493,7 @@ public class ItineraryOverviewController { for (Integer i = 0; i < listGraphEdge.length(); i++) { JSONObject edge = listGraphEdge.getJSONObject(i); Platform.runLater(() -> { + System.out.println("graph.addEdgeNoUpdate(" + edge.getInt("tail") + "," + edge.getInt("head") + ");"); webEngine.executeScript("graph.addEdgeNoUpdate(" + edge.getInt("tail") + "," + edge.getInt("head") + ");"); }); } 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 e391f9060a64ff16745d552f46136a17da6f19e3..f519544cf831913bf6139f59dc99af74d90c3760 100644 --- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.css +++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.css @@ -75,6 +75,21 @@ 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; @@ -97,18 +112,40 @@ g.conceptG:hover[nodeType][obs] circle{ } g.selected[nodeType][obs] circle{ - fill: #ff6464; + 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=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; @@ -124,6 +161,13 @@ path.link[linkType=cs]{ cursor: default; } +path.link[linkType=drag]{ + fill: none; + stroke: #000000; + stroke-width: 6px; + cursor: default; +} + path.link:hover{ stroke: #53aab1; } @@ -137,5 +181,5 @@ path.link.hidden{ } path.link.selected { - stroke: #ff6464; + stroke: #a0a0a0; } 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 91a6fc49ccf879c70734f2aa11e0366277f51fa1..345038fe2f6aadd14b9fb9e012b08fa5c75a0cc1 100644 --- a/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js +++ b/src/main/resources/fr/inra/po2vocabmanager/graph/graph-creator.js @@ -61,6 +61,28 @@ .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") @@ -96,8 +118,8 @@ .attr("height", height); // displayed when dragging between nodes thisGraph.dragLine = svgG.append('svg:path') - .attr('class', 'link dragline hidden') - .attr('d', 'M0,0L0,0') + .attr('class', 'link') + .attr('linkType', 'drag') .style('marker-end', 'url(#mark-end-arrow)'); // svg nodes and edges @@ -424,7 +446,7 @@ thisGraph.dragLine.classed("hidden", true); - if (mouseDownNode !== d && (mouseDownNode.type === "step" || d.type === "step")) { + if (mouseDownNode !== d && ((mouseDownNode.type === "step" || d.type === "step") && (mouseDownNode.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, @@ -534,20 +556,24 @@ 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 === "step" && thisGraph.javaFXEditable()) { - //alert("Step 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); - } + thisGraph.javaFXDelEdge(selectedEdge); + thisGraph.edges.splice(thisGraph.edges.indexOf(selectedEdge), 1); + state.selectedEdge = null; + thisGraph.updateGraph(false); + } break; } };