net.sourceforge.plantuml.sequencediagram.graphic.DrawableSet 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-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://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.sequencediagram.graphic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import net.sourceforge.plantuml.klimt.UClip;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.drawing.txt.UGraphicTxt;
import net.sourceforge.plantuml.klimt.font.StringBounder;
import net.sourceforge.plantuml.klimt.geom.XDimension2D;
import net.sourceforge.plantuml.klimt.shape.AbstractTextBlock;
import net.sourceforge.plantuml.klimt.shape.TextBlock;
import net.sourceforge.plantuml.sequencediagram.Doll;
import net.sourceforge.plantuml.sequencediagram.Event;
import net.sourceforge.plantuml.sequencediagram.Newpage;
import net.sourceforge.plantuml.sequencediagram.Participant;
import net.sourceforge.plantuml.sequencediagram.ParticipantEnglober;
import net.sourceforge.plantuml.skin.Area;
import net.sourceforge.plantuml.skin.Component;
import net.sourceforge.plantuml.skin.ComponentType;
import net.sourceforge.plantuml.skin.Context2D;
import net.sourceforge.plantuml.skin.LineParam;
import net.sourceforge.plantuml.skin.SimpleContext2D;
import net.sourceforge.plantuml.skin.SkinParamBackcolored;
import net.sourceforge.plantuml.skin.rose.Rose;
import net.sourceforge.plantuml.style.ISkinParam;
import net.sourceforge.plantuml.url.Url;
public class DrawableSet {
private final Map participants = new LinkedHashMap();
private final Map events = new HashMap();
private final Map participantEnglobers2 = new LinkedHashMap();
private final List eventsList = new ArrayList<>();
private final Rose skin;
private final ISkinParam skinParam;
private XDimension2D dimension;
private double topStartingY;
DrawableSet(Rose skin, ISkinParam skinParam) {
this.skin = Objects.requireNonNull(skin);
this.skinParam = Objects.requireNonNull(skinParam);
}
public ParticipantBox getVeryfirst() {
return participants.values().iterator().next().getParticipantBox();
}
public final Rose getSkin() {
return skin;
}
public final ISkinParam getSkinParam() {
return skinParam;
}
public Collection getAllEvents() {
return Collections.unmodifiableCollection(eventsList);
}
public Set getAllParticipants() {
return Collections.unmodifiableSet(participants.keySet());
}
public Collection getAllLivingParticipantBox() {
return Collections.unmodifiableCollection(participants.values());
}
public Collection getAllGraphicalElements() {
final Collection result = new ArrayList<>();
for (Event ev : eventsList)
result.add(events.get(ev));
return Collections.unmodifiableCollection(result);
}
public LivingParticipantBox getLivingParticipantBox(Participant p) {
return participants.get(p);
}
public GraphicalElement getEvent(Event ev) {
return events.get(ev);
}
public double getHeadHeight(StringBounder stringBounder) {
double r = 0;
for (Participant p : participants.keySet()) {
final double y = getHeadAndEngloberHeight(p, stringBounder);
r = Math.max(r, y);
}
return r;
}
public double getHeadAndEngloberHeight(Participant p, StringBounder stringBounder) {
final LivingParticipantBox box = participants.get(p);
final double height = box.getParticipantBox().getHeadHeight(stringBounder);
final Doll doll = getParticipantEnglober(p, stringBounder);
if (doll == null)
return height;
final Component comp = skin.createComponent(doll.getUsedStyles(), ComponentType.ENGLOBER, null, skinParam,
doll.getParticipantEnglober().getTitle());
final double heightEnglober = comp.getPreferredHeight(stringBounder);
return height + heightEnglober;
}
public List getExistingParticipantEnglober(StringBounder stringBounder) {
final List result = new ArrayList<>();
Doll pending = null;
for (Map.Entry ent : participantEnglobers2.entrySet()) {
final ParticipantEnglober englober = ent.getValue();
if (englober == null) {
pending = null;
continue;
}
assert englober != null;
if (pending != null && englober == pending.getParticipantEnglober()) {
pending.addParticipant(ent.getKey());
continue;
}
pending = Doll.createPuma(englober, ent.getKey(), getSkinParam(), skin, stringBounder,
skinParam.getCurrentStyleBuilder());
result.add(pending);
}
return Collections.unmodifiableList(result);
}
public double getOffsetForEnglobers(StringBounder stringBounder) {
double result = 0;
for (Doll englober : getExistingParticipantEnglober(stringBounder)) {
final Component comp = skin.createComponent(null, ComponentType.ENGLOBER, null, skinParam,
englober.getParticipantEnglober().getTitle());
final double height = comp.getPreferredHeight(stringBounder);
if (height > result)
result = height;
}
return result;
}
static private final int MARGIN_FOR_ENGLOBERS = 4;
static private final int MARGIN_FOR_ENGLOBERS1 = 2;
public double getTailHeight(StringBounder stringBounder, boolean showTail) {
final double marginForEnglobers = getExistingParticipantEnglober(stringBounder).size() > 0
? MARGIN_FOR_ENGLOBERS
: 0;
if (showTail == false)
return 1 + marginForEnglobers;
double r = 0;
for (LivingParticipantBox livingParticipantBox : participants.values()) {
final double y = livingParticipantBox.getParticipantBox().getTailHeight(stringBounder);
r = Math.max(r, y);
}
return r + marginForEnglobers;
}
public void addParticipant(Participant p, ParticipantEnglober participantEnglober) {
participants.put(p, null);
participantEnglobers2.put(p, participantEnglober);
}
public void setLivingParticipantBox(Participant p, LivingParticipantBox box) {
if (participants.containsKey(p) == false)
throw new IllegalArgumentException();
participants.put(p, box);
}
public void addEvent(Event event, GraphicalElement object) {
if (events.keySet().contains(event) == false)
eventsList.add(event);
events.put(event, object);
}
public void addEvent(Newpage newpage, GraphicalNewpage object, Event justBefore) {
final int idx = eventsList.indexOf(justBefore);
if (idx == -1)
throw new IllegalArgumentException();
eventsList.add(idx, newpage);
events.put(newpage, object);
assert events.size() == eventsList.size();
}
void setDimension(XDimension2D dim) {
if (dimension != null)
throw new IllegalStateException();
this.dimension = dim;
}
public XDimension2D getDimension() {
return dimension;
}
TextBlock asTextBlock(final double delta, final double width, final Page page, final boolean showTail) {
return new AbstractTextBlock() {
public void drawU(UGraphic ug) {
drawU22(ug, delta, width, page, showTail);
}
public XDimension2D calculateDimension(StringBounder stringBounder) {
final double height = page.getHeight();
return new XDimension2D(width, height);
}
};
}
void drawU22(final UGraphic ug, final double delta, double width, Page page, boolean showTail) {
final double height = page.getHeight();
final UGraphic ugTranslated = clipAndTranslate2(delta, width, page, ug);
final SimpleContext2D context = new SimpleContext2D(true);
this.drawDolls(ug, height - MARGIN_FOR_ENGLOBERS1, context);
this.drawPlaygroundU(ugTranslated, context);
this.drawLineU22(ug, showTail, page);
this.drawHeadTailU(ug, page, showTail ? height - getTailHeight(ug.getStringBounder(), true) : 0);
this.drawPlaygroundU(ugTranslated, new SimpleContext2D(false));
}
private UGraphic clipAndTranslate2(final double delta, double width, Page p, UGraphic ug) {
ug = ug.apply(new UClip(0, p.getBodyRelativePosition(), width, p.getBodyHeight() + 1));
ug = ug.apply(getTranslate4(delta));
return ug;
}
private UTranslate getTranslate4(final double delta) {
if (delta > 0)
return UTranslate.dy(-delta);
return UTranslate.none();
}
private void drawLineU22(UGraphic ug, boolean showTail, Page page) {
// http://plantuml.sourceforge.net/qa/?qa=4826/lifelines-broken-for-txt-seq-diagrams-when-create-is-used
// ::revert when __CORE__
final boolean isTxt = ug instanceof UGraphicTxt;
// final boolean isTxt = false;
// ::done
for (LivingParticipantBox box : getAllLivingParticipantBox()) {
final double create = box.getCreate();
final double startMin = page.getBodyRelativePosition() - box.magicMargin(ug.getStringBounder());
final double endMax = startMin + page.getBodyHeight() + 2 * box.magicMargin(ug.getStringBounder());
double start = startMin;
if (create > 0) {
if (create > page.getNewpage2())
continue;
if (create >= page.getNewpage1() && create < page.getNewpage2()) {
if (isTxt)
start = (int) create;
else
start += create - page.getNewpage1() + 2 * box.magicMargin(ug.getStringBounder());
}
}
final double myDelta = page.getNewpage1() - page.getHeaderHeight();
box.drawLineU22(ug, start, endMax, showTail, myDelta);
}
}
private void drawHeadTailU(UGraphic ug, Page page, double positionTail) {
for (Map.Entry ent : participants.entrySet()) {
final Participant p = ent.getKey();
final LivingParticipantBox box = ent.getValue();
final double create = box.getCreate();
boolean showHead = true;
if (create > 0) {
if (create > page.getNewpage2())
continue;
if (create >= page.getNewpage1() && create < page.getNewpage2())
showHead = false;
}
final Url url = p.getUrl();
if (url != null)
ug.startUrl(url);
box.getParticipantBox().drawHeadTailU(ug, topStartingY, showHead, positionTail);
if (url != null)
ug.closeUrl();
}
}
private double getMaxX() {
return dimension.getWidth();
}
private double getMaxY() {
return dimension.getHeight();
}
private void drawPlaygroundU(UGraphic ug, Context2D context) {
for (Participant p : getAllParticipants())
drawLifeLineU(ug, p);
for (GraphicalElement element : getAllGraphicalElements())
element.drawU(ug, getMaxX(), context);
}
private void drawDolls(UGraphic ug, double height, Context2D context) {
for (Doll doll : getExistingParticipantEnglober(ug.getStringBounder())) {
double x1 = getX1(doll);
final double x2 = getX2(ug.getStringBounder(), doll);
final Component comp = getEngloberComponent(doll);
final double width = x2 - x1;
final double preferedWidth = getEngloberPreferedWidth(ug.getStringBounder(), doll);
if (preferedWidth > width) {
// if (englober.getFirst2() == englober.getLast2()) {
x1 -= (preferedWidth - width) / 2;
// }
final XDimension2D dim = new XDimension2D(preferedWidth, height);
comp.drawU(ug.apply(new UTranslate(x1, 1)), new Area(dim), context);
} else {
final XDimension2D dim = new XDimension2D(width, height);
comp.drawU(ug.apply(new UTranslate(x1, 1)), new Area(dim), context);
}
}
}
public double getEngloberPreferedWidth(StringBounder stringBounder, Doll doll) {
return getEngloberComponent(doll).getPreferredWidth(stringBounder);
}
private Component getEngloberComponent(Doll doll) {
final ParticipantEnglober participantEnglober = doll.getParticipantEnglober();
final ISkinParam s = participantEnglober.getBoxColor() == null ? skinParam
: new SkinParamBackcolored(skinParam, participantEnglober.getBoxColor());
return skin.createComponent(doll.getUsedStyles(), ComponentType.ENGLOBER, null, s,
participantEnglober.getTitle());
}
public double getX1(Doll doll) {
final Participant first = doll.getFirst2TOBEPRIVATE();
final ParticipantBox firstBox = participants.get(first).getParticipantBox();
return firstBox.getStartingX() + 1;
}
public double getX2(StringBounder stringBounder, Doll doll) {
final Participant last = doll.getLast2TOBEPRIVATE();
final ParticipantBox lastBox = participants.get(last).getParticipantBox();
return lastBox.getMaxX(stringBounder) - 1;
}
private void drawLifeLineU(UGraphic ug, Participant p) {
final LifeLine line = getLivingParticipantBox(p).getLifeLine();
line.finish(getMaxY());
line.drawU(ug, getSkin(), skinParam);
}
private Doll getParticipantEnglober(Participant p, StringBounder stringBounder) {
for (Doll pe : getExistingParticipantEnglober(stringBounder))
if (pe.contains(p))
return pe;
return null;
}
public void setTopStartingY(double topStartingY) {
this.topStartingY = topStartingY;
}
Participant getFirst(Collection someParticipants) {
final List list = new ArrayList<>(participants.keySet());
int min = -1;
for (Participant p : someParticipants) {
final int n = list.indexOf(p);
assert n != -1;
if (min == -1 || min > n)
min = n;
}
return list.get(min);
}
Participant getLast(Collection someParticipants) {
final List list = new ArrayList<>(participants.keySet());
int max = -1;
for (Participant p : someParticipants) {
final int n = list.indexOf(p);
assert n != -1;
if (max == -1 || max < n)
max = n;
}
return list.get(max);
}
public double getArrowThickness() {
final UStroke result = skinParam.getThickness(LineParam.sequenceArrow, null);
if (result == null)
return 1;
return result.getThickness();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy