net.sourceforge.plantuml.graph.Graph3 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: 9786 $
*
*/
package net.sourceforge.plantuml.graph;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Dimension2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.plantuml.Dimension2DDouble;
import net.sourceforge.plantuml.EmptyImageBuilder;
import net.sourceforge.plantuml.Log;
import net.sourceforge.plantuml.cucadiagram.IEntity;
import net.sourceforge.plantuml.geom.InflationTransform;
import net.sourceforge.plantuml.geom.Kingdom;
import net.sourceforge.plantuml.geom.LineSegmentInt;
import net.sourceforge.plantuml.geom.Point2DInt;
import net.sourceforge.plantuml.geom.Pointable;
import net.sourceforge.plantuml.geom.Polyline;
import net.sourceforge.plantuml.geom.PolylineImpl;
import net.sourceforge.plantuml.geom.XMoveable;
import net.sourceforge.plantuml.geom.kinetic.Frame;
import net.sourceforge.plantuml.geom.kinetic.Path;
import net.sourceforge.plantuml.geom.kinetic.Point2DCharge;
import net.sourceforge.plantuml.geom.kinetic.World;
import net.sourceforge.plantuml.graphic.StringBounderUtils;
import net.sourceforge.plantuml.ugraphic.ColorMapperIdentity;
public class Graph3 {
final private static Graphics2D dummyGraphics2D;
private final int spaceWidth = 40;
private final int spaceHeight = 40;
private final int minDistBetweenPoint = 20;
// private final int boxWidth = 20;
// private final int boxHeight = 20;
private final double margin = 30;
private final Board board;
private final List polylines = new ArrayList();
private final Map nodePoints = new LinkedHashMap();
private int maxRow;
private int maxCol;
static {
final EmptyImageBuilder builder = new EmptyImageBuilder(10, 10, Color.WHITE);
dummyGraphics2D = builder.getGraphics2D();
}
class ANodePoint implements Pointable, XMoveable {
final private ANode node;
private int deltaX = 0;
private int deltaY = 0;
public ANodePoint(ANode node) {
this.node = node;
}
public Point2DInt getPosition() {
return new Point2DInt(board.getCol(node) * spaceWidth + deltaX, node.getRow() * spaceHeight + deltaY);
}
public void moveX(int delta) {
this.deltaX += delta;
}
public void moveY(int delta) {
this.deltaY += delta;
}
public ANode getNode() {
return node;
}
}
private Collection convertANodeSet(Set nodesSet) {
final Collection result = new HashSet();
for (ANode n : nodesSet) {
assert nodePoints.get(n) != null;
result.add(nodePoints.get(n));
}
return result;
}
private int addedWidth = 0;
private int addedHeight = 0;
public Graph3(Board board) {
board.normalize();
this.board = board;
for (ANode n : board.getNodes()) {
maxRow = Math.max(maxRow, n.getRow());
maxCol = Math.max(maxCol, board.getCol(n));
}
for (ANode n : board.getNodes()) {
nodePoints.put(n, new ANodePoint(n));
}
// for (ALink link : board.getLinks()) {
// final Pointable pp1 = nodePoints.get(link.getNode1());
// final Pointable pp2 = nodePoints.get(link.getNode2());
// polylines.add(new PolylineImpl(pp1, pp2));
// }
// manyPasses(board);
// polylines.clear();
computePolylines(board);
final InflationTransform inflationTransform = new InflationTransform();
for (ANodePoint nodePoint : nodePoints.values()) {
final Point2DInt p = nodePoint.getPosition();
final AbstractEntityImage image = getImage(nodePoint.getNode());
int widthCell = (int) image.getDimension(StringBounderUtils.asStringBounder(dummyGraphics2D)).getWidth();
int heightCell = (int) image.getDimension(StringBounderUtils.asStringBounder(dummyGraphics2D)).getHeight();
if (widthCell % 2 == 1) {
widthCell++;
}
if (heightCell % 2 == 1) {
heightCell++;
}
inflationTransform.addInflationX(p.getXint(), widthCell);
addedWidth += widthCell;
inflationTransform.addInflationY(p.getYint(), heightCell);
addedHeight += heightCell;
}
for (PolylineImpl p : polylines) {
p.inflate(inflationTransform);
}
for (ANodePoint nodePoint : nodePoints.values()) {
final Point2DInt pos = nodePoint.getPosition();
final Point2DInt pos2 = inflationTransform.inflatePoint2DInt(pos);
nodePoint.moveX(pos2.getXint() - pos.getXint());
nodePoint.moveY(pos2.getYint() - pos.getYint());
}
// Kinematic
for (ANodePoint point : nodePoints.values()) {
final double x = point.getPosition().getX();
final double y = point.getPosition().getY();
final Dimension2D dim = getImage(point.getNode()).getDimension(
StringBounderUtils.asStringBounder(dummyGraphics2D));
final Frame frame = new Frame(x, y, (int) dim.getWidth(), (int) dim.getHeight());
frames.put(point, frame);
world.addFrame(frame);
}
for (PolylineImpl polyline : polylines) {
final Frame f1 = frames.get(polyline.getStart());
final Frame f2 = frames.get(polyline.getEnd());
final Path path = new Path(f1, f2);
for (Point2DInt pt : polyline.getIntermediates()) {
path.addIntermediate(new Point2DCharge(pt.getX(), pt.getY()));
}
world.addPath(path);
}
world.renderContinue();
Log.info("Starting moving");
final long start = System.currentTimeMillis();
final int limit = 100;
for (int i = 0; i < limit; i++) {
Log.info("i=" + i);
final double move = world.onePass();
if (move < 1) {
Log.info("i=" + i + " " + move);
// break;
}
if (i == limit - 1) {
Log.info("Aborting");
}
}
final long duration = System.currentTimeMillis() - start;
Log.info("Ending moving (" + duration + " ms)");
}
private final World world = new World();
private final Map frames = new LinkedHashMap();
private void computePolylines(Board board) {
final Collection latter = new ArrayList();
final Kingdom kingdom = new Kingdom();
final List links = new ArrayList(board.getLinks());
Collections.sort(links, board.getLinkComparator());
for (ALink link : links) {
final Pointable pp1 = nodePoints.get(link.getNode1());
final Pointable pp2 = nodePoints.get(link.getNode2());
if (kingdom.isSimpleSegmentPossible(pp1.getPosition(), pp2.getPosition())) {
Log.println("OK for " + link);
kingdom.addDirectLink(pp1.getPosition(), pp2.getPosition());
polylines.add(new PolylineImpl(pp1, pp2));
} else {
Log.println("Latter for " + link);
latter.add(link);
}
}
Log.println("latters=" + latter.size());
for (ALink link : latter) {
Log.println("Alatter=" + link);
}
for (ALink link : latter) {
Log.println("Blatter=" + link);
final Pointable pp1 = nodePoints.get(link.getNode1());
final Pointable pp2 = nodePoints.get(link.getNode2());
polylines.add((PolylineImpl) kingdom.getPath(pp1, pp2));
}
}
private void manyPasses(Board board) {
final Collection> xmoveableGroups = getXMoveables(board);
Log.println("COST_INIT=" + getCost());
for (int i = 0; i < 300; i++) {
final boolean changed = onePass(xmoveableGroups);
if (changed == false) {
break;
}
}
Log.println("COST_FIN=" + getCost());
}
private Collection> getXMoveables(Board board) {
final Set> nodesGroups = new HashSet>();
final Collection nodes = board.getNodes();
for (ANode root : nodes) {
for (int i = 0; i < board.getLinks().size(); i++) {
final Set group = board.getConnectedNodes(root, i);
if (group.size() < nodes.size()) {
nodesGroups.add(group);
}
}
}
final Collection> xmoveableGroups = new ArrayList>();
for (Set nodesSet : nodesGroups) {
xmoveableGroups.add(convertANodeSet(nodesSet));
}
return xmoveableGroups;
}
private void moveX(Collection boxes, int delta) {
for (XMoveable b : boxes) {
b.moveX(delta);
}
}
private static final int STEP = 1;
private boolean onePass(Collection> subLists) {
boolean changed = false;
for (Collection toMove : subLists) {
final double initCost = getCost();
assert reversable(initCost, toMove);
moveX(toMove, STEP);
if (getCost() < initCost) {
changed = true;
} else {
moveX(toMove, -STEP);
moveX(toMove, -STEP);
if (getCost() < initCost) {
changed = true;
} else {
moveX(toMove, STEP);
assert getCost() == initCost : "c1=" + getCost() + " init=" + initCost;
}
}
assert getCost() <= initCost;
}
// Log.println("COSTB=" + getCost());
return changed;
}
private boolean reversable(double initCost, Collection toMove) {
moveX(toMove, STEP);
moveX(toMove, -STEP);
assert getCost() == initCost;
moveX(toMove, STEP);
moveX(toMove, -STEP * 2);
moveX(toMove, STEP);
assert getCost() == initCost;
return true;
}
private double getCostOld() {
if (mindistRespected() == false) {
return Double.MAX_VALUE;
}
double result = 0;
for (PolylineImpl p : polylines) {
result += getLength(p);
for (PolylineImpl other : polylines) {
if (other == p) {
continue;
}
if (p.doesTouch(other)) {
result += getLength(other);
}
}
}
return result;
}
private double getCost() {
double result = 0;
for (PolylineImpl p1 : polylines) {
for (PolylineImpl p2 : polylines) {
result += getCost(p1, p2);
}
}
final List all = new ArrayList(nodePoints.values());
for (int i = 0; i < all.size() - 1; i++) {
for (int j = i + 1; j < all.size(); j++) {
final double len = new LineSegmentInt(all.get(i).getPosition(), all.get(j).getPosition()).getLength();
result += minDistBetweenPoint * minDistBetweenPoint / len / len;
}
}
return result;
}
private double getCost(PolylineImpl p1, PolylineImpl p2) {
assert p1.nbSegments() == 1;
assert p2.nbSegments() == 1;
final LineSegmentInt seg1 = p1.getFirst();
final LineSegmentInt seg2 = p2.getFirst();
final double len1 = seg1.getLength();
if (p1 == p2) {
return len1 / minDistBetweenPoint;
}
final double len2 = seg2.getLength();
// return len1 * len2 * Math.exp(-seg1.getDistance(seg2));
return len1 * len2 / seg1.getDistance(seg2) / minDistBetweenPoint / minDistBetweenPoint;
// return len1 * len2 * Math.exp(-seg1.getDistance(seg2)) /
// minDistBetweenPoint / minDistBetweenPoint;
}
private boolean mindistRespected() {
final List all = new ArrayList(nodePoints.values());
for (int i = 0; i < all.size() - 1; i++) {
for (int j = i + 1; j < all.size(); j++) {
final double len = new LineSegmentInt(all.get(i).getPosition(), all.get(j).getPosition()).getLength();
if (len <= minDistBetweenPoint) {
return false;
}
}
}
return true;
}
private double getLength(final Polyline p) {
final double len = p.getLength();
assert len > 0;
return Math.log(1 + len);
}
public Dimension2D getDimension() {
final double width = spaceWidth * maxCol;
final int height = spaceWidth * maxRow; // + boxHeight * (maxRow + 1);
return new Dimension2DDouble(width + 2 * margin + addedWidth, height + 2 * margin + addedHeight);
}
public void draw(final Graphics2D g2d) {
g2d.translate(margin, margin);
for (Path p : world.getPaths()) {
for (Line2D seg : p.segments()) {
g2d.setColor(Color.BLUE);
g2d.draw(seg);
g2d.setColor(Color.RED);
g2d.drawOval((int) seg.getX1(), (int) seg.getY1(), 1, 1);
}
}
g2d.setColor(Color.GREEN);
for (ANodePoint nodePoint : nodePoints.values()) {
final Frame frame = frames.get(nodePoint);
final AbstractEntityImage image = getImage(nodePoint.getNode());
final double width = image.getDimension(StringBounderUtils.asStringBounder(g2d)).getWidth();
final double height = image.getDimension(StringBounderUtils.asStringBounder(g2d)).getHeight();
g2d.translate(frame.getX() - width / 2, frame.getY() - height / 2);
image.draw(new ColorMapperIdentity(), g2d);
g2d.translate(-frame.getX() + width / 2, -frame.getY() + height / 2);
}
}
public void draw2(final Graphics2D g2d) {
g2d.translate(margin, margin);
g2d.setColor(Color.BLUE);
for (Polyline p : polylines) {
if (p == null) {
Log.println("Polyline NULL!!");
continue;
}
for (LineSegmentInt seg : p.segments()) {
g2d.drawLine(seg.getP1().getXint(), seg.getP1().getYint(), seg.getP2().getXint(), seg.getP2().getYint());
}
}
g2d.setColor(Color.GREEN);
for (ANodePoint nodePoint : nodePoints.values()) {
final Point2DInt p = nodePoint.getPosition();
// Log.println("p=" + p);
final AbstractEntityImage image = getImage(nodePoint.getNode());
final int width = (int) (image.getDimension(StringBounderUtils.asStringBounder(g2d)).getWidth());
final int height = (int) (image.getDimension(StringBounderUtils.asStringBounder(g2d)).getHeight());
g2d.translate(p.getXint() - width / 2, p.getYint() - height / 2);
image.draw(new ColorMapperIdentity(), g2d);
g2d.translate(-p.getXint() + width / 2, -p.getYint() + height / 2);
// g2d.fillOval(p.getXint() - 2, p.getYint() - 2, 5, 5);
// g2d.drawRect(p.getXint() - 4, p.getYint() - 4, 8, 8);
}
}
private AbstractEntityImage getImage(ANode n) {
return new EntityImageFactory().createEntityImage((IEntity) n.getUserData());
}
}