net.sourceforge.plantuml.svek.SvekLine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plantuml Show documentation
Show all versions of plantuml Show documentation
PlantUML is a component that allows to quickly write :
* sequence diagram,
* use case diagram,
* class diagram,
* activity diagram,
* component diagram,
* state diagram
* object diagram
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2023, Arnaud Roques
*
* Project Info: http://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* http://plantuml.com/patreon (only 1$ per month!)
* http://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.svek;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import net.sourceforge.plantuml.AlignmentParam;
import net.sourceforge.plantuml.ColorParam;
import net.sourceforge.plantuml.Dimension2DDouble;
import net.sourceforge.plantuml.Direction;
import net.sourceforge.plantuml.Hideable;
import net.sourceforge.plantuml.ISkinParam;
import net.sourceforge.plantuml.LineParam;
import net.sourceforge.plantuml.Log;
import net.sourceforge.plantuml.Pragma;
import net.sourceforge.plantuml.StringUtils;
import net.sourceforge.plantuml.UmlDiagramType;
import net.sourceforge.plantuml.Url;
import net.sourceforge.plantuml.awt.geom.Dimension2D;
import net.sourceforge.plantuml.command.Position;
import net.sourceforge.plantuml.cucadiagram.Display;
import net.sourceforge.plantuml.cucadiagram.EntityPort;
import net.sourceforge.plantuml.cucadiagram.IEntity;
import net.sourceforge.plantuml.cucadiagram.IGroup;
import net.sourceforge.plantuml.cucadiagram.LeafType;
import net.sourceforge.plantuml.cucadiagram.Link;
import net.sourceforge.plantuml.cucadiagram.LinkArrow;
import net.sourceforge.plantuml.cucadiagram.LinkDecor;
import net.sourceforge.plantuml.cucadiagram.LinkMiddleDecor;
import net.sourceforge.plantuml.cucadiagram.LinkType;
import net.sourceforge.plantuml.cucadiagram.NoteLinkStrategy;
import net.sourceforge.plantuml.cucadiagram.Stereotype;
import net.sourceforge.plantuml.cucadiagram.dot.DotSplines;
import net.sourceforge.plantuml.cucadiagram.dot.GraphvizVersion;
import net.sourceforge.plantuml.descdiagram.command.StringWithArrow;
import net.sourceforge.plantuml.graphic.FontConfiguration;
import net.sourceforge.plantuml.graphic.HorizontalAlignment;
import net.sourceforge.plantuml.graphic.StringBounder;
import net.sourceforge.plantuml.graphic.TextBlock;
import net.sourceforge.plantuml.graphic.TextBlockUtils;
import net.sourceforge.plantuml.graphic.UDrawable;
import net.sourceforge.plantuml.graphic.USymbolFolder;
import net.sourceforge.plantuml.graphic.VerticalAlignment;
import net.sourceforge.plantuml.graphic.color.ColorType;
import net.sourceforge.plantuml.graphic.color.Colors;
import net.sourceforge.plantuml.posimo.BezierUtils;
import net.sourceforge.plantuml.posimo.DotPath;
import net.sourceforge.plantuml.posimo.Moveable;
import net.sourceforge.plantuml.posimo.Positionable;
import net.sourceforge.plantuml.posimo.PositionableUtils;
import net.sourceforge.plantuml.skin.VisibilityModifier;
import net.sourceforge.plantuml.skin.rose.Rose;
import net.sourceforge.plantuml.style.StyleBuilder;
import net.sourceforge.plantuml.svek.extremity.Extremity;
import net.sourceforge.plantuml.svek.extremity.ExtremityFactory;
import net.sourceforge.plantuml.svek.extremity.ExtremityFactoryExtends;
import net.sourceforge.plantuml.svek.extremity.ExtremityOther;
import net.sourceforge.plantuml.svek.image.EntityImageNoteLink;
import net.sourceforge.plantuml.ugraphic.UGraphic;
import net.sourceforge.plantuml.ugraphic.UGroupType;
import net.sourceforge.plantuml.ugraphic.ULine;
import net.sourceforge.plantuml.ugraphic.UPolygon;
import net.sourceforge.plantuml.ugraphic.UStroke;
import net.sourceforge.plantuml.ugraphic.UTranslate;
import net.sourceforge.plantuml.ugraphic.color.HColor;
import net.sourceforge.plantuml.ugraphic.color.HColorNone;
public class SvekLine implements Moveable, Hideable, GuideLine {
private static final Dimension2DDouble CONSTRAINT_SPOT = new Dimension2DDouble(10, 10);
private final Cluster ltail;
private final Cluster lhead;
private final Link link;
private final EntityPort startUid;
private final EntityPort endUid;
private final TextBlock startTailText;
private final TextBlock endHeadText;
private final TextBlock labelText;
private boolean divideLabelWidthByTwo = false;
private final int lineColor;
private final int noteLabelColor;
private final int startTailColor;
private final int endHeadColor;
private final StringBounder stringBounder;
private final Bibliotekon bibliotekon;
private DotPath dotPath;
private Positionable startTailLabelXY;
private Positionable endHeadLabelXY;
private Positionable labelXY;
private UDrawable extremity2;
private UDrawable extremity1;
private double dx;
private double dy;
private boolean opale;
private Cluster projectionCluster;
private final Pragma pragma;
private final HColor backgroundColor;
private final boolean useRankSame;
private final UStroke defaultThickness;
private HColor arrowLollipopColor;
private final ISkinParam skinParam;
private final double labelShield;
@Override
public String toString() {
return super.toString() + " color=" + lineColor;
}
public Direction getArrowDirection() {
if (getLinkArrow() == LinkArrow.BACKWARD)
return getArrowDirectionInternal().getInv();
return getArrowDirectionInternal();
}
private Direction getArrowDirectionInternal() {
if (isAutolink()) {
final double startAngle = dotPath.getStartAngle();
return Direction.LEFT;
}
final Point2D start = dotPath.getStartPoint();
final Point2D end = dotPath.getEndPoint();
final double ang = Math.atan2(end.getX() - start.getX(), end.getY() - start.getY());
if (ang > -Math.PI / 4 && ang < Math.PI / 4)
return Direction.DOWN;
if (ang > Math.PI * 3 / 4 || ang < -Math.PI * 3 / 4)
return Direction.UP;
return end.getX() > start.getX() ? Direction.RIGHT : Direction.LEFT;
}
public double getArrowDirection2() {
if (getLinkArrow() == LinkArrow.BACKWARD)
return Math.PI + getArrowDirectionInternal2();
return getArrowDirectionInternal2();
}
private double getArrowDirectionInternal2() {
if (isAutolink()) {
final double startAngle = dotPath.getStartAngle();
return startAngle;
}
final Point2D start = dotPath.getStartPoint();
final Point2D end = dotPath.getEndPoint();
final double ang = Math.atan2(end.getX() - start.getX(), end.getY() - start.getY());
return ang;
}
private Cluster getCluster2(Bibliotekon bibliotekon, IEntity entityMutable) {
for (Cluster cl : bibliotekon.allCluster())
if (cl.getGroups().contains(entityMutable))
return cl;
throw new IllegalArgumentException();
}
public SvekLine(Link link, ColorSequence colorSequence, ISkinParam skinParam, StringBounder stringBounder,
FontConfiguration font, Bibliotekon bibliotekon, Pragma pragma) {
this.link = Objects.requireNonNull(link);
this.skinParam = skinParam;
// this.umlType = link.getUmlDiagramType();
this.useRankSame = skinParam.useRankSame();
this.startUid = link.getEntityPort1(bibliotekon);
this.endUid = link.getEntityPort2(bibliotekon);
Cluster ltail = null;
if (startUid.startsWith(Cluster.CENTER_ID))
ltail = getCluster2(bibliotekon, link.getEntity1());
Cluster lhead = null;
if (endUid.startsWith(Cluster.CENTER_ID))
lhead = getCluster2(bibliotekon, link.getEntity2());
if (link.getColors() != null) {
skinParam = link.getColors().mute(skinParam);
font = font.mute(link.getColors());
}
this.backgroundColor = skinParam.getBackgroundColor();
this.defaultThickness = skinParam.getThickness(LineParam.arrow, null);
this.arrowLollipopColor = skinParam.getHtmlColor(ColorParam.arrowLollipop, null, false);
if (arrowLollipopColor == null)
this.arrowLollipopColor = backgroundColor;
this.pragma = pragma;
this.bibliotekon = bibliotekon;
this.stringBounder = stringBounder;
this.ltail = ltail;
this.lhead = lhead;
this.lineColor = colorSequence.getValue();
this.noteLabelColor = colorSequence.getValue();
this.startTailColor = colorSequence.getValue();
this.endHeadColor = colorSequence.getValue();
TextBlock labelOnly;
if (Display.isNull(link.getLabel())) {
labelOnly = TextBlockUtils.EMPTY_TEXT_BLOCK;
if (getLinkArrow() != LinkArrow.NONE_OR_SEVERAL)
labelOnly = StringWithArrow.addMagicArrow(labelOnly, this, font);
} else {
final HorizontalAlignment alignment = getMessageTextAlignment(link.getUmlDiagramType(), skinParam);
final boolean hasSeveralGuideLines = link.getLabel().hasSeveralGuideLines();
final TextBlock block;
if (hasSeveralGuideLines)
block = StringWithArrow.addSeveralMagicArrows(link.getLabel(), this, font, alignment, skinParam);
else
block = link.getLabel().create9(font, alignment, skinParam, skinParam.maxMessageSize());
labelOnly = addVisibilityModifier(block, link, skinParam);
if (getLinkArrow() != LinkArrow.NONE_OR_SEVERAL && hasSeveralGuideLines == false)
labelOnly = StringWithArrow.addMagicArrow(labelOnly, this, font);
}
final TextBlock noteOnly;
if (link.getNote() == null) {
noteOnly = TextBlockUtils.EMPTY_TEXT_BLOCK;
} else {
noteOnly = new EntityImageNoteLink(link.getNote(), link.getNoteColors(), skinParam, link.getStyleBuilder());
if (link.getNoteLinkStrategy() == NoteLinkStrategy.HALF_NOT_PRINTED
|| link.getNoteLinkStrategy() == NoteLinkStrategy.HALF_PRINTED_FULL)
divideLabelWidthByTwo = true;
}
if (link.getNotePosition() == Position.LEFT)
labelText = TextBlockUtils.mergeLR(noteOnly, labelOnly, VerticalAlignment.CENTER);
else if (link.getNotePosition() == Position.RIGHT)
labelText = TextBlockUtils.mergeLR(labelOnly, noteOnly, VerticalAlignment.CENTER);
else if (link.getNotePosition() == Position.TOP)
labelText = TextBlockUtils.mergeTB(noteOnly, labelOnly, HorizontalAlignment.CENTER);
else
labelText = TextBlockUtils.mergeTB(labelOnly, noteOnly, HorizontalAlignment.CENTER);
if (link.getQualifier1() == null)
startTailText = null;
else
startTailText = Display.getWithNewlines(link.getQualifier1()).create(font, HorizontalAlignment.CENTER,
skinParam);
if (link.getQualifier2() == null)
endHeadText = null;
else
endHeadText = Display.getWithNewlines(link.getQualifier2()).create(font, HorizontalAlignment.CENTER,
skinParam);
if (link.getType().getMiddleDecor() == LinkMiddleDecor.NONE)
this.labelShield = 0;
else
this.labelShield = 7;
}
private TextBlock addVisibilityModifier(TextBlock block, Link link, ISkinParam skinParam) {
final VisibilityModifier visibilityModifier = link.getVisibilityModifier();
if (visibilityModifier != null) {
final Rose rose = new Rose();
final HColor fore = rose.getHtmlColor(skinParam, visibilityModifier.getForeground());
TextBlock visibility = visibilityModifier.getUBlock(skinParam.classAttributeIconSize(), fore, null, false);
visibility = TextBlockUtils.withMargin(visibility, 0, 1, 2, 0);
block = TextBlockUtils.mergeLR(visibility, block, VerticalAlignment.CENTER);
}
final double marginLabel = startUid.equalsId(endUid) ? 6 : 1;
return TextBlockUtils.withMargin(block, marginLabel, marginLabel);
}
private HorizontalAlignment getMessageTextAlignment(UmlDiagramType umlDiagramType, ISkinParam skinParam) {
if (umlDiagramType == UmlDiagramType.STATE)
return skinParam.getHorizontalAlignment(AlignmentParam.stateMessageAlignment, null, false, null);
return skinParam.getDefaultTextAlignment(HorizontalAlignment.CENTER);
}
public boolean hasNoteLabelText() {
return labelText != null && labelText != TextBlockUtils.EMPTY_TEXT_BLOCK;
}
private LinkArrow getLinkArrow() {
return link.getLinkArrow();
}
public void appendLine(GraphvizVersion graphvizVersion, StringBuilder sb, DotMode dotMode, DotSplines dotSplines) {
// Log.println("inverted=" + isInverted());
// if (isInverted()) {
// sb.append(endUid);
// sb.append("->");
// sb.append(startUid);
// } else {
sb.append(startUid.getFullString());
sb.append("->");
sb.append(endUid.getFullString());
// }
sb.append("[");
final LinkType linkType = link.getTypePatchCluster();
String decoration = linkType.getSpecificDecorationSvek();
if (decoration.length() > 0 && decoration.endsWith(",") == false)
decoration += ",";
sb.append(decoration);
int length = link.getLength();
if (graphvizVersion.ignoreHorizontalLinks() && length == 1)
length = 2;
if (useRankSame) {
if (pragma.horizontalLineBetweenDifferentPackageAllowed() || link.isInvis() || length != 1) {
// if (graphvizVersion.isJs() == false) {
sb.append("minlen=" + (length - 1));
sb.append(",");
// }
}
} else {
sb.append("minlen=" + (length - 1));
sb.append(",");
}
sb.append("color=\"" + StringUtils.sharp000000(lineColor) + "\"");
if (hasNoteLabelText() || link.getLinkConstraint() != null) {
sb.append(",");
if (graphvizVersion.useXLabelInsteadOfLabel() || dotMode == DotMode.NO_LEFT_RIGHT_AND_XLABEL
|| dotSplines == DotSplines.ORTHO) {
sb.append("xlabel=<");
} else {
sb.append("label=<");
}
Dimension2D dimNote = hasNoteLabelText() ? labelText.calculateDimension(stringBounder) : CONSTRAINT_SPOT;
dimNote = Dimension2DDouble.delta(dimNote, 2 * labelShield);
appendTable(sb, eventuallyDivideByTwo(dimNote), noteLabelColor, graphvizVersion);
sb.append(">");
}
if (startTailText != null) {
sb.append(",");
sb.append("taillabel=<");
appendTable(sb, startTailText.calculateDimension(stringBounder), startTailColor, graphvizVersion);
sb.append(">");
}
if (endHeadText != null) {
sb.append(",");
sb.append("headlabel=<");
appendTable(sb, endHeadText.calculateDimension(stringBounder), endHeadColor, graphvizVersion);
sb.append(">");
}
if (link.isInvis()) {
sb.append(",");
sb.append("style=invis");
}
if (link.isConstraint() == false || link.hasTwoEntryPointsSameContainer())
sb.append(",constraint=false");
if (link.getSametail() != null)
sb.append(",sametail=" + link.getSametail());
sb.append("];");
SvekUtils.println(sb);
}
private Dimension2D eventuallyDivideByTwo(Dimension2D dim) {
if (divideLabelWidthByTwo)
return new Dimension2DDouble(dim.getWidth() / 2, dim.getHeight());
return dim;
}
public String rankSame() {
// if (graphvizVersion == GraphvizVersion.V2_34_0) {
// return null;
// }
if (pragma.horizontalLineBetweenDifferentPackageAllowed() == false && link.getLength() == 1
/* && graphvizVersion.isJs() == false */) {
return "{rank=same; " + getStartUidPrefix() + "; " + getEndUidPrefix() + "}";
}
return null;
}
public static void appendTable(StringBuilder sb, Dimension2D dim, int col, GraphvizVersion graphvizVersion) {
final int w = (int) dim.getWidth();
final int h = (int) dim.getHeight();
appendTable(sb, w, h, col);
}
public static void appendTable(StringBuilder sb, int w, int h, int col) {
sb.append("");
sb.append("");
sb.append("");
sb.append(" ");
sb.append(" ");
sb.append("
");
}
public final String getStartUidPrefix() {
return startUid.getPrefix();
}
public final String getEndUidPrefix() {
return endUid.getPrefix();
}
private UDrawable getExtremity(LinkDecor decor, PointListIterator pointListIterator, final Point2D center,
double angle, Cluster cluster, SvekNode nodeContact) {
final ExtremityFactory extremityFactory = decor.getExtremityFactory(backgroundColor);
if (cluster != null) {
if (extremityFactory != null) {
// System.err.println("angle=" + angle * 180 / Math.PI);
return extremityFactory.createUDrawable(center, angle, null);
}
if (decor == LinkDecor.EXTENDS)
return new ExtremityFactoryExtends(backgroundColor).createUDrawable(center, angle, null);
return null;
}
if (extremityFactory != null) {
final List points = pointListIterator.next();
if (points.size() == 0)
return extremityFactory.createUDrawable(center, angle, null);
final Point2D p0 = points.get(0);
final Point2D p1 = points.get(1);
final Point2D p2 = points.get(2);
Side side = null;
if (nodeContact != null)
side = nodeContact.getClusterPosition().getClosestSide(p1);
return extremityFactory.createUDrawable(p0, p1, p2, side);
} else if (decor == LinkDecor.NONE) {
final UPolygon sh = new UPolygon(pointListIterator.cloneMe().next());
final Point2D contact = sh.checkMiddleContactForSpecificTriangle(center);
if (contact != null) {
return new UDrawable() {
public void drawU(UGraphic ug) {
ULine line = new ULine(contact.getX() - center.getX(), contact.getY() - center.getY());
ug = ug.apply(new UTranslate(center));
ug.draw(line);
}
};
}
} else if (decor != LinkDecor.NONE) {
final UPolygon sh = new UPolygon(pointListIterator.next());
return new ExtremityOther(sh);
}
return null;
}
public void solveLine(SvgResult fullSvg) {
if (this.link.isInvis())
return;
int idx = fullSvg.getIndexFromColor(this.lineColor);
if (idx == -1) {
return;
// throw new IllegalStateException();
}
idx = fullSvg.indexOf("d=\"", idx);
if (idx == -1)
throw new IllegalStateException();
final int end = fullSvg.indexOf("\"", idx + 3);
final SvgResult path = fullSvg.substring(idx + 3, end);
if (DotPath.isPathConsistent(path.getSvg()) == false)
return;
dotPath = new DotPath(path);
if (projectionCluster != null) {
// System.err.println("Line::solveLine1 projectionCluster=" +
// projectionCluster.getClusterPosition());
projectionCluster.manageEntryExitPoint(stringBounder);
// System.err.println("Line::solveLine2 projectionCluster=" +
// projectionCluster.getClusterPosition());
// if (lhead != null)
// System.err.println("Line::solveLine ltail=" + lhead.getClusterPosition());
// if (ltail != null)
// System.err.println("Line::solveLine ltail=" + ltail.getClusterPosition());
}
dotPath = dotPath.simulateCompound(lhead, ltail);
final SvgResult lineSvg = fullSvg.substring(end);
PointListIterator pointListIterator = lineSvg.getPointsWithThisColor(lineColor);
final LinkType linkType = link.getType();
this.extremity1 = getExtremity(linkType.getDecor2(), pointListIterator, dotPath.getStartPoint(),
dotPath.getStartAngle() + Math.PI, ltail, bibliotekon.getNode(link.getEntity1()));
this.extremity2 = getExtremity(linkType.getDecor1(), pointListIterator, dotPath.getEndPoint(),
dotPath.getEndAngle(), lhead, bibliotekon.getNode(link.getEntity2()));
if (link.getEntity1().getLeafType() == LeafType.LOLLIPOP_HALF)
bibliotekon.getNode(link.getEntity1()).addImpact(dotPath.getStartAngle() + Math.PI);
if (link.getEntity2().getLeafType() == LeafType.LOLLIPOP_HALF)
bibliotekon.getNode(link.getEntity2()).addImpact(dotPath.getEndAngle());
if (extremity1 instanceof Extremity && extremity2 instanceof Extremity) {
final Point2D p1 = ((Extremity) extremity1).somePoint();
final Point2D p2 = ((Extremity) extremity2).somePoint();
if (p1 != null && p2 != null) {
// http://plantuml.sourceforge.net/qa/?qa=4240/some-relations-point-wrong-direction-when-the-linetype-ortho
final double dist1start = p1.distance(dotPath.getStartPoint());
final double dist1end = p1.distance(dotPath.getEndPoint());
final double dist2start = p2.distance(dotPath.getStartPoint());
final double dist2end = p2.distance(dotPath.getEndPoint());
if (dist1start > dist1end && dist2end > dist2start) {
pointListIterator = lineSvg.getPointsWithThisColor(lineColor);
this.extremity2 = getExtremity(linkType.getDecor1(), pointListIterator, dotPath.getEndPoint(),
dotPath.getEndAngle(), lhead, bibliotekon.getNode(link.getEntity2()));
this.extremity1 = getExtremity(linkType.getDecor2(), pointListIterator, dotPath.getStartPoint(),
dotPath.getStartAngle() + Math.PI, ltail, bibliotekon.getNode(link.getEntity1()));
}
}
}
if (hasNoteLabelText() || link.getLinkConstraint() != null) {
final Point2D pos = getXY(fullSvg, this.noteLabelColor);
if (pos != null) {
// corner1.manage(pos);
this.labelXY = hasNoteLabelText() ? TextBlockUtils.asPositionable(labelText, stringBounder, pos)
: TextBlockUtils.asPositionable(CONSTRAINT_SPOT, stringBounder, pos);
}
}
if (this.startTailText != null) {
final Point2D pos = getXY(fullSvg, this.startTailColor);
if (pos != null) {
// corner1.manage(pos);
this.startTailLabelXY = TextBlockUtils.asPositionable(startTailText, stringBounder, pos);
}
}
if (this.endHeadText != null) {
final Point2D pos = getXY(fullSvg, this.endHeadColor);
if (pos != null) {
// corner1.manage(pos);
this.endHeadLabelXY = TextBlockUtils.asPositionable(endHeadText, stringBounder, pos);
// corner1.manage(pos.getX() - 15, pos.getY());
}
}
if (isOpalisable() == false)
setOpale(false);
}
private boolean isOpalisable() {
return dotPath.getBeziers().size() <= 1;
}
private Point2D.Double getXY(SvgResult svgResult, int color) {
final int idx = svgResult.getIndexFromColor(color);
if (idx == -1)
return null;
return SvekUtils.getMinXY(svgResult.substring(idx).extractList(SvgResult.POINTS_EQUALS));
}
public void drawU(UGraphic ug, UStroke suggestedStroke, HColor color, Set ids) {
if (opale)
return;
if (link.isInvis())
return;
if (dotPath == null) {
Log.info("DotPath is null for " + this);
return;
}
ug.draw(link.commentForSvg());
final Map typeIDent = new EnumMap<>(UGroupType.class);
typeIDent.put(UGroupType.CLASS,
"link " + link.getEntity1().getCode() + " " + link.getEntity2().getCode() + " selected");
typeIDent.put(UGroupType.ID, "link_" + link.getEntity1().getCode() + "_" + link.getEntity2().getCode());
ug.startGroup(typeIDent);
double x = 0;
double y = 0;
final Url url = link.getUrl();
if (url != null)
ug.startUrl(url);
if (link.isAutoLinkOfAGroup()) {
final Cluster cl = bibliotekon.getCluster((IGroup) link.getEntity1());
if (cl != null) {
x += cl.getWidth();
x -= dotPath.getStartPoint().getX() - cl.getMinX();
}
}
x += dx;
y += dy;
if (this.link.getColors() != null) {
final HColor newColor = this.link.getColors().getColor(ColorType.ARROW, ColorType.LINE);
if (newColor != null)
color = newColor;
} else if (this.link.getSpecificColor() != null)
color = this.link.getSpecificColor();
ug = ug.apply(new HColorNone().bg()).apply(color);
final LinkType linkType = link.getType();
UStroke stroke = suggestedStroke == null || linkType.getStyle().isNormal() == false
? linkType.getStroke3(defaultThickness)
: suggestedStroke;
if (link.getColors() != null && link.getColors().getSpecificLineStroke() != null)
stroke = link.getColors().getSpecificLineStroke();
ug = ug.apply(stroke);
// double moveEndY = 0;
DotPath todraw = dotPath;
if (link.getEntity2().isGroup() && link.getEntity2().getUSymbol() instanceof USymbolFolder) {
final Cluster endCluster = bibliotekon.getCluster((IGroup) link.getEntity2());
if (endCluster != null) {
final double deltaFolderH = endCluster.checkFolderPosition(dotPath.getEndPoint(),
ug.getStringBounder());
todraw = dotPath.copy();
todraw.moveEndPoint(0, deltaFolderH);
// moveEndY = deltaFolderH;
}
}
if (extremity1 instanceof Extremity && extremity2 instanceof Extremity) {
// http://forum.plantuml.net/9421/arrow-inversion-with-skinparam-linetype-ortho-missing-arrow
final Point2D p1 = ((Extremity) extremity1).isTooSmallSoGiveThePointCloserToThisOne(todraw.getStartPoint());
if (p1 != null)
todraw.forceStartPoint(p1.getX(), p1.getY());
final Point2D p2 = ((Extremity) extremity2).isTooSmallSoGiveThePointCloserToThisOne(todraw.getEndPoint());
if (p2 != null)
todraw.forceEndPoint(p2.getX(), p2.getY());
}
final String comment = link.idCommentForSvg();
final String tmp = uniq(ids, comment);
todraw.setCommentAndCodeLine(tmp, link.getCodeLine());
drawRainbow(ug.apply(new UTranslate(x, y)), color, todraw, link.getSupplementaryColors(), stroke);
ug = ug.apply(new UStroke()).apply(color);
if (hasNoteLabelText() && this.labelXY != null
&& link.getNoteLinkStrategy() != NoteLinkStrategy.HALF_NOT_PRINTED)
this.labelText.drawU(ug.apply(new UTranslate(x + this.labelXY.getPosition().getX() + labelShield,
y + this.labelXY.getPosition().getY() + labelShield)));
if (this.startTailText != null && this.startTailLabelXY != null && this.startTailLabelXY.getPosition() != null)
this.startTailText.drawU(ug.apply(new UTranslate(x + this.startTailLabelXY.getPosition().getX(),
y + this.startTailLabelXY.getPosition().getY())));
if (this.endHeadText != null && this.endHeadLabelXY != null && this.endHeadLabelXY.getPosition() != null)
this.endHeadText.drawU(ug.apply(new UTranslate(x + this.endHeadLabelXY.getPosition().getX(),
y + this.endHeadLabelXY.getPosition().getY())));
if (linkType.getMiddleDecor() != LinkMiddleDecor.NONE) {
final PointAndAngle middle = dotPath.getMiddle();
final double angleRad = middle.getAngle();
final double angleDeg = -angleRad * 180.0 / Math.PI;
final UDrawable mi = linkType.getMiddleDecor().getMiddleFactory(arrowLollipopColor, backgroundColor)
.createUDrawable(angleDeg - 45);
mi.drawU(ug.apply(new UTranslate(x + middle.getX(), y + middle.getY())));
}
if (url != null)
ug.closeUrl();
if (link.getLinkConstraint() != null) {
final double xConstraint = x + this.labelXY.getPosition().getX();
final double yConstraint = y + this.labelXY.getPosition().getY();
// ug.apply(new UTranslate(xConstraint, yConstraint)).draw(new URectangle(10, 10));
final List square = getSquare(xConstraint, yConstraint);
final Set bez = dotPath.sample();
Point2D minPt = null;
double minDist = Double.MAX_VALUE;
for (Point2D pt : square)
for (Point2D pt2 : bez) {
final double distance = pt2.distance(pt);
if (minPt == null || distance < minDist) {
minPt = pt;
minDist = distance;
}
}
link.getLinkConstraint().setPosition(link, minPt);
link.getLinkConstraint().drawMe(ug, skinParam);
}
ug.closeGroup();
}
private List getSquare(double x, double y) {
final List result = new ArrayList<>();
result.add(new Point2D.Double(x, y));
result.add(new Point2D.Double(x + 5, y));
result.add(new Point2D.Double(x + 10, y));
result.add(new Point2D.Double(x, y + 5));
result.add(new Point2D.Double(x + 10, y + 5));
result.add(new Point2D.Double(x, y + 10));
result.add(new Point2D.Double(x + 5, y + 10));
result.add(new Point2D.Double(x + 10, y + 10));
return result;
}
private String uniq(final Set ids, final String comment) {
boolean changed = ids.add(comment);
if (changed)
return comment;
int i = 1;
while (true) {
final String candidate = comment + "-" + i;
changed = ids.add(candidate);
if (changed)
return candidate;
i++;
}
}
private void drawRainbow(UGraphic ug, HColor color, DotPath todraw, List supplementaryColors,
UStroke stroke) {
ug.draw(todraw);
final LinkType linkType = link.getType();
if (this.extremity2 != null) {
UGraphic ug2 = ug.apply(color).apply(stroke.onlyThickness());
if (linkType.getDecor1().isFill())
ug2 = ug2.apply(color.bg());
else
ug2 = ug2.apply(new HColorNone().bg());
// System.err.println("Line::draw EXTREMITY1");
this.extremity2.drawU(ug2);
}
if (this.extremity1 != null) {
UGraphic ug2 = ug.apply(color).apply(stroke.onlyThickness());
if (linkType.getDecor2().isFill())
ug2 = ug2.apply(color.bg());
else
ug2 = ug2.apply(new HColorNone().bg());
// System.err.println("Line::draw EXTREMITY2");
this.extremity1.drawU(ug2);
}
int i = 0;
for (Colors colors : supplementaryColors) {
ug.apply(new UTranslate(2 * (i + 1), 2 * (i + 1))).apply(colors.getColor(ColorType.LINE)).draw(todraw);
i++;
}
}
public boolean isInverted() {
return link.isInverted();
}
private double getDecorDzeta() {
final LinkType linkType = link.getType();
final int size1 = linkType.getDecor1().getMargin();
final int size2 = linkType.getDecor2().getMargin();
return size1 + size2;
}
public double getHorizontalDzeta(StringBounder stringBounder) {
if (startUid.equalsId(endUid))
return getDecorDzeta();
final ArithmeticStrategy strategy;
if (isHorizontal())
strategy = new ArithmeticStrategySum();
else
return 0;
if (hasNoteLabelText())
strategy.eat(labelText.calculateDimension(stringBounder).getWidth());
if (startTailText != null)
strategy.eat(startTailText.calculateDimension(stringBounder).getWidth());
if (endHeadText != null)
strategy.eat(endHeadText.calculateDimension(stringBounder).getWidth());
return strategy.getResult() + getDecorDzeta();
}
private boolean isHorizontal() {
return link.getLength() == 1;
}
public double getVerticalDzeta(StringBounder stringBounder) {
if (startUid.equalsId(endUid))
return getDecorDzeta();
if (isHorizontal())
return 0;
final ArithmeticStrategy strategy = new ArithmeticStrategySum();
if (hasNoteLabelText())
strategy.eat(labelText.calculateDimension(stringBounder).getHeight());
if (startTailText != null)
strategy.eat(startTailText.calculateDimension(stringBounder).getHeight());
if (endHeadText != null)
strategy.eat(endHeadText.calculateDimension(stringBounder).getHeight());
return strategy.getResult() + getDecorDzeta();
}
public void manageCollision(Collection allNodes) {
for (SvekNode sh : allNodes) {
final Positionable cl = PositionableUtils.addMargin(sh, 8, 8);
if (startTailText != null && startTailLabelXY != null && PositionableUtils.intersect(cl, startTailLabelXY))
startTailLabelXY = PositionableUtils.moveAwayFrom(cl, startTailLabelXY);
if (endHeadText != null && endHeadLabelXY != null && PositionableUtils.intersect(cl, endHeadLabelXY))
endHeadLabelXY = PositionableUtils.moveAwayFrom(cl, endHeadLabelXY);
}
// final Positionable start = getStartTailPositionnable();
// if (start != null) {
// for (Shape sh : allShapes) {
// if (cut(start, sh)) {
// avoid(startTailLabelXY, start, sh);
// }
// }
// }
//
// final Positionable end = getEndHeadPositionnable();
// if (end != null) {
// for (Shape sh : allShapes) {
// if (cut(end, sh)) {
// avoid(endHeadLabelXY, end, sh);
// }
// }
// }
}
private void avoid(Point2D.Double move, Positionable pos, SvekNode sh) {
final Oscillator oscillator = new Oscillator();
final Point2D.Double orig = new Point2D.Double(move.x, move.y);
while (cut(pos, sh)) {
final Point2D.Double m = oscillator.nextPosition();
move.setLocation(orig.x + m.x, orig.y + m.y);
}
}
private boolean cut(Positionable pos, SvekNode sh) {
return BezierUtils.intersect(pos, sh) || tooClose(pos);
}
private boolean tooClose(Positionable pos) {
final double dist = dotPath.getMinDist(BezierUtils.getCenter(pos));
final Dimension2D dim = pos.getSize();
// Log.println("dist=" + dist);
return dist < (dim.getWidth() / 2 + 2) || dist < (dim.getHeight() / 2 + 2);
}
public void moveSvek(double deltaX, double deltaY) {
this.dx += deltaX;
this.dy += deltaY;
}
public final DotPath getDotPath() {
final DotPath result = dotPath.copy();
result.moveSvek(dx, dy);
return result;
}
public int getLength() {
return link.getLength();
}
public void setOpale(boolean opale) {
this.link.setOpale(opale);
this.opale = opale;
}
public boolean isOpale() {
return opale;
}
public boolean isHorizontalSolitary() {
return link.isHorizontalSolitary();
}
public boolean isLinkFromOrTo(IEntity group) {
return link.getEntity1() == group || link.getEntity2() == group;
}
public boolean hasEntryPoint() {
return link.hasEntryPoint();
}
public void setProjectionCluster(Cluster cluster) {
this.projectionCluster = cluster;
}
public boolean isHidden() {
return link.isHidden();
}
public boolean sameConnections(SvekLine other) {
return link.sameConnections(other.link);
}
private boolean isAutolink() {
return link.getEntity1() == link.getEntity2();
}
public Point2D getMyPoint(IEntity entity) {
if (link.getEntity1() == entity)
return moveDelta(dotPath.getStartPoint());
if (link.getEntity2() == entity)
return moveDelta(dotPath.getEndPoint());
throw new IllegalArgumentException();
}
private Point2D moveDelta(Point2D pt) {
return new Point2D.Double(pt.getX() + dx, pt.getY() + dy);
}
public boolean isLink(Link link) {
return this.link == link;
}
public Point2D getStartContactPoint() {
if (dotPath == null)
return null;
final Point2D start = dotPath.getStartPoint();
if (start == null)
return null;
return new Point2D.Double(dx + start.getX(), dy + start.getY());
}
public Point2D getEndContactPoint() {
final Point2D end = dotPath.getEndPoint();
if (end == null)
return null;
return new Point2D.Double(dx + end.getX(), dy + end.getY());
}
public IEntity getOther(IEntity entity) {
if (link.contains(entity))
return link.getOther(entity);
return null;
}
public StyleBuilder getCurrentStyleBuilder() {
return link.getStyleBuilder();
}
public Stereotype getStereotype() {
return link.getStereotype();
}
}