All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.invation.code.toval.graphic.misc.CircularPointGroup Maven / Gradle / Ivy

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