# Swing, Hexagons, and odd drawing O MY

• April 8th, 2012, 10:52 AM
Souls512
Swing, Hexagons, and odd drawing O MY
I am attempting to make a Hexagon based board.

My first attempt was a JPanel that I repeatedly drew a hexagon image on. This had slowness issues when placed in a JScrollPane.

Searching for a new way I found the code below. This seemed pretty sensible so I started playing around with it.

However, it turns out that when you click one of the hexagons there is an odd painting effect going on. The paintComponent function looks as though it would only paint the area inside the hex, however when clicking a hex it looks as though it paints a blank "Square" that the control would live in and then paints the hexagon.

Being new to swing I am unsure even how to track down why it isn't simply painting the hexagon.

Any assistance would be greatly appreciated.

Code :

```package CodeSnips;     import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.event.MouseEvent; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.SwingUtilities;   /** * A six sided toggle button. This is not guaranteed to be a perfect hexagon, it is just guaranteed to have six sides in * the form of a hexagon. To be a perfect hexagon the size of this component must have a height to width ratio of * 1 to 0.866 * * @author keang * @date 5 Jun 2009 * */ public class HexButton extends JToggleButton //public class HexButton extends JComponent { private static final long serialVersionUID = 4865976127980106774L;   private Polygon hexagon = new Polygon();     //public HexButton   /** * @param arg0 */ public HexButton(String arg0) { //super(arg0); }     @Override public boolean contains(Point p) { return hexagon.contains(p); }   @Override public boolean contains(int x, int y) { return hexagon.contains(x, y); }   @Override public void setSize(Dimension d) { super.setSize(d); calculateCoords(); }   @Override public void setSize(int w, int h) { super.setSize(w, h); calculateCoords(); }   @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); calculateCoords(); }   @Override public void setBounds(Rectangle r) { super.setBounds(r); calculateCoords(); }   @Override protected void processMouseEvent(MouseEvent e) { if ( contains(e.getPoint()) ) super.processMouseEvent(e); }   private void calculateCoords() { int w = getWidth()-1; int h = getHeight()-1;   int ratio = (int)(h*.25); int nPoints = 6; int[] hexX = new int[nPoints]; int[] hexY = new int[nPoints];   agressiveCoords(w, h, ratio, hexX, hexY); //passiveCoords(w, h, ratio, hexX, hexY);     hexagon = new Polygon(hexX, hexY, nPoints); }     private void agressiveCoords(int w, int h, int ratio, int[] hexX, int[] hexY) { hexX[0] = w/2; hexY[0] = 0;   hexX[1] = w; hexY[1] = ratio;   hexX[2] = w; hexY[2] = h - ratio;   hexX[3] = w/2; hexY[3] = h;   hexX[4] = 0; hexY[4] = h - ratio;   hexX[5] = 0; hexY[5] = ratio; }   private void passiveCoords(int w, int h, int ratio, int[] hexX, int[] hexY) { hexY[0] = w/2; hexX[0] = 0;   hexY[1] = w; hexX[1] = ratio;   hexY[2] = w; hexX[2] = h - ratio;   hexY[3] = w/2; hexX[3] = h;   hexY[4] = 0; hexX[4] = h - ratio;   hexY[5] = 0; hexX[5] = ratio; }   @Override protected void paintComponent(Graphics g) { if ( isSelected() ) { g.setColor(Color.lightGray); } else { g.setColor(getBackground()); }   g.fillPolygon(hexagon);   g.setColor(getForeground()); g.drawPolygon(hexagon);   FontMetrics fm = getFontMetrics(getFont()); Rectangle viewR = getBounds(); Rectangle iconR = new Rectangle(); Rectangle textR = new Rectangle();     //SwingUtilities.layoutCompoundLabel(this, fm, getText(), null, // SwingUtilities.CENTER, SwingUtilities.CENTER, SwingUtilities.BOTTOM, SwingUtilities.CENTER, // viewR, iconR, textR, 0);   //Point loc = getLocation(); //g.drawString(getText(), textR.x-loc.x, textR.y-loc.y+fm.getAscent()); }   @Override protected void paintBorder(Graphics g) { // do not paint a border }   /** * Application Entry Point */ public static void main(String[] args) { JFrame frame = new JFrame("Hex Button Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);   JPanel p = new JPanel(); p.setLayout(new HexLayout(4, 0, 4));   for ( int i = 0; i < 24; i++ ) { HexButton b = new HexButton("B"+(i+1)); b.setForeground(Color.black); b.setBackground(Color.green);   p.add(b); }   frame.add(p); frame.pack(); frame.setSize(700, 400); frame.setVisible(true); }   }```

Code :

```package CodeSnips;   import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager;   /** * This layout manager is based on java.awt.GridLayout * * The <code>GridLayout</code> class is a layout manager that * lays out a container's components in a hexagonal grid. * The container is divided into equal-sized hexagons, * and one component is placed in each hexagon. * * * * * @author keang * @date 5 Jun 2009 * */ public class HexLayout implements LayoutManager, java.io.Serializable { private static final long serialVersionUID = -858342723067286796L;   /** * This is the gap (in pixels) which specifies the space * between components. They can be changed at any time. * This should be a non-negative integer. * * @serial * @see #getHgap() * @see #setHgap(int) */ int cgap; /** * This is the number of rows specified for the grid. The number * of rows can be changed at any time. * This should be a non negative integer, where '0' means * 'any number' meaning that the number of Rows in that * dimension depends on the other dimension. * * @serial * @see #getRows() * @see #setRows(int) */ int rows; /** * This is the number of columns specified for the grid. The number * of columns can be changed at any time. * This should be a non negative integer, where '0' means * 'any number' meaning that the number of Columns in that * dimension depends on the other dimension. * * @serial * @see #getColumns() * @see #setColumns(int) */ int cols;   /** * Creates a grid layout with a default of one column per component, * in a single row. * @since JDK1.1 */ public HexLayout() { this(1, 0, 0); }   /** * Creates a grid layout with the specified number of rows and * columns. All components in the layout are given equal size. * <p> * One, but not both, of <code>rows</code> and <code>cols</code> can * be zero, which means that any number of objects can be placed in a * row or in a column. * @param r the rows, with the value zero meaning * any number of rows. * @param c the columns, with the value zero meaning * any number of columns. */ public HexLayout(int r, int c) { this(r, c, 0); }   /** * Creates a grid layout with the specified number of rows and * columns. All components in the layout are given equal size. * <p> * In addition, the gap between components is set to the * specified value. * <p> * One, but not both, of <code>rows</code> and <code>cols</code> can * be zero, which means that any number of objects can be placed in a * row or in a column. * <p> * All <code>GridLayout</code> constructors defer to this one. * @param r the rows, with the value zero meaning * any number of rows * @param c the columns, with the value zero meaning * any number of columns * @param hgap the gap around the component * @exception IllegalArgumentException if the value of both * <code>rows</code> and <code>cols</code> is * set to zero */ public HexLayout(int r, int c, int hgap) { if ((r == 0) && (c == 0)) { throw new IllegalArgumentException("rows and cols cannot both be zero"); } this.rows = r; this.cols = c; this.cgap = hgap; }   /** * Gets the number of rows in this layout. * @return the number of rows in this layout */ public int getRows() { return rows; }   /** * Sets the number of rows in this layout to the specified value. * @param r the number of rows in this layout * @exception IllegalArgumentException if the value of both * <code>rows</code> and <code>cols</code> is set to zero */ public void setRows(int r) { if ((r == 0) && (this.cols == 0)) { throw new IllegalArgumentException("rows and cols cannot both be zero"); } this.rows = r; }   /** * Gets the number of columns in this layout. * @return the number of columns in this layout */ public int getColumns() { return cols; }   /** * Sets the number of columns in this layout to the specified value. * Setting the number of columns has no affect on the layout * if the number of rows specified by a constructor or by * the <tt>setRows</tt> method is non-zero. In that case, the number * of columns displayed in the layout is determined by the total * number of components and the number of rows specified. * @param c the number of columns in this layout * @exception IllegalArgumentException if the value of both * <code>rows</code> and <code>cols</code> is set to zero */ public void setColumns(int c) { if ((c == 0) && (this.rows == 0)) { throw new IllegalArgumentException("rows and cols cannot both be zero"); } this.cols = c; }   /** * Gets the gap between components. * @return the gap between components */ public int getGap() { return cgap; }   /** * Sets the gap between components to the specified value. * @param gap the gap between components */ public void setGap(int gap) { this.cgap = gap; }   /** * Adds the specified component with the specified name to the layout. * @param name the name of the component * @param comp the component to be added */ public void addLayoutComponent(String name, Component comp) { // do nothing }   /** * Removes the specified component from the layout. * @param comp the component to be removed */ public void removeLayoutComponent(Component comp) { // do nothing }   /** * Determines the preferred size of the container argument using * this grid layout. * <p> * The preferred width of a grid layout is the largest preferred width * of all of the components in the container times the number of columns, * plus the horizontal padding times the number of columns minus one, * plus the left and right insets of the target container, plus the width * of half a component if there is more than one row. * <p> * The preferred height of a grid layout is the largest preferred height * of all of the components in the container plus three quarters of the * largest minimum height of all of the components in the container times * the number of rows greater than one, * plus the vertical padding times the number of rows minus one, plus * the top and bottom insets of the target container. * * @param parent the container in which to do the layout * @return the preferred dimensions to lay out the * subcomponents of the specified container * @see java.awt.GridLayout#minimumLayoutSize * @see java.awt.Container#getPreferredSize() */ public Dimension preferredLayoutSize(Container parent) { synchronized ( parent.getTreeLock() ) { Insets insets = parent.getInsets(); int ncomponents = parent.getComponentCount(); int nrows = rows; int ncols = cols;   if ( nrows > 0 ) { ncols = (ncomponents + nrows - 1) / nrows; } else { nrows = (ncomponents + ncols - 1) / ncols; }   int w = 0; int h = 0; for ( int i = 0; i < ncomponents; i++ ) { Component comp = parent.getComponent(i); Dimension d = comp.getPreferredSize(); if ( w < d.width ) { w = d.width; } if ( h < d.height ) { h = d.height; } }   int dx = insets.left + insets.right + ncols * w + (ncols - 1) * cgap; int dy = insets.top + insets.bottom + nrows * h + (nrows - 1) * cgap;   if ( nrows > 1 ) { dx = dx + (int)(w * 0.5f);   dy /= nrows; dy = dy + (int)(dy * (nrows - 1) * 0.75f); }   return new Dimension(dx, dy); } }   /** * Determines the minimum size of the container argument using this * grid layout. * <p> * The minimum width of a grid layout is the largest minimum width * of all of the components in the container times the number of columns, * plus the horizontal padding times the number of columns minus one, * plus the left and right insets of the target container, plus the width * of half a component if there is more than one row. * <p> * The minimum height of a grid layout is the largest minimum height * of all of the components in the container plus three quarters of the * largest minimum height of all of the components in the container times * the number of rows greater than one, * plus the vertical padding times the number of rows minus one, plus * the top and bottom insets of the target container. * * @param parent the container in which to do the layout * @return the minimum dimensions needed to lay out the * subcomponents of the specified container * @see java.awt.GridLayout#preferredLayoutSize * @see java.awt.Container#doLayout */ public Dimension minimumLayoutSize(Container parent) { synchronized ( parent.getTreeLock() ) { Insets insets = parent.getInsets(); int ncomponents = parent.getComponentCount(); int nrows = rows; int ncols = cols;   if ( nrows > 0 ) { ncols = (ncomponents + nrows - 1) / nrows; } else { nrows = (ncomponents + ncols - 1) / ncols; } int w = 0; int h = 0; for ( int i = 0; i < ncomponents; i++ ) { Component comp = parent.getComponent(i); Dimension d = comp.getMinimumSize(); if ( w < d.width ) { w = d.width; } if ( h < d.height ) { h = d.height; } }   int dx = insets.left + insets.right + ncols * w + (ncols - 1) * cgap; int dy = insets.top + insets.bottom + nrows * h + (nrows - 1) * cgap;   if ( nrows > 1 ) { dx = dx + (int)(w * 0.5f);   dy /= nrows; dy = dy + (int)(dy * (nrows - 1) * 0.75f); }   return new Dimension(dx, dy); } }   /** * Lays out the specified container using this layout. * <p> * This method reshapes the components in the specified target * container in order to satisfy the constraints of the * <code>GridLayout</code> object. * <p> * The grid layout manager determines the size of individual * components by dividing the free space in the container into * equal-sized portions according to the number of rows and columns * in the layout. The container's free space equals the container's * size minus any insets and any specified horizontal or vertical * gap. All components in a grid layout are given the same size. * * @param parent the container in which to do the layout * @see java.awt.Container * @see java.awt.Container#doLayout */ public void layoutContainer(Container parent) { synchronized (parent.getTreeLock()) { Insets insets = parent.getInsets(); int ncomponents = parent.getComponentCount(); int nrows = rows; int ncols = cols;   if ( ncomponents == 0 ) { return; } if ( nrows > 0 ) { ncols = (ncomponents + nrows - 1) / nrows; } else { nrows = (ncomponents + ncols - 1) / ncols; }   int w = parent.getWidth() - (insets.left + insets.right); int h = parent.getHeight() - (insets.top + insets.bottom);   w = (int)((w - (ncols - 1) * cgap) / (ncols + (nrows>1?0.5f:0.0f)));   float effectiveRows = 1 + ((nrows - 1) * 0.75f); h = (int)((h - (nrows - 1) * cgap) / effectiveRows);   int xoffset = (w+cgap)/2; int yoffset = (int)(h * 0.75f); boolean staggeredRow = false;   for ( int r = 0, y = insets.top; r < nrows; r++, y += yoffset + cgap ) { int offset = 0;   if ( staggeredRow ) offset = xoffset;   for ( int c = 0, x = insets.left; c < ncols; c++, x += w + cgap ) { int i = r * ncols + c;   if ( i < ncomponents ) { parent.getComponent(i).setBounds(x+offset, y, w, h); }   }   staggeredRow = !staggeredRow; } } }   /** * Returns the string representation of this grid layout's values. * @return a string representation of this grid layout */ public String toString() { return getClass().getName() + "[gap=" + cgap + ",rows=" + rows + ",cols=" + cols + "]"; }   }```
• April 8th, 2012, 11:07 AM
Souls512
Re: Swing, Hexagons, and odd drawing O MY

I have gone though this code extensively.
I am simply new to Swing and not sure why it does not behave the way it "looks" like it should.

The gist is:
There is a Hexagon object and a HexagonLayout.

The Layout simply sets the hexagons up in a panel so they look like a hexagon map by changing where they think they are. Pretty simple really.

The Hexagon object really just draws the hexagon. It is a JToggleButton but has its paintComponent overridden.

I just don't get why clicking the toggle button changes/paints on area that is not within the bounds on the hexagon.
• April 8th, 2012, 12:03 PM
Norm
Re: Swing, Hexagons, and odd drawing O MY
When I execute the program and click on a green hex, its color is changed to gray. The new color does not go outside of the hex area.

Can you explain what you think is wrong with the way the code works?
• April 8th, 2012, 12:20 PM
Souls512
Re: Swing, Hexagons, and odd drawing O MY
I have attached an image of what I see when running the code and clicking on the image.

If that is not what you are seeing ... very odd. I am using Eclipse (not that I would think that would matter)

What version of java are you compiling against?
I would hate to think I have spent so long looking at this for it to be an environmental issue.

Attachment 1156
• April 8th, 2012, 12:39 PM
Norm
Re: Swing, Hexagons, and odd drawing O MY
I'm using F:\Java\jdk1.6.0_29\bin\javac.exe for compiles.
and java version "1.6.0_29" for execution

Cross posted at
http://www.java-forums.org/awt-swing...tml#post277684
• April 8th, 2012, 11:48 PM
Souls512
Re: Swing, Hexagons, and odd drawing O MY
Easy fix, changed the HexButton to inherit from JComponent and implemented MouseInputListener for the clicks.
Works like a charm.

Thanks for the feedback!