From e3f46ffc9d4a6930adc83ebb8e6730f19708cc94 Mon Sep 17 00:00:00 2001 From: Jussi Koskela Date: Tue, 14 Apr 2020 12:41:45 +0300 Subject: [PATCH] Improvements to styling of connection lines gitlab #519 Change-Id: Ic08546e5aab985d6e4c365dc9877a692829b0ab2 --- .../rendering/AggregateConnectionStyle.java | 66 +++++++++++ .../rendering/BasicConnectionStyle.java | 110 +++++++++++++++++- .../graph/Diagram.pgraph | 8 +- .../RouteGraphConnectionClassFactory.java | 2 +- .../diagram/adapter/RouteGraphUtils.java | 38 ++++-- .../diagram/connection/ConnectionVisuals.java | 12 ++ .../query/ConnectionVisualsRequest.java | 3 +- .../graph/G2D.pgraph | 2 + .../g2d/nodes/connection/RouteGraphNode.java | 37 +++--- 9 files changed, 247 insertions(+), 31 deletions(-) create mode 100644 bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/AggregateConnectionStyle.java diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/AggregateConnectionStyle.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/AggregateConnectionStyle.java new file mode 100644 index 000000000..7bf76246f --- /dev/null +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/AggregateConnectionStyle.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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 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; + } + +} diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/BasicConnectionStyle.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/BasicConnectionStyle.java index 2f218d8fa..94de76bb4 100644 --- a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/BasicConnectionStyle.java +++ b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/BasicConnectionStyle.java @@ -17,9 +17,11 @@ import java.awt.RenderingHints; 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; /** @@ -37,12 +39,13 @@ public class BasicConnectionStyle implements ConnectionStyle, 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; @@ -50,10 +53,16 @@ public class BasicConnectionStyle implements ConnectionStyle, Serializable { 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() { @@ -101,11 +110,101 @@ public class BasicConnectionStyle implements ConnectionStyle, Serializable { 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) { @@ -266,4 +365,7 @@ public class BasicConnectionStyle implements ConnectionStyle, Serializable { return rounding; } + public double getOffset() { + return offset; + } } diff --git a/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph b/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph index 6ea207f88..a38de5bc9 100644 --- a/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph +++ b/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph @@ -173,4 +173,10 @@ DIA.ConnectionCrossingStyle.Type 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 { @@ -589,6 +609,7 @@ public class RouteGraphUtils { 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, @@ -597,7 +618,8 @@ public class RouteGraphUtils { lineStroke, routeLineStroke, degenerateLineLength, - rounding); + rounding, + offset); } public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) { diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/connection/ConnectionVisuals.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/connection/ConnectionVisuals.java index 802881e16..e7a31692b 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/connection/ConnectionVisuals.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/connection/ConnectionVisuals.java @@ -27,12 +27,17 @@ public class ConnectionVisuals { 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; @@ -40,6 +45,7 @@ public class ConnectionVisuals { this.stroke = stroke; this.branchPointRadius = branchPointRadius; this.rounding = rounding; + this.offset = offset; } public Color toColor() { @@ -57,6 +63,7 @@ public class ConnectionVisuals { 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; @@ -83,6 +90,11 @@ public class ConnectionVisuals { 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; diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/query/ConnectionVisualsRequest.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/query/ConnectionVisualsRequest.java index 73e3615ce..619e913f0 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/query/ConnectionVisualsRequest.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/query/ConnectionVisualsRequest.java @@ -54,8 +54,9 @@ public class ConnectionVisualsRequest extends ResourceRead { 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) { diff --git a/bundles/org.simantics.g2d.ontology/graph/G2D.pgraph b/bundles/org.simantics.g2d.ontology/graph/G2D.pgraph index ce06a4109..4c44b6152 100644 --- a/bundles/org.simantics.g2d.ontology/graph/G2D.pgraph +++ b/bundles/org.simantics.g2d.ontology/graph/G2D.pgraph @@ -94,6 +94,8 @@ G2D.StrokeType.Scaling : G2D.StrokeType G2D.StrokeType.Nonscaling : G2D.StrokeType G2D.HasRounding L0.Double +G2D.HasOffset L0.Double G2D.HasBranchPointRadius L0.Double G2D.LineEnd 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 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; } -- 2.43.2