2011-08-08 17:48:29 +00:00
|
|
|
/* ========================================================================
|
|
|
|
* PlantUML : a free UML diagram generator
|
|
|
|
* ========================================================================
|
|
|
|
*
|
2013-12-10 19:36:50 +00:00
|
|
|
* (C) Copyright 2009-2013, Arnaud Roques
|
2011-08-08 17:48:29 +00:00
|
|
|
*
|
|
|
|
* 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
|
2013-12-10 19:36:50 +00:00
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
2011-08-08 17:48:29 +00:00
|
|
|
* 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;
|
2013-12-10 19:36:50 +00:00
|
|
|
import java.awt.geom.Point2D;
|
2011-08-08 17:48:29 +00:00
|
|
|
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;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.FontParam;
|
|
|
|
import net.sourceforge.plantuml.ISkinParam;
|
2011-08-08 17:48:29 +00:00
|
|
|
import net.sourceforge.plantuml.StringUtils;
|
|
|
|
import net.sourceforge.plantuml.UmlDiagramType;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.UniqueSequence;
|
|
|
|
import net.sourceforge.plantuml.Url;
|
|
|
|
import net.sourceforge.plantuml.cucadiagram.EntityPosition;
|
|
|
|
import net.sourceforge.plantuml.cucadiagram.EntityUtils;
|
2011-09-08 10:42:27 +00:00
|
|
|
import net.sourceforge.plantuml.cucadiagram.IEntity;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.cucadiagram.IGroup;
|
|
|
|
import net.sourceforge.plantuml.cucadiagram.Member;
|
|
|
|
import net.sourceforge.plantuml.cucadiagram.MethodsOrFieldsArea;
|
|
|
|
import net.sourceforge.plantuml.cucadiagram.Stereotype;
|
2011-08-08 17:48:29 +00:00
|
|
|
import net.sourceforge.plantuml.cucadiagram.dot.DotData;
|
|
|
|
import net.sourceforge.plantuml.graphic.HtmlColor;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.graphic.HtmlColorTransparent;
|
2011-09-08 10:42:27 +00:00
|
|
|
import net.sourceforge.plantuml.graphic.StringBounder;
|
2011-08-08 17:48:29 +00:00
|
|
|
import net.sourceforge.plantuml.graphic.TextBlock;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.graphic.TextBlockEmpty;
|
|
|
|
import net.sourceforge.plantuml.graphic.TextBlockWidth;
|
2011-09-08 10:42:27 +00:00
|
|
|
import net.sourceforge.plantuml.posimo.Moveable;
|
2011-08-08 17:48:29 +00:00
|
|
|
import net.sourceforge.plantuml.skin.rose.Rose;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.svek.image.EntityImageState;
|
|
|
|
import net.sourceforge.plantuml.ugraphic.UChangeBackColor;
|
|
|
|
import net.sourceforge.plantuml.ugraphic.UChangeColor;
|
2011-08-08 17:48:29 +00:00
|
|
|
import net.sourceforge.plantuml.ugraphic.UGraphic;
|
2011-09-08 10:42:27 +00:00
|
|
|
import net.sourceforge.plantuml.ugraphic.ULine;
|
2011-08-08 17:48:29 +00:00
|
|
|
import net.sourceforge.plantuml.ugraphic.URectangle;
|
|
|
|
import net.sourceforge.plantuml.ugraphic.UStroke;
|
2013-12-10 19:36:50 +00:00
|
|
|
import net.sourceforge.plantuml.ugraphic.UTranslate;
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2011-09-08 10:42:27 +00:00
|
|
|
public class Cluster implements Moveable {
|
2011-08-08 17:48:29 +00:00
|
|
|
|
|
|
|
private final Cluster parent;
|
2013-12-10 19:36:50 +00:00
|
|
|
private final IGroup group;
|
2011-08-08 17:48:29 +00:00
|
|
|
private final List<Shape> shapes = new ArrayList<Shape>();
|
|
|
|
private final List<Cluster> children = new ArrayList<Cluster>();
|
|
|
|
private final int color;
|
|
|
|
private final int colorTitle;
|
2013-12-10 19:36:50 +00:00
|
|
|
private final ISkinParam skinParam;
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private int titleAndAttributeWidth;
|
|
|
|
private int titleAndAttributeHeight;
|
|
|
|
private TextBlock ztitle;
|
|
|
|
private TextBlock zstereo;
|
2011-09-08 10:42:27 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private boolean hasEntryOrExitPoint() {
|
|
|
|
for (Shape sh : shapes) {
|
|
|
|
if (sh.getEntityPosition() != EntityPosition.NORMAL) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public Cluster(ColorSequence colorSequence, ISkinParam skinParam, IGroup root) {
|
|
|
|
this(null, root, colorSequence, skinParam);
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private Cluster(Cluster parent, IGroup group, ColorSequence colorSequence, ISkinParam skinParam) {
|
|
|
|
if (group == null) {
|
|
|
|
throw new IllegalStateException();
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
this.parent = parent;
|
|
|
|
this.group = group;
|
|
|
|
this.color = colorSequence.getValue();
|
|
|
|
this.colorTitle = colorSequence.getValue();
|
2013-12-10 19:36:50 +00:00
|
|
|
this.skinParam = skinParam;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return super.toString() + " " + group;
|
|
|
|
}
|
|
|
|
|
|
|
|
public final Cluster getParent() {
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addShape(Shape sh) {
|
2013-12-10 19:36:50 +00:00
|
|
|
if (sh == null) {
|
|
|
|
throw new IllegalArgumentException();
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
this.shapes.add(sh);
|
|
|
|
sh.setCluster(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
public final List<Shape> getShapes() {
|
|
|
|
return Collections.unmodifiableList(shapes);
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private List<Shape> getShapesOrderedTop(Collection<Line> lines) {
|
2011-08-08 17:48:29 +00:00
|
|
|
final List<Shape> firsts = new ArrayList<Shape>();
|
|
|
|
final Set<String> tops = new HashSet<String>();
|
|
|
|
final Map<String, Shape> shs = new HashMap<String, Shape>();
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
for (final Iterator<Shape> 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<Shape> getShapesEntryExit(EntityPosition position) {
|
|
|
|
final List<Shape> result = new ArrayList<Shape>();
|
|
|
|
|
|
|
|
for (final Iterator<Shape> it = shapes.iterator(); it.hasNext();) {
|
|
|
|
final Shape sh = it.next();
|
|
|
|
if (sh.getEntityPosition() == position) {
|
|
|
|
result.add(sh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<Shape> getShapesOrderedWithoutTop(Collection<Line> lines) {
|
|
|
|
final List<Shape> all = new ArrayList<Shape>(shapes);
|
|
|
|
final Set<String> tops = new HashSet<String>();
|
|
|
|
final Map<String, Shape> shs = new HashMap<String, Shape>();
|
|
|
|
|
2011-08-08 17:48:29 +00:00
|
|
|
for (final Iterator<Shape> it = all.iterator(); it.hasNext();) {
|
|
|
|
final Shape sh = it.next();
|
2013-12-10 19:36:50 +00:00
|
|
|
if (sh.getEntityPosition() != EntityPosition.NORMAL) {
|
|
|
|
it.remove();
|
|
|
|
continue;
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
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());
|
2013-12-10 19:36:50 +00:00
|
|
|
if (sh != null) {
|
|
|
|
all.remove(sh);
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
|
2011-08-08 17:48:29 +00:00
|
|
|
if (l.isInverted()) {
|
|
|
|
final Shape sh = shs.get(l.getStartUid());
|
2013-12-10 19:36:50 +00:00
|
|
|
if (sh != null) {
|
|
|
|
all.remove(sh);
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return all;
|
|
|
|
}
|
|
|
|
|
|
|
|
public final List<Cluster> getChildren() {
|
|
|
|
return Collections.unmodifiableList(children);
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
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;
|
2011-08-08 17:48:29 +00:00
|
|
|
this.children.add(child);
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public final IGroup getGroup() {
|
2011-08-08 17:48:29 +00:00
|
|
|
return group;
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public final int getTitleAndAttributeWidth() {
|
|
|
|
return titleAndAttributeWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
public final int getTitleAndAttributeHeight() {
|
|
|
|
return titleAndAttributeHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public double getWidth() {
|
|
|
|
return maxX - minX;
|
|
|
|
}
|
|
|
|
|
|
|
|
public double getMinX() {
|
|
|
|
return minX;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public ClusterPosition getClusterPosition() {
|
|
|
|
return new ClusterPosition(minX, minY, maxX, maxY);
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setTitlePosition(double x, double y) {
|
|
|
|
this.xTitle = x;
|
|
|
|
this.yTitle = y;
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public void drawU(UGraphic ug, HtmlColor borderColor, DotData dotData) {
|
|
|
|
final Url url = group.getUrl99();
|
|
|
|
if (url != null) {
|
|
|
|
ug.startUrl(url);
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
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();
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
2011-09-08 10:42:27 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
}
|
2011-09-08 10:42:27 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private void manageEntryExitPoint(DotData dotData, StringBounder stringBounder) {
|
|
|
|
final Collection<ClusterPosition> insides = new ArrayList<ClusterPosition>();
|
|
|
|
final List<Point2D> points = new ArrayList<Point2D>();
|
|
|
|
for (Shape sh : shapes) {
|
|
|
|
if (sh.getEntityPosition() == EntityPosition.NORMAL) {
|
|
|
|
insides.add(sh.getClusterPosition());
|
|
|
|
} else {
|
|
|
|
points.add(sh.getClusterPosition().getPointCenter());
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
for (Cluster in : children) {
|
|
|
|
insides.add(in.getClusterPosition());
|
2011-09-08 10:42:27 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
final FrontierCalculator frontierCalculator = new FrontierCalculator(getClusterPosition(), insides, points);
|
|
|
|
if (titleAndAttributeHeight > 0 && titleAndAttributeWidth > 0) {
|
|
|
|
frontierCalculator.ensureMinWidth(titleAndAttributeWidth + 10);
|
2011-09-08 10:42:27 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
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)));
|
2011-09-08 10:42:27 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
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);
|
|
|
|
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private HtmlColor getColor(DotData dotData, ColorParam colorParam, String stereo) {
|
|
|
|
return new Rose().getHtmlColor(dotData.getSkinParam(), colorParam, stereo);
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private void drawUState(UGraphic ug, HtmlColor borderColor, DotData dotData) {
|
2011-08-08 17:48:29 +00:00
|
|
|
final Dimension2D total = new Dimension2DDouble(maxX - minX, maxY - minY);
|
2013-12-10 19:36:50 +00:00
|
|
|
final double suppY;
|
|
|
|
if (ztitle == null) {
|
|
|
|
suppY = 0;
|
|
|
|
} else {
|
|
|
|
suppY = ztitle.calculateDimension(ug.getStringBounder()).getHeight() + IEntityImage.MARGIN
|
|
|
|
+ IEntityImage.MARGIN_LINE;
|
|
|
|
}
|
|
|
|
|
2011-08-08 17:48:29 +00:00
|
|
|
HtmlColor stateBack = getBackColor();
|
|
|
|
if (stateBack == null) {
|
2013-12-10 19:36:50 +00:00
|
|
|
stateBack = getColor(dotData, ColorParam.stateBackground, group.getStereotype() == null ? null : group
|
|
|
|
.getStereotype().getLabel());
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
final HtmlColor background = getColor(dotData, ColorParam.background, null);
|
2013-12-10 19:36:50 +00:00
|
|
|
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());
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
if (ztitle != null) {
|
|
|
|
ztitle.drawU(ug.apply(new UTranslate(xTitle, yTitle)));
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
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<Member> members = group.getFieldsToDisplay();
|
|
|
|
if (members.size() == 0) {
|
|
|
|
attribute = new TextBlockEmpty();
|
|
|
|
} else {
|
|
|
|
attribute = new MethodsOrFieldsArea(members, FontParam.STATE_ATTRIBUTE, dotData.getSkinParam());
|
|
|
|
}
|
|
|
|
return attribute;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setPosition(double minX, double minY, double maxX, double maxY) {
|
|
|
|
this.minX = minX;
|
|
|
|
this.maxX = maxX;
|
|
|
|
this.minY = minY;
|
|
|
|
this.maxY = maxY;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private boolean isThereALinkFromOrToGroup(Collection<Line> lines) {
|
|
|
|
for (Line line : lines) {
|
|
|
|
if (line.isLinkFromOrToGroup(group)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public void printCluster1(StringBuilder sb, Collection<Line> lines) {
|
|
|
|
for (Shape sh : getShapesOrderedTop(lines)) {
|
|
|
|
sh.appendShape(sb);
|
|
|
|
}
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private List<IShapePseudo> addProtection(List<Shape> entries, double width) {
|
|
|
|
final List<IShapePseudo> result = new ArrayList<IShapePseudo>();
|
|
|
|
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<Shape> 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<? extends IShapePseudo> entries = getShapesEntryExit(EntityPosition.ENTRY_POINT);
|
|
|
|
final List<Shape> shapesEntryExitList = getShapesEntryExit(EntityPosition.ENTRY_POINT);
|
|
|
|
final double maxWith = getMaxWidthFromLabelForEntryExit(shapesEntryExitList, stringBounder);
|
|
|
|
final double naturalSpace = 70;
|
|
|
|
final List<? extends IShapePseudo> 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);
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
final List<Shape> 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public boolean printCluster2(StringBuilder sb, Collection<Line> lines, StringBounder stringBounder, DotMode dotMode) {
|
|
|
|
// Log.println("Cluster::printCluster " + this);
|
|
|
|
|
|
|
|
boolean added = false;
|
|
|
|
for (Shape sh : getShapesOrderedWithoutTop(lines)) {
|
2011-08-08 17:48:29 +00:00
|
|
|
sh.appendShape(sb);
|
2013-12-10 19:36:50 +00:00
|
|
|
added = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dotMode != DotMode.NO_LEFT_RIGHT) {
|
|
|
|
appendRankSame(sb, lines);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Cluster child : getChildren()) {
|
|
|
|
child.printInternal(sb, lines, stringBounder, dotMode);
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
return added;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void appendRankSame(StringBuilder sb, Collection<Line> lines) {
|
|
|
|
for (String same : getRankSame(lines)) {
|
2011-08-08 17:48:29 +00:00
|
|
|
sb.append(same);
|
|
|
|
SvekUtils.println(sb);
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private Set<String> getRankSame(Collection<Line> lines) {
|
|
|
|
final Set<String> rankSame = new HashSet<String>();
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
return rankSame;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void fillRankMin(Set<String> 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;
|
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public static String getSpecialPointId(IEntity group) {
|
|
|
|
return CENTER_ID + group.getUid();
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
public final static String CENTER_ID = "za";
|
2011-09-08 10:42:27 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
private boolean protection0() {
|
|
|
|
if (skinParam.useSwimlanes()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2011-09-08 10:42:27 +00:00
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
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<Line> lines, StringBounder stringBounder, DotMode dotMode) {
|
|
|
|
final boolean thereALinkFromOrToGroup = isThereALinkFromOrToGroup(lines);
|
|
|
|
if (thereALinkFromOrToGroup) {
|
2011-08-08 17:48:29 +00:00
|
|
|
subgraphCluster(sb, "a");
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
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;
|
|
|
|
}
|
2011-09-08 10:42:27 +00:00
|
|
|
if (protection0) {
|
|
|
|
subgraphCluster(sb, "p0");
|
|
|
|
}
|
2011-08-08 17:48:29 +00:00
|
|
|
sb.append("subgraph " + getClusterId() + " {");
|
|
|
|
sb.append("style=solid;");
|
|
|
|
sb.append("color=\"" + StringUtils.getAsHtml(color) + "\";");
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
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);
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
// if (hasEntryOrExitPoint) {
|
|
|
|
// printClusterEntryExit(sb);
|
|
|
|
// subgraphCluster(sb, "ee");
|
|
|
|
// }
|
|
|
|
|
|
|
|
if (thereALinkFromOrToGroup) {
|
|
|
|
sb.append(getSpecialPointId(group) + " [shape=point,width=.01,label=\"\"];");
|
2011-08-08 17:48:29 +00:00
|
|
|
subgraphCluster(sb, "i");
|
|
|
|
}
|
2011-09-08 10:42:27 +00:00
|
|
|
if (protection1) {
|
|
|
|
subgraphCluster(sb, "p1");
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
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("}");
|
2011-09-08 10:42:27 +00:00
|
|
|
if (protection1) {
|
|
|
|
sb.append("}");
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
if (thereALinkFromOrToGroup) {
|
|
|
|
sb.append("}");
|
2011-08-08 17:48:29 +00:00
|
|
|
sb.append("}");
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
if (hasEntryOrExitPoint) {
|
2011-09-08 10:42:27 +00:00
|
|
|
sb.append("}");
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
if (protection0) {
|
2011-08-08 17:48:29 +00:00
|
|
|
sb.append("}");
|
|
|
|
}
|
|
|
|
SvekUtils.println(sb);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void subgraphCluster(StringBuilder sb, String id) {
|
2013-12-10 19:36:50 +00:00
|
|
|
subgraphCluster(sb, id, "\"\"");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void subgraphCluster(StringBuilder sb, String id, String label) {
|
2011-08-08 17:48:29 +00:00
|
|
|
final String uid = getClusterId() + id;
|
|
|
|
sb.append("subgraph " + uid + " {");
|
2013-12-10 19:36:50 +00:00
|
|
|
sb.append("label=" + label + ";");
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public int getColor() {
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getTitleColor() {
|
|
|
|
return colorTitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
private final HtmlColor getBackColor() {
|
2013-12-10 19:36:50 +00:00
|
|
|
if (EntityUtils.groupRoot(group)) {
|
2011-09-08 10:42:27 +00:00
|
|
|
return null;
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
final HtmlColor result = group.getSpecificBackColor();
|
2011-09-08 10:42:27 +00:00
|
|
|
if (result != null) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
if (parent == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return parent.getBackColor();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isClusterOf(IEntity ent) {
|
2013-12-10 19:36:50 +00:00
|
|
|
if (ent.isGroup() == false) {
|
2011-09-08 10:42:27 +00:00
|
|
|
return false;
|
|
|
|
}
|
2013-12-10 19:36:50 +00:00
|
|
|
return group == ent;
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-10 19:36:50 +00:00
|
|
|
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();
|
|
|
|
// }
|
|
|
|
|
2011-08-08 17:48:29 +00:00
|
|
|
}
|