de.invation.code.toval.graphic.misc.CircularPointGroup Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of TOVAL Show documentation
Show all versions of TOVAL Show documentation
TOVAL comprises a set of java classes for common programming issues. It includes utils for arrays, lists, sets and collections for convenient handling and modification, but also support for mathematic definitions concerning logic (clauses + resolution) together with some algorithms for permutations, powersets and resolution. Additionally it contains a number of types for multisets, matrices with object keys and much more.
The newest version!
package de.invation.code.toval.graphic.misc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import de.invation.code.toval.graphic.component.VisualCircularPointGroupPanel;
import de.invation.code.toval.misc.CollectionUtils;
/**
* This class manages a number of points and places them in a circular way according to the minimum distance between any two points.
* For up to 4 points the class places the points in a trivial way:
*
* 1: o
* 2: o o
* 3: o
* o o
* 4: o o
* o o
*
* For 5 or more points, the class places on point at the origin and then introduces one or more circles around this central point
* and places them on the circular line(s).
*
* @author ts552
*
*/
public class CircularPointGroup {
private final String trivialPointFormat = " trivial: %s \n";
private final String toStringFormat = "CircularPointGroup { \n points: %s \n%s } \n";
private final String circleFormat = " Circle %s [%s/%s]: %s \n";
/**
* The point which is used as reference when calculating the coordinates of the managed points.
*/
private Position origin = new Position(0,0);
/**
* Minimum distance between any two points.
*/
private int minDistance;
/**
* Diameter of points.
*/
private int pointDiameter;
/**
* Managed points grouped by color.
*/
private Map points = new HashMap();
/**
* Number of managed points.
*/
private int numPoints = 0;
/**
* Managed points circles.
* Each pointCircle manages a number of points and places them in a circle.
*/
private ArrayList pointCircles = new ArrayList();
/**
* The order in which colors are assigned to managed points.
*/
private ArrayList colorOrder = new ArrayList();
/**
* Names for trivial points.
* If there are less than 5 points, they are not positioned on PointCircles.
* for up to 4 points there exist a trivial positioning procedure.
* @see CircularPointGroup#getTrivialPoint(TrivialPoint)
*/
private enum TrivialPoint {FIRST, SECOND, THIRD, FORTH};
/**
* Indicates the debugging state of this class.
* If true status messages are printed on the standard output.
*/
private boolean debuggingActivated = false;
/**
* Creates a new CircularPointGroup.
* @param minDistance The minimum distance between each two points.
* @param pointDiameter The diameter of points.
*/
public CircularPointGroup(int minDistance, int pointDiameter){
this.minDistance = minDistance;
this.pointDiameter = pointDiameter;
}
/**
* Returns the minimum required number of points (pixels) needed to paint all points within all PointCircles.
* @return The required points (pixels) for painting all points.
*/
public int getRequiredDiameter(){
return (int) getOutmostPointCircle().getDiameter()+pointDiameter;
}
/**
* Returns the different colors for which points are managed.
* @return A set of colors.
*/
public Set getColors(){
return points.keySet();
}
/**
* Returns the actual standard point diameter.
* @return The diameter of managed points.
*/
public int getPointDiameter(){
return pointDiameter;
}
/**
* Adds a number of points to the CircularPointGroup.
* @param color The color of the points.
* @param number The number of points.
*/
public void addPoints(PColor color, int number){
debug("Add " + number + " point(s) for color " + color);
if(points.containsKey(color)){
System.out.println("color is known");
points.put(color, points.get(color)+number);
} else {
points.put(color, number);
if(color.equals(PColor.black)){
colorOrder.add(0, color);
} else {
colorOrder.add(color);
}
}
if(numPoints+number>4){
debug("Introduce point circle(s)");
int pointsToAdd = number - (numPoints > 4 ? 0 : 1-numPoints);
int addedPoints = 0;
while(getOutmostPointCircle()==null || (addedPoints=getOutmostPointCircle().addManagedPoints(pointsToAdd))
* This is particularly helpful for adding new points since only the outmost circle may have space.
* @return The outmost PointCircle
* @see PointCircle
*/
private PointCircle getOutmostPointCircle(){
if(pointCircles.isEmpty())
return null;
return pointCircles.get(pointCircles.size()-1);
}
/**
* Removes all points of a specific color (if there are points at all).
* @param color The color for which all points should be removed.
*/
public void removeColor(PColor color){
if(!points.containsKey(color))
System.out.println("pille");
removePoints(color, points.get(color));
}
/**
* Removes the specified number of points for a specific color.
* @param color The color for which points should be removed.
* @param number The number of points that sould be removed.
* @throws IllegalArgumentException if the number of points is negative of higher than the number of managed points.
*/
public void removePoints(PColor color, int number) throws IllegalArgumentException {
debug("Remove "+number+" points for color "+color);
if(!points.containsKey(color))
throw new IllegalArgumentException("No coordinates for the given color!");
if(number<0)
throw new IllegalArgumentException("Cannot remove a negative number of points!");
if(number>points.get(color))
throw new IllegalArgumentException("There are only "+numPoints+" managed points.");
if(number==0)
return;
if(numPoints < 5){
debug("Entering trivial mode.");
//Trivial case: There are no point circles
numPoints -= number;
debug(number+" points removed.");
return;
} else {
debug("Entering non-trivial mode");
int pointsToRemove = number;
int removedPoints = 0;
debug("Points left to remove: "+pointsToRemove);
while(getOutmostPointCircle()!=null && (removedPoints = getOutmostPointCircle().removeManagedPoints(pointsToRemove)) < pointsToRemove){
debug("Removed points: "+removedPoints);
debug("Remove PointCircle "+pointCircles.size());
pointCircles.remove(getOutmostPointCircle());
pointsToRemove -= removedPoints;
debug("Points left to remove: "+pointsToRemove);
}
debug("Points removed from circle "+pointCircles.size()+": "+removedPoints);
if(getOutmostPointCircle().size() == 0){
debug("Remove PointCircle "+pointCircles.size());
pointCircles.remove(getOutmostPointCircle());
}
numPoints -= number;
}
//Remove color from color order if not points are left
if(number==points.get(color)){
debug("Remove color "+color+" from color order");
colorOrder.remove(color);
}
}
/**
* Returns all point coordinates for a specific color.
* @param color The color for which the point coordinates are desired.
* @return A list of point coordinates.
* @throws IllegalArgumentException if there are no coordinates for the given color.
*/
public ArrayList getCoordinatesFor(PColor color) throws IllegalArgumentException {
debug("");
debug("Retrieve coordinates for color "+color);
if(!points.containsKey(color))
throw new IllegalArgumentException("No coordinates for the given color!");
ArrayList result = new ArrayList();
debug("Determine total position");
int position = 0;
int addedPoints = 0;
for(PColor c: points.keySet()){
if(!c.equals(color)){
position += points.get(c);
} else {
break;
}
}
debug("Position: "+(position+1)+"/"+numPoints);
debug("Coords to retrieve: "+points.get(color));
if(numPoints<5){
//Trivial cases
debug("Entering mode for trivial cases.");
ArrayList trivialPoints = new ArrayList(points.get(color));
for(int i=position; i= pointCircles.get(actCircle).size() - startNumber + 1){
debug("Retrieve all coords from number " + startNumber);
result.addAll(pointCircles.get(actCircle).getCoordinatesFrom(startNumber));
addedPoints += pointCircles.get(actCircle).size() - startNumber + 1;
debug(" -> Retrieved: " + (pointCircles.get(actCircle).size() - startNumber + 1));
} else {
debug("Retrieve coords from number " + startNumber + " to number " + (startNumber + points.get(color) - addedPoints - 1));
result.addAll(pointCircles.get(actCircle).getCoordinates(startNumber, startNumber + points.get(color) - addedPoints - 1));
debug(" -> Retrieved: " + (points.get(color) - addedPoints));
addedPoints += points.get(color) - addedPoints;
}
startNumber = 1;
actCircle++;
}
} catch(Exception e){
//Should not happen since we know the right parameters.
e.printStackTrace();
}
//End non-trivial mode
}
return result;
}
/**
* Returns the position of a point in trivial mode (when less than 5 points are managed.
* In case only one point is managed, its position equals the origin.
* In case two points are managed, they are placed on a line with equal distance to the origin and minDistance to each other.
* In case three points are managed, they are each positioned at a corner of a equilateral triangle whose center is the origin.
* In case four points are managed, they are each positiones at a corner of a square whose center is the origin.
* @param trivial The point for which the coordinates are desired (first, second, third or forth)
* @return The coordinated for the desired trivial point.
* @throws Exception
*/
private Position getTrivialPoint(TrivialPoint trivial) throws Exception{
if(numPoints==0)
throw new Exception("No managed points.");
if(numPoints>4)
throw new Exception("No trivial positioning for more than 4 points!");
if(trivial.ordinal()+1>numPoints)
throw new IllegalArgumentException("There are only "+numPoints+" managed points.");
double dist1,dist2,dist3;
dist1 = (minDistance+pointDiameter)/2.0;
switch(numPoints){
case 1:
return origin;
case 2:
switch(trivial){
case FIRST:
return new Position(origin.getX(), (int) Math.round(origin.getY()-dist1));
case SECOND:
return new Position(origin.getX(), (int) Math.round(origin.getY()+dist1));
default:
throw new Exception();
}
case 3:
dist2 = (minDistance+pointDiameter) / Math.pow(3, 1.0/3);
dist3 = ((minDistance+pointDiameter) * Math.pow(3, 1.0/3))/6;
switch(trivial){
case FIRST:
return new Position(origin.getX(), (int) Math.round(origin.getY()-dist2));
case SECOND:
return new Position((int) Math.round(origin.getX()-dist1), (int) Math.round(origin.getY()+dist3));
case THIRD:
return new Position((int) Math.round(origin.getX()+dist1), (int) Math.round(origin.getY()+dist3));
default:
throw new Exception();
}
case 4:
switch(trivial){
case FIRST:
return new Position((int) Math.round(origin.getX()-dist1), (int) Math.round(origin.getY()-dist1));
case SECOND:
return new Position((int) Math.round(origin.getX()+dist1), (int) Math.round(origin.getY()-dist1));
case THIRD:
return new Position((int) Math.round(origin.getX()-dist1), (int) Math.round(origin.getY()+dist1));
case FORTH:
return new Position((int) Math.round(origin.getX()+dist1), (int) Math.round(origin.getY()+dist1));
default:
throw new Exception();
}
default:
throw new Exception();
}
}
/**
* Sets the debugging mode.
* If true, status messages are printed to the standard output.
* @param active The desired debug mode.
*/
public void setDebugMode(boolean active){
debuggingActivated = active;
}
/**
* Prints debug messages to the standard output unless debugging is deactivated.
* @param message The status message to be printed.
*/
private void debug(String message){
if(debuggingActivated){
System.out.println(message);
}
}
@Override
public String toString(){
StringBuilder builder = new StringBuilder();
if(numPoints>0){
try {
if(numPoints < 5){
switch(numPoints){
case 4: builder.append(String.format(trivialPointFormat, getTrivialPoint(TrivialPoint.FORTH)));
case 3: builder.append(String.format(trivialPointFormat, getTrivialPoint(TrivialPoint.THIRD)));
case 2: builder.append(String.format(trivialPointFormat, getTrivialPoint(TrivialPoint.SECOND)));
case 1: builder.append(String.format(trivialPointFormat, getTrivialPoint(TrivialPoint.FIRST)));
}
} else {
builder.append(String.format(trivialPointFormat, origin));
for(int i=0; i
*
* A PointCirclewith order n has the following properties:
* m = minimum distance between any two points
* d = diameter of points
* radius: n*(m+d)
* capacity: ceil(2*n*PI)
*/
private class PointCircle{
/**
* The order of the PointCircle.
*/
private int order;
/**
* The number of points that can be managed (positioned).
*/
private int capacity;
/**
* The radius of the circle that is used for placement.
*/
private double radius;
/**
* The number of managed points.
*/
private int managedPoints;
/**
* The coordinates on the circular line that are used to place points.
*/
private Position[] coordinates;
/**
* Creates a new PointCircle with the given order.
* @param order The desired order for the PointCircle.
*/
public PointCircle(int order){
this.order = order;
this.capacity = (int) Math.floor(2*Math.PI*order);
coordinates = new Position[capacity];
initialize();
}
/**
* Determines the properties of the underlying circle and the point coordinates.
*/
public void initialize(){
radius = order*(minDistance+pointDiameter);
for (int i=0; i
* In case of a capacity overrun (not the complete number of points can be added to the PointCircle),
* the return value is less than number .
* @param number The number of newly introduced points.
* @return The number of points that could be added.
*/
public Integer addManagedPoints(int number){
debug("Add " + number + " points to circle " + order);
if(number < 0)
throw new IllegalArgumentException("Cannot add a negative number of points.");
if(number == 0)
return 0;
if(capacity >= managedPoints+number){
managedPoints += number;
return number;
} else {
int space = capacity-managedPoints;
managedPoints = capacity;
return space;
}
}
/**
* Reduces the number of managed points.
* In case the PointCircle contains less than number points,
* the return value is less than number .
* @param number The number of points to remove.
* @return The number of points that could be removed.
*/
public Integer removeManagedPoints(int number){
debug("Remove " + number + " points from circle " + order);
if(number < 0)
throw new IllegalArgumentException("Cannot add a negative number of points.");
if(number == 0)
return 0;
if(managedPoints >= number){
debug("Circle contains at least "+number+" points.");
managedPoints -= number;
return number;
} else {
debug("Circle contains less than "+number+" points.");
int tmp = managedPoints;
managedPoints = 0;
return tmp;
}
}
/**
* Returns the coordinates for point numbers from to from .
* Point numbers are counted from 1.
* @param from The starting point number
* @param to The ending point number.
* @return A list of coordinates for all points in the given range.
* @throws Exception in case of invalid parameter values.
*/
public ArrayList getCoordinates(int from, int to) throws Exception {
if(from<0 || to<0)
throw new IllegalArgumentException("Negative values are not permitted!");
if(from>to)
throw new IllegalArgumentException("From-value must be lower or equal than to-value!");
if(from>managedPoints || to>managedPoints)
throw new IllegalArgumentException("This PointCircle holds only "+managedPoints+" points.");
ArrayList result = new ArrayList();
if(from==to){
result.add(coordinates[from-1]);
} else {
for(int i=from-1; ifrom.
* Point numbers are counted from 1.
* @param from Starting point number.
* @return A List of coordinates for all points in the given range.
* @throws Exception in case of invalid parameter values.
*/
public ArrayList getCoordinatesFrom(int from) throws Exception {
return getCoordinates(from, managedPoints);
}
}
public static void main(String[] args) throws Exception {
CircularPointGroup g = new CircularPointGroup(5, 10);
g.addPoints(PColor.black, 1);
g.addPoints(PColor.blue, 5);
VisualCircularPointGroupPanel vg = new VisualCircularPointGroupPanel(g);
vg.asFrame();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy