I've been working on a custom layout manager for a long time now, and I'm almost done. I call it OrbitLayout and it displays components in concentric circles. I'm itching to show it off, so if you'd like to give it a try, here it is: To add components, use the following method: Container con; Componenent com; // initialize them, etc con.setLayout(new OrbitLayout(0,25); // for some spacing int orbit = (choose an orbit here from 0 to n, where 0 is the center) con.add(com, new Integer(orbit)); Try adding a few components (buttons work best) to a few orbits (although the center orbit 0 can only hold 1 component). Please tell me what you think!!! Micah

<code> import java.util.*; import java.awt.*; /** * This layout manager is used to display components in concentric circles surrounding * a central component (or no central component). For the purposes of this * source file, each circle is an orbit and the components are satellites. * * @author Micah Wedemeyer * @version 1.0 */ public class OrbitLayout implements LayoutManager2 { /** * A constant used to deliniate the central component that all other components orbit. */ final public static int CENTER = 0; /** * The minimum spacing between components in the same orbit. */ private int satGap; /** * The minimum spacing between each orbit. */ private int orbitGap; /** * The current outer-most orbit. */ private int outerOrbit; /** * A list of orbits. Each element in the list is a list itself of satellites * in that orbit. The center object is at orbits[0], * the first orbit at orbits[1], and the nth at orbits[n]. */ private ArrayList orbits; /** * A central component that all the satellites orbit */ private Component center; /** * Creates an OrbitLayout with the specified spacing. * @param satGap The minimum distance between satellites in the same orbit. * @param orbitGap The minimum distance between concentric orbits. */ public OrbitLayout(int satGap, int orbitGap) { this.satGap = satGap; this.orbitGap = orbitGap; outerOrbit = 0; center = null; orbits = new ArrayList(); orbits.add(new OrbitList(0)); } /** * Does not use, must be overridden for interface. */ public void addLayoutComponent(String s, Component c) { } /** * Adds a component to the layout. * @param c The component to be added. * @param constraints (an Integer object) The orbit to add the component to. */ public void addLayoutComponent(Component c, Object constraints) { if (constraints instanceof Integer) { Integer orbit = (Integer)constraints; addLayoutComponent(c, orbit.intValue()); } } /** * Adds a component to the specified orbit. * @param comp The component to be added. * @param orbit The orbit to add the component to. * @see OrbitLayout#CENTER */ public void addLayoutComponent (Component comp, int orbit) { if (orbit < 0)<br /> throw new IllegalArgumentException("Orbit must be non-negative.");<br /> /* Special Case: The orbit is actually the center. */<br /> if (orbit == CENTER) {<br /> center = comp;<br /> // sets orbits[0] to an OrbitList containing only the center<br /> OrbitList o = new OrbitList(0);<br /> o.add(center);<br /> orbits.set(0, o);<br /> invalidateAllOrbits();<br /> return;<br /> }<br /> /**<br /> * If the new orbit is higher than the outerOrbit, allocate a new ArrayList for<br /> * the new orbit and all intervening orbits. This keeps the orbits List from<br /> * having null slots for empty orbits. This will make iteration easier.<br /> */<br /> if (orbit > outerOrbit) { for (int i = outerOrbit + 1; i <= orbit; i++)<br /> orbits.add(new OrbitList(i));<br /> outerOrbit = orbit;<br /> }<br /> // Adds the component to its correct orbit.<br /> ((OrbitList)orbits.get(orbit)).add(comp);<br /> // Forces resizing of all external orbits.<br /> invalidateOrbits(orbit);<br /> }<br /> /**<br /> * Removes a component from the layout.<br /> * @param comp The component to be removed from this layout.<br /> */<br /> public void removeLayoutComponent (Component comp) {<br /> if (comp == center) {<br /> center = null;<br /> ((OrbitList)orbits.get(0)).clear();<br /> invalidateAllOrbits();<br /> return;<br /> }<br /> for (Iterator i = orbits.iterator(); i.hasNext(); ) {<br /> OrbitList orb = (OrbitList)i.next();<br /> for (Iterator j = orb.iterator(); j.hasNext(); ) {<br /> Component c = (Component)j.next();<br /> if (c == comp) {<br /> int currentOrbit = orbits.indexOf(orb);<br /> invalidateOrbits(currentOrbit);<br /> j.remove();<br /> return;<br /> }<br /> }<br /> }<br /> }<br /> /**<br /> * =================================<br /> * NEED TO FIX<br /> * ================================<br /> */<br /> public Dimension preferredLayoutSize(Container target) {<br /> // Not sure what to do here, yet.<br /> return target.getPreferredSize();<br /> }<br /> /**<br /> * ===============================<br /> * NEED TO FIX<br /> * ===========================<br /> */<br /> public Dimension minimumLayoutSize(Container target) {<br /> // Not sure what to do here, yet<br /> return target.getMinimumSize();<br /> }<br /> /**<br /> * The maximum size for this layout manager.<br /> * @return the max size for this layout manager.<br /> */<br /> public Dimension maximumLayoutSize(Container target) {<br /> return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);<br /> }<br /> /**<br /> * Returns the alignment along the x axis. This specifies how<br /> * the component would like to be aligned relative to other<br /> * components. The value should be a number between 0 and 1<br /> * where 0 represents alignment along the origin, 1 is aligned<br /> * the furthest away from the origin, 0.5 is centered, etc.<br /> */<br /> public float getLayoutAlignmentX(Container parent) {<br /> return 0.5f;<br /> }<br /> /**<br /> * Returns the alignment along the y axis. This specifies how<br /> * the component would like to be aligned relative to other<br /> * components. The value should be a number between 0 and 1<br /> * where 0 represents alignment along the origin, 1 is aligned<br /> * the furthest away from the origin, 0.5 is centered, etc.<br /> */<br /> public float getLayoutAlignmentY(Container parent) {<br /> return 0.5f;<br /> }<br /> /**<br /> * Invalidates the layout, flushing cached data.<br /> */<br /> public void invalidateLayout(Container parent) {<br /> /* Nothing to do here...I think. */<br /> }<br /> /**<br /> * Lays out the components in the specified container.<br /> * @param parent the container which needs to be laid out.<br /> */<br /> public void layoutContainer (Container parent) {<br /> Insets insets = parent.getInsets();<br /> // In order to make calculations easy, we will assume that (0,0)<br /> // is at the center of the container. We can do conversions at the end.<br /> Point origin = new Point();<br /> origin.x = (parent.getWidth() - insets.left - insets.right) / 2;<br /> origin.y = (parent.getHeight() - insets.top - insets.bottom) / 2;<br /> for (Iterator i = orbits.iterator(); i.hasNext(); ) {<br /> OrbitList currentOrb = (OrbitList)i.next();<br /> if (currentOrb.size() != 0) {<br /> // Calculate the angle in radians between the centerpoints of each<br /> // component. Since there are 2*pi radians in a circle, to evenly space<br /> // the components, we just space them by 2*pi/# of components.<br /> // Additional spacing (satGap) must be added later<br /> double spaceAngle = (Math.PI * 2) / currentOrb.size();<br /> // A multiplier for the spaceAngle. The first component will be placed<br /> // at Theta=0, the second at Theta=(spaceAngle), the third at<br /> // Theta=(spaceAngle * 2), etc.<br /> int angleMult = 0;<br /> for (Iterator j = currentOrb.iterator(); j.hasNext(); ) {<br /> Component c = (Component)j.next();<br /> polarLayout(c, currentOrb.getRadius(), spaceAngle * angleMult, origin);<br /> angleMult++;<br /> }<br /> }<br /> }<br /> }<br /> /**<br /> * Lays out a single component in the container using polar to cartesian<br /> * coordinate conversion.<br /> * @param c The component to be laid out.<br /> * @param radius The radius of the centerpoint of the component.<br /> * @param theta The angle from the origin (center of container) of the centerpoint<br /> * of the component.<br /> * @param origin The centerpoint of the container.<br /> */<br /> private void polarLayout(Component c, int radius, double theta, Point origin) {<br /> Dimension d = c.getPreferredSize();<br /> // Determine the centerpoint for the component in Cartesian coordinates<br /> // using the standard formula for polar -> Cartesian conversion. Point centerpoint = new Point(); centerpoint.x = (int)(radius * Math.cos(theta)) + origin.x; centerpoint.y = (int)(radius * Math.sin(theta)) + origin.y; // Use the preferred size and the centerpoint to calculate the top left // corner of where the component should be laid out. Rectangle bounds = new Rectangle(); bounds.x = centerpoint.x - (d.width / 2); bounds.y = centerpoint.y - (d.height / 2); bounds.width = d.width; bounds.height = d.height; c.setBounds(bounds); } /** * Invalidates all orbits at and above a specified orbit. * This will force a recalculation at the next sizing or layout. * This method is called when an orbit is changed (satellite added or removed). * Each orbit above a changed orbit must also be recalculated, so this invalidates * all orbits above. */ private void invalidateOrbits(int orb) { for (ListIterator i = orbits.listIterator(orb); i.hasNext(); ) { OrbitList o = (OrbitList)i.next(); o.invalidate(); } } /** * Invalidates the radius of every orbit. * @see invalidateRadii(int) */ private void invalidateAllOrbits() { invalidateOrbits(0); } /** * Draws circles to represent the orbits. * @param parent The container that is being laid out. */ public void drawOrbits(Container parent) { Insets insets = parent.getInsets(); Graphics g = parent.getGraphics(); // In order to make calculations easy, we will assume that (0,0) // is at the center of the container. We can do conversions at the end. Point origin = new Point(); origin.x = (parent.getWidth() - insets.left - insets.right) / 2; origin.y = (parent.getHeight() - insets.top - insets.bottom) / 2; for (Iterator i = orbits.iterator(); i.hasNext(); ) { OrbitList currentOrb = (OrbitList)i.next(); int r = currentOrb.getRadius(); Point topLeft = new Point(); topLeft.x = origin.x - r; topLeft.y = origin.y - r; g.drawOval(topLeft.x, topLeft.y, r * 2, r * 2); } // Draw a small orbit for the centerpoint g.fillOval(origin.x - 2, origin.y - 2, 4, 4); } /** * A class to represent each orbit. Each OrbitList is a list of satellites, * plus the radius at which these satellites should be placed. In order to cut * down execution time, it will not recalculate radius and maxOverlap each time an element is added. * * Since radius is a function of the radii of closer * orbits, this sets up a recursive relation. In order to escape the nasty time requirements * of recursive relations, I made it possible to only recalculate the radii of those orbits * which are invalid. * * It is important to remember that when adding or removing satellites from an * orbit, the orbit MUST BE invalidated. This will prevent resizing problems. * * NOTE: This is not for a list of orbits, it is to represent a single orbit. */ class OrbitList extends ArrayList { /** * The orbit that this list represents. */ private int orbit; /** * The radius of current orbit in pixels. */ private int radius; /** * This is 1/2 the length of the main diagonal of the largest component in this orbit. * This is important because this is the maximum amount that extends past an * orbit. Therefore, if orbits are spaced at least that much, there will be * no overlap. */ private int maxOverlap; /** * Tells whether the current radius and maxOverlap is correct. */ private boolean valid; /** * Creates an empty OrbitList without setting a correct radius. This will * be done later, during calls to <code>getRadius()</code>. * @param orbit The orbit to be represented. */ public OrbitList(int orbit) { super(); this.orbit = orbit; maxOverlap = 0; radius = 0; valid = false; } /** * Gets the current valid radius. If the current radius is invalid, it computes * and returns the valid one.

* NOTE: This is not a simple O(1) get method. There may be time consuming * calculations involved. I tried to make it as efficient as possible. * @return The current valid radius. */ public int getRadius() { calculateRadius(); return radius; } /** * Returns the maximum overlap. * @return The maximum overlap of this orbit. */ public int getMaxOverlap() { if (!isValid()) calculateOverlap(); return maxOverlap; } /** * Calculates and sets the correct radius. */ private void calculateRadius() { // If valid, don't do anything. if (isValid()) return; else if (orbit == 0) { radius = 0; } else { OrbitList previous = (OrbitList)orbits.get(orbit - 1); radius = previous.getRadius() + previous.getMaxOverlap() + this.getMaxOverlap() + orbitGap; } valid = true; } /** * Calculates and sets the maximum overlap of this orbit. This is 1/2 the length * of the main diagonal of the largest component. This is important because * this is the maximum amount that extends past an orbit. Therefore, if orbits * are spaced at least that much, there will be no overlap. */ private void calculateOverlap () { maxOverlap = 0; for (Iterator i = this.iterator(); i.hasNext(); ) { Component c = (Component)i.next(); Dimension d = c.getPreferredSize(); int w = d.width; int h = d.height; int over = (int)(Math.sqrt((w * w) + (h * h)) / 2); maxOverlap = Math.max(maxOverlap, over); } } /** * Invalidates the current orbit, forcing radius and maxOverlap to be recalculated * during the next call to <code>getRadius()</code> or <code>getMaxOverlap()</code>. */ public void invalidate() { valid = false; } /** * Determines if the current radius and maxOverlap are valid. * @return true if the radius and maxOverlap are valid, false otherwise. */ public boolean isValid() { return valid; } } } </code>