--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2020 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Semantum Oy - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.connection.rendering;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AggregateConnectionStyle implements ConnectionStyle, Serializable{
+
+ private static final long serialVersionUID = 5888959070127628457L;
+ private List<ConnectionStyle> styles = new ArrayList<>();
+
+ public void addStyle(ConnectionStyle style) {
+ styles.add(style);
+ }
+
+ @Override
+ public void drawBranchPoint(Graphics2D g, double x, double y) {
+ for (ConnectionStyle style : styles) {
+ style.drawBranchPoint(g, x, y);
+ }
+ }
+
+ @Override
+ public void drawLine(Graphics2D g, double x1, double y1, double x2, double y2, boolean isTransient) {
+ for (ConnectionStyle style : styles) {
+ style.drawLine(g, x1, y1, x2, y2, isTransient);
+ }
+ }
+
+ @Override
+ public void drawPath(Graphics2D g, Path2D path, boolean isTransient) {
+ for (ConnectionStyle style : styles) {
+ style.drawPath(g, path, isTransient);
+ }
+ }
+
+ @Override
+ public void drawDegeneratedLine(Graphics2D g, double x, double y, boolean isHorizontal, boolean isTransient) {
+ for (ConnectionStyle style : styles) {
+ style.drawDegeneratedLine(g, x, y, isHorizontal, isTransient);
+ }
+ }
+
+ @Override
+ public double getDegeneratedLineLength() {
+ double max = 0;
+ for (ConnectionStyle style : styles) {
+ max = Math.max(max, style.getDegeneratedLineLength());
+ }
+ return max;
+ }
+
+}
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
+import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
import java.io.Serializable;
/**
final Stroke routeLineStroke;
final double degenerateLineLength;
final double rounding;
+ final double offset;
transient Line2D line = new Line2D.Double();
transient Ellipse2D ellipse = new Ellipse2D.Double();
public BasicConnectionStyle(Color lineColor, Color branchPointColor, double branchPointRadius, Stroke lineStroke, Stroke routeLineStroke, double degenerateLineLength,
- double rounding) {
+ double rounding, double offset) {
this.lineColor = lineColor;
this.branchPointColor = branchPointColor;
this.branchPointRadius = branchPointRadius;
this.routeLineStroke = routeLineStroke;
this.degenerateLineLength = degenerateLineLength;
this.rounding = rounding;
+ this.offset = offset;
+ }
+
+ public BasicConnectionStyle(Color lineColor, Color branchPointColor, double branchPointRadius, Stroke lineStroke, Stroke routeLineStroke, double degenerateLineLength,
+ double rounding) {
+ this(lineColor, branchPointColor, branchPointRadius, lineStroke, routeLineStroke, degenerateLineLength, rounding, 0.0);
}
public BasicConnectionStyle(Color lineColor, Color branchPointColor, double branchPointRadius, Stroke lineStroke, Stroke routeLineStroke, double degenerateLineLength) {
- this(lineColor, branchPointColor, branchPointRadius, lineStroke, routeLineStroke, degenerateLineLength, 0.0);
+ this(lineColor, branchPointColor, branchPointRadius, lineStroke, routeLineStroke, degenerateLineLength, 0.0, 0.0);
}
public Color getLineColor() {
if(rounding > 0.0) {
Object oldRenderingHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.draw(round(path));
+ path = round(path);
+ if (offset != 0) {
+ path = offsetPath(path, offset);
+ }
+ g.draw(path);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldRenderingHint);
}
- else
+ else {
+ if (offset != 0) {
+ path = offsetPath(path, offset);
+ }
g.draw(path);
+ }
+ }
+
+ private static Point2D getNormal(Point2D dir) {
+ return new Point2D.Double(-dir.getY(), dir.getX());
+ }
+
+ private static Point2D normalize(Point2D v) {
+ double d = Math.sqrt(v.getX() * v.getX() + v.getY() * v.getY());
+ v.setLocation(v.getX() / d, v.getY() / d);
+ return v;
+ }
+
+ private static Path2D offsetPath(Path2D path, double offset) {
+ Path2D result = new Path2D.Double();
+ PathIterator iter = new FlatteningPathIterator(path.getPathIterator(null), 0.05, 10);
+
+ double c[] = new double[6];
+ double initialX = 0;
+ double initialY = 0;
+ boolean first = true;
+ Point2D prevDir = null;
+ Point2D prevPos = null;
+
+ while (!iter.isDone()) {
+ int i = iter.currentSegment(c);
+ switch (i) {
+ case PathIterator.SEG_MOVETO:
+ if (first) {
+ initialX = c[0];
+ initialY = c[1];
+ first = false;
+ }
+ if (prevDir != null) {
+ Point2D N = normalize(getNormal(prevDir));
+ result.lineTo(prevPos.getX() + N.getX() * offset , prevPos.getY() + N.getY() * offset);
+ }
+ prevPos = new Point2D.Double(c[0], c[1]);
+ prevDir = null;
+ break;
+ case PathIterator.SEG_LINETO:
+ case PathIterator.SEG_CLOSE:
+ if (i == PathIterator.SEG_CLOSE) {
+ c[0] = initialX;
+ c[1] = initialY;
+ }
+ Point2D currentDir = new Point2D.Double(c[0] - prevPos.getX(), c[1] - prevPos.getY());
+ if (currentDir.getX() == 0.0 && currentDir.getY() == 0) break;
+
+ if (prevDir == null) {
+ Point2D N = normalize(getNormal(currentDir));
+ result.moveTo(prevPos.getX() + N.getX() * offset, prevPos.getY() + N.getY() * offset);
+ prevPos = new Point2D.Double(c[0], c[i]);
+ prevDir = currentDir;
+ } else {
+ Point2D N1 = normalize(getNormal(prevDir));
+ Point2D N2 = normalize(getNormal(currentDir));
+ Point2D N = normalize(new Point2D.Double(N1.getX() + N2.getX(), N1.getY() + N2.getY()));
+ double dot = N1.getX() * N.getX() + N1.getY() * N.getY();
+
+ if (!Double.isFinite(dot) || Math.abs(dot) < 0.1) {
+ result.lineTo(prevPos.getX() + (N1.getX() + N1.getY()) * offset, prevPos.getY() + (N1.getY() - N1.getX()) * offset);
+ result.lineTo(prevPos.getX() + (N2.getX() + N1.getY()) * offset, prevPos.getY() + (N2.getY() - N1.getX()) * offset);
+ prevPos = new Point2D.Double(c[0], c[i]);
+ prevDir = currentDir;
+ } else {
+ double Nx = N.getX() * offset / dot;
+ double Ny = N.getY() * offset / dot;
+ result.lineTo(prevPos.getX() + Nx, prevPos.getY() + Ny);
+ prevPos = new Point2D.Double(c[0], c[i]);
+ prevDir = currentDir;
+ }
+ }
+
+ break;
+ }
+ iter.next();
+ }
+ if (prevDir != null) {
+ Point2D N = normalize(getNormal(prevDir));
+ result.lineTo(prevPos.getX() + N.getX() * offset , prevPos.getY() + N.getY() * offset);
+ }
+ return result;
}
private Path2D round(Path2D path) {
return rounding;
}
+ public double getOffset() {
+ return offset;
+ }
}
DIA.ConnectionCrossingStyle.Type.Arc : DIA.ConnectionCrossingStyle.Type
DIA.ConnectionCrossingStyle.Type.Square : DIA.ConnectionCrossingStyle.Type
DIA.ConnectionCrossingStyle.Type.Gap : DIA.ConnectionCrossingStyle.Type
-DIA.ConnectionCrossingStyle.Type.None : DIA.ConnectionCrossingStyle.Type
\ No newline at end of file
+DIA.ConnectionCrossingStyle.Type.None : DIA.ConnectionCrossingStyle.Type
+
+DIA.ConnectionStyle <T L0.Entity
+DIA.ConnectionLineStyle <T L0.Entity
+
+DIA.HasConnectionStyle <R L0.IsRelatedTo : L0.FunctionalRelation
+ L0.HasRange DIA.ConnectionStyle
RouteGraph rg = RouteGraphUtils.load(graph, diagramRuntime, connection, canvas, diagram, element, modelingRules, backendConnections);
// Load connection line style.
- ConnectionStyle style = RouteGraphUtils.readConnectionStyle(graph, modelingRules, connection, STR);
+ ConnectionStyle style = RouteGraphUtils.readConnectionStyle(graph, modelingRules, connection, STR, DIA);
StyledRouteGraphRenderer renderer = RouteGraphUtils.getRenderer(graph, style);
// Finish element load
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import org.simantics.db.common.procedure.adapter.TransientCacheListener;
import org.simantics.db.common.request.ResourceRead2;
import org.simantics.db.common.request.UnaryRead;
+import org.simantics.db.common.utils.ListUtils;
import org.simantics.db.common.utils.NameUtils;
import org.simantics.db.exception.DatabaseException;
import org.simantics.diagram.connection.ConnectionVisuals;
import org.simantics.diagram.connection.RouteNode;
import org.simantics.diagram.connection.RouteTerminal;
import org.simantics.diagram.connection.RouteTerminalPosition;
+import org.simantics.diagram.connection.rendering.AggregateConnectionStyle;
import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
import org.simantics.diagram.connection.rendering.ConnectionStyle;
import org.simantics.diagram.connection.rendering.ExampleConnectionStyle;
import org.simantics.g2d.element.IElement;
import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
import org.simantics.g2d.element.handler.TerminalLayout;
-import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
import org.simantics.g2d.elementclass.FlagClass.Type;
+import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
import org.simantics.layer0.Layer0;
import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
import org.simantics.scenegraph.utils.GeometryUtils;
* @return
* @throws DatabaseException
*/
- protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR) throws DatabaseException {
+ protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR, DiagramResource DIA) throws DatabaseException {
+ Resource connectionStyle = graph.getPossibleObject(connection, DIA.HasConnectionStyle);
Resource connectionType = null;
- if (modelingRules != null)
- connectionType = modelingRules.getConnectionType(graph, connection);
- if (connectionType == null)
- connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
- return connectionType != null ? readConnectionStyleFromConnectionType(graph, connectionType) : DEFAULT_CONNECTION_STYLE;
+ if (connectionStyle == null) {
+ if (modelingRules != null)
+ connectionType = modelingRules.getConnectionType(graph, connection);
+ if (connectionType == null)
+ connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
+ connectionStyle = graph.getPossibleObject(connectionType, DIA.HasConnectionStyle);
+ }
+ if (connectionStyle != null) {
+ List<Resource> lineStyles = ListUtils.toList(graph, connectionStyle);
+ if (lineStyles.size() != 1) {
+ AggregateConnectionStyle aggregate = new AggregateConnectionStyle();
+ for (Resource connectionLine : ListUtils.toList(graph, connectionStyle)) {
+ aggregate.addStyle(readConnectionStyleFromConnectionType(graph, connectionLine));
+ }
+ return aggregate;
+ } else {
+ return readConnectionStyleFromConnectionType(graph, lineStyles.get(0));
+ }
+ } else {
+ return connectionType != null ? readConnectionStyleFromConnectionType(graph, connectionType) : DEFAULT_CONNECTION_STYLE;
+ }
}
protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {
lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);
Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);
double rounding = cv.rounding == null ? 0.0 : cv.rounding;
+ double offset = cv.offset == null ? 0.0 : cv.offset;
return new BasicConnectionStyle(
lineColor,
lineStroke,
routeLineStroke,
degenerateLineLength,
- rounding);
+ rounding,
+ offset);
}
public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {
public final Stroke stroke;
public final Double branchPointRadius;
public final Double rounding;
+ public final Double offset;
public ConnectionVisuals(float[] color, StrokeType strokeType, Stroke stroke, Double rounding) {
this(color, strokeType, stroke, null, rounding);
}
public ConnectionVisuals(float[] color, StrokeType strokeType, Stroke stroke, Double branchPointRadius, Double rounding) {
+ this(color, strokeType, stroke, null, rounding, null);
+ }
+
+ public ConnectionVisuals(float[] color, StrokeType strokeType, Stroke stroke, Double branchPointRadius, Double rounding, Double offset) {
if (color != null && color.length < 3)
throw new IllegalArgumentException("colors must have at least 3 components (rgb), got " + color.length);
this.color = color;
this.stroke = stroke;
this.branchPointRadius = branchPointRadius;
this.rounding = rounding;
+ this.offset = offset;
}
public Color toColor() {
result = prime * result + ((branchPointRadius == null) ? 0 : branchPointRadius.hashCode());
result = prime * result + Arrays.hashCode(color);
result = prime * result + ((rounding == null) ? 0 : rounding.hashCode());
+ result = prime * result + ((offset == null) ? 0 : offset.hashCode());
result = prime * result + ((stroke == null) ? 0 : stroke.hashCode());
result = prime * result + ((strokeType == null) ? 0 : strokeType.hashCode());
return result;
return false;
} else if (!rounding.equals(other.rounding))
return false;
+ if (offset == null) {
+ if (other.offset != null)
+ return false;
+ } else if (!offset.equals(other.offset))
+ return false;
if (stroke == null) {
if (other.stroke != null)
return false;
Stroke stroke = G2DUtils.getStroke(g, g.getPossibleObject(structuralConnectionType, g2d.HasStroke));
Double branchPointRadius = g.getPossibleRelatedValue(structuralConnectionType, g2d.HasBranchPointRadius, Bindings.DOUBLE);
Double rounding = g.getPossibleRelatedValue(structuralConnectionType, g2d.HasRounding, Bindings.DOUBLE);
+ Double offset = g.getPossibleRelatedValue(structuralConnectionType, g2d.HasOffset, Bindings.DOUBLE);
- return new ConnectionVisuals(color, strokeType, stroke, branchPointRadius, rounding);
+ return new ConnectionVisuals(color, strokeType, stroke, branchPointRadius, rounding, offset);
}
StrokeType toStrokeType(Resource strokeType) {
G2D.StrokeType.Nonscaling : G2D.StrokeType
G2D.HasRounding <R L0.HasProperty : L0.FunctionalRelation
--> L0.Double
+G2D.HasOffset <R L0.HasProperty : L0.FunctionalRelation
+ --> L0.Double
G2D.HasBranchPointRadius <R L0.HasProperty : L0.FunctionalRelation
--> L0.Double
G2D.LineEnd <T L0.Property
selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());
}
} else {
+ // TODO: support AggregateConnectionStyle
selectionStroke = SELECTION_STROKE;
}
}
if(dynamicColor != null || dynamicStroke != null) {
BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);
- try {
- Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class, double.class);
- renderer = new StyledRouteGraphRenderer(c.newInstance(
- dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
- baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
- dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
- dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
- baseStyle.getDegeneratedLineLength(), baseStyle.getRounding()));
- } catch (Exception e) {
- renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(
- dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
- baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
- dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
- dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
- baseStyle.getDegeneratedLineLength(), baseStyle.getRounding()));
+ if (baseStyle != null) {
+ try {
+ Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class, double.class, double.class);
+ renderer = new StyledRouteGraphRenderer(c.newInstance(
+ dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
+ baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
+ dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
+ dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
+ baseStyle.getDegeneratedLineLength(), baseStyle.getRounding(), baseStyle.getOffset()));
+ } catch (Exception e) {
+ renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(
+ dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
+ baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
+ dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(),
+ dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
+ baseStyle.getDegeneratedLineLength(), baseStyle.getRounding(), baseStyle.getOffset()));
+ }
+ } else {
+ // TODO: support AggregateConnectionStyle
+ renderer = baseRenderer;
}
-
} else {
renderer = baseRenderer;
}