Browse Source

DTDesign plugin: Can navigate ontology by context menu

Joeri Exelmans 2 years ago
parent
commit
a8abbc332a
1 changed files with 170 additions and 48 deletions
  1. 170 48
      src/main/webapp/myPlugins/dtdesign.js

+ 170 - 48
src/main/webapp/myPlugins/dtdesign.js

@@ -60,8 +60,7 @@ WHERE {
     ?as_element a ?another_more_concrete_type
     FILTER(?another_more_concrete_type != ?as_type)
   }
-}
-`;
+}`;
 
 const getCellIri = cellId => `\
 PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
@@ -76,14 +75,13 @@ WHERE {
     ?element a ?more_concrete_type .
     FILTER(?more_concrete_type != ?type)
   }
-`;
+}`;
 
 const getAllOutgoingRelations = iri => `\
 SELECT DISTINCT ?rel ?other
 WHERE {
   <${iri}> ?rel ?other .
-}
-`;
+}`;
 
 const getOutgoingLink = (iri, link_type) => `\
 PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
@@ -99,21 +97,93 @@ WHERE {
     ?element a ?more_concrete_type .
     FILTER(?more_concrete_type != ?type)
   }
+}`;
+
+const getCellStuff = (cellId) => `\
+PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
+PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+
+SELECT DISTINCT ?element ?type
+WHERE {
+  ?element drawio:hasDrawioId "${cellId}" .
+  ?element a ?type .
+  ?type rdfs:subClassOf object_diagram:Object .
+  NOT EXISTS {
+    ?more_concrete_type rdfs:subClassOf ?type .
+    ?element a ?more_concrete_type .
+    FILTER(?more_concrete_type != ?type)
+  }
+}`;
+
+const getDiagramAndCellId = cellIri => `\
+PREFIX drawio: <http://ua.be/sdo2l/vocabulary/formalisms/drawio#>
+PREFIX object_diagram: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>
+
+SELECT DISTINCT ?diagramName ?cellId
+WHERE {
+  <${cellIri}> drawio:hasDrawioId ?cellId .
+  <${cellIri}> object_diagram:inModel ?model .
+  ?model object_diagram:hasName ?diagramName .
+}`;
+
+const getSubClassRelations = `
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+
+SELECT DISTINCT ?subclass ?superclass
+WHERE {
+  ?subclass rdfs:subClassOf ?superclass
+}`;
+
+// mapping from class-IRI to array of superclass-IRIs
+const superclasses = new Map();
+
+function getQueries(type) {
+  const sups = superclasses.get(type) || [];
+  const result = getBaseQueries(type).concat(... sups.map(sup => getBaseQueries(sup)));
+  return result;
 }
-`;
 
-function getQueries(element, type) {
+// Some hardcoded relations...
+function getBaseQueries(type) {
+  const queries = [];
   if (type.endsWith("formalisms/drawio#Cell")) {
-    return [
-      getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/cs_as#parsedAs"),
-    ]
+    queries.push({
+      query: "formalisms/cs_as#parsedAs",
+      name: "Is parsed as",
+    });
   }
   if (type.endsWith("formalisms/pm#Activity")) {
-    return [
-      getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/formalisms/pm#isTransformation"),
-    ];
+    queries.push({
+      query: "formalisms/pm#isTransformation",
+      name: "Is typed by transformation",
+    });
+  }
+  if (type.endsWith("formalisms/pm#Artifact")) {
+    queries.push({
+      query: "formalisms/pm#hasType",
+      name: "Is typed by formalism",
+    });
+  }
+  if (type.endsWith("formalisms/object_diagram#Object")) {
+    queries.push({
+      query: "formalisms/cs_as#renderedAs",
+      name: "Is rendered as",
+    });
   }
-  return [];
+  if (type.endsWith("base/ftg#Transformation")) {
+    queries.push({
+      query: "formalisms/pm#occursAsActivity",
+      name: "Occurs as activity",
+    });
+  }
+  if (type.endsWith("base/ftg#Formalism")) {
+    queries.push({
+      query: "formalisms/pm#occursAsArtifact",
+      name: "Occurs as activity",
+    });
+  }
+  return queries;
 }
 
 
@@ -138,6 +208,18 @@ const defaultSettings = {
 
 Draw.loadPlugin(function(ui) {
 
+  executeSPARQL(getSubClassRelations)
+  .then(results => {
+    for (const {subclass, superclass} of results) {
+      const superclasslist = superclasses.get(subclass.value) || (() => {
+        const list = [];
+        superclasses.set(subclass.value, list);
+        return list;
+      })();
+      superclasslist.push(superclass.value);
+    }
+  });
+
   function getSetting(prop) {
     const result = localStorage.getItem("dtdesign-"+prop);
     if (result !== null) {
@@ -243,31 +325,68 @@ Draw.loadPlugin(function(ui) {
     });
   }
 
-  ui.actions.addAction('whatIsThis', mxUtils.bind(this, function() {}));
+  let highlight;
+
+  // Given a cell IRI, open the diagram that contains the cell
+  function goto(cellIri) {
+    executeSPARQL(getDiagramAndCellId(cellIri))
+    .then(([{diagramName, cellId}]) => {
+      // drop the "_drawio" at the end:
+      const actualDiagramName = diagramName.value.substring(0, diagramName.value.length-7);
+      loadPage(actualDiagramName)
+      .then(() => {
+        if (highlight) {
+          highlight.destroy();
+        }
+        const [cell] = ui.editor.graph.getCellsById([cellId.value]);
+        highlight = new mxCellHighlight(ui.editor.graph, "#eb34e8", 6);
+        highlight.highlight(ui.editor.graph.view.getState(cell));        
+      })
+    })
+  }
 
+  // Override context menu when right-clicking somewhere in the diagram:
   const oldFactoryMethod = ui.editor.graph.popupMenuHandler.factoryMethod;
   ui.editor.graph.popupMenuHandler.factoryMethod = function(menu, cell, evt) {
     const modelverseMenu = menu.addItem("ModelVerse", null, null);
-    const isAMenu = menu.addItem("Is a...", null, null, modelverseMenu);
-    menu.addSeparator();
     oldFactoryMethod.apply(this, arguments);
     if (cell && cell.vertex) {
-      executeSPARQL(whatIsCell(cell.id))
-      .then(json => {
-        for (const {cell, rel_type, as_type, as_element} of json) {
-            const tracelink_type = rel_type.value.split("#")[1];
-            const typeMenu = menu.addItem(dropVocabularyPrefix(as_type.value) + " - " + tracelink_type, null, null, isAMenu);
-
-            const queries = getQueries(as_element.value, as_type.value);
-            Promise.all(queries.map(q => {
-              executeSPARQL(q)
-              .then(json => {
-                console.log("query", q, "json:", json);
-              });
-            }))
+      const alreadyVisited = new Set();
+      function getInfo(element, type, maxRecursion, parent, relName) {
+        if (alreadyVisited.has(element+'---'+type)) {
+          // console.log('alreadyVisited')
+          return; // don't go in circles
         }
-        console.log(json);
-      });
+        const shortType = dropVocabularyPrefix(type);
+        // console.log(path, element, type);
+        let newParent;
+        if (superclasses.get(type).some(t => t.endsWith("drawio#Cell"))) {
+          newParent = menu.addItem(relName + ' ' + shortType, null, () => goto(element), parent);
+        } else {
+          newParent = menu.addItem(relName + ' ' + shortType, null, null, parent);
+        }
+        alreadyVisited.add(element+'---'+type);
+        if (maxRecursion <= 0) {
+          // console.log('maxRecursion 0')
+          return;
+        }
+        const queries = getQueries(type);
+        for (const {query, name} of queries) {
+          executeSPARQL(getOutgoingLink(element, "http://ua.be/sdo2l/vocabulary/"+query))
+          .then(results => {
+            // console.log(path, name, "results:", results);
+            for (const {element, type} of results) {
+              getInfo(element.value, type.value, maxRecursion-1, newParent, name);
+            }
+          })
+        }
+      }
+      executeSPARQL(getCellStuff(cell.id))
+      .then(results => {
+        for (const {element, type} of results) {
+          getInfo(element.value, type.value, 8, modelverseMenu, "Is a");
+        }
+      })
     }
   }
 
@@ -317,6 +436,24 @@ Draw.loadPlugin(function(ui) {
   const modelsDiv = document.createElement('div');
     lsDiv.appendChild(modelsDiv);
 
+  // Load a model and add it as a new page to the editor
+  function loadPage(pageName) {
+    return fetch(BACKEND + "/diagrams/" + pageName)
+    .then(res => res.text())
+    .then(xmltext => {
+      // console.log(xmltext);
+      const parser = new DOMParser();
+      const doc = parser.parseFromString(xmltext, "application/xml");
+      const node = doc.documentElement;
+      const page = new DiagramPage(node);
+      // if page with same name already exists, erase it:
+      ui.pages = ui.pages.filter(page => page.node.getAttribute("name") != pageName);
+      ui.pages.push(page);
+      ui.currentPage = page;
+      ui.editor.setGraphXml(node);
+    });
+  }
+
   // Refreshes the list of models shown in the ModelVerse window
   function refreshModels() {
     fetch(BACKEND + "/diagrams", {
@@ -327,22 +464,7 @@ Draw.loadPlugin(function(ui) {
       modelsDiv.replaceChildren(...json.map(modelName => {
         const div = document.createElement('div');
           div.style.padding = '3px 0px';
-        const loadButton = mxUtils.button("Open in New Page", function() {
-          fetch(BACKEND + "/diagrams/" + modelName)
-          .then(res => res.text())
-          .then(xmltext => {
-            // console.log(xmltext);
-            const parser = new DOMParser();
-            const doc = parser.parseFromString(xmltext, "application/xml");
-            const node = doc.documentElement;
-            const page = new DiagramPage(node);
-            // if page with same name already exists, erase it:
-            ui.pages = ui.pages.filter(page => page.node.getAttribute("name") != modelName);
-            ui.pages.push(page);
-            ui.currentPage = page;
-            ui.editor.setGraphXml(node);
-          })
-        });
+        const loadButton = mxUtils.button("Open in New Page", () => loadPage(modelName));
           loadButton.style.marginLeft = "12px";
         div.appendChild(document.createTextNode(modelName));
         div.appendChild(loadButton);