All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.plantuml.svek.SvekLine Maven / Gradle / Ivy

Go to download

PlantUML is a component that allows to quickly write : * sequence diagram, * use case diagram, * class diagram, * activity diagram, * component diagram, * state diagram * object diagram

There is a newer version: 8059
Show newest version
/* ========================================================================
 * 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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy