net.sourceforge.plantuml.svek.Cluster 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-2013, Arnaud Roques
*
* Project Info: http://plantuml.sourceforge.net
*
* 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.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* Original Author: Arnaud Roques
*
* Revision $Revision: 4236 $
*
*/
package net.sourceforge.plantuml.svek;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.plantuml.ColorParam;
import net.sourceforge.plantuml.Dimension2DDouble;
import net.sourceforge.plantuml.FontParam;
import net.sourceforge.plantuml.ISkinParam;
import net.sourceforge.plantuml.StringUtils;
import net.sourceforge.plantuml.UmlDiagramType;
import net.sourceforge.plantuml.UniqueSequence;
import net.sourceforge.plantuml.Url;
import net.sourceforge.plantuml.cucadiagram.EntityPosition;
import net.sourceforge.plantuml.cucadiagram.EntityUtils;
import net.sourceforge.plantuml.cucadiagram.IEntity;
import net.sourceforge.plantuml.cucadiagram.IGroup;
import net.sourceforge.plantuml.cucadiagram.Member;
import net.sourceforge.plantuml.cucadiagram.MethodsOrFieldsArea;
import net.sourceforge.plantuml.cucadiagram.Stereotype;
import net.sourceforge.plantuml.cucadiagram.dot.DotData;
import net.sourceforge.plantuml.graphic.HtmlColor;
import net.sourceforge.plantuml.graphic.HtmlColorTransparent;
import net.sourceforge.plantuml.graphic.StringBounder;
import net.sourceforge.plantuml.graphic.TextBlock;
import net.sourceforge.plantuml.graphic.TextBlockEmpty;
import net.sourceforge.plantuml.graphic.TextBlockWidth;
import net.sourceforge.plantuml.posimo.Moveable;
import net.sourceforge.plantuml.skin.rose.Rose;
import net.sourceforge.plantuml.svek.image.EntityImageState;
import net.sourceforge.plantuml.ugraphic.UChangeBackColor;
import net.sourceforge.plantuml.ugraphic.UChangeColor;
import net.sourceforge.plantuml.ugraphic.UGraphic;
import net.sourceforge.plantuml.ugraphic.ULine;
import net.sourceforge.plantuml.ugraphic.URectangle;
import net.sourceforge.plantuml.ugraphic.UStroke;
import net.sourceforge.plantuml.ugraphic.UTranslate;
public class Cluster implements Moveable {
private final Cluster parent;
private final IGroup group;
private final List shapes = new ArrayList();
private final List children = new ArrayList();
private final int color;
private final int colorTitle;
private final ISkinParam skinParam;
private int titleAndAttributeWidth;
private int titleAndAttributeHeight;
private TextBlock ztitle;
private TextBlock zstereo;
private double xTitle;
private double yTitle;
private double minX;
private double minY;
private double maxX;
private double maxY;
public void moveSvek(double deltaX, double deltaY) {
this.xTitle += deltaX;
this.minX += deltaX;
this.maxX += deltaX;
this.yTitle += deltaY;
this.minY += deltaY;
this.maxY += deltaY;
}
private boolean hasEntryOrExitPoint() {
for (Shape sh : shapes) {
if (sh.getEntityPosition() != EntityPosition.NORMAL) {
return true;
}
}
return false;
}
public Cluster(ColorSequence colorSequence, ISkinParam skinParam, IGroup root) {
this(null, root, colorSequence, skinParam);
}
private Cluster(Cluster parent, IGroup group, ColorSequence colorSequence, ISkinParam skinParam) {
if (group == null) {
throw new IllegalStateException();
}
this.parent = parent;
this.group = group;
this.color = colorSequence.getValue();
this.colorTitle = colorSequence.getValue();
this.skinParam = skinParam;
}
@Override
public String toString() {
return super.toString() + " " + group;
}
public final Cluster getParent() {
return parent;
}
public void addShape(Shape sh) {
if (sh == null) {
throw new IllegalArgumentException();
}
this.shapes.add(sh);
sh.setCluster(this);
}
public final List getShapes() {
return Collections.unmodifiableList(shapes);
}
private List getShapesOrderedTop(Collection lines) {
final List firsts = new ArrayList();
final Set tops = new HashSet();
final Map shs = new HashMap();
for (final Iterator it = shapes.iterator(); it.hasNext();) {
final Shape sh = it.next();
shs.put(sh.getUid(), sh);
if (sh.isTop() && sh.getEntityPosition() == EntityPosition.NORMAL) {
firsts.add(sh);
tops.add(sh.getUid());
}
}
for (Line l : lines) {
if (tops.contains(l.getStartUid())) {
final Shape sh = shs.get(l.getEndUid());
if (sh != null && sh.getEntityPosition() == EntityPosition.NORMAL) {
firsts.add(0, sh);
}
}
if (l.isInverted()) {
final Shape sh = shs.get(l.getStartUid());
if (sh != null && sh.getEntityPosition() == EntityPosition.NORMAL) {
firsts.add(0, sh);
}
}
}
return firsts;
}
private List getShapesEntryExit(EntityPosition position) {
final List result = new ArrayList();
for (final Iterator it = shapes.iterator(); it.hasNext();) {
final Shape sh = it.next();
if (sh.getEntityPosition() == position) {
result.add(sh);
}
}
return result;
}
private List getShapesOrderedWithoutTop(Collection lines) {
final List all = new ArrayList(shapes);
final Set tops = new HashSet();
final Map shs = new HashMap();
for (final Iterator it = all.iterator(); it.hasNext();) {
final Shape sh = it.next();
if (sh.getEntityPosition() != EntityPosition.NORMAL) {
it.remove();
continue;
}
shs.put(sh.getUid(), sh);
if (sh.isTop()) {
tops.add(sh.getUid());
it.remove();
}
}
for (Line l : lines) {
if (tops.contains(l.getStartUid())) {
final Shape sh = shs.get(l.getEndUid());
if (sh != null) {
all.remove(sh);
}
}
if (l.isInverted()) {
final Shape sh = shs.get(l.getStartUid());
if (sh != null) {
all.remove(sh);
}
}
}
return all;
}
public final List getChildren() {
return Collections.unmodifiableList(children);
}
public Cluster createChild(IGroup g, int titleAndAttributeWidth, int titleAndAttributeHeight, TextBlock title,
TextBlock stereo, ColorSequence colorSequence, ISkinParam skinParam) {
final Cluster child = new Cluster(this, g, colorSequence, skinParam);
child.titleAndAttributeWidth = titleAndAttributeWidth;
child.titleAndAttributeHeight = titleAndAttributeHeight;
child.ztitle = title;
child.zstereo = stereo;
this.children.add(child);
return child;
}
public final IGroup getGroup() {
return group;
}
public final int getTitleAndAttributeWidth() {
return titleAndAttributeWidth;
}
public final int getTitleAndAttributeHeight() {
return titleAndAttributeHeight;
}
public double getWidth() {
return maxX - minX;
}
public double getMinX() {
return minX;
}
public ClusterPosition getClusterPosition() {
return new ClusterPosition(minX, minY, maxX, maxY);
}
public void setTitlePosition(double x, double y) {
this.xTitle = x;
this.yTitle = y;
}
public void drawU(UGraphic ug, HtmlColor borderColor, DotData dotData) {
final Url url = group.getUrl99();
if (url != null) {
ug.startUrl(url);
}
try {
if (hasEntryOrExitPoint()) {
manageEntryExitPoint(dotData, ug.getStringBounder());
}
if (skinParam.useSwimlanes()) {
drawSwinLinesState(ug, borderColor, dotData);
return;
}
final boolean isState = dotData.getUmlDiagramType() == UmlDiagramType.STATE;
if (isState) {
drawUState(ug, borderColor, dotData);
return;
}
PackageStyle style = group.getPackageStyle();
if (style == null) {
style = dotData.getSkinParam().getPackageStyle();
}
if (ztitle != null || zstereo != null) {
final HtmlColor stateBack = getStateBackColor(getBackColor(), dotData.getSkinParam(),
group.getStereotype() == null ? null : group.getStereotype().getLabel());
final ClusterDecoration decoration = new ClusterDecoration(style, group.getUSymbol(), ztitle, zstereo,
stateBack, minX, minY, maxX, maxY);
decoration.drawU(ug, borderColor, dotData.getSkinParam().shadowing());
return;
}
final URectangle rect = new URectangle(maxX - minX, maxY - minY);
if (dotData.getSkinParam().shadowing()) {
rect.setDeltaShadow(3.0);
}
final HtmlColor stateBack = getStateBackColor(getBackColor(), dotData.getSkinParam(),
group.getStereotype() == null ? null : group.getStereotype().getLabel());
ug = ug.apply(new UChangeBackColor(stateBack)).apply(new UChangeColor(borderColor));
ug.apply(new UStroke(2)).apply(new UTranslate(minX, minY)).draw(rect);
} finally {
if (url != null) {
ug.closeAction();
}
}
}
private void manageEntryExitPoint(DotData dotData, StringBounder stringBounder) {
final Collection insides = new ArrayList();
final List points = new ArrayList();
for (Shape sh : shapes) {
if (sh.getEntityPosition() == EntityPosition.NORMAL) {
insides.add(sh.getClusterPosition());
} else {
points.add(sh.getClusterPosition().getPointCenter());
}
}
for (Cluster in : children) {
insides.add(in.getClusterPosition());
}
final FrontierCalculator frontierCalculator = new FrontierCalculator(getClusterPosition(), insides, points);
if (titleAndAttributeHeight > 0 && titleAndAttributeWidth > 0) {
frontierCalculator.ensureMinWidth(titleAndAttributeWidth + 10);
}
final ClusterPosition forced = frontierCalculator.getSuggestedPosition();
xTitle += ((forced.getMinX() - minX) + (forced.getMaxX() - maxX)) / 2;
minX = forced.getMinX();
minY = forced.getMinY();
maxX = forced.getMaxX();
maxY = forced.getMaxY();
yTitle = minY + IEntityImage.MARGIN;
final double widthTitle = ztitle.calculateDimension(stringBounder).getWidth();
xTitle = minX + ((maxX - minX - widthTitle) / 2);
}
private void drawSwinLinesState(UGraphic ug, HtmlColor borderColor, DotData dotData) {
if (ztitle != null) {
ztitle.drawU(ug.apply(new UTranslate(xTitle, 0)));
}
final ULine line = new ULine(0, maxY - minY);
ug = ug.apply(new UChangeColor(borderColor));
ug.apply(new UTranslate(minX, 0)).draw(line);
ug.apply(new UTranslate(maxX, 0)).draw(line);
}
private HtmlColor getColor(DotData dotData, ColorParam colorParam, String stereo) {
return new Rose().getHtmlColor(dotData.getSkinParam(), colorParam, stereo);
}
private void drawUState(UGraphic ug, HtmlColor borderColor, DotData dotData) {
final Dimension2D total = new Dimension2DDouble(maxX - minX, maxY - minY);
final double suppY;
if (ztitle == null) {
suppY = 0;
} else {
suppY = ztitle.calculateDimension(ug.getStringBounder()).getHeight() + IEntityImage.MARGIN
+ IEntityImage.MARGIN_LINE;
}
HtmlColor stateBack = getBackColor();
if (stateBack == null) {
stateBack = getColor(dotData, ColorParam.stateBackground, group.getStereotype() == null ? null : group
.getStereotype().getLabel());
}
final HtmlColor background = getColor(dotData, ColorParam.background, null);
final TextBlockWidth attribute = getTextBlockAttribute(dotData);
final double attributeHeight = attribute.calculateDimension(ug.getStringBounder()).getHeight();
final RoundedContainer r = new RoundedContainer(total, suppY, attributeHeight
+ (attributeHeight > 0 ? IEntityImage.MARGIN : 0), borderColor, stateBack, background);
r.drawU(ug.apply(new UTranslate(minX, minY)), dotData.getSkinParam().shadowing());
if (ztitle != null) {
ztitle.drawU(ug.apply(new UTranslate(xTitle, yTitle)));
}
if (attributeHeight > 0) {
attribute.asTextBlock(total.getWidth()).drawU(
ug.apply(new UTranslate(minX + IEntityImage.MARGIN, minY + suppY + IEntityImage.MARGIN / 2.0)));
}
final Stereotype stereotype = group.getStereotype();
final boolean withSymbol = stereotype != null && stereotype.isWithOOSymbol();
if (withSymbol) {
EntityImageState.drawSymbol(ug.apply(new UChangeColor(borderColor)), maxX, maxY);
}
}
private TextBlockWidth getTextBlockAttribute(DotData dotData) {
final TextBlockWidth attribute;
final List members = group.getFieldsToDisplay();
if (members.size() == 0) {
attribute = new TextBlockEmpty();
} else {
attribute = new MethodsOrFieldsArea(members, FontParam.STATE_ATTRIBUTE, dotData.getSkinParam());
}
return attribute;
}
public void setPosition(double minX, double minY, double maxX, double maxY) {
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
private boolean isThereALinkFromOrToGroup(Collection lines) {
for (Line line : lines) {
if (line.isLinkFromOrToGroup(group)) {
return true;
}
}
return false;
}
public void printCluster1(StringBuilder sb, Collection lines) {
for (Shape sh : getShapesOrderedTop(lines)) {
sh.appendShape(sb);
}
}
private List addProtection(List entries, double width) {
final List result = new ArrayList();
result.add(entries.get(0));
for (int i = 1; i < entries.size(); i++) {
result.add(new ShapePseudoImpl("psd" + UniqueSequence.getValue(), width, 5));
result.add(entries.get(i));
}
return result;
}
private double getMaxWidthFromLabelForEntryExit(List entries, StringBounder stringBounder) {
double result = -Double.MAX_VALUE;
for (Shape shape : entries) {
final double w = getMaxWidthFromLabelForEntryExit(shape, stringBounder);
if (w > result) {
result = w;
}
}
return result;
}
private double getMaxWidthFromLabelForEntryExit(Shape shape, StringBounder stringBounder) {
return shape.getMaxWidthFromLabelForEntryExit(stringBounder);
}
public void printClusterEntryExit(StringBuilder sb, StringBounder stringBounder) {
// final List entries = getShapesEntryExit(EntityPosition.ENTRY_POINT);
final List shapesEntryExitList = getShapesEntryExit(EntityPosition.ENTRY_POINT);
final double maxWith = getMaxWidthFromLabelForEntryExit(shapesEntryExitList, stringBounder);
final double naturalSpace = 70;
final List entries;
if (maxWith > naturalSpace) {
entries = addProtection(shapesEntryExitList, maxWith - naturalSpace);
} else {
entries = shapesEntryExitList;
}
if (entries.size() > 0) {
sb.append("{rank=source;");
for (IShapePseudo sh : entries) {
sb.append(sh.getUid() + ";");
}
sb.append("}");
for (IShapePseudo sh : entries) {
sh.appendShape(sb);
}
}
final List exits = getShapesEntryExit(EntityPosition.EXIT_POINT);
if (exits.size() > 0) {
sb.append("{rank=sink;");
for (Shape sh : exits) {
sb.append(sh.getUid() + ";");
}
sb.append("}");
for (Shape sh : exits) {
sh.appendShape(sb);
}
}
}
public boolean printCluster2(StringBuilder sb, Collection lines, StringBounder stringBounder, DotMode dotMode) {
// Log.println("Cluster::printCluster " + this);
boolean added = false;
for (Shape sh : getShapesOrderedWithoutTop(lines)) {
sh.appendShape(sb);
added = true;
}
if (dotMode != DotMode.NO_LEFT_RIGHT) {
appendRankSame(sb, lines);
}
for (Cluster child : getChildren()) {
child.printInternal(sb, lines, stringBounder, dotMode);
}
return added;
}
private void appendRankSame(StringBuilder sb, Collection lines) {
for (String same : getRankSame(lines)) {
sb.append(same);
SvekUtils.println(sb);
}
}
private Set getRankSame(Collection lines) {
final Set rankSame = new HashSet();
for (Line l : lines) {
if (l.hasEntryPoint()) {
continue;
}
final String startUid = l.getStartUid();
final String endUid = l.getEndUid();
if (isInCluster(startUid) && isInCluster(endUid)) {
final String same = l.rankSame();
if (same != null) {
rankSame.add(same);
}
}
}
return rankSame;
}
public void fillRankMin(Set rankMin) {
for (Shape sh : getShapes()) {
if (sh.isTop()) {
rankMin.add(sh.getUid());
}
}
for (Cluster child : getChildren()) {
child.fillRankMin(rankMin);
}
}
private boolean isInCluster(String uid) {
for (Shape sh : shapes) {
if (sh.getUid().equals(uid)) {
return true;
}
}
return false;
}
public String getClusterId() {
return "cluster" + color;
}
public static String getSpecialPointId(IEntity group) {
return CENTER_ID + group.getUid();
}
public final static String CENTER_ID = "za";
private boolean protection0() {
if (skinParam.useSwimlanes()) {
return false;
}
return true;
}
private boolean protection1() {
if (skinParam.useSwimlanes()) {
return false;
}
return true;
}
public String getMinPoint() {
if (skinParam.useSwimlanes()) {
return "minPoint" + color;
}
return null;
}
public String getMaxPoint() {
if (skinParam.useSwimlanes()) {
return "maxPoint" + color;
}
return null;
}
private String getSourceInPoint() {
if (skinParam.useSwimlanes()) {
return "sourceIn" + color;
}
return null;
}
private String getSinkInPoint() {
if (skinParam.useSwimlanes()) {
return "sinkIn" + color;
}
return null;
}
private void printInternal(StringBuilder sb, Collection lines, StringBounder stringBounder, DotMode dotMode) {
final boolean thereALinkFromOrToGroup = isThereALinkFromOrToGroup(lines);
if (thereALinkFromOrToGroup) {
subgraphCluster(sb, "a");
}
final boolean hasEntryOrExitPoint = hasEntryOrExitPoint();
if (hasEntryOrExitPoint) {
for (Line line : lines) {
if (line.isLinkFromOrToGroup(group)) {
line.setProjectionCluster(this);
}
}
}
boolean protection0 = protection0();
boolean protection1 = protection1();
if (hasEntryOrExitPoint) {
protection0 = false;
protection1 = false;
}
if (protection0) {
subgraphCluster(sb, "p0");
}
sb.append("subgraph " + getClusterId() + " {");
sb.append("style=solid;");
sb.append("color=\"" + StringUtils.getAsHtml(color) + "\";");
final boolean isLabel = getTitleAndAttributeHeight() > 0 && getTitleAndAttributeWidth() > 0;
final String label;
if (isLabel) {
final StringBuilder sblabel = new StringBuilder("<");
Line.appendTable(sblabel, getTitleAndAttributeWidth(), getTitleAndAttributeHeight() - 5, colorTitle);
sblabel.append(">");
label = sblabel.toString();
} else {
label = "\"\"";
}
if (hasEntryOrExitPoint) {
printClusterEntryExit(sb, stringBounder);
subgraphCluster(sb, "ee", label);
} else {
sb.append("label=" + label + ";");
SvekUtils.println(sb);
}
// if (hasEntryOrExitPoint) {
// printClusterEntryExit(sb);
// subgraphCluster(sb, "ee");
// }
if (thereALinkFromOrToGroup) {
sb.append(getSpecialPointId(group) + " [shape=point,width=.01,label=\"\"];");
subgraphCluster(sb, "i");
}
if (protection1) {
subgraphCluster(sb, "p1");
}
if (skinParam.useSwimlanes()) {
sb.append("{rank = source; ");
sb.append(getSourceInPoint());
sb.append(" [shape=point,width=.01,label=\"\"];");
sb.append(getMinPoint() + "->" + getSourceInPoint() + " [weight=999];");
sb.append("}");
SvekUtils.println(sb);
sb.append("{rank = sink; ");
sb.append(getSinkInPoint());
sb.append(" [shape=point,width=.01,label=\"\"];");
sb.append("}");
sb.append(getSinkInPoint() + "->" + getMaxPoint() + " [weight=999];");
SvekUtils.println(sb);
}
SvekUtils.println(sb);
printCluster1(sb, lines);
final boolean added = printCluster2(sb, lines, stringBounder, dotMode);
if (hasEntryOrExitPoint && added == false) {
final String empty = "empty" + color;
sb.append(empty + " [shape=point,width=.01,label=\"\"];");
}
sb.append("}");
if (protection1) {
sb.append("}");
}
if (thereALinkFromOrToGroup) {
sb.append("}");
sb.append("}");
}
if (hasEntryOrExitPoint) {
sb.append("}");
}
if (protection0) {
sb.append("}");
}
SvekUtils.println(sb);
}
private void subgraphCluster(StringBuilder sb, String id) {
subgraphCluster(sb, id, "\"\"");
}
private void subgraphCluster(StringBuilder sb, String id, String label) {
final String uid = getClusterId() + id;
sb.append("subgraph " + uid + " {");
sb.append("label=" + label + ";");
}
public int getColor() {
return color;
}
public int getTitleColor() {
return colorTitle;
}
private final HtmlColor getBackColor() {
if (EntityUtils.groupRoot(group)) {
return null;
}
final HtmlColor result = group.getSpecificBackColor();
if (result != null) {
return result;
}
if (parent == null) {
return null;
}
return parent.getBackColor();
}
public boolean isClusterOf(IEntity ent) {
if (ent.isGroup() == false) {
return false;
}
return group == ent;
}
public static HtmlColor getStateBackColor(HtmlColor stateBack, ISkinParam skinParam, String stereotype) {
if (stateBack == null) {
stateBack = skinParam.getHtmlColor(ColorParam.packageBackground, stereotype, false);
}
if (stateBack == null) {
stateBack = skinParam.getHtmlColor(ColorParam.background, stereotype, false);
}
if (stateBack == null /* || stateBack instanceof HtmlColorTransparent */) {
stateBack = new HtmlColorTransparent();
}
return stateBack;
}
// public Point2D projection(double x, double y) {
// final double v1 = Math.abs(minX - x);
// final double v2 = Math.abs(maxX - x);
// final double v3 = Math.abs(minY - y);
// final double v4 = Math.abs(maxY - y);
// if (v1 <= v2 && v1 <= v3 && v1 <= v4) {
// return new Point2D.Double(minX, y);
// }
// if (v2 <= v1 && v2 <= v3 && v2 <= v4) {
// return new Point2D.Double(maxX, y);
// }
// if (v3 <= v1 && v3 <= v2 && v3 <= v4) {
// return new Point2D.Double(x, minY);
// }
// if (v4 <= v1 && v4 <= v1 && v4 <= v3) {
// return new Point2D.Double(x, maxY);
// }
// throw new IllegalStateException();
// }
}