From 915fa24aa49169a83518cfe374327d9b92fa01f1 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sat, 24 Aug 2024 10:08:12 -0400 Subject: [PATCH 01/24] Target platform changes? --- .../org.eclipse.elk.targetplatform.target | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target b/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target index 304932db5..ee6064e20 100644 --- a/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target +++ b/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target @@ -1,19 +1,21 @@ - + + + + + + - - - - + - - + + From d07e112d6d0af4b9a9c4091a1ad6f6f8b0944f98 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sat, 24 Aug 2024 10:08:20 -0400 Subject: [PATCH 02/24] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9381709b4..927b79c45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.project .DS_Store *.class bin/ @@ -19,3 +20,5 @@ docs/static/img_gen/ docs/public org.eclipse.core.resources.prefs org.eclipse.m2e.core.prefs +build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target + From d34db9027219c829a286609dc08e3c2c0da86d19 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 07:58:53 -0400 Subject: [PATCH 03/24] Supply missing map clear --- .../src/org/eclipse/elk/graph/json/JsonImporter.xtend | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index 7990e0f7f..c2194b7b3 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -94,7 +94,8 @@ final class JsonImporter { nodeJsonMap.clear portJsonMap.clear edgeJsonMap.clear - edgeSectionJsonMap.clear + edgeSectionJsonMap.clear + labelJsonMap.clear } private def transformChildNodes(Object jsonNodeA, ElkNode parent) { From 9976add3b9619db1264d1998486db53716caae0b Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 08:05:53 -0400 Subject: [PATCH 04/24] Add maps to support coordinate adjustments * Record the original parent of each edge, before the call to `updateContainment()`. * Split the layout transfer into two passes: Pass 1 does everything except edges; Pass 2 does edges. This allows us to compute the global coords of each node, before working on the edges. * In this commit, we add some diagnostic output to the final JSON, which is probably not ultimately wanted. (Global coords, and original parents.) --- .../eclipse/elk/graph/json/JsonImporter.xtend | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index c2194b7b3..f06cd9dbe 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -58,6 +58,11 @@ final class JsonImporter { val Map edgeJsonMap = Maps.newHashMap val Map edgeSectionJsonMap = Maps.newHashMap val Map labelJsonMap = Maps.newHashMap + + /* Maps to help in adjusting coordinates */ + val Map edgeOriginalParentMap = Maps.newHashMap + val Map nodeGlobalXMap = Maps.newHashMap + val Map nodeGlobalYMap = Maps.newHashMap var Object inputModel @@ -91,11 +96,16 @@ final class JsonImporter { portIdMap.clear edgeIdMap.clear edgeSectionIdMap.clear + nodeJsonMap.clear portJsonMap.clear edgeJsonMap.clear edgeSectionJsonMap.clear labelJsonMap.clear + + edgeOriginalParentMap.clear + nodeGlobalXMap.clear + nodeGlobalYMap.clear } private def transformChildNodes(Object jsonNodeA, ElkNode parent) { @@ -528,20 +538,30 @@ final class JsonImporter { def transferLayout(ElkNode graph) { // transfer layout of all elements (including root) ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | - element.transferLayoutInt + element.transferLayoutInt1 + ] + ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | + element.transferLayoutInt2 ] } - private def dispatch transferLayoutInt(ElkNode node) { + private def dispatch transferLayoutInt1(ElkNode node) { val jsonObj = nodeJsonMap.get(node) if (jsonObj === null) { throw formatError("Node did not exist in input.") } + // Compute global coords, to support coordinate adjustments. + val parent = node.getParent + val dx = nodeGlobalXMap.get(parent) ?: 0 + val dy = nodeGlobalYMap.get(parent) ?: 0 + nodeGlobalXMap.put(node, node.x + dx) + nodeGlobalYMap.put(node, node.y + dy) + // transfer positions and dimension node.transferShapeLayout(jsonObj) } - private def dispatch transferLayoutInt(ElkPort port) { + private def dispatch transferLayoutInt1(ElkPort port) { val jsonObj = portJsonMap.get(port) if (jsonObj === null) { throw formatError("Port did not exist in input.") @@ -551,7 +571,7 @@ final class JsonImporter { port.transferShapeLayout(jsonObj) } - private def dispatch transferLayoutInt(ElkEdge edge) { + private def dispatch transferLayoutInt2(ElkEdge edge) { val jsonObj = edgeJsonMap.get(edge).toJsonObject if (jsonObj === null) { throw formatError("Edge did not exist in input.") @@ -646,20 +666,38 @@ final class JsonImporter { } } - jsonObj.addJsonObj("container", edge.getContainingNode().identifier) + jsonObj.addJsonObj("container", edge.getContainingNode.identifier) + + jsonObj.addJsonObj("originalParent", edgeOriginalParentMap.get(edgeId).identifier) } - private def dispatch transferLayoutInt(ElkLabel label) { + private def dispatch transferLayoutInt1(ElkLabel label) { val jsonObj = labelJsonMap.get(label) // transfer positions and dimension label.transferShapeLayout(jsonObj) } - private def dispatch transferLayoutInt(Object obj) { + private def dispatch transferLayoutInt1(Object obj) { + // don't care about the rest + } + + private def dispatch transferLayoutInt2(Object obj) { // don't care about the rest } + private def transferShapeLayout(ElkNode shape, Object jsonObjA) { + val jsonObj = jsonObjA.toJsonObject + // pos and dimension + jsonObj.addJsonObj("x", shape.x) + jsonObj.addJsonObj("y", shape.y) + jsonObj.addJsonObj("width", shape.width) + jsonObj.addJsonObj("height", shape.height) + + jsonObj.addJsonObj("x_g", nodeGlobalXMap.get(shape)) + jsonObj.addJsonObj("y_g", nodeGlobalYMap.get(shape)) + } + private def transferShapeLayout(ElkShape shape, Object jsonObjA) { val jsonObj = jsonObjA.toJsonObject // pos and dimension @@ -709,6 +747,7 @@ final class JsonImporter { edgeIdMap.put(id, edge) edgeJsonMap.put(edge, obj) + edgeOriginalParentMap.put(id, edge.getContainingNode) return edge } From 3e63089fc616f952aa4fdcfef5d29c27da4e7f12 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 10:07:37 -0400 Subject: [PATCH 05/24] Add comments --- .../src/org/eclipse/elk/graph/json/JsonImporter.xtend | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index f06cd9dbe..523e0d1de 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -536,10 +536,12 @@ final class JsonImporter { * Transfer the layout back to the formerly imported graph, using {@link #transform(Object)}. */ def transferLayout(ElkNode graph) { - // transfer layout of all elements (including root) + // First pass handles everything except edges, and computes global coordinates for all nodes. ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | element.transferLayoutInt1 ] + + // Second pass handles edges. ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | element.transferLayoutInt2 ] From 7208ee3a6fb2791a0e2d85a1b7b1d89645c4ccd1 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 10:09:42 -0400 Subject: [PATCH 06/24] Add `org.eclipse.elk.json.edgeCoords` property For now, we don't do anything with it except note the enum ordinal in the JSON for each edge. --- .../src/org/eclipse/elk/core/Core.melk | 13 +++++++++ .../eclipse/elk/core/options/EdgeCoords.java | 29 +++++++++++++++++++ .../eclipse/elk/graph/json/JsonImporter.xtend | 19 ++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk index d5b059bab..e1a39ce9a 100644 --- a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk @@ -302,6 +302,19 @@ advanced option omitNodeMicroLayout: boolean { targets parents } +// --- JSON +group json { + + option edgeCoords: EdgeCoords { + label "Edge Coords" + description + "Specify the coordinate system for edge route points." + default = EdgeCoords.INHERIT + targets parents + } + +} + // --- SPACING group spacing { diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java new file mode 100644 index 000000000..b69783e2a --- /dev/null +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010, 2015 Kiel University and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.elk.core.options; + +/** + * Definition of edge coordinate systems. To be accessed using {@link CoreOptions#JSON_EDGE_COORDS}. + */ +public enum EdgeCoords { + + /** + * Inherit the parent node's coordinate system. The root node has no parent node; here, this setting defaults to + * {@link #CONTAINER}. + */ + INHERIT, + /** relative to the edge's proper container node. */ + CONTAINER, + /** relative to the edge's JSON parent node. */ + PARENT, + /** relative to the root node, a.k.a. global coordinates. */ + ROOT; + +} diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index 523e0d1de..afc111460 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -17,6 +17,7 @@ import com.google.common.collect.Multimap import java.util.Map import org.eclipse.elk.core.data.LayoutMetaDataService import org.eclipse.elk.core.options.CoreOptions +import org.eclipse.elk.core.options.EdgeCoords import org.eclipse.elk.core.util.IndividualSpacings import org.eclipse.elk.graph.ElkEdge import org.eclipse.elk.graph.ElkEdgeSection @@ -572,6 +573,20 @@ final class JsonImporter { // transfer positions and dimension port.transferShapeLayout(jsonObj) } + + private def EdgeCoords getEdgeCoordsMode(ElkNode node) { + var ancestor = node + var ecm = ancestor.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT + while (ecm === EdgeCoords.INHERIT) { + ancestor = ancestor.getParent + if (ancestor !== null) { + ecm = ancestor.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT + } else { + ecm = EdgeCoords.CONTAINER + } + } + return ecm + } private def dispatch transferLayoutInt2(ElkEdge edge) { val jsonObj = edgeJsonMap.get(edge).toJsonObject @@ -580,6 +595,10 @@ final class JsonImporter { } val edgeId = jsonObj.id + + val originalParent = edgeOriginalParentMap.get(edgeId) + val edgeCoordsMode = originalParent.getEdgeCoordsMode + jsonObj.addJsonObj("edgeCoords", edgeCoordsMode.ordinal) // what we need to transfer are the edge sections if (!edge.sections.nullOrEmpty) { From 4d49d8be9b99f5ab7e3fd71692d9395cf538f103 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 10:53:14 -0400 Subject: [PATCH 07/24] Adjust edge coordinates according to mode --- .../eclipse/elk/graph/json/JsonImporter.xtend | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index afc111460..28eee1d6b 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -61,7 +61,7 @@ final class JsonImporter { val Map labelJsonMap = Maps.newHashMap /* Maps to help in adjusting coordinates */ - val Map edgeOriginalParentMap = Maps.newHashMap + val Map edgeOriginalParentMap = Maps.newHashMap val Map nodeGlobalXMap = Maps.newHashMap val Map nodeGlobalYMap = Maps.newHashMap @@ -587,6 +587,22 @@ final class JsonImporter { } return ecm } + + private def Double adjustX(ElkEdge edge, EdgeCoords mode, Double x) { + switch mode { + case EdgeCoords.ROOT: x + edge.getContainingNode.globalX + case EdgeCoords.PARENT: x + edge.getContainingNode.globalX - edge.originalParent.globalX + default: x + } + } + + private def Double adjustY(ElkEdge edge, EdgeCoords mode, Double y) { + switch mode { + case EdgeCoords.ROOT: y + edge.getContainingNode.globalY + case EdgeCoords.PARENT: y + edge.getContainingNode.globalY - edge.originalParent.globalY + default: y + } + } private def dispatch transferLayoutInt2(ElkEdge edge) { val jsonObj = edgeJsonMap.get(edge).toJsonObject @@ -596,9 +612,9 @@ final class JsonImporter { val edgeId = jsonObj.id - val originalParent = edgeOriginalParentMap.get(edgeId) - val edgeCoordsMode = originalParent.getEdgeCoordsMode - jsonObj.addJsonObj("edgeCoords", edgeCoordsMode.ordinal) + val ecm = edge.originalParent.getEdgeCoordsMode + + jsonObj.addJsonObj("edgeCoords", ecm.ordinal) // what we need to transfer are the edge sections if (!edge.sections.nullOrEmpty) { @@ -618,14 +634,14 @@ final class JsonImporter { // Start Point val startPoint = newJsonObject - startPoint.addJsonObj("x", elkSection.startX) - startPoint.addJsonObj("y", elkSection.startY) + startPoint.addJsonObj("x", edge.adjustX(ecm, elkSection.startX)) + startPoint.addJsonObj("y", edge.adjustY(ecm, elkSection.startY)) jsonSection.addJsonObj("startPoint", startPoint) // End Point val endPoint = newJsonObject - endPoint.addJsonObj("x", elkSection.endX) - endPoint.addJsonObj("y", elkSection.endY) + endPoint.addJsonObj("x", edge.adjustX(ecm, elkSection.endX)) + endPoint.addJsonObj("y", edge.adjustY(ecm, elkSection.endY)) jsonSection.addJsonObj("endPoint", endPoint) // Bend Points @@ -633,8 +649,8 @@ final class JsonImporter { val bendPoints = newJsonArray elkSection.bendPoints.forEach [ pnt | val jsonPnt = newJsonObject - jsonPnt.addJsonObj("x", pnt.x) - jsonPnt.addJsonObj("y", pnt.y) + jsonPnt.addJsonObj("x", edge.adjustX(ecm, pnt.x)) + jsonPnt.addJsonObj("y", edge.adjustY(ecm, pnt.y)) bendPoints.addJsonArr(jsonPnt) ] jsonSection.addJsonObj("bendPoints", bendPoints) @@ -679,8 +695,8 @@ final class JsonImporter { val jsonJPs = newJsonArray jps.forEach[ jp | val jsonPnt = newJsonObject - jsonPnt.addJsonObj("x", jp.x) - jsonPnt.addJsonObj("y", jp.y) + jsonPnt.addJsonObj("x", edge.adjustX(ecm, jp.x)) + jsonPnt.addJsonObj("y", edge.adjustY(ecm, jp.y)) jsonJPs.addJsonArr(jsonPnt) ] jsonObj.addJsonObj("junctionPoints", jsonJPs) @@ -689,7 +705,7 @@ final class JsonImporter { jsonObj.addJsonObj("container", edge.getContainingNode.identifier) - jsonObj.addJsonObj("originalParent", edgeOriginalParentMap.get(edgeId).identifier) + jsonObj.addJsonObj("originalParent", edge.originalParent.identifier) } private def dispatch transferLayoutInt1(ElkLabel label) { @@ -768,7 +784,7 @@ final class JsonImporter { edgeIdMap.put(id, edge) edgeJsonMap.put(edge, obj) - edgeOriginalParentMap.put(id, edge.getContainingNode) + edgeOriginalParentMap.put(edge, edge.getContainingNode) return edge } @@ -781,5 +797,17 @@ final class JsonImporter { return edgeSection } + + private def ElkNode originalParent(ElkEdge edge) { + return edgeOriginalParentMap.get(edge) + } + + private def Double globalX(ElkNode node) { + return nodeGlobalXMap.get(node) + } + + private def Double globalY(ElkNode node) { + return nodeGlobalYMap.get(node) + } } From 4f76fa01685e6089d823302443e2298ef19077ac Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 12:12:58 -0400 Subject: [PATCH 08/24] Use extension methods --- .../src/org/eclipse/elk/graph/json/JsonImporter.xtend | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index 28eee1d6b..102b893f1 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -553,10 +553,10 @@ final class JsonImporter { if (jsonObj === null) { throw formatError("Node did not exist in input.") } - // Compute global coords, to support coordinate adjustments. + // Compute global coordinates, to support coordinate adjustments. val parent = node.getParent - val dx = nodeGlobalXMap.get(parent) ?: 0 - val dy = nodeGlobalYMap.get(parent) ?: 0 + val dx = parent.globalX ?: 0 + val dy = parent.globalY ?: 0 nodeGlobalXMap.put(node, node.x + dx) nodeGlobalYMap.put(node, node.y + dy) From 3683032bce2e86209c6a1fbc721703b907fd6531 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 14:08:45 -0400 Subject: [PATCH 09/24] Add unit tests --- .../elk/graph/json/test/EdgeCoordsTest.xtend | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend new file mode 100644 index 000000000..b251128d2 --- /dev/null +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2024 Kiel University and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.elk.graph.json.test + +import com.google.gson.JsonParser +import org.eclipse.elk.core.RecursiveGraphLayoutEngine +import org.eclipse.elk.core.util.BasicProgressMonitor +import org.eclipse.elk.core.util.Maybe +import org.eclipse.elk.graph.json.ElkGraphJson +import org.eclipse.elk.graph.json.JsonImporter +import org.junit.Test + +import static org.junit.Assert.* + +/** + */ +class EdgeCoordsTest { + + val graph = ''' + { + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN" + }, + "children": [ + { "id": "A", + "children": [ + { "id": "x", "width": 50, "height": 90 }, + { "id": "B", + "children": [ + { "id": "y", "width": 50, "height": 90 }, + { "id": "z", "width": 50, "height": 90 } + ], + "edges": [ + { "id": "e1", "sources": [ "y" ], "targets": [ "z" ] }, + { "id": "e2", "sources": [ "x" ], "targets": [ "z" ] } + ] + } + ] + } + ] + } + ''' + + val secContainer = ''' + { + "id": "e2_s0", + "startPoint": { + "x": 62, + "y": 124 + }, + "endPoint": { + "x": 169, + "y": 99 + }, + "bendPoints": [ + { + "x": 159, + "y": 124 + }, + { + "x": 159, + "y": 99 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ''' + + val secParent = ''' + { + "id": "e2_s0", + "startPoint": { + "x": -25, + "y": 112 + }, + "endPoint": { + "x": 82, + "y": 87 + }, + "bendPoints": [ + { + "x": 72, + "y": 112 + }, + { + "x": 72, + "y": 87 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ''' + + val secRoot = ''' + { + "id": "e2_s0", + "startPoint": { + "x": 74, + "y": 136 + }, + "endPoint": { + "x": 181, + "y": 111 + }, + "bendPoints": [ + { + "x": 171, + "y": 136 + }, + { + "x": 171, + "y": 111 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ''' + + @Test + def void edgeCoordsTest() { + + val cases = #[ + #['CONTAINER', secContainer], + #['PARENT', secParent], + #['ROOT', secRoot] + ] + + for (p : cases) { + val mode = p.get(0) + val expectedString = p.get(1) + + val parser = new JsonParser() + val jsonGraph = parser.parse(graph).asJsonObject + + jsonGraph.get("properties").asJsonObject.addProperty( + "org.eclipse.elk.json.edgeCoords", mode + ) + + val mby = new Maybe + val root = ElkGraphJson.forGraph(jsonGraph) + .rememberImporter(mby) + .toElk + + new RecursiveGraphLayoutEngine().layout(root, new BasicProgressMonitor) + + mby.get.transferLayout(root) + + val nodeB = jsonGraph.get("children").asJsonArray + .get(0).asJsonObject + .get("children").asJsonArray + .get(1).asJsonObject + val edge2 = nodeB.get("edges").asJsonArray + .get(1).asJsonObject + val computedSec = edge2.get("sections").asJsonArray + .get(0).asJsonObject + + val expectedSec = parser.parse(expectedString).asJsonObject + + assertEquals(expectedSec, computedSec) + } + + } + +} \ No newline at end of file From 0300a9732dbe7098813421f5eb4ee978c7ff813d Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 14:15:03 -0400 Subject: [PATCH 10/24] Use do-while loop --- .../src/org/eclipse/elk/graph/json/JsonImporter.xtend | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index 102b893f1..e39e8a828 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -576,15 +576,15 @@ final class JsonImporter { private def EdgeCoords getEdgeCoordsMode(ElkNode node) { var ancestor = node - var ecm = ancestor.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT - while (ecm === EdgeCoords.INHERIT) { - ancestor = ancestor.getParent + var ecm = EdgeCoords.INHERIT + do { if (ancestor !== null) { ecm = ancestor.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT } else { ecm = EdgeCoords.CONTAINER } - } + ancestor = ancestor.getParent + } while (ecm === EdgeCoords.INHERIT) return ecm } From f72a435ab285212531664bb879b6df08b751cca3 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 14:18:19 -0400 Subject: [PATCH 11/24] Remove diagnostic output --- .../eclipse/elk/graph/json/JsonImporter.xtend | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index e39e8a828..e2c475f7a 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -613,8 +613,6 @@ final class JsonImporter { val edgeId = jsonObj.id val ecm = edge.originalParent.getEdgeCoordsMode - - jsonObj.addJsonObj("edgeCoords", ecm.ordinal) // what we need to transfer are the edge sections if (!edge.sections.nullOrEmpty) { @@ -704,8 +702,6 @@ final class JsonImporter { } jsonObj.addJsonObj("container", edge.getContainingNode.identifier) - - jsonObj.addJsonObj("originalParent", edge.originalParent.identifier) } private def dispatch transferLayoutInt1(ElkLabel label) { @@ -723,18 +719,6 @@ final class JsonImporter { // don't care about the rest } - private def transferShapeLayout(ElkNode shape, Object jsonObjA) { - val jsonObj = jsonObjA.toJsonObject - // pos and dimension - jsonObj.addJsonObj("x", shape.x) - jsonObj.addJsonObj("y", shape.y) - jsonObj.addJsonObj("width", shape.width) - jsonObj.addJsonObj("height", shape.height) - - jsonObj.addJsonObj("x_g", nodeGlobalXMap.get(shape)) - jsonObj.addJsonObj("y_g", nodeGlobalYMap.get(shape)) - } - private def transferShapeLayout(ElkShape shape, Object jsonObjA) { val jsonObj = jsonObjA.toJsonObject // pos and dimension From 6d152c9f4e7bb5ca9a90900edc177311db4dc634 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sun, 25 Aug 2024 14:23:56 -0400 Subject: [PATCH 12/24] Revert incidental changes --- .gitignore | 3 --- .../org.eclipse.elk.targetplatform.target | 16 +++++++--------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 927b79c45..9381709b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.project .DS_Store *.class bin/ @@ -20,5 +19,3 @@ docs/static/img_gen/ docs/public org.eclipse.core.resources.prefs org.eclipse.m2e.core.prefs -build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target - diff --git a/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target b/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target index ee6064e20..304932db5 100644 --- a/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target +++ b/build/org.eclipse.elk.targetplatform/org.eclipse.elk.targetplatform.target @@ -1,21 +1,19 @@ - + - - - - - - - - + + + + + + From 1cdd8591fb3cc66b4e064cadfd825cca951fdf34 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Mon, 26 Aug 2024 12:03:22 -0400 Subject: [PATCH 13/24] Use static method instead of deprecated json parser constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Applying suggestions from code review) Co-authored-by: Sören Domrös --- .../src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend index b251128d2..6f9318b8f 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -141,8 +141,7 @@ class EdgeCoordsTest { val mode = p.get(0) val expectedString = p.get(1) - val parser = new JsonParser() - val jsonGraph = parser.parse(graph).asJsonObject + val jsonGraph = JsonParser.parseString(graph).asJsonObject jsonGraph.get("properties").asJsonObject.addProperty( "org.eclipse.elk.json.edgeCoords", mode @@ -166,7 +165,7 @@ class EdgeCoordsTest { val computedSec = edge2.get("sections").asJsonArray .get(0).asJsonObject - val expectedSec = parser.parse(expectedString).asJsonObject + val expectedSec = JsonParser.parseString(expectedString).asJsonObject assertEquals(expectedSec, computedSec) } From b1a02bcd070a7f67d5984d54593126911a225372 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Mon, 26 Aug 2024 12:04:39 -0400 Subject: [PATCH 14/24] Avoid NPE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (applying suggestion from code review) Co-authored-by: Sören Domrös --- .../src/org/eclipse/elk/graph/json/JsonImporter.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index e2c475f7a..85cdbc88e 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -580,10 +580,10 @@ final class JsonImporter { do { if (ancestor !== null) { ecm = ancestor.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT + ancestor = ancestor.getParent } else { ecm = EdgeCoords.CONTAINER } - ancestor = ancestor.getParent } while (ecm === EdgeCoords.INHERIT) return ecm } From 331b06add16434c68fff9e038bc1b7ff4fc734ba Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Mon, 26 Aug 2024 12:30:12 -0400 Subject: [PATCH 15/24] Remove additional cases of deprecated json parser constructor --- .../src/org/eclipse/elk/graph/json/test/SectionsTest.xtend | 7 +++---- .../eclipse/elk/graph/json/test/TransferLayoutTest.xtend | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/SectionsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/SectionsTest.xtend index 4528cf366..aa5d953b5 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/SectionsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/SectionsTest.xtend @@ -100,8 +100,7 @@ class SectionsTest { // string -> json - val parser = new JsonParser - val json = parser.parse(graph).asJsonObject + val json = JsonParser.parseString(graph).asJsonObject // json -> elk val importer = new Maybe() @@ -159,8 +158,8 @@ class SectionsTest { */ private def toJsonGraphAndBackToString(String graph) { // string -> json - val parser = new JsonParser - val json = parser.parse(graph).asJsonObject + val json = JsonParser.parseString(graph).asJsonObject + // json -> elk val importer = new Maybe() val root = ElkGraphJson.forGraph(json).rememberImporter(importer).toElk diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/TransferLayoutTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/TransferLayoutTest.xtend index 1c3def9db..08787d15b 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/TransferLayoutTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/TransferLayoutTest.xtend @@ -63,8 +63,7 @@ class TransferLayoutTest { @Test def void transferLayoutEdgeSectionExistsTest() { - val parser = new JsonParser() - val jsonGraph = parser.parse(graph).asJsonObject + val jsonGraph = JsonParser.parseString(graph).asJsonObject val mby = new Maybe val root = ElkGraphJson.forGraph(jsonGraph) From 8c03b807f82cbe35cef91c3232c5ca75e9d66e5d Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Tue, 27 Aug 2024 08:23:32 -0400 Subject: [PATCH 16/24] Use `PlainJavaInitialization` in test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sören Domrös --- .../org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend index 6f9318b8f..2741e457b 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -10,11 +10,13 @@ package org.eclipse.elk.graph.json.test import com.google.gson.JsonParser +import org.eclipse.elk.alg.test.PlainJavaInitialization import org.eclipse.elk.core.RecursiveGraphLayoutEngine import org.eclipse.elk.core.util.BasicProgressMonitor import org.eclipse.elk.core.util.Maybe import org.eclipse.elk.graph.json.ElkGraphJson import org.eclipse.elk.graph.json.JsonImporter +import org.junit.BeforeClass import org.junit.Test import static org.junit.Assert.* @@ -22,7 +24,11 @@ import static org.junit.Assert.* /** */ class EdgeCoordsTest { - + + @BeforeClass + static def void init() { + PlainJavaInitialization.initializePlainJavaLayout + } val graph = ''' { "id": "root", From 9a4d24fe7da6dee1a3557c6871f58abca1897248 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Wed, 28 Aug 2024 09:10:08 -0400 Subject: [PATCH 17/24] Determine edge coord modes more efficiently Now determined for each node only once, on the first traversal. --- .../eclipse/elk/graph/json/JsonImporter.xtend | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index 85cdbc88e..d8e9cb678 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -64,6 +64,7 @@ final class JsonImporter { val Map edgeOriginalParentMap = Maps.newHashMap val Map nodeGlobalXMap = Maps.newHashMap val Map nodeGlobalYMap = Maps.newHashMap + val Map nodeEdgeCoordsMap = Maps.newHashMap var Object inputModel @@ -107,6 +108,7 @@ final class JsonImporter { edgeOriginalParentMap.clear nodeGlobalXMap.clear nodeGlobalYMap.clear + nodeEdgeCoordsMap.clear } private def transformChildNodes(Object jsonNodeA, ElkNode parent) { @@ -560,6 +562,13 @@ final class JsonImporter { nodeGlobalXMap.put(node, node.x + dx) nodeGlobalYMap.put(node, node.y + dy) + // Determine coordinate mode in force at this node. + var ecm = node.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT + if (ecm === EdgeCoords.INHERIT) { + ecm = parent.edgeCoordsMode ?: EdgeCoords.CONTAINER + } + nodeEdgeCoordsMap.put(node, ecm) + // transfer positions and dimension node.transferShapeLayout(jsonObj) } @@ -574,20 +583,6 @@ final class JsonImporter { port.transferShapeLayout(jsonObj) } - private def EdgeCoords getEdgeCoordsMode(ElkNode node) { - var ancestor = node - var ecm = EdgeCoords.INHERIT - do { - if (ancestor !== null) { - ecm = ancestor.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT - ancestor = ancestor.getParent - } else { - ecm = EdgeCoords.CONTAINER - } - } while (ecm === EdgeCoords.INHERIT) - return ecm - } - private def Double adjustX(ElkEdge edge, EdgeCoords mode, Double x) { switch mode { case EdgeCoords.ROOT: x + edge.getContainingNode.globalX @@ -612,7 +607,7 @@ final class JsonImporter { val edgeId = jsonObj.id - val ecm = edge.originalParent.getEdgeCoordsMode + val ecm = edge.originalParent.edgeCoordsMode // what we need to transfer are the edge sections if (!edge.sections.nullOrEmpty) { @@ -793,5 +788,9 @@ final class JsonImporter { private def Double globalY(ElkNode node) { return nodeGlobalYMap.get(node) } + + private def EdgeCoords edgeCoordsMode(ElkNode node) { + return nodeEdgeCoordsMap.get(node) + } } From 5e581f5e3240ce1f3f20a105396b55bebc1836aa Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Wed, 28 Aug 2024 12:13:32 -0400 Subject: [PATCH 18/24] Simplify adjustment method signature Also move definitions down in file. --- .../eclipse/elk/graph/json/JsonImporter.xtend | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index d8e9cb678..fe1c17935 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -582,22 +582,6 @@ final class JsonImporter { // transfer positions and dimension port.transferShapeLayout(jsonObj) } - - private def Double adjustX(ElkEdge edge, EdgeCoords mode, Double x) { - switch mode { - case EdgeCoords.ROOT: x + edge.getContainingNode.globalX - case EdgeCoords.PARENT: x + edge.getContainingNode.globalX - edge.originalParent.globalX - default: x - } - } - - private def Double adjustY(ElkEdge edge, EdgeCoords mode, Double y) { - switch mode { - case EdgeCoords.ROOT: y + edge.getContainingNode.globalY - case EdgeCoords.PARENT: y + edge.getContainingNode.globalY - edge.originalParent.globalY - default: y - } - } private def dispatch transferLayoutInt2(ElkEdge edge) { val jsonObj = edgeJsonMap.get(edge).toJsonObject @@ -606,8 +590,6 @@ final class JsonImporter { } val edgeId = jsonObj.id - - val ecm = edge.originalParent.edgeCoordsMode // what we need to transfer are the edge sections if (!edge.sections.nullOrEmpty) { @@ -627,14 +609,14 @@ final class JsonImporter { // Start Point val startPoint = newJsonObject - startPoint.addJsonObj("x", edge.adjustX(ecm, elkSection.startX)) - startPoint.addJsonObj("y", edge.adjustY(ecm, elkSection.startY)) + startPoint.addJsonObj("x", edge.adjustX(elkSection.startX)) + startPoint.addJsonObj("y", edge.adjustY(elkSection.startY)) jsonSection.addJsonObj("startPoint", startPoint) // End Point val endPoint = newJsonObject - endPoint.addJsonObj("x", edge.adjustX(ecm, elkSection.endX)) - endPoint.addJsonObj("y", edge.adjustY(ecm, elkSection.endY)) + endPoint.addJsonObj("x", edge.adjustX(elkSection.endX)) + endPoint.addJsonObj("y", edge.adjustY(elkSection.endY)) jsonSection.addJsonObj("endPoint", endPoint) // Bend Points @@ -642,8 +624,8 @@ final class JsonImporter { val bendPoints = newJsonArray elkSection.bendPoints.forEach [ pnt | val jsonPnt = newJsonObject - jsonPnt.addJsonObj("x", edge.adjustX(ecm, pnt.x)) - jsonPnt.addJsonObj("y", edge.adjustY(ecm, pnt.y)) + jsonPnt.addJsonObj("x", edge.adjustX(pnt.x)) + jsonPnt.addJsonObj("y", edge.adjustY(pnt.y)) bendPoints.addJsonArr(jsonPnt) ] jsonSection.addJsonObj("bendPoints", bendPoints) @@ -688,8 +670,8 @@ final class JsonImporter { val jsonJPs = newJsonArray jps.forEach[ jp | val jsonPnt = newJsonObject - jsonPnt.addJsonObj("x", edge.adjustX(ecm, jp.x)) - jsonPnt.addJsonObj("y", edge.adjustY(ecm, jp.y)) + jsonPnt.addJsonObj("x", edge.adjustX(jp.x)) + jsonPnt.addJsonObj("y", edge.adjustY(jp.y)) jsonJPs.addJsonArr(jsonPnt) ] jsonObj.addJsonObj("junctionPoints", jsonJPs) @@ -723,6 +705,24 @@ final class JsonImporter { jsonObj.addJsonObj("height", shape.height) } + private def Double adjustX(ElkEdge edge, Double x) { + val mode = edge.originalParent.edgeCoordsMode + return switch mode { + case EdgeCoords.ROOT: x + edge.getContainingNode.globalX + case EdgeCoords.PARENT: x + edge.getContainingNode.globalX - edge.originalParent.globalX + default: x + } + } + + private def Double adjustY(ElkEdge edge, Double y) { + val mode = edge.originalParent.edgeCoordsMode + return switch mode { + case EdgeCoords.ROOT: y + edge.getContainingNode.globalY + case EdgeCoords.PARENT: y + edge.getContainingNode.globalY - edge.originalParent.globalY + default: y + } + } + private def dispatch idByElement(ElkNode node) { return nodeIdMap.inverse.get(node) } From 0f8a544867866a7353bb35cb11e4db9b8d80eb76 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Wed, 28 Aug 2024 12:14:36 -0400 Subject: [PATCH 19/24] Make edge labels use same coord system as edge --- .../eclipse/elk/graph/json/JsonImporter.xtend | 23 +++++-- .../elk/graph/json/test/EdgeCoordsTest.xtend | 65 +++++++++++++++---- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index fe1c17935..f6f2551ad 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -539,12 +539,13 @@ final class JsonImporter { * Transfer the layout back to the formerly imported graph, using {@link #transform(Object)}. */ def transferLayout(ElkNode graph) { - // First pass handles everything except edges, and computes global coordinates for all nodes. + // First pass handles nodes and ports, and + // determines global coordinates and coordinate modes for all nodes. ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | element.transferLayoutInt1 ] - // Second pass handles edges. + // Second pass handles edges and labels. ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | element.transferLayoutInt2 ] @@ -681,11 +682,12 @@ final class JsonImporter { jsonObj.addJsonObj("container", edge.getContainingNode.identifier) } - private def dispatch transferLayoutInt1(ElkLabel label) { + private def dispatch transferLayoutInt2(ElkLabel label) { val jsonObj = labelJsonMap.get(label) // transfer positions and dimension - label.transferShapeLayout(jsonObj) + val parent = label.getParent + parent.transferLabelLayout(label, jsonObj) } private def dispatch transferLayoutInt1(Object obj) { @@ -705,6 +707,19 @@ final class JsonImporter { jsonObj.addJsonObj("height", shape.height) } + private def dispatch transferLabelLayout(ElkEdge edge, ElkLabel label, Object jsonObjA) { + val jsonObj = jsonObjA.toJsonObject + // pos and dimension + jsonObj.addJsonObj("x", edge.adjustX(label.x)) + jsonObj.addJsonObj("y", edge.adjustY(label.y)) + jsonObj.addJsonObj("width", label.width) + jsonObj.addJsonObj("height", label.height) + } + + private def dispatch transferLabelLayout(ElkGraphElement element, ElkLabel label, Object jsonObjA) { + transferShapeLayout(label, jsonObjA) + } + private def Double adjustX(ElkEdge edge, Double x) { val mode = edge.originalParent.edgeCoordsMode return switch mode { diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend index 2741e457b..ee0171257 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -47,7 +47,11 @@ class EdgeCoordsTest { ], "edges": [ { "id": "e1", "sources": [ "y" ], "targets": [ "z" ] }, - { "id": "e2", "sources": [ "x" ], "targets": [ "z" ] } + { "id": "e2", "sources": [ "x" ], "targets": [ "z" ], + "labels": [ + { "text": "Foo", "width": 30, "height": 12 } + ] + } ] } ] @@ -64,16 +68,16 @@ class EdgeCoordsTest { "y": 124 }, "endPoint": { - "x": 169, + "x": 219, "y": 99 }, "bendPoints": [ { - "x": 159, + "x": 209, "y": 124 }, { - "x": 159, + "x": 209, "y": 99 } ], @@ -82,11 +86,21 @@ class EdgeCoordsTest { } ''' + val labelContainer = ''' + { + "text": "Foo", + "width": 30, + "height": 12, + "x": 82, + "y": 127 + } + ''' + val secParent = ''' { "id": "e2_s0", "startPoint": { - "x": -25, + "x": -75, "y": 112 }, "endPoint": { @@ -108,6 +122,16 @@ class EdgeCoordsTest { } ''' + val labelParent = ''' + { + "text": "Foo", + "width": 30, + "height": 12, + "x": -55, + "y": 115 + } + ''' + val secRoot = ''' { "id": "e2_s0", @@ -116,16 +140,16 @@ class EdgeCoordsTest { "y": 136 }, "endPoint": { - "x": 181, + "x": 231, "y": 111 }, "bendPoints": [ { - "x": 171, + "x": 221, "y": 136 }, { - "x": 171, + "x": 221, "y": 111 } ], @@ -133,19 +157,30 @@ class EdgeCoordsTest { "outgoingShape": "z" } ''' + + val labelRoot = ''' + { + "text": "Foo", + "width": 30, + "height": 12, + "x": 94, + "y": 139 + } + ''' @Test def void edgeCoordsTest() { val cases = #[ - #['CONTAINER', secContainer], - #['PARENT', secParent], - #['ROOT', secRoot] + #['CONTAINER', secContainer, labelContainer], + #['PARENT', secParent, labelParent], + #['ROOT', secRoot, labelRoot] ] for (p : cases) { val mode = p.get(0) - val expectedString = p.get(1) + val expectedSecString = p.get(1) + val expectedLabelString = p.get(2) val jsonGraph = JsonParser.parseString(graph).asJsonObject @@ -170,10 +205,14 @@ class EdgeCoordsTest { .get(1).asJsonObject val computedSec = edge2.get("sections").asJsonArray .get(0).asJsonObject + val computedLabel = edge2.get("labels").asJsonArray + .get(0).asJsonObject - val expectedSec = JsonParser.parseString(expectedString).asJsonObject + val expectedSec = JsonParser.parseString(expectedSecString).asJsonObject + val expectedLabel = JsonParser.parseString(expectedLabelString).asJsonObject assertEquals(expectedSec, computedSec) + assertEquals(expectedLabel, computedLabel) } } From 81e082bddf432938be286fa1fd36ad86a985bbe2 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Wed, 28 Aug 2024 12:42:33 -0400 Subject: [PATCH 20/24] Add EOF newline --- .../src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend index ee0171257..e2e77b416 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -217,4 +217,4 @@ class EdgeCoordsTest { } -} \ No newline at end of file +} From 52fed6dbe4e33ee3323de5bb1f77aa509e5f1565 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Thu, 29 Aug 2024 08:17:05 -0400 Subject: [PATCH 21/24] Support `org.eclipse.elk.json.shapeCoords` property --- .../src/org/eclipse/elk/core/Core.melk | 8 + .../eclipse/elk/core/options/EdgeCoords.java | 5 +- .../eclipse/elk/core/options/ShapeCoords.java | 28 + .../eclipse/elk/graph/json/JsonImporter.xtend | 151 +- .../elk/graph/json/test/EdgeCoordsTest.xtend | 1450 +++++++++++++++-- 5 files changed, 1449 insertions(+), 193 deletions(-) create mode 100644 plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/ShapeCoords.java diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk index e1a39ce9a..37602fe8a 100644 --- a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk @@ -305,6 +305,14 @@ advanced option omitNodeMicroLayout: boolean { // --- JSON group json { + option shapeCoords: ShapeCoords { + label "Shape Coords" + description + "Specify the coordinate system for shapes." + default = ShapeCoords.INHERIT + targets parents + } + option edgeCoords: EdgeCoords { label "Edge Coords" description diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java index b69783e2a..b2945e8fe 100644 --- a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/EdgeCoords.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2015 Kiel University and others. + * Copyright (c) 2024 Kiel University and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -10,7 +10,8 @@ package org.eclipse.elk.core.options; /** - * Definition of edge coordinate systems. To be accessed using {@link CoreOptions#JSON_EDGE_COORDS}. + * Edge coordinate systems for JSON output. To be accessed using {@link CoreOptions#JSON_EDGE_COORDS}. + * Applies to edges, and to labels of edges. */ public enum EdgeCoords { diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/ShapeCoords.java b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/ShapeCoords.java new file mode 100644 index 000000000..4d776bce6 --- /dev/null +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/options/ShapeCoords.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2024 Kiel University and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.elk.core.options; + +/** + * Shape coordinate systems for JSON output. To be accessed using {@link CoreOptions#JSON_SHAPE_COORDS}. + * Applies to nodes, ports, and labels of nodes and ports. + */ +public enum ShapeCoords { + + /** + * Inherit the parent shape's coordinate system. The root node has no parent shape; here, this setting defaults to + * {@link #PARENT}. + */ + INHERIT, + /** relative to the shape's JSON parent shape. */ + PARENT, + /** relative to the root node, a.k.a. global coordinates. */ + ROOT; + +} diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index f6f2551ad..cfab6696b 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -18,6 +18,7 @@ import java.util.Map import org.eclipse.elk.core.data.LayoutMetaDataService import org.eclipse.elk.core.options.CoreOptions import org.eclipse.elk.core.options.EdgeCoords +import org.eclipse.elk.core.options.ShapeCoords import org.eclipse.elk.core.util.IndividualSpacings import org.eclipse.elk.graph.ElkEdge import org.eclipse.elk.graph.ElkEdgeSection @@ -62,9 +63,10 @@ final class JsonImporter { /* Maps to help in adjusting coordinates */ val Map edgeOriginalParentMap = Maps.newHashMap - val Map nodeGlobalXMap = Maps.newHashMap - val Map nodeGlobalYMap = Maps.newHashMap - val Map nodeEdgeCoordsMap = Maps.newHashMap + val Map globalXMap = Maps.newHashMap + val Map globalYMap = Maps.newHashMap + val Map shapeCoordsMap = Maps.newHashMap + val Map edgeCoordsMap = Maps.newHashMap var Object inputModel @@ -106,9 +108,10 @@ final class JsonImporter { labelJsonMap.clear edgeOriginalParentMap.clear - nodeGlobalXMap.clear - nodeGlobalYMap.clear - nodeEdgeCoordsMap.clear + globalXMap.clear + globalYMap.clear + shapeCoordsMap.clear + edgeCoordsMap.clear } private def transformChildNodes(Object jsonNodeA, ElkNode parent) { @@ -539,8 +542,7 @@ final class JsonImporter { * Transfer the layout back to the formerly imported graph, using {@link #transform(Object)}. */ def transferLayout(ElkNode graph) { - // First pass handles nodes and ports, and - // determines global coordinates and coordinate modes for all nodes. + // First pass handles nodes and ports. ElkGraphUtil.propertiesSkippingIteratorFor(graph, true).forEach [ element | element.transferLayoutInt1 ] @@ -556,21 +558,8 @@ final class JsonImporter { if (jsonObj === null) { throw formatError("Node did not exist in input.") } - // Compute global coordinates, to support coordinate adjustments. - val parent = node.getParent - val dx = parent.globalX ?: 0 - val dy = parent.globalY ?: 0 - nodeGlobalXMap.put(node, node.x + dx) - nodeGlobalYMap.put(node, node.y + dy) - - // Determine coordinate mode in force at this node. - var ecm = node.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT - if (ecm === EdgeCoords.INHERIT) { - ecm = parent.edgeCoordsMode ?: EdgeCoords.CONTAINER - } - nodeEdgeCoordsMap.put(node, ecm) - - // transfer positions and dimension + node.recordGlobalCoords + node.recordCoordinateModes node.transferShapeLayout(jsonObj) } @@ -579,8 +568,8 @@ final class JsonImporter { if (jsonObj === null) { throw formatError("Port did not exist in input.") } - - // transfer positions and dimension + port.recordGlobalCoords + port.recordCoordinateModes port.transferShapeLayout(jsonObj) } @@ -590,6 +579,8 @@ final class JsonImporter { throw formatError("Edge did not exist in input.") } + edge.recordCoordinateModes + val edgeId = jsonObj.id // what we need to transfer are the edge sections @@ -684,10 +675,9 @@ final class JsonImporter { private def dispatch transferLayoutInt2(ElkLabel label) { val jsonObj = labelJsonMap.get(label) - - // transfer positions and dimension - val parent = label.getParent - parent.transferLabelLayout(label, jsonObj) + label.recordGlobalCoords + label.recordCoordinateModes + label.transferShapeLayout(jsonObj) } private def dispatch transferLayoutInt1(Object obj) { @@ -700,27 +690,15 @@ final class JsonImporter { private def transferShapeLayout(ElkShape shape, Object jsonObjA) { val jsonObj = jsonObjA.toJsonObject + val parent = shape.jsonParent // pos and dimension - jsonObj.addJsonObj("x", shape.x) - jsonObj.addJsonObj("y", shape.y) + jsonObj.addJsonObj("x", parent?.adjustX(shape.x) ?: shape.x) + jsonObj.addJsonObj("y", parent?.adjustY(shape.y) ?: shape.y) jsonObj.addJsonObj("width", shape.width) jsonObj.addJsonObj("height", shape.height) } - private def dispatch transferLabelLayout(ElkEdge edge, ElkLabel label, Object jsonObjA) { - val jsonObj = jsonObjA.toJsonObject - // pos and dimension - jsonObj.addJsonObj("x", edge.adjustX(label.x)) - jsonObj.addJsonObj("y", edge.adjustY(label.y)) - jsonObj.addJsonObj("width", label.width) - jsonObj.addJsonObj("height", label.height) - } - - private def dispatch transferLabelLayout(ElkGraphElement element, ElkLabel label, Object jsonObjA) { - transferShapeLayout(label, jsonObjA) - } - - private def Double adjustX(ElkEdge edge, Double x) { + private def dispatch Double adjustX(ElkEdge edge, Double x) { val mode = edge.originalParent.edgeCoordsMode return switch mode { case EdgeCoords.ROOT: x + edge.getContainingNode.globalX @@ -729,7 +707,15 @@ final class JsonImporter { } } - private def Double adjustY(ElkEdge edge, Double y) { + private def dispatch Double adjustX(ElkShape shape, Double x) { + val mode = shape.shapeCoordsMode + return switch mode { + case ShapeCoords.ROOT: x + shape.globalX + default: x + } + } + + private def dispatch Double adjustY(ElkEdge edge, Double y) { val mode = edge.originalParent.edgeCoordsMode return switch mode { case EdgeCoords.ROOT: y + edge.getContainingNode.globalY @@ -738,6 +724,14 @@ final class JsonImporter { } } + private def dispatch Double adjustY(ElkShape shape, Double y) { + val mode = shape.shapeCoordsMode + return switch mode { + case ShapeCoords.ROOT: y + shape.globalY + default: y + } + } + private def dispatch idByElement(ElkNode node) { return nodeIdMap.inverse.get(node) } @@ -792,20 +786,73 @@ final class JsonImporter { return edgeSection } + private def recordCoordinateModes(ElkGraphElement element) { + val parent = element.jsonParent + + var scm = element.getProperty(CoreOptions.JSON_SHAPE_COORDS) ?: ShapeCoords.INHERIT + if (scm === ShapeCoords.INHERIT) { + scm = parent.shapeCoordsMode ?: ShapeCoords.PARENT + } + shapeCoordsMap.put(element, scm) + + var ecm = element.getProperty(CoreOptions.JSON_EDGE_COORDS) ?: EdgeCoords.INHERIT + if (ecm === EdgeCoords.INHERIT) { + ecm = parent.edgeCoordsMode ?: EdgeCoords.CONTAINER + } + edgeCoordsMap.put(element, ecm) + } + + private def recordGlobalCoords(ElkShape shape) { + val parent = shape.jsonParent + val shapeA = parent?.shapeAncestor + val dx = shapeA.globalX ?: 0 + val dy = shapeA.globalY ?: 0 + globalXMap.put(shape, shape.x + dx) + globalYMap.put(shape, shape.y + dy) + } + + private def dispatch shapeAncestor(ElkEdge edge) { + return edge.originalParent + } + + private def dispatch shapeAncestor(ElkShape shape) { + return shape + } + + private def dispatch jsonParent(ElkNode node) { + return node.getParent + } + + private def dispatch jsonParent(ElkPort port) { + return port.getParent + } + + private def dispatch jsonParent(ElkEdge edge) { + return edge.originalParent + } + + private def dispatch jsonParent(ElkLabel label) { + return label.getParent + } + private def ElkNode originalParent(ElkEdge edge) { return edgeOriginalParentMap.get(edge) } - private def Double globalX(ElkNode node) { - return nodeGlobalXMap.get(node) + private def Double globalX(ElkShape shape) { + return globalXMap.get(shape) + } + + private def Double globalY(ElkShape shape) { + return globalYMap.get(shape) } - private def Double globalY(ElkNode node) { - return nodeGlobalYMap.get(node) + private def ShapeCoords shapeCoordsMode(ElkGraphElement element) { + return shapeCoordsMap.get(element) } - private def EdgeCoords edgeCoordsMode(ElkNode node) { - return nodeEdgeCoordsMap.get(node) + private def EdgeCoords edgeCoordsMode(ElkGraphElement element) { + return edgeCoordsMap.get(element) } } diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend index e2e77b416..7945510cd 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -38,183 +38,1355 @@ class EdgeCoordsTest { }, "children": [ { "id": "A", - "children": [ - { "id": "x", "width": 50, "height": 90 }, - { "id": "B", - "children": [ - { "id": "y", "width": 50, "height": 90 }, - { "id": "z", "width": 50, "height": 90 } - ], - "edges": [ - { "id": "e1", "sources": [ "y" ], "targets": [ "z" ] }, - { "id": "e2", "sources": [ "x" ], "targets": [ "z" ], - "labels": [ - { "text": "Foo", "width": 30, "height": 12 } - ] - } - ] - } - ] + "children": [ + { "id": "x", "width": 50, "height": 90 }, + { "id": "B", + "labels": [ { "text": "B", "width": 10, "height": 12 } ], + "ports": [ + { "id": "p", "width": 10, "height": 10, + "labels": [ { "text": "p", "width": 10, "height": 12 } ] + } + ], + "children": [ + { "id": "y", "width": 50, "height": 90 }, + { "id": "z", "width": 50, "height": 90 } + ], + "edges": [ + { "id": "e1", "sources": [ "y" ], "targets": [ "z" ] }, + { "id": "e2", "sources": [ "x" ], "targets": [ "z" ], + "labels": [ { "text": "e2", "width": 20, "height": 12 } ] + }, + { "id": "e3", "sources": [ "x" ], "targets": [ "p" ] }, + { "id": "e4", "sources": [ "p" ], "targets": [ "y" ] } + ] + } + ] } ] } ''' + + @Test + def void edgeCoordsTest() { + + val cases = #[ + #['PARENT', 'CONTAINER', outputPC], + #['PARENT', 'PARENT', outputPP], + #['PARENT', 'ROOT', outputPR], + #['ROOT', 'CONTAINER', outputRC], + #['ROOT', 'PARENT', outputRP], + #['ROOT', 'ROOT', outputRR] + ] + + for (c : cases) { + val scm = c.get(0) + val ecm = c.get(1) + val expectedOutputString = c.get(2) + + val jsonGraph = JsonParser.parseString(graph).asJsonObject + + val obj = jsonGraph.get("properties").asJsonObject + obj.addProperty( + "org.eclipse.elk.json.shapeCoords", scm + ) + obj.addProperty( + "org.eclipse.elk.json.edgeCoords", ecm + ) + + val mby = new Maybe + val root = ElkGraphJson.forGraph(jsonGraph) + .rememberImporter(mby) + .toElk + + new RecursiveGraphLayoutEngine().layout(root, new BasicProgressMonitor) + + mby.get.transferLayout(root) + + val computedOutput = jsonGraph.asJsonObject + + val expectedOutput = JsonParser.parseString(expectedOutputString).asJsonObject + + assertEquals(expectedOutput, computedOutput) + } + + } - val secContainer = ''' + val outputPC = ''' { - "id": "e2_s0", - "startPoint": { - "x": 62, - "y": 124 - }, - "endPoint": { - "x": 219, - "y": 99 + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN", + "org.eclipse.elk.json.shapeCoords": "PARENT", + "org.eclipse.elk.json.edgeCoords": "CONTAINER" }, - "bendPoints": [ + "children": [ { - "x": 209, - "y": 124 - }, - { - "x": 209, - "y": 99 + "id": "A", + "children": [ + { + "id": "x", + "width": 50, + "height": 90, + "x": 12, + "y": 39 + }, + { + "id": "B", + "labels": [ + { + "text": "B", + "width": 10, + "height": 12, + "x": 0, + "y": 0 + } + ], + "ports": [ + { + "id": "p", + "width": 10, + "height": 10, + "labels": [ + { + "text": "p", + "width": 10, + "height": 12, + "x": -11, + "y": -13 + } + ], + "x": -10, + "y": 52 + } + ], + "children": [ + { + "id": "y", + "width": 50, + "height": 90, + "x": 12, + "y": 12 + }, + { + "id": "z", + "width": 50, + "height": 90, + "x": 82, + "y": 27 + } + ], + "edges": [ + { + "id": "e1", + "sources": [ + "y" + ], + "targets": [ + "z" + ], + "sections": [ + { + "id": "e1_s0", + "startPoint": { + "x": 62, + "y": 57 + }, + "endPoint": { + "x": 82, + "y": 57 + }, + "incomingShape": "y", + "outgoingShape": "z" + } + ], + "container": "B" + }, + { + "id": "e2", + "sources": [ + "x" + ], + "targets": [ + "z" + ], + "labels": [ + { + "text": "e2", + "width": 20, + "height": 12, + "x": 82, + "y": 102 + } + ], + "sections": [ + { + "id": "e2_s0", + "startPoint": { + "x": 62, + "y": 99 + }, + "endPoint": { + "x": 225, + "y": 99 + }, + "bendPoints": [ + { + "x": 112, + "y": 99 + }, + { + "x": 112, + "y": 124 + }, + { + "x": 215, + "y": 124 + }, + { + "x": 215, + "y": 99 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ], + "container": "A" + }, + { + "id": "e3", + "sources": [ + "x" + ], + "targets": [ + "p" + ], + "sections": [ + { + "id": "e3_s0", + "startPoint": { + "x": 62, + "y": 69 + }, + "endPoint": { + "x": 133, + "y": 69 + }, + "incomingShape": "x", + "outgoingShape": "p" + } + ], + "container": "A" + }, + { + "id": "e4", + "sources": [ + "p" + ], + "targets": [ + "y" + ], + "sections": [ + { + "id": "e4_s0", + "startPoint": { + "x": -10, + "y": 57 + }, + "endPoint": { + "x": 12, + "y": 57 + }, + "incomingShape": "p", + "outgoingShape": "y" + } + ], + "container": "B" + } + ], + "x": 143, + "y": 12, + "width": 144, + "height": 129 + } + ], + "x": 12, + "y": 12, + "width": 299, + "height": 153 } ], - "incomingShape": "x", - "outgoingShape": "z" + "x": 0, + "y": 0, + "width": 323, + "height": 177 } ''' - val labelContainer = ''' + val outputPP = ''' { - "text": "Foo", - "width": 30, - "height": 12, - "x": 82, - "y": 127 + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN", + "org.eclipse.elk.json.shapeCoords": "PARENT", + "org.eclipse.elk.json.edgeCoords": "PARENT" + }, + "children": [ + { + "id": "A", + "children": [ + { + "id": "x", + "width": 50, + "height": 90, + "x": 12, + "y": 39 + }, + { + "id": "B", + "labels": [ + { + "text": "B", + "width": 10, + "height": 12, + "x": 0, + "y": 0 + } + ], + "ports": [ + { + "id": "p", + "width": 10, + "height": 10, + "labels": [ + { + "text": "p", + "width": 10, + "height": 12, + "x": -11, + "y": -13 + } + ], + "x": -10, + "y": 52 + } + ], + "children": [ + { + "id": "y", + "width": 50, + "height": 90, + "x": 12, + "y": 12 + }, + { + "id": "z", + "width": 50, + "height": 90, + "x": 82, + "y": 27 + } + ], + "edges": [ + { + "id": "e1", + "sources": [ + "y" + ], + "targets": [ + "z" + ], + "sections": [ + { + "id": "e1_s0", + "startPoint": { + "x": 62, + "y": 57 + }, + "endPoint": { + "x": 82, + "y": 57 + }, + "incomingShape": "y", + "outgoingShape": "z" + } + ], + "container": "B" + }, + { + "id": "e2", + "sources": [ + "x" + ], + "targets": [ + "z" + ], + "labels": [ + { + "text": "e2", + "width": 20, + "height": 12, + "x": -61, + "y": 90 + } + ], + "sections": [ + { + "id": "e2_s0", + "startPoint": { + "x": -81, + "y": 87 + }, + "endPoint": { + "x": 82, + "y": 87 + }, + "bendPoints": [ + { + "x": -31, + "y": 87 + }, + { + "x": -31, + "y": 112 + }, + { + "x": 72, + "y": 112 + }, + { + "x": 72, + "y": 87 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ], + "container": "A" + }, + { + "id": "e3", + "sources": [ + "x" + ], + "targets": [ + "p" + ], + "sections": [ + { + "id": "e3_s0", + "startPoint": { + "x": -81, + "y": 57 + }, + "endPoint": { + "x": -10, + "y": 57 + }, + "incomingShape": "x", + "outgoingShape": "p" + } + ], + "container": "A" + }, + { + "id": "e4", + "sources": [ + "p" + ], + "targets": [ + "y" + ], + "sections": [ + { + "id": "e4_s0", + "startPoint": { + "x": -10, + "y": 57 + }, + "endPoint": { + "x": 12, + "y": 57 + }, + "incomingShape": "p", + "outgoingShape": "y" + } + ], + "container": "B" + } + ], + "x": 143, + "y": 12, + "width": 144, + "height": 129 + } + ], + "x": 12, + "y": 12, + "width": 299, + "height": 153 + } + ], + "x": 0, + "y": 0, + "width": 323, + "height": 177 } ''' - val secParent = ''' + val outputPR = ''' { - "id": "e2_s0", - "startPoint": { - "x": -75, - "y": 112 - }, - "endPoint": { - "x": 82, - "y": 87 + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN", + "org.eclipse.elk.json.shapeCoords": "PARENT", + "org.eclipse.elk.json.edgeCoords": "ROOT" }, - "bendPoints": [ + "children": [ { - "x": 72, - "y": 112 - }, - { - "x": 72, - "y": 87 + "id": "A", + "children": [ + { + "id": "x", + "width": 50, + "height": 90, + "x": 12, + "y": 39 + }, + { + "id": "B", + "labels": [ + { + "text": "B", + "width": 10, + "height": 12, + "x": 0, + "y": 0 + } + ], + "ports": [ + { + "id": "p", + "width": 10, + "height": 10, + "labels": [ + { + "text": "p", + "width": 10, + "height": 12, + "x": -11, + "y": -13 + } + ], + "x": -10, + "y": 52 + } + ], + "children": [ + { + "id": "y", + "width": 50, + "height": 90, + "x": 12, + "y": 12 + }, + { + "id": "z", + "width": 50, + "height": 90, + "x": 82, + "y": 27 + } + ], + "edges": [ + { + "id": "e1", + "sources": [ + "y" + ], + "targets": [ + "z" + ], + "sections": [ + { + "id": "e1_s0", + "startPoint": { + "x": 217, + "y": 81 + }, + "endPoint": { + "x": 237, + "y": 81 + }, + "incomingShape": "y", + "outgoingShape": "z" + } + ], + "container": "B" + }, + { + "id": "e2", + "sources": [ + "x" + ], + "targets": [ + "z" + ], + "labels": [ + { + "text": "e2", + "width": 20, + "height": 12, + "x": 94, + "y": 114 + } + ], + "sections": [ + { + "id": "e2_s0", + "startPoint": { + "x": 74, + "y": 111 + }, + "endPoint": { + "x": 237, + "y": 111 + }, + "bendPoints": [ + { + "x": 124, + "y": 111 + }, + { + "x": 124, + "y": 136 + }, + { + "x": 227, + "y": 136 + }, + { + "x": 227, + "y": 111 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ], + "container": "A" + }, + { + "id": "e3", + "sources": [ + "x" + ], + "targets": [ + "p" + ], + "sections": [ + { + "id": "e3_s0", + "startPoint": { + "x": 74, + "y": 81 + }, + "endPoint": { + "x": 145, + "y": 81 + }, + "incomingShape": "x", + "outgoingShape": "p" + } + ], + "container": "A" + }, + { + "id": "e4", + "sources": [ + "p" + ], + "targets": [ + "y" + ], + "sections": [ + { + "id": "e4_s0", + "startPoint": { + "x": 145, + "y": 81 + }, + "endPoint": { + "x": 167, + "y": 81 + }, + "incomingShape": "p", + "outgoingShape": "y" + } + ], + "container": "B" + } + ], + "x": 143, + "y": 12, + "width": 144, + "height": 129 + } + ], + "x": 12, + "y": 12, + "width": 299, + "height": 153 } ], - "incomingShape": "x", - "outgoingShape": "z" + "x": 0, + "y": 0, + "width": 323, + "height": 177 } ''' - val labelParent = ''' + val outputRC = ''' { - "text": "Foo", - "width": 30, - "height": 12, - "x": -55, - "y": 115 + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN", + "org.eclipse.elk.json.shapeCoords": "ROOT", + "org.eclipse.elk.json.edgeCoords": "CONTAINER" + }, + "children": [ + { + "id": "A", + "children": [ + { + "id": "x", + "width": 50, + "height": 90, + "x": 24, + "y": 51 + }, + { + "id": "B", + "labels": [ + { + "text": "B", + "width": 10, + "height": 12, + "x": 155, + "y": 24 + } + ], + "ports": [ + { + "id": "p", + "width": 10, + "height": 10, + "labels": [ + { + "text": "p", + "width": 10, + "height": 12, + "x": 134, + "y": 63 + } + ], + "x": 145, + "y": 76 + } + ], + "children": [ + { + "id": "y", + "width": 50, + "height": 90, + "x": 167, + "y": 36 + }, + { + "id": "z", + "width": 50, + "height": 90, + "x": 237, + "y": 51 + } + ], + "edges": [ + { + "id": "e1", + "sources": [ + "y" + ], + "targets": [ + "z" + ], + "sections": [ + { + "id": "e1_s0", + "startPoint": { + "x": 62, + "y": 57 + }, + "endPoint": { + "x": 82, + "y": 57 + }, + "incomingShape": "y", + "outgoingShape": "z" + } + ], + "container": "B" + }, + { + "id": "e2", + "sources": [ + "x" + ], + "targets": [ + "z" + ], + "labels": [ + { + "text": "e2", + "width": 20, + "height": 12, + "x": 82, + "y": 102 + } + ], + "sections": [ + { + "id": "e2_s0", + "startPoint": { + "x": 62, + "y": 99 + }, + "endPoint": { + "x": 225, + "y": 99 + }, + "bendPoints": [ + { + "x": 112, + "y": 99 + }, + { + "x": 112, + "y": 124 + }, + { + "x": 215, + "y": 124 + }, + { + "x": 215, + "y": 99 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ], + "container": "A" + }, + { + "id": "e3", + "sources": [ + "x" + ], + "targets": [ + "p" + ], + "sections": [ + { + "id": "e3_s0", + "startPoint": { + "x": 62, + "y": 69 + }, + "endPoint": { + "x": 133, + "y": 69 + }, + "incomingShape": "x", + "outgoingShape": "p" + } + ], + "container": "A" + }, + { + "id": "e4", + "sources": [ + "p" + ], + "targets": [ + "y" + ], + "sections": [ + { + "id": "e4_s0", + "startPoint": { + "x": -10, + "y": 57 + }, + "endPoint": { + "x": 12, + "y": 57 + }, + "incomingShape": "p", + "outgoingShape": "y" + } + ], + "container": "B" + } + ], + "x": 155, + "y": 24, + "width": 144, + "height": 129 + } + ], + "x": 12, + "y": 12, + "width": 299, + "height": 153 + } + ], + "x": 0, + "y": 0, + "width": 323, + "height": 177 } ''' - val secRoot = ''' + val outputRP = ''' { - "id": "e2_s0", - "startPoint": { - "x": 74, - "y": 136 + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN", + "org.eclipse.elk.json.shapeCoords": "ROOT", + "org.eclipse.elk.json.edgeCoords": "PARENT" }, - "endPoint": { - "x": 231, - "y": 111 - }, - "bendPoints": [ - { - "x": 221, - "y": 136 - }, + "children": [ { - "x": 221, - "y": 111 + "id": "A", + "children": [ + { + "id": "x", + "width": 50, + "height": 90, + "x": 24, + "y": 51 + }, + { + "id": "B", + "labels": [ + { + "text": "B", + "width": 10, + "height": 12, + "x": 155, + "y": 24 + } + ], + "ports": [ + { + "id": "p", + "width": 10, + "height": 10, + "labels": [ + { + "text": "p", + "width": 10, + "height": 12, + "x": 134, + "y": 63 + } + ], + "x": 145, + "y": 76 + } + ], + "children": [ + { + "id": "y", + "width": 50, + "height": 90, + "x": 167, + "y": 36 + }, + { + "id": "z", + "width": 50, + "height": 90, + "x": 237, + "y": 51 + } + ], + "edges": [ + { + "id": "e1", + "sources": [ + "y" + ], + "targets": [ + "z" + ], + "sections": [ + { + "id": "e1_s0", + "startPoint": { + "x": 62, + "y": 57 + }, + "endPoint": { + "x": 82, + "y": 57 + }, + "incomingShape": "y", + "outgoingShape": "z" + } + ], + "container": "B" + }, + { + "id": "e2", + "sources": [ + "x" + ], + "targets": [ + "z" + ], + "labels": [ + { + "text": "e2", + "width": 20, + "height": 12, + "x": -61, + "y": 90 + } + ], + "sections": [ + { + "id": "e2_s0", + "startPoint": { + "x": -81, + "y": 87 + }, + "endPoint": { + "x": 82, + "y": 87 + }, + "bendPoints": [ + { + "x": -31, + "y": 87 + }, + { + "x": -31, + "y": 112 + }, + { + "x": 72, + "y": 112 + }, + { + "x": 72, + "y": 87 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ], + "container": "A" + }, + { + "id": "e3", + "sources": [ + "x" + ], + "targets": [ + "p" + ], + "sections": [ + { + "id": "e3_s0", + "startPoint": { + "x": -81, + "y": 57 + }, + "endPoint": { + "x": -10, + "y": 57 + }, + "incomingShape": "x", + "outgoingShape": "p" + } + ], + "container": "A" + }, + { + "id": "e4", + "sources": [ + "p" + ], + "targets": [ + "y" + ], + "sections": [ + { + "id": "e4_s0", + "startPoint": { + "x": -10, + "y": 57 + }, + "endPoint": { + "x": 12, + "y": 57 + }, + "incomingShape": "p", + "outgoingShape": "y" + } + ], + "container": "B" + } + ], + "x": 155, + "y": 24, + "width": 144, + "height": 129 + } + ], + "x": 12, + "y": 12, + "width": 299, + "height": 153 } ], - "incomingShape": "x", - "outgoingShape": "z" + "x": 0, + "y": 0, + "width": 323, + "height": 177 } + ''' - val labelRoot = ''' + val outputRR = ''' { - "text": "Foo", - "width": 30, - "height": 12, - "x": 94, - "y": 139 - } - ''' - - @Test - def void edgeCoordsTest() { - - val cases = #[ - #['CONTAINER', secContainer, labelContainer], - #['PARENT', secParent, labelParent], - #['ROOT', secRoot, labelRoot] - ] - - for (p : cases) { - val mode = p.get(0) - val expectedSecString = p.get(1) - val expectedLabelString = p.get(2) - - val jsonGraph = JsonParser.parseString(graph).asJsonObject - - jsonGraph.get("properties").asJsonObject.addProperty( - "org.eclipse.elk.json.edgeCoords", mode - ) - - val mby = new Maybe - val root = ElkGraphJson.forGraph(jsonGraph) - .rememberImporter(mby) - .toElk - - new RecursiveGraphLayoutEngine().layout(root, new BasicProgressMonitor) - - mby.get.transferLayout(root) - - val nodeB = jsonGraph.get("children").asJsonArray - .get(0).asJsonObject - .get("children").asJsonArray - .get(1).asJsonObject - val edge2 = nodeB.get("edges").asJsonArray - .get(1).asJsonObject - val computedSec = edge2.get("sections").asJsonArray - .get(0).asJsonObject - val computedLabel = edge2.get("labels").asJsonArray - .get(0).asJsonObject - - val expectedSec = JsonParser.parseString(expectedSecString).asJsonObject - val expectedLabel = JsonParser.parseString(expectedLabelString).asJsonObject - - assertEquals(expectedSec, computedSec) - assertEquals(expectedLabel, computedLabel) + "id": "root", + "properties": { + "algorithm": "layered", + "org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN", + "org.eclipse.elk.json.shapeCoords": "ROOT", + "org.eclipse.elk.json.edgeCoords": "ROOT" + }, + "children": [ + { + "id": "A", + "children": [ + { + "id": "x", + "width": 50, + "height": 90, + "x": 24, + "y": 51 + }, + { + "id": "B", + "labels": [ + { + "text": "B", + "width": 10, + "height": 12, + "x": 155, + "y": 24 + } + ], + "ports": [ + { + "id": "p", + "width": 10, + "height": 10, + "labels": [ + { + "text": "p", + "width": 10, + "height": 12, + "x": 134, + "y": 63 + } + ], + "x": 145, + "y": 76 + } + ], + "children": [ + { + "id": "y", + "width": 50, + "height": 90, + "x": 167, + "y": 36 + }, + { + "id": "z", + "width": 50, + "height": 90, + "x": 237, + "y": 51 + } + ], + "edges": [ + { + "id": "e1", + "sources": [ + "y" + ], + "targets": [ + "z" + ], + "sections": [ + { + "id": "e1_s0", + "startPoint": { + "x": 217, + "y": 81 + }, + "endPoint": { + "x": 237, + "y": 81 + }, + "incomingShape": "y", + "outgoingShape": "z" + } + ], + "container": "B" + }, + { + "id": "e2", + "sources": [ + "x" + ], + "targets": [ + "z" + ], + "labels": [ + { + "text": "e2", + "width": 20, + "height": 12, + "x": 94, + "y": 114 + } + ], + "sections": [ + { + "id": "e2_s0", + "startPoint": { + "x": 74, + "y": 111 + }, + "endPoint": { + "x": 237, + "y": 111 + }, + "bendPoints": [ + { + "x": 124, + "y": 111 + }, + { + "x": 124, + "y": 136 + }, + { + "x": 227, + "y": 136 + }, + { + "x": 227, + "y": 111 + } + ], + "incomingShape": "x", + "outgoingShape": "z" + } + ], + "container": "A" + }, + { + "id": "e3", + "sources": [ + "x" + ], + "targets": [ + "p" + ], + "sections": [ + { + "id": "e3_s0", + "startPoint": { + "x": 74, + "y": 81 + }, + "endPoint": { + "x": 145, + "y": 81 + }, + "incomingShape": "x", + "outgoingShape": "p" + } + ], + "container": "A" + }, + { + "id": "e4", + "sources": [ + "p" + ], + "targets": [ + "y" + ], + "sections": [ + { + "id": "e4_s0", + "startPoint": { + "x": 145, + "y": 81 + }, + "endPoint": { + "x": 167, + "y": 81 + }, + "incomingShape": "p", + "outgoingShape": "y" + } + ], + "container": "B" + } + ], + "x": 155, + "y": 24, + "width": 144, + "height": 129 } - + ], + "x": 12, + "y": 12, + "width": 299, + "height": 153 + } + ], + "x": 0, + "y": 0, + "width": 323, + "height": 177 } - + ''' } From 5d7cf1a8776d10c6862bcfde2203ce3d745c75f1 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Thu, 29 Aug 2024 09:27:16 -0400 Subject: [PATCH 22/24] Update docs to cover coordinate options --- .../tooldevelopers/graphdatastructure.md | 2 + .../graphdatastructure/coordinatesystem.md | 44 +++++++++++++++++++ .../graphdatastructure/jsonformat.md | 9 ++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/docs/content/documentation/tooldevelopers/graphdatastructure.md b/docs/content/documentation/tooldevelopers/graphdatastructure.md index b47d6f569..22e66890d 100644 --- a/docs/content/documentation/tooldevelopers/graphdatastructure.md +++ b/docs/content/documentation/tooldevelopers/graphdatastructure.md @@ -118,6 +118,8 @@ Since ELK graphs are based on EMF, you can simply obtain an instance of the `Elk One method of particular value to layout algorithm developers is `firstEdgeSection(edge, reset, removeOthers)`, which returns the first edge section of the given edge, optionally resetting its layout data and removing all other edge sections. If the edge doesn't have any edge sections yet, one is created and added to it. This method is handy for applying layout results. * **Edge Containment:** Unless one uses one of the `create` methods to create edges, finding the node an edge should be contained in may be annoying (thus, don't). The `updateContainment(ElkEdge)` method automatically computes the best containment and puts the edge there. This requires at least one of the edge's end points to be set, since that is what determines the best containment. +Note that, when graphs are imported into ELK from JSON, `updateContainment()` is automatically called on each edge. Authors of graphs in JSON format are therefore not responsible for placing edges +inside their proper container nodes. While computing the containment is a little intricate (hence the utility method), the actual rule that determines the best containment is rather simple: it is the lowest common ancestor of all end points, where the ancestors of a node are defined as the sequence of nodes from the node itself to the graph's root node. Taking the graph at the top of this page as an example, the rule has the following implications for different types of edges: diff --git a/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md b/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md index 654baf6bf..aa6375f97 100644 --- a/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md +++ b/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md @@ -14,3 +14,47 @@ The coordinates of most elements are relative to their parent element. There are * Edge labels are relative to the coordinate system their edge is relative to. * Source points, bend points, and target points of edges are relative to the edge's containing node. Following the usual containment conventions (as implemented in `ElkGraphUtil`) results in what is displayed in the image above. + +## JSON + +One way of using ELK is to define a graph using JSON, and then +transfer the computed layout back to the initial JSON graph. +See [_JSON Format_]({{< relref "documentation/tooldevelopers/graphdatastructure/jsonformat.md" >}}). +When working this way, it is possible to control what type of coordinates you get, +using the `org.eclipse.elk.json.shapeCoords` and `org.eclipse.elk.json.edgeCoords` layout options. + +### Shape Coords + +This setting controls the coordinate system that will be used for all nodes, ports, and labels of nodes and ports. + +There are three possible values: + +* `INHERIT`: Inherit the value from the parent element. +* `PARENT`: Coordinates are relative to the parent element. +* `ROOT`: Coordinates are relative to the root node, i.e. global coordinates. + +The default value is `INHERIT`, except at the root node, where the default is `PARENT`. +Thus, the default behavior is as described in the image at the top of this page. +Setting `ROOT` allows you to instead use a "flat" coordinate system. + +### Edge Coords + +This setting controls the coordinate system that will be used for all source, bend, and target points of edges, +as well as all edge labels. + +There are four possible values: + +* `INHERIT`: Inherit the value from the parent element. +* `CONTAINER`: Coordinates are relative to the edge's proper container node (see below). +* `PARENT`: Coordinates are relative to the node in which the edge was defined in the given JSON. +* `ROOT`: Coordinates are relative to the root node, i.e. global coordinates. + +The default value is `INHERIT`, except at the root node, where the default is `CONTAINER`. +Thus, the default behavior is as described at the top of this page. + +Edge containment is a tricky subject, and the definition of an edge's proper container is given at the end +of the page on the +[_Graph Data Structure_]({{< relref "documentation/tooldevelopers/graphdatastructure.md" >}}). +Since you are allowed to define an edge under any node whatsoever in the input JSON, the edge's *parent* node +(the one it is defined under) may be different from the edge's *container*. Therefore, while `CONTAINER` is the +default mode for historical reasons, users may find it easier to use `PARENT` or `ROOT`. diff --git a/docs/content/documentation/tooldevelopers/graphdatastructure/jsonformat.md b/docs/content/documentation/tooldevelopers/graphdatastructure/jsonformat.md index f11c2554b..3c30c95b9 100644 --- a/docs/content/documentation/tooldevelopers/graphdatastructure/jsonformat.md +++ b/docs/content/documentation/tooldevelopers/graphdatastructure/jsonformat.md @@ -55,9 +55,12 @@ labels. Nodes can have an arbitrary number of ports. Edges can connect to a node either directly or through one of its ports. A node can also contain an arbitrary number of child nodes. A graph is actually nothing more than a simple node whose children are the top-level nodes of the graph. -Finally, a node can contain edges. These edges do not necessarily connect to the node, but will -usually connect its children. The edge coordinates will be interpreted relative to the upper -left corner of the node which contains it. +Finally, a node can contain edges. While it is common to define those edges under a given node +that connect that node's children, in fact any edge may be defined under any node, regardless of its +end points. This allows for flexibility when defining hierarchy-crossing edges, as well as for +alternative schemes, such as defining all edges at the root level. +See [_Coordinate System_]({{< relref "documentation/tooldevelopers/graphdatastructure/coordinatesystem.md" >}}) +for the rules for interpreting edge coordinates. ```json { From 2312415726ef20615cf31c65d6993ce1aad241a8 Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Thu, 29 Aug 2024 18:01:08 -0400 Subject: [PATCH 23/24] Write `container` property into edges only in CONTAINER mode Also, mention this in the docs. --- .../graphdatastructure/coordinatesystem.md | 16 ++++--- .../eclipse/elk/graph/json/JsonImporter.xtend | 4 +- .../elk/graph/json/test/EdgeCoordsTest.xtend | 48 +++++++------------ 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md b/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md index aa6375f97..3e1016b19 100644 --- a/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md +++ b/docs/content/documentation/tooldevelopers/graphdatastructure/coordinatesystem.md @@ -49,12 +49,16 @@ There are four possible values: * `PARENT`: Coordinates are relative to the node in which the edge was defined in the given JSON. * `ROOT`: Coordinates are relative to the root node, i.e. global coordinates. -The default value is `INHERIT`, except at the root node, where the default is `CONTAINER`. +The default value is `INHERIT`, except at the root node, where the default is (for historical reasons) `CONTAINER`. Thus, the default behavior is as described at the top of this page. -Edge containment is a tricky subject, and the definition of an edge's proper container is given at the end -of the page on the +The definition of an edge's proper container is given at the end of the page on the [_Graph Data Structure_]({{< relref "documentation/tooldevelopers/graphdatastructure.md" >}}). -Since you are allowed to define an edge under any node whatsoever in the input JSON, the edge's *parent* node -(the one it is defined under) may be different from the edge's *container*. Therefore, while `CONTAINER` is the -default mode for historical reasons, users may find it easier to use `PARENT` or `ROOT`. +It involves lowest common ancestors, and can be tricky to compute correctly. +In particular, since you are allowed to define an edge under any node whatsoever in the input JSON, the edge's +*parent* node (the one it is defined under) may be different from its container. + +If you elect to work in `CONTAINER` mode, ELK helps you by writing a `container` property into each edge after layout, +giving the `id` of the edge's container node. This can then be used to interpret the coordinates. + +If you elect to work in `PARENT` or `ROOT` mode instead, then you do not need to know about edge containers at all. diff --git a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend index cfab6696b..99dfb6418 100644 --- a/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend +++ b/plugins/org.eclipse.elk.graph.json/src/org/eclipse/elk/graph/json/JsonImporter.xtend @@ -670,7 +670,9 @@ final class JsonImporter { } } - jsonObj.addJsonObj("container", edge.getContainingNode.identifier) + if (edge.originalParent.edgeCoordsMode === EdgeCoords.CONTAINER) { + jsonObj.addJsonObj("container", edge.getContainingNode.identifier) + } } private def dispatch transferLayoutInt2(ElkLabel label) { diff --git a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend index 7945510cd..7a1d2a05f 100644 --- a/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend +++ b/test/org.eclipse.elk.graph.json.test/src/org/eclipse/elk/graph/json/test/EdgeCoordsTest.xtend @@ -412,8 +412,7 @@ class EdgeCoordsTest { "incomingShape": "y", "outgoingShape": "z" } - ], - "container": "B" + ] }, { "id": "e2", @@ -464,8 +463,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "z" } - ], - "container": "A" + ] }, { "id": "e3", @@ -489,8 +487,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "p" } - ], - "container": "A" + ] }, { "id": "e4", @@ -514,8 +511,7 @@ class EdgeCoordsTest { "incomingShape": "p", "outgoingShape": "y" } - ], - "container": "B" + ] } ], "x": 143, @@ -625,8 +621,7 @@ class EdgeCoordsTest { "incomingShape": "y", "outgoingShape": "z" } - ], - "container": "B" + ] }, { "id": "e2", @@ -677,8 +672,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "z" } - ], - "container": "A" + ] }, { "id": "e3", @@ -702,8 +696,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "p" } - ], - "container": "A" + ] }, { "id": "e4", @@ -727,8 +720,7 @@ class EdgeCoordsTest { "incomingShape": "p", "outgoingShape": "y" } - ], - "container": "B" + ] } ], "x": 143, @@ -1051,8 +1043,7 @@ class EdgeCoordsTest { "incomingShape": "y", "outgoingShape": "z" } - ], - "container": "B" + ] }, { "id": "e2", @@ -1103,8 +1094,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "z" } - ], - "container": "A" + ] }, { "id": "e3", @@ -1128,8 +1118,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "p" } - ], - "container": "A" + ] }, { "id": "e4", @@ -1153,8 +1142,7 @@ class EdgeCoordsTest { "incomingShape": "p", "outgoingShape": "y" } - ], - "container": "B" + ] } ], "x": 155, @@ -1265,8 +1253,7 @@ class EdgeCoordsTest { "incomingShape": "y", "outgoingShape": "z" } - ], - "container": "B" + ] }, { "id": "e2", @@ -1317,8 +1304,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "z" } - ], - "container": "A" + ] }, { "id": "e3", @@ -1342,8 +1328,7 @@ class EdgeCoordsTest { "incomingShape": "x", "outgoingShape": "p" } - ], - "container": "A" + ] }, { "id": "e4", @@ -1367,8 +1352,7 @@ class EdgeCoordsTest { "incomingShape": "p", "outgoingShape": "y" } - ], - "container": "B" + ] } ], "x": 155, From 82ea83df77279a5bf1f97202580e162fb7065d2a Mon Sep 17 00:00:00 2001 From: Steve Kieffer Date: Sat, 31 Aug 2024 09:16:40 -0400 Subject: [PATCH 24/24] Improve property descriptions --- .../org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk index 37602fe8a..4ae8418bc 100644 --- a/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk +++ b/plugins/org.eclipse.elk.core/src/org/eclipse/elk/core/Core.melk @@ -308,7 +308,8 @@ group json { option shapeCoords: ShapeCoords { label "Shape Coords" description - "Specify the coordinate system for shapes." + "For layouts transferred into JSON graphs, specify the coordinate system + to be used for nodes, ports, and labels of nodes and ports." default = ShapeCoords.INHERIT targets parents } @@ -316,7 +317,8 @@ group json { option edgeCoords: EdgeCoords { label "Edge Coords" description - "Specify the coordinate system for edge route points." + "For layouts transferred into JSON graphs, specify the coordinate system + to be used for edge route points and edge labels." default = EdgeCoords.INHERIT targets parents }