Skip to content

Commit

Permalink
Merge pull request #1040 from lf-lang/diagrams
Browse files Browse the repository at this point in the history
[Diagrams] Improved stability of the positioning of edges connected to reactions.
  • Loading branch information
lhstrh authored Mar 14, 2022
2 parents 943141c + 35f5c02 commit 09beff1
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -722,14 +722,18 @@ private Collection<KNode> transformReactorNetwork(
// connect input
KPort port = null;
for (TriggerInstance<?> trigger : reaction.triggers) {
port = addInvisiblePort(node);
setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST);
int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0;
int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0;
if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) {
// manual adjustment disabling automatic one
setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET,
(double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS);
// Create new port if there is no previous one or each dependency should have its own one
if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) {
port = addInvisiblePort(node);
setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST);

int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0;
int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0;
if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) {
// manual adjustment disabling automatic one
setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET,
(double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS);
}
}

if (trigger.isStartup()) {
Expand Down Expand Up @@ -764,20 +768,22 @@ private Collection<KNode> transformReactorNetwork(
}

// connect dependencies
//port = null // create new ports
for (TriggerInstance<?> dep : reaction.sources) {
if (reaction.triggers.contains(dep)) continue;
if (!(getBooleanValue(REACTIONS_USE_HYPEREDGES) && port != null)) {
port = addInvisiblePort(node);
setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST);
int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0;
int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0;
if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) {
// manual adjustment disabling automatic one
setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET,
(double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS);
}
}
if (reaction.triggers.contains(dep)) continue; // skip

// Create new port if there is no previous one or each dependency should have its own one
if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) {
port = addInvisiblePort(node);
setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST);

int triggersSize = reaction.triggers != null ? reaction.triggers.size() : 0;
int sourcesSize = reaction.sources != null ? reaction.sources.size() : 0;
if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || triggersSize + sourcesSize == 1) {
// manual adjustment disabling automatic one
setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET,
(double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS);
}
}

if (dep instanceof PortInstance) {
KPort src = null;
Expand All @@ -794,11 +800,14 @@ private Collection<KNode> transformReactorNetwork(
}

// connect outputs
port = null; // create new ports
port = null; // enforce new ports for outputs
Set<TriggerInstance<?>> iterSet = reaction.effects != null ? reaction.effects : new HashSet<>();
for (TriggerInstance<?> effect : iterSet) {
port = addInvisiblePort(node);
setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST);
// Create new port if there is no previous one or each dependency should have its own one
if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) {
port = addInvisiblePort(node);
setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST);
}

