org.stathissideris.ascii2image.graphics.Diagram Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plantuml-mit Show documentation
Show all versions of plantuml-mit Show documentation
PlantUML is a component that allows to quickly write diagrams from text.
// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
* |
* | PlantUML : a free UML diagram generator
* |
* +=======================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/liberapay (only 1€ per month!)
* https://plantuml.com/paypal
*
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the MIT License.
*
* See http://opensource.org/licenses/MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* PlantUML can occasionally display sponsored or advertising messages. Those
* messages are usually generated on welcome or error images and never on
* functional diagrams.
* See https://plantuml.com/professional if you want to remove them
*
* Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
* are owned by the author of their corresponding sources code (that is, their
* textual description in PlantUML language). Those images are not covered by
* this MIT license.
*
* The generated images can then be used without any reference to the MIT license.
* It is not even necessary to stipulate that they have been generated with PlantUML,
* although this will be appreciated by the PlantUML team.
*
* There is an exception : if the textual description in PlantUML language is also covered
* by any license, then the generated images are logically covered
* by the very same license.
*
* This is the IGY distribution (Install GraphViz by Yourself).
* You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
* (see https://plantuml.com/graphviz-dot )
*
* Icons provided by OpenIconic : https://useiconic.com/open
* Archimate sprites provided by Archi : http://www.archimatetool.com
* Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
* Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
* ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
* ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
* CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
* Brotli (c) by the Brotli Authors https://github.com/google/brotli
* Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
* Twemoji (c) by Twitter at https://twemoji.twitter.com/
*
*/
package org.stathissideris.ascii2image.graphics;
import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Iterator;
import org.stathissideris.ascii2image.core.ConversionOptions;
import org.stathissideris.ascii2image.core.Pair;
import org.stathissideris.ascii2image.core.ProcessingOptions;
import org.stathissideris.ascii2image.text.AbstractionGrid;
import org.stathissideris.ascii2image.text.CellSet;
import org.stathissideris.ascii2image.text.TextGrid;
/**
*
* @author Efstathios Sideris
*/
public class Diagram {
// ::remove folder when __HAXE__
private static final boolean DEBUG = false;
private static final boolean VERBOSE_DEBUG = false;
private ArrayList shapes = new ArrayList();
private ArrayList compositeShapes = new ArrayList();
private ArrayList textObjects = new ArrayList();
private int width, height;
private int cellWidth, cellHeight;
/**
*
* An outline of the inner workings of this very important (and monstrous)
* constructor is presented here. Boundary processing is the first step
* of the process:
*
*
* - Copy the grid into a work grid and remove all type-on-line
* and point markers from the work grid
* - Split grid into distinct shapes by plotting the grid
* onto an AbstractionGrid and its getDistinctShapes() method.
* - Find all the possible boundary sets of each of the
* distinct shapes. This can produce duplicate shapes (if the boundaries
* are the same when filling from the inside and the outside).
* - Remove duplicate boundaries.
* - Remove obsolete boundaries. Obsolete boundaries are the ones that are
* the sum of their parts when plotted as filled shapes. (see method
* removeObsoleteShapes())
* - Seperate the found boundary sets to open, closed or mixed
* (See CellSet class on how its done).
* - Are there any closed boundaries?
*
* - YES. Subtract all the closed boundaries from each of the
* open ones. That should convert the mixed shapes into open.
* - NO. In this (harder) case, we use the method
* breakTrulyMixedBoundaries() of CellSet to break boundaries
* into open and closed shapes (would work in any case, but it's
* probably slower than the other method). This method is based
* on tracing from the lines' ends and splitting when we get to
* an intersection.
*
*
* - If we had to eliminate any mixed shapes, we seperate the found
* boundary sets again to open, closed or mixed.
*
*
* At this stage, the boundary processing is all complete and we
* proceed with using those boundaries to create the shapes:
*
*
* - Create closed shapes.
* - Create open shapes. That's when the line end corrections are
* also applied, concerning the positioning of the ends of lines
* see methods connectEndsToAnchors() and moveEndsToCellEdges() of
* DiagramShape.
* - Assign color codes to closed shapes.
* - Assing extended markup tags to closed shapes.
*
- Create arrowheads.
*
- Create point markers.
*
*
* Finally, the text processing occurs: [pending]
*
* @param grid
* @param options
* @param processingOptions
*/
public Diagram(TextGrid grid, ConversionOptions options, ProcessingOptions processingOptions) {
this.cellWidth = options.renderingOptions.getCellWidth();
this.cellHeight = options.renderingOptions.getCellHeight();
FontMeasurer fontMeasurer = new FontMeasurer(
options.renderingOptions.getFont(), options.renderingOptions.getForceFontSize());
width = grid.getWidth() * cellWidth;
height = grid.getHeight() * cellHeight;
TextGrid workGrid = new TextGrid(grid);
workGrid.replaceTypeOnLine();
workGrid.replacePointMarkersOnLine();
if(DEBUG) workGrid.printDebug();
int width = grid.getWidth();
int height = grid.getHeight();
//split distinct shapes using AbstractionGrid
AbstractionGrid temp = new AbstractionGrid(workGrid, workGrid.getAllBoundaries());
ArrayList boundarySetsStep1 = temp.getDistinctShapes();
if(DEBUG){
System.out.println("******* Distinct shapes found using AbstractionGrid *******");
Iterator dit = boundarySetsStep1.iterator();
while (dit.hasNext()) {
CellSet set = (CellSet) dit.next();
set.printAsGrid();
}
System.out.println("******* Same set of shapes after processing them by filling *******");
}
//Find all the boundaries by using the special version of the filling method
//(fills in a different buffer than the buffer it reads from)
ArrayList boundarySetsStep2 = new ArrayList();
Iterator boundarySetIt = boundarySetsStep1.iterator();
while (boundarySetIt.hasNext()) {
CellSet set = (CellSet) boundarySetIt.next();
//the fill buffer keeps track of which cells have been
//filled already
TextGrid fillBuffer = new TextGrid(width * 3, height * 3);
for(int yi = 0; yi < height * 3; yi++){
for(int xi = 0; xi < width * 3; xi++){
if(fillBuffer.isBlank(xi, yi)){
TextGrid copyGrid = new AbstractionGrid(workGrid, set).getCopyOfInternalBuffer();
CellSet boundaries =
copyGrid
.findBoundariesExpandingFrom(copyGrid.new Cell(xi, yi));
if(boundaries.size() == 0) continue; //i'm not sure why these occur
boundarySetsStep2.add(boundaries.makeScaledOneThirdEquivalent());
copyGrid = new AbstractionGrid(workGrid, set).getCopyOfInternalBuffer();
CellSet filled =
copyGrid
.fillContinuousArea(copyGrid.new Cell(xi, yi), '*');
fillBuffer.fillCellsWith(filled, '*');
fillBuffer.fillCellsWith(boundaries, '-');
if(DEBUG){
//System.out.println("Fill buffer:");
//fillBuffer.printDebug();
boundaries.makeScaledOneThirdEquivalent().printAsGrid();
System.out.println("-----------------------------------");
}
}
}
}
}
if (DEBUG)
System.out.println("******* Removed duplicates *******");
boundarySetsStep2 = CellSet.removeDuplicateSets(boundarySetsStep2);
if(DEBUG){
Iterator dit = boundarySetsStep2.iterator();
while (dit.hasNext()) {
CellSet set = (CellSet) dit.next();
set.printAsGrid();
}
}
int originalSize = boundarySetsStep2.size();
boundarySetsStep2 = CellSet.removeDuplicateSets(boundarySetsStep2);
if(DEBUG) {
System.out.println(
"******* Removed duplicates: there were "
+originalSize
+" shapes and now there are "
+boundarySetsStep2.size());
}
//split boundaries to open, closed and mixed
if (DEBUG)
System.out.println("******* First evaluation of openess *******");
ArrayList open = new ArrayList();
ArrayList closed = new ArrayList();
ArrayList mixed = new ArrayList();
Iterator sets = boundarySetsStep2.iterator();
while(sets.hasNext()){
CellSet set = (CellSet) sets.next();
int type = set.getType(workGrid);
if(type == CellSet.TYPE_CLOSED) closed.add(set);
else if(type == CellSet.TYPE_OPEN) open.add(set);
else if(type == CellSet.TYPE_MIXED) mixed.add(set);
if(DEBUG){
if(type == CellSet.TYPE_CLOSED) System.out.println("Closed boundaries:");
else if(type == CellSet.TYPE_OPEN) System.out.println("Open boundaries:");
else if(type == CellSet.TYPE_MIXED) System.out.println("Mixed boundaries:");
set.printAsGrid();
}
}
boolean hadToEliminateMixed = false;
if(mixed.size() > 0 && closed.size() > 0) {
// mixed shapes can be eliminated by
// subtracting all the closed shapes from them
if (DEBUG)
System.out.println("******* Eliminating mixed shapes (basic algorithm) *******");
hadToEliminateMixed = true;
//subtract from each of the mixed sets all the closed sets
sets = mixed.iterator();
while(sets.hasNext()){
CellSet set = (CellSet) sets.next();
Iterator closedSets = closed.iterator();
while(closedSets.hasNext()){
CellSet closedSet = (CellSet) closedSets.next();
set.subtractSet(closedSet);
}
// this is necessary because some mixed sets produce
// several distinct open sets after you subtract the
// closed sets from them
if(set.getType(workGrid) == CellSet.TYPE_OPEN) {
boundarySetsStep2.remove(set);
boundarySetsStep2.addAll(set.breakIntoDistinctBoundaries(workGrid));
}
}
} else if(mixed.size() > 0 && closed.size() == 0) {
// no closed shape exists, will have to
// handle mixed shape on its own
// an example of this case is the following:
// +-----+
// | A |C B
// + ---+-------------------
// | |
// +-----+
hadToEliminateMixed = true;
if (DEBUG)
System.out.println("******* Eliminating mixed shapes (advanced algorithm for truly mixed shapes) *******");
sets = mixed.iterator();
while(sets.hasNext()){
CellSet set = (CellSet) sets.next();
boundarySetsStep2.remove(set);
boundarySetsStep2.addAll(set.breakTrulyMixedBoundaries(workGrid));
}
} else {
if (DEBUG)
System.out.println("No mixed shapes found. Skipped mixed shape elimination step");
}
if(hadToEliminateMixed){
if (DEBUG)
System.out.println("******* Second evaluation of openess *******");
//split boundaries again to open, closed and mixed
open = new ArrayList();
closed = new ArrayList();
mixed = new ArrayList();
sets = boundarySetsStep2.iterator();
while(sets.hasNext()){
CellSet set = (CellSet) sets.next();
int type = set.getType(workGrid);
if(type == CellSet.TYPE_CLOSED) closed.add(set);
else if(type == CellSet.TYPE_OPEN) open.add(set);
else if(type == CellSet.TYPE_MIXED) mixed.add(set);
if(DEBUG){
if(type == CellSet.TYPE_CLOSED) System.out.println("Closed boundaries:");
else if(type == CellSet.TYPE_OPEN) System.out.println("Open boundaries:");
else if(type == CellSet.TYPE_MIXED) System.out.println("Mixed boundaries:");
set.printAsGrid();
}
}
}
boolean removedAnyObsolete = removeObsoleteShapes(workGrid, closed);
boolean allCornersRound = false;
if(processingOptions.areAllCornersRound()) allCornersRound = true;
//make shapes from the boundary sets
//make closed shapes
ArrayList closedShapes = new ArrayList();
sets = closed.iterator();
while(sets.hasNext()){
CellSet set = (CellSet) sets.next();
DiagramComponent shape = DiagramComponent.createClosedFromBoundaryCells(workGrid, set, cellWidth, cellHeight, allCornersRound);
if(shape != null){
if(shape instanceof DiagramShape){
addToShapes((DiagramShape) shape);
closedShapes.add(shape);
} else if(shape instanceof CompositeDiagramShape)
addToCompositeShapes((CompositeDiagramShape) shape);
}
}
if(processingOptions.performSeparationOfCommonEdges())
separateCommonEdges(closedShapes);
//make open shapes
sets = open.iterator();
while(sets.hasNext()){
CellSet set = (CellSet) sets.next();
if(set.size() == 1){ //single cell "shape"
TextGrid.Cell cell = (TextGrid.Cell) set.getFirst();
if(!grid.cellContainsDashedLineChar(cell)) {
DiagramShape shape = DiagramShape.createSmallLine(workGrid, cell, cellWidth, cellHeight);
if(shape != null) {
addToShapes(shape);
shape.connectEndsToAnchors(workGrid, this);
}
}
} else { //normal shape
DiagramComponent shape =
CompositeDiagramShape
.createOpenFromBoundaryCells(
workGrid, set, cellWidth, cellHeight, allCornersRound);
if(shape != null){
if(shape instanceof CompositeDiagramShape){
addToCompositeShapes((CompositeDiagramShape) shape);
((CompositeDiagramShape) shape).connectEndsToAnchors(workGrid, this);
} else if(shape instanceof DiagramShape) {
addToShapes((DiagramShape) shape);
((DiagramShape) shape).connectEndsToAnchors(workGrid, this);
((DiagramShape) shape).moveEndsToCellEdges(grid, this);
}
}
}
}
//assign color codes to shapes
//TODO: text on line should not change its color
//TODO: each color tag should be assigned to the smallest containing shape (like shape tags)
Iterator cellColorPairs = grid.findColorCodes().iterator();
while(cellColorPairs.hasNext()){
TextGrid.CellColorPair pair =
(TextGrid.CellColorPair) cellColorPairs.next();
ShapePoint point =
new ShapePoint(getCellMidX(pair.cell), getCellMidY(pair.cell));
Iterator shapes = getShapes().iterator();
while(shapes.hasNext()){
DiagramShape shape = (DiagramShape) shapes.next();
if(shape.contains(point)) shape.setFillColor(pair.color);
}
}
//assign markup to shapes
Iterator cellTagPairs = grid.findMarkupTags().iterator();
while(cellTagPairs.hasNext()){
TextGrid.CellTagPair pair =
(TextGrid.CellTagPair) cellTagPairs.next();
ShapePoint point =
new ShapePoint(getCellMidX(pair.cell), getCellMidY(pair.cell));
//find the smallest shape that contains the tag
DiagramShape containingShape = null;
Iterator shapes = getShapes().iterator();
while(shapes.hasNext()){
DiagramShape shape = (DiagramShape) shapes.next();
if(shape.contains(point)){
if(containingShape == null){
containingShape = shape;
} else {
if(shape.isSmallerThan(containingShape)){
containingShape = shape;
}
}
}
}
//this tag is not within a shape, skip
if(containingShape == null) continue;
//TODO: the code below could be a lot more concise
if(pair.tag.equals("d")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("d");
if(def == null)
containingShape.setType(DiagramShape.TYPE_DOCUMENT);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else if(pair.tag.equals("s")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("s");
if(def == null)
containingShape.setType(DiagramShape.TYPE_STORAGE);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else if(pair.tag.equals("io")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("io");
if(def == null)
containingShape.setType(DiagramShape.TYPE_IO);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else if(pair.tag.equals("c")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("c");
if(def == null)
containingShape.setType(DiagramShape.TYPE_DECISION);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else if(pair.tag.equals("mo")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("mo");
if(def == null)
containingShape.setType(DiagramShape.TYPE_MANUAL_OPERATION);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else if(pair.tag.equals("tr")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("tr");
if(def == null)
containingShape.setType(DiagramShape.TYPE_TRAPEZOID);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else if(pair.tag.equals("o")){
CustomShapeDefinition def =
processingOptions.getFromCustomShapes("o");
if(def == null)
containingShape.setType(DiagramShape.TYPE_ELLIPSE);
else {
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
} else {
CustomShapeDefinition def =
processingOptions.getFromCustomShapes(pair.tag);
containingShape.setType(DiagramShape.TYPE_CUSTOM);
containingShape.setDefinition(def);
}
}
//make arrowheads
Iterator arrowheadCells = workGrid.findArrowheads().iterator();
while(arrowheadCells.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) arrowheadCells.next();
DiagramShape arrowhead = DiagramShape.createArrowhead(workGrid, cell, cellWidth, cellHeight);
if(arrowhead != null) addToShapes(arrowhead);
else System.err.println("Could not create arrowhead shape. Unexpected error.");
}
//make point markers
Iterator markersIt = grid.getPointMarkersOnLine().iterator();
while (markersIt.hasNext()) {
TextGrid.Cell cell = (TextGrid.Cell) markersIt.next();
DiagramShape mark = new DiagramShape();
mark.addToPoints(new ShapePoint(
getCellMidX(cell),
getCellMidY(cell)
));
mark.setType(DiagramShape.TYPE_POINT_MARKER);
mark.setFillColor(Color.white);
shapes.add(mark);
}
removeDuplicateShapes();
if(DEBUG) System.out.println("Shape count: "+shapes.size());
if(DEBUG) System.out.println("Composite shape count: "+compositeShapes.size());
//copy again
workGrid = new TextGrid(grid);
workGrid.removeNonText();
// ****** handle text *******
//break up text into groups
TextGrid textGroupGrid = new TextGrid(workGrid);
CellSet gaps = textGroupGrid.getAllBlanksBetweenCharacters();
//kludge
textGroupGrid.fillCellsWith(gaps, '|');
CellSet nonBlank = textGroupGrid.getAllNonBlank();
ArrayList textGroups = nonBlank.breakIntoDistinctBoundaries();
if(DEBUG) System.out.println(textGroups.size()+" text groups found");
Font font = fontMeasurer.getFontFor(cellHeight);
Iterator textGroupIt = textGroups.iterator();
while(textGroupIt.hasNext()){
CellSet textGroupCellSet = (CellSet) textGroupIt.next();
TextGrid isolationGrid = new TextGrid(width, height);
workGrid.copyCellsTo(textGroupCellSet, isolationGrid);
ArrayList strings = isolationGrid.findStrings();
Iterator it = strings.iterator();
while(it.hasNext()){
TextGrid.CellStringPair pair = (TextGrid.CellStringPair) it.next();
TextGrid.Cell cell = pair.cell;
String string = pair.string;
if (DEBUG)
System.out.println("Found string "+string);
TextGrid.Cell lastCell = isolationGrid.new Cell(cell.x + string.length() - 1, cell.y);
int minX = getCellMinX(cell);
int y = getCellMaxY(cell);
int maxX = getCellMaxX(lastCell);
DiagramText textObject;
if(fontMeasurer.getWidthFor(string, font) > maxX - minX){ //does not fit horizontally
Font lessWideFont = fontMeasurer.getFontFor(maxX - minX, string);
textObject = new DiagramText(minX, y, string, lessWideFont, fontMeasurer);
} else textObject = new DiagramText(minX, y, string, font, fontMeasurer);
textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell));
//TODO: if the strings start with bullets they should be aligned to the left
//position text correctly
int otherStart = isolationGrid.otherStringsStartInTheSameColumn(cell);
int otherEnd = isolationGrid.otherStringsEndInTheSameColumn(lastCell);
if(0 == otherStart && 0 == otherEnd) {
textObject.centerHorizontallyBetween(minX, maxX);
} else if(otherEnd > 0 && otherStart == 0) {
textObject.alignRightEdgeTo(maxX);
} else if(otherEnd > 0 && otherStart > 0){
if(otherEnd > otherStart){
textObject.alignRightEdgeTo(maxX);
} else if(otherEnd == otherStart){
textObject.centerHorizontallyBetween(minX, maxX);
}
}
addToTextObjects(textObject);
}
}
if (DEBUG)
System.out.println("Positioned text");
//correct the color of the text objects according
//to the underlying color
Iterator shapes = this.getAllDiagramShapes().iterator();
while(shapes.hasNext()){
DiagramShape shape = (DiagramShape) shapes.next();
Color fillColor = shape.getFillColor();
if(shape.isClosed()
&& shape.getType() != DiagramShape.TYPE_ARROWHEAD
&& fillColor != null
&& BitmapRenderer.isColorDark(fillColor)){
Iterator textObjects = getTextObjects().iterator();
while(textObjects.hasNext()){
DiagramText textObject = (DiagramText) textObjects.next();
if(shape.intersects(textObject.getBounds())){
textObject.setColor(Color.white);
}
}
}
}
//set outline to true for test within custom shapes
shapes = this.getAllDiagramShapes().iterator();
while(shapes.hasNext()){
DiagramShape shape = (DiagramShape) shapes.next();
if(shape.getType() == DiagramShape.TYPE_CUSTOM){
Iterator textObjects = getTextObjects().iterator();
while(textObjects.hasNext()){
DiagramText textObject = (DiagramText) textObjects.next();
textObject.setHasOutline(true);
textObject.setColor(DiagramText.DEFAULT_COLOR);
}
}
}
if (DEBUG)
System.out.println("Corrected color of text according to underlying color");
}
/**
* Returns a list of all DiagramShapes in the Diagram, including
* the ones within CompositeDiagramShapes
*
* @return
*/
public ArrayList getAllDiagramShapes(){
ArrayList shapes = new ArrayList();
shapes.addAll(this.getShapes());
Iterator shapesIt = this.getCompositeShapes().iterator();
while(shapesIt.hasNext()){
CompositeDiagramShape compShape = (CompositeDiagramShape) shapesIt.next();
shapes.addAll(compShape.getShapes());
}
return shapes;
}
/**
* Removes the sets from sets
that are the sum of their parts
* when plotted as filled shapes.
*
* @return true if it removed any obsolete.
*
*/
private boolean removeObsoleteShapes(TextGrid grid, ArrayList sets){
if (DEBUG)
System.out.println("******* Removing obsolete shapes *******");
boolean removedAny = false;
ArrayList filledSets = new ArrayList();
Iterator it;
if(VERBOSE_DEBUG) {
System.out.println("******* Sets before *******");
it = sets.iterator();
while(it.hasNext()){
CellSet set = (CellSet) it.next();
set.printAsGrid();
}
}
//make filled versions of all the boundary sets
it = sets.iterator();
while(it.hasNext()){
CellSet set = (CellSet) it.next();
set = set.getFilledEquivalent(grid);
if(set == null){
return false;
} else filledSets.add(set);
}
ArrayList toBeRemovedIndices = new ArrayList();
it = filledSets.iterator();
while(it.hasNext()){
CellSet set = (CellSet) it.next();
if(VERBOSE_DEBUG){
System.out.println("*** Deciding if the following should be removed:");
set.printAsGrid();
}
//find the other sets that have common cells with set
ArrayList common = new ArrayList();
common.add(set);
Iterator it2 = filledSets.iterator();
while(it2.hasNext()){
CellSet set2 = (CellSet) it2.next();
if(set != set2 && set.hasCommonCells(set2)){
common.add(set2);
}
}
//it only makes sense for more than 2 sets
if(common.size() == 2) continue;
//find largest set
CellSet largest = set;
it2 = common.iterator();
while(it2.hasNext()){
CellSet set2 = (CellSet) it2.next();
if(set2.size() > largest.size()){
largest = set2;
}
}
if(VERBOSE_DEBUG){
System.out.println("Largest:");
largest.printAsGrid();
}
//see if largest is sum of others
common.remove(largest);
//make the sum set of the small sets on a grid
TextGrid gridOfSmalls = new TextGrid(largest.getMaxX() + 2, largest.getMaxY() + 2);
CellSet sumOfSmall = new CellSet();
it2 = common.iterator();
while(it2.hasNext()){
CellSet set2 = (CellSet) it2.next();
if(VERBOSE_DEBUG){
System.out.println("One of smalls:");
set2.printAsGrid();
}
gridOfSmalls.fillCellsWith(set2, '*');
}
if(VERBOSE_DEBUG){
System.out.println("Sum of smalls:");
gridOfSmalls.printDebug();
}
TextGrid gridLargest = new TextGrid(largest.getMaxX() + 2, largest.getMaxY() + 2);
gridLargest.fillCellsWith(largest, '*');
int index = filledSets.indexOf(largest);
if(gridLargest.equals(gridOfSmalls)
&& !toBeRemovedIndices.contains(index)) {
toBeRemovedIndices.add(index);
if (DEBUG){
System.out.println("Decided to remove set:");
largest.printAsGrid();
}
} else if (DEBUG){
System.out.println("This set WILL NOT be removed:");
largest.printAsGrid();
}
//if(gridLargest.equals(gridOfSmalls)) toBeRemovedIndices.add(new Integer(index));
}
ArrayList setsToBeRemoved = new ArrayList();
it = toBeRemovedIndices.iterator();
while(it.hasNext()){
int i = ((Integer) it.next()).intValue();
setsToBeRemoved.add(sets.get(i));
}
it = setsToBeRemoved.iterator();
while(it.hasNext()){
CellSet set = (CellSet) it.next();
removedAny = true;
sets.remove(set);
}
if(VERBOSE_DEBUG) {
System.out.println("******* Sets after *******");
it = sets.iterator();
while(it.hasNext()){
CellSet set = (CellSet) it.next();
set.printAsGrid();
}
}
return removedAny;
}
public float getMinimumOfCellDimension(){
return Math.min(getCellWidth(), getCellHeight());
}
private void separateCommonEdges(ArrayList shapes){
float offset = getMinimumOfCellDimension() / 5;
ArrayList edges = new ArrayList();
//get all adges
Iterator it = shapes.iterator();
while (it.hasNext()) {
DiagramShape shape = (DiagramShape) it.next();
edges.addAll(shape.getEdges());
}
//group edges into pairs of touching edges
ArrayList> listOfPairs = new ArrayList>();
it = edges.iterator();
//all-against-all touching test for the edges
int startIndex = 1; //skip some to avoid duplicate comparisons and self-to-self comparisons
while(it.hasNext()){
ShapeEdge edge1 = (ShapeEdge) it.next();
for(int k = startIndex; k < edges.size(); k++) {
ShapeEdge edge2 = edges.get(k);
if(edge1.touchesWith(edge2)) {
listOfPairs.add(new Pair(edge1, edge2));
}
}
startIndex++;
}
ArrayList movedEdges = new ArrayList();
//move equivalent edges inwards
it = listOfPairs.iterator();
while(it.hasNext()){
Pair pair = (Pair) it.next();
if(!movedEdges.contains(pair.first)) {
pair.first.moveInwardsBy(offset);
movedEdges.add(pair.first);
}
if(!movedEdges.contains(pair.second)) {
pair.second.moveInwardsBy(offset);
movedEdges.add(pair.second);
}
}
}
//TODO: removes more than it should
private void removeDuplicateShapes() {
ArrayList originalShapes = new ArrayList();
Iterator shapesIt = getShapesIterator();
while(shapesIt.hasNext()){
DiagramShape shape = (DiagramShape) shapesIt.next();
boolean isOriginal = true;
Iterator originals = originalShapes.iterator();
while(originals.hasNext()){
DiagramShape originalShape = (DiagramShape) originals.next();
if(shape.equals(originalShape)){
isOriginal = false;
}
}
if(isOriginal) originalShapes.add(shape);
}
shapes.clear();
shapes.addAll(originalShapes);
}
private void addToTextObjects(DiagramText shape){
textObjects.add(shape);
}
private void addToCompositeShapes(CompositeDiagramShape shape){
compositeShapes.add(shape);
}
private void addToShapes(DiagramShape shape){
shapes.add(shape);
}
public Iterator getShapesIterator(){
return shapes.iterator();
}
/**
* @return
*/
public int getHeight() {
return height;
}
/**
* @return
*/
public int getWidth() {
return width;
}
/**
* @return
*/
public int getCellWidth() {
return cellWidth;
}
/**
* @return
*/
public int getCellHeight() {
return cellHeight;
}
/**
* @return
*/
public ArrayList getCompositeShapes() {
return compositeShapes;
}
/**
* @return
*/
public ArrayList getShapes() {
return shapes;
}
public int getCellMinX(TextGrid.Cell cell){
return getCellMinX(cell, cellWidth);
}
public static int getCellMinX(TextGrid.Cell cell, int cellXSize){
return cell.x * cellXSize;
}
public int getCellMidX(TextGrid.Cell cell){
return getCellMidX(cell, cellWidth);
}
public static int getCellMidX(TextGrid.Cell cell, int cellXSize){
return cell.x * cellXSize + cellXSize / 2;
}
public int getCellMaxX(TextGrid.Cell cell){
return getCellMaxX(cell, cellWidth);
}
public static int getCellMaxX(TextGrid.Cell cell, int cellXSize){
return cell.x * cellXSize + cellXSize;
}
public int getCellMinY(TextGrid.Cell cell){
return getCellMinY(cell, cellHeight);
}
public static int getCellMinY(TextGrid.Cell cell, int cellYSize){
return cell.y * cellYSize;
}
public int getCellMidY(TextGrid.Cell cell){
return getCellMidY(cell, cellHeight);
}
public static int getCellMidY(TextGrid.Cell cell, int cellYSize){
return cell.y * cellYSize + cellYSize / 2;
}
public int getCellMaxY(TextGrid.Cell cell){
return getCellMaxY(cell, cellHeight);
}
public static int getCellMaxY(TextGrid.Cell cell, int cellYSize){
return cell.y * cellYSize + cellYSize;
}
public TextGrid.Cell getCellFor(ShapePoint point){
if(point == null) throw new IllegalArgumentException("ShapePoint cannot be null");
//TODO: the fake grid is a problem
TextGrid g = new TextGrid();
return g.new Cell((int) point.x / cellWidth,
(int) point.y / cellHeight);
}
/**
* @return
*/
public ArrayList getTextObjects() {
return textObjects;
}
}