/* ======================================================================== * 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 * Contribution : Hisashi Miyashita * * */ package net.sourceforge.plantuml.svek; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; 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.FontParam; import net.sourceforge.plantuml.ISkinParam; import net.sourceforge.plantuml.SkinParamUtils; import net.sourceforge.plantuml.StringUtils; import net.sourceforge.plantuml.UmlDiagramType; import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.UseStyle; import net.sourceforge.plantuml.awt.geom.Dimension2D; 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.Stereotype; import net.sourceforge.plantuml.cucadiagram.dot.GraphvizVersion; import net.sourceforge.plantuml.graphic.HorizontalAlignment; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.graphic.TextBlock; import net.sourceforge.plantuml.graphic.USymbol; import net.sourceforge.plantuml.graphic.USymbols; import net.sourceforge.plantuml.graphic.color.ColorType; import net.sourceforge.plantuml.graphic.color.Colors; import net.sourceforge.plantuml.posimo.Moveable; import net.sourceforge.plantuml.style.PName; import net.sourceforge.plantuml.style.SName; import net.sourceforge.plantuml.style.Style; import net.sourceforge.plantuml.style.StyleSignature; import net.sourceforge.plantuml.style.StyleSignatureBasic; import net.sourceforge.plantuml.svek.image.EntityImageState; import net.sourceforge.plantuml.ugraphic.UComment; import net.sourceforge.plantuml.ugraphic.UGraphic; import net.sourceforge.plantuml.ugraphic.UGroupType; import net.sourceforge.plantuml.ugraphic.ULine; import net.sourceforge.plantuml.ugraphic.URectangle; 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.HColorBackground; import net.sourceforge.plantuml.ugraphic.color.HColorUtils; import net.sourceforge.plantuml.utils.UniqueSequence; public class Cluster implements Moveable { private static final String RANK_SAME = "same"; private static final String RANK_SOURCE = "source"; private static final String RANK_SINK = "sink"; private static final String ID_EE = "ee"; public final static String CENTER_ID = "za"; private final Cluster parentCluster; private final IGroup group; private final List nodes = 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 Set entityPositionsExceptNormal() { final Set result = EnumSet.noneOf(EntityPosition.class); for (SvekNode sh : nodes) if (sh.getEntityPosition() != EntityPosition.NORMAL) result.add(sh.getEntityPosition()); return Collections.unmodifiableSet(result); } public Cluster(ColorSequence colorSequence, ISkinParam skinParam, IGroup root) { this(null, colorSequence, skinParam, root); } private ColorParam border; private Cluster(Cluster parentCluster, ColorSequence colorSequence, ISkinParam skinParam, IGroup group) { if (group == null) throw new IllegalStateException(); this.parentCluster = parentCluster; this.group = group; if (group.getUSymbol() != null) border = group.getUSymbol().getColorParamBorder(); this.color = colorSequence.getValue(); this.colorTitle = colorSequence.getValue(); this.skinParam = group.getColors().mute(skinParam); } @Override public String toString() { return super.toString() + " " + group; } public final Cluster getParentCluster() { return parentCluster; } public void addNode(SvekNode node) { this.nodes.add(Objects.requireNonNull(node)); node.setCluster(this); } public final List getNodes() { return Collections.unmodifiableList(nodes); } private List getNodesOrderedTop(Collection lines) { final List firsts = new ArrayList<>(); final Set tops = new HashSet<>(); final Map shs = new HashMap(); for (final Iterator it = nodes.iterator(); it.hasNext();) { final SvekNode node = it.next(); shs.put(node.getUid(), node); if (node.isTop() && node.getEntityPosition() == EntityPosition.NORMAL) { firsts.add(node); tops.add(node.getUid()); } } for (SvekLine l : lines) { if (tops.contains(l.getStartUidPrefix())) { final SvekNode sh = shs.get(l.getEndUidPrefix()); if (sh != null && sh.getEntityPosition() == EntityPosition.NORMAL) firsts.add(0, sh); } if (l.isInverted()) { final SvekNode sh = shs.get(l.getStartUidPrefix()); if (sh != null && sh.getEntityPosition() == EntityPosition.NORMAL) firsts.add(0, sh); } } return firsts; } private List getNodesOrderedWithoutTop(Collection lines) { final List all = new ArrayList<>(nodes); final Set tops = new HashSet<>(); final Map shs = new HashMap(); for (final Iterator it = all.iterator(); it.hasNext();) { final SvekNode 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 (SvekLine l : lines) { if (tops.contains(l.getStartUidPrefix())) { final SvekNode sh = shs.get(l.getEndUidPrefix()); if (sh != null) all.remove(sh); } if (l.isInverted()) { final SvekNode sh = shs.get(l.getStartUidPrefix()); if (sh != null) all.remove(sh); } } return all; } public final List getChildren() { return Collections.unmodifiableList(children); } public Cluster createChild(int titleAndAttributeWidth, int titleAndAttributeHeight, TextBlock title, TextBlock stereo, ColorSequence colorSequence, ISkinParam skinParam, IGroup g) { final Cluster child = new Cluster(this, colorSequence, skinParam, g); child.titleAndAttributeWidth = titleAndAttributeWidth; child.titleAndAttributeHeight = titleAndAttributeHeight; child.ztitle = title; child.zstereo = stereo; this.children.add(child); return child; } public final Set getGroups() { return Collections.singleton(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; } private static HColor getColor(ColorParam colorParam, ISkinParam skinParam, Stereotype stereotype) { return SkinParamUtils.getColor(skinParam, stereotype, colorParam); } static public StyleSignatureBasic getDefaultStyleDefinition(SName diagramStyleName, USymbol symbol) { if (diagramStyleName == SName.stateDiagram) return StyleSignatureBasic.of(SName.root, SName.element, SName.stateDiagram, SName.state, SName.group); if (symbol == null) return StyleSignatureBasic.of(SName.root, SName.element, diagramStyleName, SName.group); return StyleSignatureBasic.of(SName.root, SName.element, diagramStyleName, SName.group, symbol.getSName()); } static public StyleSignature getDefaultStyleDefinitionStateGroup(Stereotype stereotype) { if (stereotype == null) return StyleSignatureBasic.of(SName.root, SName.element, SName.stateDiagram, SName.state, SName.group); return StyleSignatureBasic.of(SName.root, SName.element, SName.stateDiagram, SName.state, SName.group) .withTOBECHANGED(stereotype); } public void drawU(UGraphic ug, UmlDiagramType umlDiagramType, ISkinParam skinParam2unused) { if (group.isHidden()) return; final String fullName = group.getCodeGetName(); if (fullName.startsWith("##") == false) ug.draw(new UComment("cluster " + fullName)); final USymbol uSymbol = group.getUSymbol() == null ? USymbols.PACKAGE : group.getUSymbol(); Style style = getDefaultStyleDefinition(umlDiagramType.getStyleName(), uSymbol) .withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder()); final double shadowing = style.value(PName.Shadowing).asDouble(); HColor borderColor; if (group.getColors().getColor(ColorType.LINE) != null) borderColor = group.getColors().getColor(ColorType.LINE); else borderColor = style.value(PName.LineColor).asColor(skinParam.getThemeStyle(), skinParam.getIHtmlColorSet()); final double rounded = style.value(PName.RoundCorner).asDouble(); final double diagonalCorner = style.value(PName.DiagonalCorner).asDouble(); ug.startGroup(Collections.singletonMap(UGroupType.ID, "cluster_" + fullName)); final Url url = group.getUrl99(); if (url != null) ug.startUrl(url); try { if (entityPositionsExceptNormal().size() > 0) manageEntryExitPoint(ug.getStringBounder()); if (skinParam.useSwimlanes(umlDiagramType)) { drawSwinLinesState(ug, borderColor); return; } final boolean isState = umlDiagramType == UmlDiagramType.STATE; if (isState && group.getUSymbol() == null) { drawUState(ug, umlDiagramType, rounded, shadowing); return; } PackageStyle packageStyle = group.getPackageStyle(); if (packageStyle == null) packageStyle = skinParam.packageStyle(); if (border != null) { final HColor tmp = skinParam.getHtmlColor(border, group.getStereotype(), false); if (tmp != null) borderColor = tmp; } final UStroke stroke = getStrokeInternal(group, style); HColor backColor = getBackColor(umlDiagramType, style); backColor = getBackColor(backColor, skinParam, group.getStereotype(), umlDiagramType.getStyleName(), group.getUSymbol()); if (ztitle != null || zstereo != null) { final ClusterDecoration decoration = new ClusterDecoration(packageStyle, group.getUSymbol(), ztitle, zstereo, minX, minY, maxX, maxY, stroke); decoration.drawU(ug, backColor, borderColor, shadowing, rounded, skinParam.getHorizontalAlignment(AlignmentParam.packageTitleAlignment, null, false, null), skinParam.getStereotypeAlignment(), diagonalCorner); return; } final URectangle rect = new URectangle(maxX - minX, maxY - minY); rect.setDeltaShadow(shadowing); ug = ug.apply(backColor.bg()).apply(borderColor); ug.apply(new UStroke(2)).apply(new UTranslate(minX, minY)).draw(rect); } finally { if (url != null) ug.closeUrl(); ug.closeGroup(); } } static public UStroke getStrokeInternal(IGroup group, Style style) { final Colors colors = group.getColors(); if (colors.getSpecificLineStroke() != null) return colors.getSpecificLineStroke(); return style.getStroke(); } public void manageEntryExitPoint(StringBounder stringBounder) { final Collection insides = new ArrayList<>(); final List points = new ArrayList<>(); for (SvekNode sh : nodes) 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, HColor borderColor) { if (ztitle != null) ztitle.drawU(ug.apply(UTranslate.dx(xTitle))); final ULine line = ULine.vline(maxY - minY); ug = ug.apply(borderColor); ug.apply(UTranslate.dx(minX)).draw(line); ug.apply(UTranslate.dx(maxX)).draw(line); } // GroupPngMakerState private Style getStyleStateHeader() { return StyleSignatureBasic.of(SName.root, SName.element, SName.stateDiagram, SName.state, SName.header) .withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder()); } private Style getStyleState() { return StyleSignatureBasic.of(SName.root, SName.element, SName.stateDiagram, SName.state) .withTOBECHANGED(group.getStereotype()).getMergedStyle(skinParam.getCurrentStyleBuilder()); } // GroupPngMakerState private void drawUState(UGraphic ug, UmlDiagramType umlDiagramType, double rounded, double shadowing) { 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; final Style styleGroup = getDefaultStyleDefinitionStateGroup(group.getStereotype()) .getMergedStyle(skinParam.getCurrentStyleBuilder()); HColor borderColor = group.getColors().getColor(ColorType.LINE); if (borderColor == null) borderColor = getStyleState().value(PName.LineColor).asColor(skinParam.getThemeStyle(), skinParam.getIHtmlColorSet()); HColor backColor = group.getColors().getColor(ColorType.BACK); if (backColor == null) backColor = getStyleState().value(PName.BackGroundColor).asColor(skinParam.getThemeStyle(), skinParam.getIHtmlColorSet()); final HColor imgBackcolor = getBackColor(umlDiagramType, styleGroup); // final Style style = getStyle(FontParam.STATE_ATTRIBUTE, skinParam2); final TextBlock attribute = GeneralImageBuilder.stateHeader(group, styleGroup, skinParam); final double attributeHeight = attribute.calculateDimension(ug.getStringBounder()).getHeight(); if (total.getWidth() == 0) { System.err.println("Cluster::drawUState issue"); return; } UStroke stroke = group.getColors().getSpecificLineStroke(); if (stroke == null) stroke = getStyleState().getStroke(); final RoundedContainer r = new RoundedContainer(total, suppY, attributeHeight + (attributeHeight > 0 ? IEntityImage.MARGIN : 0), borderColor, backColor, imgBackcolor, stroke, rounded, shadowing); r.drawU(ug.apply(new UTranslate(minX, minY))); if (ztitle != null) ztitle.drawU(ug.apply(new UTranslate(xTitle, yTitle))); if (attributeHeight > 0) attribute.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(borderColor), maxX, maxY); } public void setPosition(double minX, double minY, double maxX, double maxY) { this.minX = minX; this.maxX = maxX; this.minY = minY; this.maxY = maxY; } private Style getStyle(FontParam fontParam, ISkinParam skinParam) { return fontParam.getStyleDefinition(SName.stateDiagram).getMergedStyle(skinParam.getCurrentStyleBuilder()); } private boolean isThereALinkFromOrToGroup(Collection lines) { for (SvekLine line : lines) if (line.isLinkFromOrTo(group)) return true; return false; } public void printCluster1(StringBuilder sb, Collection lines, StringBounder stringBounder) { for (SvekNode node : getNodesOrderedTop(lines)) node.appendShape(sb, stringBounder); } 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++) { // Pseudo space for the label 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 (IShapePseudo node : entries) { final double w = getMaxWidthFromLabelForEntryExit(node, stringBounder); if (w > result) result = w; } return result; } private double getMaxWidthFromLabelForEntryExit(IShapePseudo node, StringBounder stringBounder) { return node.getMaxWidthFromLabelForEntryExit(stringBounder); } private void printRanks(String rank, List entries, StringBuilder sb, StringBounder stringBounder) { if (entries.size() > 0) { sb.append("{rank=" + rank + ";"); for (IShapePseudo sh1 : entries) sb.append(sh1.getUid() + ";"); sb.append("}"); SvekUtils.println(sb); for (IShapePseudo sh2 : entries) sh2.appendShape(sb, stringBounder); SvekUtils.println(sb); if (hasPort()) { boolean arrow = false; String node = null; for (IShapePseudo sh : entries) { if (arrow) sb.append("->"); arrow = true; node = sh.getUid(); sb.append(node); } sb.append(';'); SvekUtils.println(sb); sb.append(node + "->" + empty() + ";"); SvekUtils.println(sb); } } } private List withPositionProtected(StringBounder stringBounder, Set targets) { final List result = withPosition(targets); final double maxWith = getMaxWidthFromLabelForEntryExit(result, stringBounder); final double naturalSpace = 70; if (maxWith > naturalSpace) return addProtection(result, maxWith - naturalSpace); return result; } private List withPosition(Set positions) { final List result = new ArrayList<>(); for (final Iterator it = nodes.iterator(); it.hasNext();) { final SvekNode sh = it.next(); if (positions.contains(sh.getEntityPosition())) result.add(sh); } return result; } private void printClusterEntryExit(StringBuilder sb, StringBounder stringBounder) { printRanks(RANK_SOURCE, withPositionProtected(stringBounder, EntityPosition.getInputs()), sb, stringBounder); printRanks(RANK_SAME, withPositionProtected(stringBounder, EntityPosition.getSame()), sb, stringBounder); printRanks(RANK_SINK, withPositionProtected(stringBounder, EntityPosition.getOutputs()), sb, stringBounder); } public SvekNode printCluster2(StringBuilder sb, Collection lines, StringBounder stringBounder, DotMode dotMode, GraphvizVersion graphvizVersion, UmlDiagramType type) { SvekNode added = null; for (SvekNode node : getNodesOrderedWithoutTop(lines)) { node.appendShape(sb, stringBounder); added = node; } if (skinParam.useRankSame() && dotMode != DotMode.NO_LEFT_RIGHT_AND_XLABEL && graphvizVersion.ignoreHorizontalLinks() == false) appendRankSame(sb, lines); for (Cluster child : getChildren()) child.printInternal(sb, lines, stringBounder, dotMode, graphvizVersion, type); 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 (SvekLine l : lines) { if (l.hasEntryPoint()) continue; final String startUid = l.getStartUidPrefix(); final String endUid = l.getEndUidPrefix(); if (isInCluster(startUid) && isInCluster(endUid)) { final String same = l.rankSame(); if (same != null) rankSame.add(same); } } return rankSame; } public void fillRankMin(Set rankMin) { for (SvekNode sh : getNodes()) if (sh.isTop()) rankMin.add(sh.getUid()); for (Cluster child : getChildren()) child.fillRankMin(rankMin); } private boolean isInCluster(String uid) { for (SvekNode node : nodes) if (node.getUid().equals(uid)) return true; return false; } public String getClusterId() { return "cluster" + color; } public static String getSpecialPointId(IEntity group) { return CENTER_ID + group.getUid(); } private boolean protection0(UmlDiagramType type) { if (skinParam.useSwimlanes(type)) return false; return true; } private boolean protection1(UmlDiagramType type) { if (group.getUSymbol() == USymbols.NODE) return true; if (skinParam.useSwimlanes(type)) return false; return true; } public String getMinPoint(UmlDiagramType type) { if (skinParam.useSwimlanes(type)) return "minPoint" + color; return null; } public String getMaxPoint(UmlDiagramType type) { if (skinParam.useSwimlanes(type)) return "maxPoint" + color; return null; } private String getSourceInPoint(UmlDiagramType type) { if (skinParam.useSwimlanes(type)) return "sourceIn" + color; return null; } private String getSinkInPoint(UmlDiagramType type) { if (skinParam.useSwimlanes(type)) return "sinkIn" + color; return null; } private void printInternal(StringBuilder sb, Collection lines, StringBounder stringBounder, DotMode dotMode, GraphvizVersion graphvizVersion, UmlDiagramType type) { final boolean thereALinkFromOrToGroup2 = isThereALinkFromOrToGroup(lines); boolean thereALinkFromOrToGroup1 = thereALinkFromOrToGroup2; final boolean useProtectionWhenThereALinkFromOrToGroup = graphvizVersion .useProtectionWhenThereALinkFromOrToGroup(); if (useProtectionWhenThereALinkFromOrToGroup == false) thereALinkFromOrToGroup1 = false; // final boolean thereALinkFromOrToGroup1 = false; if (thereALinkFromOrToGroup1) subgraphClusterNoLabel(sb, "a"); final Set entityPositionsExceptNormal = entityPositionsExceptNormal(); if (entityPositionsExceptNormal.size() > 0) for (SvekLine line : lines) if (line.isLinkFromOrTo(group)) line.setProjectionCluster(this); boolean protection0 = protection0(type); boolean protection1 = protection1(type); if (entityPositionsExceptNormal.size() > 0 || useProtectionWhenThereALinkFromOrToGroup == false) { protection0 = false; protection1 = false; } // if (graphvizVersion.modeSafe()) { // protection0 = false; // protection1 = false; // } if (protection0) subgraphClusterNoLabel(sb, "p0"); sb.append("subgraph " + getClusterId() + " {"); sb.append("style=solid;"); sb.append("color=\"" + StringUtils.sharp000000(color) + "\";"); final String label; if (isLabel()) { final StringBuilder sblabel = new StringBuilder("<"); SvekLine.appendTable(sblabel, getTitleAndAttributeWidth(), getTitleAndAttributeHeight() - 5, colorTitle); sblabel.append(">"); label = sblabel.toString(); final HorizontalAlignment align = skinParam.getHorizontalAlignment(AlignmentParam.packageTitleAlignment, null, false, null); sb.append("labeljust=\"" + align.getGraphVizValue() + "\";"); } else { label = "\"\""; } if (entityPositionsExceptNormal.size() > 0) { printClusterEntryExit(sb, stringBounder); if (hasPort()) subgraphClusterNoLabel(sb, ID_EE); else subgraphClusterWithLabel(sb, ID_EE, label); } else { sb.append("label=" + label + ";"); SvekUtils.println(sb); } if (thereALinkFromOrToGroup2) sb.append(getSpecialPointId(group) + " [shape=point,width=.01,label=\"\"];"); if (thereALinkFromOrToGroup1) subgraphClusterNoLabel(sb, "i"); if (protection1) subgraphClusterNoLabel(sb, "p1"); if (skinParam.useSwimlanes(type)) { sb.append("{rank = source; "); sb.append(getSourceInPoint(type)); sb.append(" [shape=point,width=.01,label=\"\"];"); sb.append(getMinPoint(type) + "->" + getSourceInPoint(type) + " [weight=999];"); sb.append("}"); SvekUtils.println(sb); sb.append("{rank = sink; "); sb.append(getSinkInPoint(type)); sb.append(" [shape=point,width=.01,label=\"\"];"); sb.append("}"); sb.append(getSinkInPoint(type) + "->" + getMaxPoint(type) + " [weight=999];"); SvekUtils.println(sb); } SvekUtils.println(sb); printCluster1(sb, lines, stringBounder); final SvekNode added = printCluster2(sb, lines, stringBounder, dotMode, graphvizVersion, type); if (entityPositionsExceptNormal.size() > 0) if (hasPort()) { sb.append(empty() + " [shape=rect,width=.01,height=.01,label="); sb.append(label); sb.append("];"); } else if (added == null) { sb.append(empty() + " [shape=point,width=.01,label=\"\"];"); } SvekUtils.println(sb); sb.append("}"); if (protection1) sb.append("}"); if (thereALinkFromOrToGroup1) { sb.append("}"); sb.append("}"); } if (entityPositionsExceptNormal.size() > 0) sb.append("}"); if (protection0) sb.append("}"); SvekUtils.println(sb); } private boolean hasPort() { for (EntityPosition pos : entityPositionsExceptNormal()) if (pos.isPort()) return true; return false; } private String empty() { // return "empty" + color; // We use the same node with one for thereALinkFromOrToGroup2 as an empty // because we cannot put a new node in the nested inside of the cluster // if thereALinkFromOrToGroup2 is enabled. return getSpecialPointId(group); } public boolean isLabel() { return getTitleAndAttributeHeight() > 0 && getTitleAndAttributeWidth() > 0; } private void subgraphClusterNoLabel(StringBuilder sb, String id) { subgraphClusterWithLabel(sb, id, "\"\""); } private void subgraphClusterWithLabel(StringBuilder sb, String id, String label) { sb.append("subgraph " + getClusterId() + id + " {"); sb.append("label=" + label + ";"); } public int getColor() { return color; } public int getTitleColor() { return colorTitle; } private final HColor getBackColor(UmlDiagramType umlDiagramType, Style style) { if (EntityUtils.groupRoot(group)) return null; final HColor result = group.getColors().getColor(ColorType.BACK); if (result != null) return result; final Stereotype stereo = group.getStereotype(); return style.value(PName.BackGroundColor).asColor(skinParam.getThemeStyle(), skinParam.getIHtmlColorSet()); // final USymbol sym = group.getUSymbol() == null ? USymbols.PACKAGE : group.getUSymbol(); // final ColorParam backparam = umlDiagramType == UmlDiagramType.ACTIVITY ? ColorParam.partitionBackground // : sym.getColorParamBack(); // final HColor c1 = skinParam.getHtmlColor(backparam, stereo, false); // if (c1 != null) // return c1; // // if (parentCluster == null) // return null; // // return parentCluster.getBackColor(umlDiagramType, style); } public boolean isClusterOf(IEntity ent) { if (ent.isGroup() == false) return false; return group == ent; } public static HColor getBackColor(HColor backColor, ISkinParam skinParam, Stereotype stereotype, SName styleName, USymbol symbol) { if (UseStyle.useBetaStyle()) { final Style style = getDefaultStyleDefinition(styleName, symbol) .getMergedStyle(skinParam.getCurrentStyleBuilder()); if (backColor == null) backColor = style.value(PName.BackGroundColor).asColor(skinParam.getThemeStyle(), skinParam.getIHtmlColorSet()); if (backColor == null || backColor.equals(HColorUtils.transparent())) backColor = HColorUtils.transparent(); return backColor; } if (backColor == null) backColor = skinParam.getHtmlColor(ColorParam.packageBackground, stereotype, false); if (backColor == null) backColor = skinParam.getHtmlColor(ColorParam.background, stereotype, false); if (backColor == null || backColor.equals(HColorUtils.transparent()) /* || stateBack instanceof HtmlColorTransparent */) { final HColor tmp = skinParam.getBackgroundColor(); backColor = new HColorBackground(tmp); } return backColor; } public double checkFolderPosition(Point2D pt, StringBounder stringBounder) { if (getClusterPosition().isPointJustUpper(pt)) { if (ztitle == null) return 0; final Dimension2D dimTitle = ztitle.calculateDimension(stringBounder); if (pt.getX() < getClusterPosition().getMinX() + dimTitle.getWidth()) return 0; return getClusterPosition().getMinY() - pt.getY() + dimTitle.getHeight(); } return 0; } // 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(); // } }