if (effect instanceof ActionInstance) {
actionSources.put((ActionInstance) effect, port);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
***************/
package org.lflang.diagram.synthesis.postprocessor;

import java.util.stream.Collectors;

import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.PortSide;
import org.eclipse.elk.graph.properties.Property;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.Pair;
import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions;

import de.cau.cs.kieler.klighd.IStyleModifier;
import de.cau.cs.kieler.klighd.IViewer;
import de.cau.cs.kieler.klighd.internal.ILayoutRecorder;
Expand All @@ -33,16 +43,8 @@
import de.cau.cs.kieler.klighd.kgraph.KNode;
import de.cau.cs.kieler.klighd.kgraph.KPoint;
import de.cau.cs.kieler.klighd.kgraph.KPort;
import java.util.List;
import java.util.Map;
import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.PortSide;
import org.eclipse.elk.graph.properties.IProperty;
import org.eclipse.elk.graph.properties.Property;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.Pair;
import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions;
import de.cau.cs.kieler.klighd.krendering.KRendering;
import de.cau.cs.kieler.klighd.krendering.KRenderingFactory;

/**
* Adjusts the port position of reactions node AFTER layout, to allow free port order but also adapt (snuggle) to pointy shape of reaction node.
Expand All @@ -57,6 +59,19 @@ public class ReactionPortAdjustment implements IStyleModifier {

@Extension
private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE;
private static KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE;

/**
* Register this modifier on a reaction rendering.
*/
public static void apply(KNode node, KRendering rendering) {
// Add modifier that fixes port positions such that edges are properly attached to the shape
var invisible = _kRenderingFactory.createKInvisibility();
invisible.setInvisible(false); // make it ineffective (just for purpose of holding modifier)
invisible.setModifierId(ReactionPortAdjustment.ID); // Add modifier to receive callback after layout
rendering.getStyles().add(invisible);
node.setProperty(PROCESSED, false);
}

@Override
public boolean modify(IStyleModifier.StyleModificationContext context) {
Expand All @@ -72,54 +87,40 @@ public boolean modify(IStyleModifier.StyleModificationContext context) {
}

// Get viewer (this is a bit brittle because it fetches the viewer from some internal property)
Map.Entry<IProperty<?>, Object> first = IterableExtensions.findFirst(
parent.getAllProperties().entrySet(),
it -> {
return it.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer") ||
it.getKey().getId().equals("klighd.layout.viewer");
}
);
Object viewer = first != null ? first.getValue() : null;

Object viewer =
parent.getAllProperties().entrySet().stream().filter(entry ->
entry.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer")
|| entry.getKey().getId().equals("klighd.layout.viewer"))
.findAny().map(entry -> entry.getValue()).orElse(null);

ILayoutRecorder recorder = null;
if (viewer instanceof IViewer) {
recorder = ((IViewer) viewer).getViewContext().getLayoutRecorder();
}


if (!knode.getPorts().isEmpty()) {
if (IterableExtensions.head(knode.getPorts()).getYpos() != 0 &&
!knode.getProperty(ReactionPortAdjustment.PROCESSED)) { // Only adjust if layout is already applied
// important for incremental update animation
// Only adjust if layout is already applied important for incremental update animation
!knode.getProperty(ReactionPortAdjustment.PROCESSED)) {
if (recorder != null) {
recorder.startRecording();
}

List<KPort> in = IterableExtensions.toList(
IterableExtensions.sortBy(
IterableExtensions.filter(
knode.getPorts(),
it -> {
return it.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST &&
!it.hasProperty(CoreOptions.PORT_BORDER_OFFSET);
}),
it -> { return it.getYpos(); })
);
var in = knode.getPorts().stream().filter(p ->
p.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST).sorted((p1, p2) ->
Float.compare(p1.getYpos(), p2.getYpos())).collect(Collectors.toList());

List<KPort> out = IterableExtensions.toList(
IterableExtensions.sortBy(
IterableExtensions.filter(
knode.getPorts(),
it -> {
return it.getProperty(CoreOptions.PORT_SIDE) == PortSide.EAST &&
!it.hasProperty(CoreOptions.PORT_BORDER_OFFSET);
}),
it -> { return it.getYpos(); })
);
var out = knode.getPorts().stream().filter(p ->
p.getProperty(CoreOptions.PORT_SIDE) == PortSide.EAST).sorted((p1, p2) ->
Float.compare(p1.getYpos(), p2.getYpos())).collect(Collectors.toList());

// Adjust
adjustPositions(IterableExtensions.indexed(in), in.size(), true);
adjustPositions(IterableExtensions.indexed(out), out.size(), false);
if (in.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) {
adjustPositions(IterableExtensions.indexed(in), in.size(), true);
}
if (out.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) {
adjustPositions(IterableExtensions.indexed(out), out.size(), false);
}
knode.setProperty(ReactionPortAdjustment.PROCESSED, true);

if (recorder!=null) {
Expand Down Expand Up @@ -166,6 +167,9 @@ public void adjustPositions(Iterable<Pair<Integer, KPort>> indexedPorts, int cou
edge.setSourcePoint(adjustedKPoint(edge.getSourcePoint(), offset));
}
}

// Save for future layout
port.setProperty(CoreOptions.PORT_BORDER_OFFSET, (double) (input ? -offset : offset));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,9 @@ public KPolygon addReactionFigure(KNode node, ReactionInstance reaction) {
_kRenderingExtensions.createKPosition(LEFT, REACTION_POINTINESS, 0, BOTTOM, 0, 0.5f)
)
);
IterableExtensions.head(baseShape.getStyles()).setModifierId(ReactionPortAdjustment.ID);

// For a shape like this, ports can only positioned correctly after the layout.
ReactionPortAdjustment.apply(node, baseShape);

KRectangle contentContainer = _kContainerRenderingExtensions.addRectangle(baseShape);
associateWith(contentContainer, reaction);
Expand Down

0 comments on commit 09beff1

Please # to comment.