Rotating a triangle around a point java - java

I am having trouble. I need to rotate an equilateral triangle around it's centre by using the drag listener and click listener. The triangle should grow but now change angles and be rotated by a point while being centred at the middle of the triangle. This is my problem, it is currently dragging by the point 3 and rotating around point 1. I have an array of values x and y and it stores 4 values each containing the initial point first at ordinal value 0 and point 1 2 and 3 at the corresponding values.
`
public class DrawTriangle extends JFrame {
enter code here
/** The Constant NUMBER_3. */
private static final int NUMBER_3 = 3;
/** The Constant EQUL_ANGLE. */
#SuppressWarnings("unused")
private static final double EQUL_ANGLE = 1;
/** The Constant TRIANGLE_POINTS. */
private static final int TRIANGLE_POINTS = 4;
/** The Constant _400. */
private static final int SIZE = 400;
/** The x points. */
private int [] xPoints = new int[TRIANGLE_POINTS];
/** The y points. */
private int [] yPoints = new int[TRIANGLE_POINTS];
private int xInitial;
private int yInitial;
/** The x. */
private double x = EQUL_ANGLE;
/** The new x. */
private double newX;
/** The new y. */
private double newY;
/**
* Instantiates a new draw triangle.
*/
public DrawTriangle() {
super("Dimitry Rakhlei");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setContentPane(new DrawTrianglePanel());
setSize(SIZE, SIZE); // you can change this size but don't make it HUGE!
setVisible(true);
}
/**
* The Class DrawTrianglePanel.
*/
private class DrawTrianglePanel extends JPanel implements MouseListener,
MouseMotionListener {
/**
* Instantiates a new draw triangle panel.
*/
public DrawTrianglePanel() {
addMouseListener(this);
addMouseMotionListener(this);
}
/**
* Drawing the triangle.
*
* #param g
* the g
* #see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
// DRAWING CODE HERE
g.drawPolygon(xPoints, yPoints, 3);
System.out.println("Paint called");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mousePressed
* (java.awt.event.MouseEvent)
*/
public void mousePressed(MouseEvent e) {
System.out.println("Mouse pressed called");
e.getPoint();
xPoints[0] = e.getPoint().x;
yPoints[0] = e.getPoint().y;
repaint();
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseReleased
* (java.awt.event.MouseEvent)
*/
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse released called");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseMotionListener#mouseDragged
* (java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
System.out.println("Mouse dragged called");
newX = e.getPoint().x;
newY = e.getPoint().y;
xPoints[1] = (int) newX;
yPoints[1] = (int) newY;
newX = xPoints[0] + (xPoints[1]-xPoints[0])*Math.cos(x) - (yPoints[1]-yPoints[0])*Math.sin(x);
newY = yPoints[0] + (xPoints[1]-xPoints[0])*Math.sin(x) + (yPoints[1]-yPoints[0])*Math.cos(x);
xPoints[2] = (int) newX;
yPoints[2] = (int) newY;
newX = xPoints[0] + (xPoints[1]-xPoints[0])*Math.cos(x) - (yPoints[1]-yPoints[0])*Math.sin(x);
newY = yPoints[0] + (xPoints[1]-xPoints[0])*Math.sin(x) + (yPoints[1]-yPoints[0])*Math.cos(x);
xPoints[3] = (int) newX;
yPoints[3] = (int) newY;
repaint();
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseEntered
* (java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
System.out.println("Mouse Entered.");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseExited
* (java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e) {
System.out.println("Mouse exited.");
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseListener#mouseClicked
* (java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e) {
}
/**
* (non-Javadoc).
*
* #param e
* the e
* #see java.awt.event.MouseMotionListener#mouseMoved
* (java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent e) {
}
}
/**
* The main method.
*
* #param args
* the arguments
*/
public static void main(String[] args) {
new DrawTriangle();
}
};`
My issue is that this code basically runs correctly but I am told the vertex point of rotation has to be in the middle of the triangle. Mine is the first point.

Start by taking a look at 2D Graphics, in particular Transforming Shapes, Text, and Images.
Basically, your "polygon" will have a definable size (the maximum x/y point), from this, you can determine the center position of the "polygon", for example...
protected Dimension getTriangleSize() {
int maxX = 0;
int maxY = 0;
for (int index = 0; index < xPoints.length; index++) {
maxX = Math.max(maxX, xPoints[index]);
}
for (int index = 0; index < yPoints.length; index++) {
maxY = Math.max(maxY, yPoints[index]);
}
return new Dimension(maxX, maxY);
}
This just returns the maximum x and y bounds of your polygon. This allows you to calculate the center position of the polygon. You'll see why in a second why you don't need to actually specify the origin point...
Next, we calculate a AffineTransform, which is the applied to the Graphics context directly...
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = new AffineTransform();
Dimension size = getTriangleSize();
int x = clickPoint.x - (size.width / 2);
int y = clickPoint.y - (size.height / 2);
at.translate(x, y);
at.rotate(Math.toRadians(angle), clickPoint.x - x, clickPoint.y - y);
g2d.setTransform(at);
g2d.drawPolygon(xPoints, yPoints, 3);
// Guide
g2d.setColor(Color.RED);
g2d.drawLine(size.width / 2, 0, size.width / 2, size.height / 2);
g2d.dispose();
This not only translates the triangle position, but will also rotate it. What this means you can create a normalised polygon (whose origin point is 0x0) and allow the Graphics context to place it where you want it, this makes life SO much easier...
Now, the rotation calculation is based on calculating the angle between two points, the "click" point and the "drag" point...
angle = -Math.toDegrees(Math.atan2(e.getPoint().x - clickPoint.x, e.getPoint().y - clickPoint.y)) + 180;
Which is based on the solution in this question
For example...
The red line is simple a guide to show that the tip of the triangle is point towards the mouse...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawTriangle extends JFrame {
/**
* The x points.
*/
private int[] xPoints = new int[]{0, 25, 50};
/**
* The y points.
*/
private int[] yPoints = new int[]{50, 0, 50};
double angle = 0f;
/**
* Instantiates a new draw triangle.
*/
public DrawTriangle() {
super("Dimitry Rakhlei");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setContentPane(new DrawTrianglePanel());
pack();
setLocationRelativeTo(null);
setVisible(true);
}
/**
* The Class DrawTrianglePanel.
*/
private class DrawTrianglePanel extends JPanel implements MouseListener,
MouseMotionListener {
private Point clickPoint;
/**
* Instantiates a new draw triangle panel.
*/
public DrawTrianglePanel() {
addMouseListener(this);
addMouseMotionListener(this);
clickPoint = new Point(100, 100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected Dimension getTriangleSize() {
int maxX = 0;
int maxY = 0;
for (int index = 0; index < xPoints.length; index++) {
maxX = Math.max(maxX, xPoints[index]);
}
for (int index = 0; index < yPoints.length; index++) {
maxY = Math.max(maxY, yPoints[index]);
}
return new Dimension(maxX, maxY);
}
/**
* Drawing the triangle.
*
* #param g the g
* #see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = new AffineTransform();
Dimension size = getTriangleSize();
int x = clickPoint.x - (size.width / 2);
int y = clickPoint.y - (size.height / 2);
at.translate(x, y);
at.rotate(Math.toRadians(angle), clickPoint.x - x, clickPoint.y - y);
g2d.setTransform(at);
g2d.drawPolygon(xPoints, yPoints, 3);
// Guide
g2d.setColor(Color.RED);
g2d.drawLine(size.width / 2, 0, size.width / 2, size.height / 2);
g2d.dispose();
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mousePressed (java.awt.event.MouseEvent)
*/
#Override
public void mousePressed(MouseEvent e) {
System.out.println("Mouse pressed called");
// clickPoint = e.getPoint();
repaint();
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseReleased (java.awt.event.MouseEvent)
*/
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse released called");
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseMotionListener#mouseDragged (java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
System.out.println("Mouse dragged called");
angle = -Math.toDegrees(Math.atan2(e.getPoint().x - clickPoint.x, e.getPoint().y - clickPoint.y)) + 180;
repaint();
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseEntered (java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
System.out.println("Mouse Entered.");
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseExited (java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e) {
System.out.println("Mouse exited.");
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseListener#mouseClicked (java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e) {
}
/**
* (non-Javadoc).
*
* #param e the e
* #see java.awt.event.MouseMotionListener#mouseMoved (java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent e) {
}
}
/**
* The main method.
*
* #param args the arguments
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new DrawTriangle();
}
});
}
}
Now, before you jump all over me and complain that the solution is "too complex", understand that I'm an idiot, seriously, my 2 year old has a better grasp on basic mathematics then I do, this is the most simplistic solution I can come up with that doesn't melt my brain and uses the dual array polygon API. Personally, I'd use the Shape API, but that's not what you started with...

Related

MouseListener to Instance of Game Panel

I'm creating a Tic Tac Toe game where I'm using MouseListener to add functionality to my game panel. When a user clicks one of the cells its suppose to generate an X or O graphic depending on whose turn it is. I've tried adding the MouseListener to the pane, but when I run it nothing happens when I click. Any ideas on how I can fix this?
Here's my game panel:
public GameMain() {
Handler handler = new Handler();
this.addMouseListener(handler);
// setup JLabel
label = new JLabel(" ");
label.setBorder(BorderFactory.createEmptyBorder(2, 5, 4, 5));
label.setOpaque(true);
label.setBackground(Color.LIGHT_GRAY);
setLayout(new BorderLayout());
add(label, BorderLayout.SOUTH);
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT + 30));
board = new Board();
initGame();
}
}
Here's my Handler class with the mouseClick() method that's suppose to run:
public class Handler extends MouseAdapter {
public void mouseClick(MouseEvent e) {
int mouseX = e.getX();
int mouseY = e.getY();
// Get the row and column clicked
int rowSelected = mouseY / CELL_SIZE;
int colSelected = mouseX / CELL_SIZE;
if (currentState == GameState.PLAYING) {
if (rowSelected >= 0 && rowSelected < board.cells.length && colSelected >= 0 && colSelected < board.cells.length &&
board.cells[rowSelected][colSelected].content == Seed.EMPTY) {
board.cells[rowSelected][colSelected].content = currentPlayer; // move
updateGame(currentPlayer, rowSelected, colSelected); // update currentState
currentPlayer = (currentPlayer == Seed.X) ? Seed.O : Seed.X;
}
} else {
initGame();
}
repaint();
}
public void handleButtonPress(Object o) {
if (o == singlePlayer) {
singlePlayerGame();
}
if (o == multiPlayer) {
multiPlayerGame();
}
}
}
Your question inspired me to take up the challenge of writing a tic-tac-toe game, since I don't have much experience in handling custom painting. The below code is copiously commented, so I hope that those comments will serve as a good explanation of the code.
I'm presuming that people with more experience than me will find flaws in the below code, which is to be expected, since, as I already mentioned, I don't have a lot of experience in this kind of programming. Nonetheless, I hope it is good enough in order to be of help you.
Note that the below code makes use of a new java feature that was introduced with JDK 14, namely Records. Hence it may also be helpful to people as a simple example of how to integrate java records into their code. If you aren't using JDK 14 or if you are but you haven't enabled preview features, you can simply replace the record definition with a simple class. Just replace the word record with class and add a constructor and "getter" methods to the code. By default, the name of a "getter" method in a record is simply the name of the field, e.g. for member minX in record Square (in below code), the "getter" method is minX().
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
/**
* A simple tic-tac-toe (a.k.a. noughts and crosses) game. User clicks mouse on an empty square of
* the game board and either a nought or a cross is drawn in that square, depending on whose turn
* it is. Each square has an index as shown below.
* <pre>
* 0 1 2
*
* 3 4 5
*
* 6 7 8
* </pre>
* See Tic-tac-toe on <i>Wikipedia</i>.
*/
public class GameMain extends JPanel implements ActionListener, MouseListener, Runnable {
/** For serializing instances of this class. */
private static final long serialVersionUID = 7014608855599083001L;
/** A cross. */
private static final char CROSS = 'X';
/** A nought. */
private static final char NOUGHT = 'O';
/** Dimension of each square in tic-tac-toe board. */
private static final int SQUARE_DIMENSION = 80;
/** Number of consecutive squares required for a win. */
private static final int REQUIRED_SQUARES = 3;
/** Total number of squares in tic-tac-toe board. */
private static final int TOTAL_SQUARES = 9;
/** Each of the 9 squares in tic-tac-toe board. */
private final Square[] SQUARES = new Square[]{new Square(1, 1, 99, 99),
new Square(101, 1, 199, 99),
new Square(201, 1, 299, 99),
new Square(1, 101, 99, 199),
new Square(101, 101, 199, 199),
new Square(201, 101, 299, 199),
new Square(1, 201, 99, 299),
new Square(101, 201, 199, 299),
new Square(201, 201, 299, 299)};
/** Text for {#link #turnLabel} at start of a new tic-tac-toe game. */
private static final String FIRST_MOVE = "X goes first";
/** Text for <i>new game</i>. button. */
private static final String NEW_GAME = "New Game";
/** Indicates start of a new game. */
private boolean newGame;
/** <tt>true</tt> means O turn and <tt>false</tt> means X turn. */
private boolean oTurn;
/** Records occupied squares, either &apos;O&apos; or &apos;X&apos; */
private char[] occupied = new char[TOTAL_SQUARES];
/** Number of unoccupied squares in tic-tac-toe board. */
private int freeCount;
/** Displays whose turn it currently is. */
private JLabel turnLabel;
/** Location of last mouse click. */
private Point clickPoint;
/**
* Creates and returns instance of this class.
*/
public GameMain() {
setPreferredSize(new Dimension(300, 300));
addMouseListener(this);
freeCount = TOTAL_SQUARES;
}
private static boolean isValidRequirement(int index) {
return index >= 0 && index < REQUIRED_SQUARES;
}
/**
* Determines whether <var>square</var> is a valid index of a square in tic-tac-toe board.
*
* #param square - will be validated.
*
* #return <tt>true</tt> if <var>square</var> is valid.
*/
private static boolean isValidSquare(int square) {
return square >= 0 && square < TOTAL_SQUARES;
}
#Override // java.awt.event.ActionEvent
public void actionPerformed(ActionEvent actnEvnt) {
String actionCommand = actnEvnt.getActionCommand();
switch (actionCommand) {
case NEW_GAME:
startNewGame();
break;
default:
JOptionPane.showMessageDialog(this,
actionCommand,
"Unhandled",
JOptionPane.WARNING_MESSAGE);
}
}
#Override // java.awt.event.MouseListener
public void mouseClicked(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mouseEntered(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mouseExited(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mousePressed(MouseEvent mousEvnt) {
// Do nothing.
}
#Override // java.awt.event.MouseListener
public void mouseReleased(MouseEvent mousEvnt) {
System.out.println("mouse clicked");
clickPoint = mousEvnt.getPoint();
repaint();
EventQueue.invokeLater(() -> {
String text;
if (isWinner()) {
text = oTurn ? "O has won!" : "X has won!";
removeMouseListener(this);
JOptionPane.showMessageDialog(this,
text,
"Winner",
JOptionPane.PLAIN_MESSAGE);
}
else if (freeCount <= 0) {
text = "Drawn game.";
removeMouseListener(this);
JOptionPane.showMessageDialog(this,
text,
"Draw",
JOptionPane.PLAIN_MESSAGE);
}
else {
oTurn = !oTurn;
text = oTurn ? "O's turn" : "X's turn";
}
turnLabel.setText(text);
});
}
#Override // java.lang.Runnable
public void run() {
createAndShowGui();
}
#Override // javax.swing.JComponent
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawLine(100, 0, 100, 300);
g2d.drawLine(200, 0, 200, 300);
g2d.drawLine(0, 100, 300, 100);
g2d.drawLine(0, 200, 300, 200);
if (!newGame) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(3));
for (int i = 0; i < TOTAL_SQUARES; i++) {
if (occupied[i] == NOUGHT) {
drawNought(i, g2d);
}
else if (occupied[i] == CROSS) {
drawCross(i, g2d);
}
}
int square = getSquare(clickPoint);
if (isFreeSquare(square)) {
if (oTurn) {
drawNought(square, g2d);
occupied[square] = NOUGHT;
}
else {
drawCross(square, g2d);
occupied[square] = CROSS;
}
freeCount--;
}
}
else {
newGame = false;
}
}
private void createAndShowGui() {
JFrame frame = new JFrame("O & X");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createTurnPanel(), BorderLayout.PAGE_START);
frame.add(this, BorderLayout.CENTER);
frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JButton createButton(String text, int mnemonic, String tooltip) {
JButton button = new JButton(text);
if (mnemonic > 0) {
button.setMnemonic(mnemonic);
}
if (tooltip != null && !tooltip.isBlank()) {
button.setToolTipText(tooltip);
}
button.addActionListener(this);
return button;
}
private JPanel createButtonsPanel() {
JPanel buttonsPanel = new JPanel();
buttonsPanel.setBorder(BorderFactory.createMatteBorder(2, 0, 0, 0, Color.GRAY));
buttonsPanel.add(createButton(NEW_GAME, KeyEvent.VK_N, "Start a new game."));
return buttonsPanel;
}
private JPanel createTurnPanel() {
JPanel turnPanel = new JPanel();
turnPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 2, 0, Color.GRAY));
turnLabel = new JLabel(FIRST_MOVE);
turnPanel.add(turnLabel);
return turnPanel;
}
/**
* Draws a {#link #CROSS} in <var>square</var> of tic-tac-toe board.
*
* #param square - index of square in tic-tac-toe board.
* #param g2d - facilitates drawing.
*/
private void drawCross(int square, Graphics2D g2d) {
if (isValidSquare(square) && g2d != null) {
g2d.setColor(Color.BLUE);
g2d.drawLine(SQUARES[square].minX() + 10,
SQUARES[square].minY() + 10,
SQUARES[square].maxX() - 10,
SQUARES[square].maxY() - 10);
g2d.drawLine(SQUARES[square].maxX() - 10,
SQUARES[square].minY() + 10,
SQUARES[square].minX() + 10,
SQUARES[square].maxY() - 10);
}
}
/**
* Draws a {#link #NOUGHT} in <var>square</var> of tic-tac-toe board.
*
* #param square - index of square in tic-tac-toe board.
* #param g2d - facilitates drawing.
*/
private void drawNought(int square, Graphics2D g2d) {
if (isValidSquare(square) && g2d != null) {
g2d.setColor(Color.RED);
g2d.drawOval(SQUARES[square].minX() + 10,
SQUARES[square].minY() + 10,
SQUARE_DIMENSION,
SQUARE_DIMENSION);
}
}
/**
* Returns the square of the tic-tac-toe board that contains <var>pt</var>.
*
* #param pt - point on tic-tac-toe board.
*
* #return index of square in tic-tac-toe board containing <var>pt</var> or -1 (negative one)
* if <var>pt</var> not in tic-tac-toe board.
*
* #see Square#contains(int, int)
*/
private int getSquare(Point pt) {
int ndx = -1;
if (pt != null) {
for (int i = 0; i < 9; i++) {
if (SQUARES[i].contains(pt.x, pt.y)) {
ndx = i;
break;
}
}
}
return ndx;
}
/**
* Determines whether <var>column</var> of tic-tac-toe board contains all {#link #CROSS} or all
* {#link #NOUGHT}.
*
* #param column - index of column in tic-tac-toe board.
*
* #return <tt>true</tt> if <var>column</var> contains all {#link #CROSS} or all {#link #NOUGHT}.
*
* #see #isValidRequirement(int)
*/
private boolean isColumnWin(int column) {
boolean isWin = false;
if (isValidRequirement(column)) {
isWin = isOccupied(column) &&
occupied[column] == occupied[column + REQUIRED_SQUARES] &&
occupied[column] == occupied[column + (REQUIRED_SQUARES * 2)];
}
return isWin;
}
/**
* Determines whether diagonal of tic-tac-toe board contains all {#link #CROSS} or all
* {#link #NOUGHT}. The board contains precisely two diagonals where each one includes the
* board&apos;s center square, i.e. the square with index 4. The other squares that constitute
* diagonals are the corner squares, including the indexes 0 (zero), 2, 6 and 8.
*
* #return <tt>true</tt> if one of the tic-tac-toe board diagonals contains all {#link #CROSS}
* or all {#link #NOUGHT}.
*
* #see #isValidRequirement(int)
*/
private boolean isDiagonalWin() {
boolean isWin = false;
isWin = (isOccupied(0) &&
occupied[0] == occupied[4] &&
occupied[0] == occupied[8])
||
(isOccupied(2) &&
occupied[2] == occupied[4] &&
occupied[2] == occupied[6]);
return isWin;
}
/**
* Determines whether <var>square</var> in tic-tac-toe board does not contain a {#link #CROSS}
* nor a {#link #NOUGHT}.
*
* #param square - index of square in tic-tac-toe board.
*
* #return <tt>true</tt> if <var>square</var> does not contain a {#link #CROSS} nor a {#link
* #NOUGHT}.
*/
private boolean isFreeSquare(int square) {
boolean freeSquare = false;
if (isValidSquare(square)) {
freeSquare = occupied[square] != CROSS && occupied[square] != NOUGHT;
}
return freeSquare;
}
/**
* Determines whether <var>row</var> of tic-tac-toe board contains all {#link #CROSS} or all
* {#link #NOUGHT}.
*
* #param row - index of row in tic-tac-toe board.
*
* #return <tt>true</tt> if <var>row</var> contains all {#link #CROSS} or all {#link #NOUGHT}.
*
* #see #isValidRequirement(int)
*/
private boolean isLineWin(int row) {
boolean isWin = false;
if (isValidRequirement(row)) {
int index = row * REQUIRED_SQUARES;
isWin = isOccupied(index) &&
occupied[index] == occupied[index + 1] &&
occupied[index] == occupied[index + 2];
}
return isWin;
}
/**
* Determines whether square at <var>index</var> in tic-tac-toe board contains either a {#link
* #CROSS} or a {#link #NOUGHT}.
*
* #param index - index of square in tic-tac-toe board.
*
* #return <tt>true</tt> if square at <var>index</var> in tic-tac-toe board contains either a
* {#link #CROSS} or a {#link #NOUGHT}.
*
* #see #isValidSquare(int)
*/
private boolean isOccupied(int index) {
boolean occupied = false;
if (isValidSquare(index)) {
occupied = this.occupied[index] == CROSS || this.occupied[index] == NOUGHT;
}
return occupied;
}
/**
* Determines if there is a winner.
*
* #return <tt>true</tt> if someone has won the game.
*
* #see #isColumnWin(int)
* #see #isDiagonalWin()
* #see #isLineWin(int)
*/
private boolean isWinner() {
return isLineWin(0) ||
isLineWin(1) ||
isLineWin(2) ||
isColumnWin(0) ||
isColumnWin(1) ||
isColumnWin(2) ||
isDiagonalWin();
}
/**
* Initializes the GUI in order to start a new game.
*/
private void startNewGame() {
freeCount = TOTAL_SQUARES;
newGame = true;
oTurn = false;
occupied = new char[TOTAL_SQUARES];
repaint();
EventQueue.invokeLater(() -> {
removeMouseListener(this);
addMouseListener(this);
turnLabel.setText(FIRST_MOVE);
});
}
/**
* This method is the first one called when this class is launched via the <tt>java</tt>
* command. It ignores the method parameter <var>args</var>.
*
* #param args - <tt>java</tt> command arguments.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new GameMain());
}
}
/**
* Represents the geometrical shape known as a square. For the purposes of this {#code record}, a
* square is defined by two points that indicate its top, left corner and its bottom, right corner.
*/
record Square (int minX, int minY, int maxX, int maxY) {
/**
* Determines whether the supplied point lies within this square.
*
* #param x - x coordinate of a point.
* #param y - y coordinate of same point (as x).
*
* #return <tt>true</tt> if supplied point lies within this square.
*/
public boolean contains(int x, int y) {
return minX <= x && x <= maxX && minY <= y && y <= maxY;
}
}

PopUp Class Seems To Speed Up Animations

I have a class called PopUp, which is a JPanel that, when activated, expands to a given size and location based on the parameters given from the center and contracts the same way when clicked.
For some reason, when the PopUp is expanding and contracting, the animations sharing the same JPanel speed up. I've witnessed this on the two programs I've used my PopUp class on.
Here is what I believe the relevant code is:
/**
* The {#code PopUp} class is a JPanel that expands to the rectangle
* created from the given x, y, width and height that expands from
* the center of the rectangle.
* <p>
* Here is an example of how the {#code PopUp} object can be initialized:
* <blockquote><pre>
* PopUp pu = new PopUp(25, 25, 575, 575, 25, Color.GRAY);
* </pre></blockquote>
* <p>
* The class {#code PopUp} includes methods for drawing the pop-up;
* choosing whether the pop-up is expanding or not; getting the
* percentage that the pop-up is expanded; and getting the maximum x, y,
* width, and height
*
* #author Gigi Bayte 2
*/
public class PopUp extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
/**
* Expanded x coordinate
*/
private double x;
/**
* Expanded y coordinate
*/
private double y;
/**
* Expanded width value
*/
private double width;
/**
* Expanded height value
*/
private double height;
/**
* Number of steps until fully expanded
*/
private int steps;
/**
* This divided by steps is the percentage the pop-up is expanded
*/
private int expansionStage = 0;
/**
* Whether or not the pop-up is expansing
*/
private boolean isExpanding = false;
/**
* Color of the pop-up
*/
private Color color;
/**
* The rectangle that represents the bounds of the pop-up
*/
private Rectangle2D popUp;
/**
* Initializes a newly created {#code PopUp} with a uniform color
* #param x The x coordinate of the expanded pop-up
* #param y The y coordinate of the expanded pop-up
* #param w The width of the expanded pop-up
* #param h The height of the expanded pop-up
* #param expansionSteps The number of steps until fully expanded
* #param popUpColor The color of the pop-up
*/
public PopUp(double x, double y, double w, double h, int expansionSteps, Color popUpColor) {
this.x = x;
this.y = y;
width = w;
height = h;
color = popUpColor;
steps = expansionSteps;
popUp = new Rectangle2D.Double(0, 0, width, height);
addMouseListener(this);
}
/**
* Draws the pop-up
* #param g Graphics object from paintComponent
*/
public final void draw(Graphics g) {
if(isExpanding)
expansionStage = Math.min(expansionStage + 1, steps);
else
expansionStage = Math.max(expansionStage - 1, 0);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform trans = new AffineTransform();
trans.translate(x + width/2 * (1 - (double) expansionStage/steps), y + height/2 * (1 - (double) expansionStage/steps));
trans.scale((double) expansionStage/steps, (double) expansionStage/steps);
setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps));
g2d.setColor(color);
Shape transformed = trans.createTransformedShape(popUp);
g2d.fill(transformed);
}
/**
* Sets whether the pop-up is expanding or not
* #param expanding Whether or not the pop-up should expand
*/
public final void setExpanding(boolean expanding) {
isExpanding = expanding;
}
#Override
public final void mouseClicked(MouseEvent e) {
isExpanding = false;
}
}
Here is a test class to run:
public class Test extends JPanel implements ActionListener, MouseListener {
private static final long serialVersionUID = 1L;
private static PopUp popUp;
private int stringX = 610;
private int stringCounter = 0;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(600, 600);
Test t = new Test();
t.setBounds(0, 0, 600, 600);
frame.add(t);
t.setVisible(true);
Timer timer = new Timer(5, t);
popUp = new PopUp(100, 100, 400, 400, 100, Color.WHITE);
frame.add(popUp);
popUp.setVisible(true);
timer.start();
frame.addMouseListener(t);
frame.setLayout(null);
frame.setUndecorated(true);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, 600, 600);
popUp.draw(g);
g.setColor(Color.WHITE);
g.drawString("This is a test", stringX, 580);
if(++stringCounter % 3 == 0) {
--stringX;
stringCounter = 0;
}
if(stringX == -10 - g.getFontMetrics().stringWidth("This is a test"))
stringX = 610;
}
#Override
public void mouseClicked(MouseEvent e) {
popUp.setExpanding(!popUp.getExpanding());
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
}
As can be seen in the above example, the text scrolling from right to left speeds up every time the pop-up is expanding or contracting.
That is the expected behavior when receiving repeated update events that invoke paintComponent(); resize this AnimationTest to reproduce the effect.
Why exactly do the repeated update events cause this? What's the logic behind it?
Each call to setBounds() in your draw() method "invalidates the component hierarchy." The Component API ensures that
When the hierarchy gets invalidated, like after changing the bounds of components, or adding/removing components to/from containers, the whole hierarchy must be validated afterwards by means of the Container.validate() method invoked on the top-most invalid container of the hierarchy.
Because the validate() method "may be a quite time-consuming operation," you can "postpone the validation of the hierarchy till a set of layout-related operations completes," as you show here; or you can pace the animation with a javax.swing.Timer, illustrated here.
Well, I found the problem. The culprit was the line here:
setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps));
Apparently scaling a JPanel to different sizes in rapid succession causes some speed warping for reasons I do not know. I'd appreciate an edit to this answer with a better explanation for this phenomenon.
I just set a static size for the JPanel and had the graphics do the rest of the work.

Error in NewJFrame with ActionListener

I am completely new to Java and I am learning how to write a program that detects and reacts to collision based on a professor's lecture video. Here is a link to the video.
All of my code should be similar to what is in his lecture. My error appears to be in the NewJFrame.java file. Why is ActionListener not working? Thanks in advance for the help.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
import java.awt.event.ActionListener;
import javafx.event.ActionEvent;
import javax.swing.Timer;
/**
*
* #author PC
*/
public class NewJFrame extends javax.swing.JFrame {
/**
* Creates new form NewJFrame
*/
public NewJFrame() {
initComponents();
bf = new BallField(getWidth(), getHeight());
add(bf);
pack();
njTimer = new Timer(1,
new ActionListener() {
public void actionPerformed(ActionEvent e) {
bf.detectCollision();
}
});
njTimer.start();
}
BallField bf;
Timer njTimer;
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
pack();
}// </editor-fold>
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
// Variables declaration - do not modify
// End of variables declaration
}
The line saying "new ActionListener() {" is underlined and I get an error message saying:
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - is not abstract and does not override abstract method actionPerformed(java.awt.event.ActionEvent) in java.awt.event.ActionListener
at collisiondetection.NewJFrame.(NewJFrame.java:28)
at collisiondetection.Main.main(Main.java:20)
NewJFrame.java:28 refers to the line that says "njTimer = new Timer(1," in the code above.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is my code for the other .java files for reference.
Main file:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
/**
*
* #author PC
*/
public class Main {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
NewJFrame njf = new NewJFrame();
njf.setVisible(true);
}
}
BallField.java:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.LinkedList;
import java.util.ListIterator;
import javax.swing.JPanel;
/**
*
* #author PC
*/
public class BallField extends JPanel {
public BallField(int width, int height)
{
setSize(new Dimension(width, height));
setMinimumSize(new Dimension(width, height));
setMaximumSize(new Dimension(width, height));
bfList = new LinkedList<Shape>();
fillList();
}
public void detectCollision()
{
if(bfList.size() == 0)
{
fillList();
}
bfBall.move();
ListIterator iter = bfList.listIterator();
boolean collision = false;
while ((collision == false && iter.hasNext()))
{
Shape sh = (Shape) iter.next();
if(sh.collide(bfBall))
{
iter.remove();
collision = true;
}
}
}
public void PaintComponent(Graphics gfx)
{
int bWidth = getWidth();
int bHeight = getHeight();
gfx.setColor(bfBackground);
gfx.fillRect(0, 0, bWidth, bHeight);
ListIterator iter = bfList.listIterator();
while(iter.hasNext())
{
Shape sh = (Shape) iter.next();
sh.drawShape(gfx);
}
bfBall.drawBall(gfx);
}
private void fillList() {
int bWidth = getWidth();
int bHeight = getHeight();
int size = Math.min(bWidth, bHeight);
size -= Math.max(bfNumRow, bfNumCol) * brickGap;
size = size / (Math.max(bfNumRow, bfNumCol) + bfEmptyRow);
// add more margin
Shape.setSize(size);
if (bfBall == null) {
bfBall = new MovingBall(size, bWidth, bHeight);
} else {
bfBall.reset();
}
for (int rowCnt = 0; rowCnt < bfNumRow; rowCnt++) {
int xloc = bWidth / 2 - (bfNumRow / 2 - rowCnt) * (size + brickGap);
for (int colCnt = 0; colCnt < bfNumCol; colCnt++) {
double rand = Math.random();
Float cR = new Float(Math.random());
Float cG = new Float(Math.random());
Float cB = new Float(Math.random());
Color bc = new Color(cR.floatValue(), cG = cG.floatValue(), cB.floatValue());
int yloc = bHeight / 2 - (bfNumCol / 2 - colCnt) * (size + brickGap);
if (rand > .5) {
Circle cb = new Circle(xloc, yloc, bc);
bfList.add(cb);
} else {
Square sb = new Square(xloc, yloc, bc);
bfList.add(sb);
}
}
}
}
LinkedList<Shape> bfList;
MovingBall bfBall;
static private final Color bfBackground = Color.white;
static private final int bfNumRow = 6;
static private final int bfNumCol = 6;
static private final int bfEmptyRow = 4;
static private final int brickGap = 4;
}
Vector2D.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
/**
*
* #author PC
*/
public class Vector2D {
public Vector2D(double x, double y, boolean u)
{
xVal = x;
yVal = y;
unit = u;
if(unit)
{
makeUnit();
}
}
public void add(Vector2D vec)
{
xVal += vec.xVal;
yVal += vec.yVal;
if(unit)
{
makeUnit();
}
}
public double getX()
{
return xVal;
}
public double getY()
{
return yVal;
}
public void reflect(Vector2D nor)
{
if((unit == false) || (nor.unit == false))
{
System.out.println("ERROR, not unit vector");
}
double ip = innerProduct(nor);
xVal += -2 * ip * nor.xVal;
yVal += -2 * ip * nor.yVal;
makeUnit();
}
private double innerProduct(Vector2D vec)
{
return (xVal * vec.xVal + yVal * vec.yVal);
}
private void makeUnit()
{
double mag = xVal * xVal + yVal * yVal;
if(mag == 0)
{
System.out.println("ERROR, zero vector");
}
else
{
mag = Math.sqrt(mag);
xVal /= mag;
yVal /= mag;
}
}
private double xVal;
private double yVal;
private boolean unit;
}
MovingBall.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
/**
*
* #author PC
*/
public class MovingBall {
public MovingBall(int sz, int bw, int bh)
{
mbSize = sz;
bWidth = bw;
bHeight = bh;
reset();
}
public Rectangle getRect()
{
return (new Rectangle((int) Math.round(mbLoc.getX() - mbSize/2), (int) Math.round(mbLoc.getY() - mbSize/2), mbSize, mbSize));
}
public void reset()
{
mbLoc = new Vector2D(mbSize, mbSize, false);
mbVel = new Vector2D(Math.random() + .1, Math.random() + .1, true);
}
public double getX()
{
return mbLoc.getX();
}
public double getY()
{
return mbLoc.getY();
}
public int getSize()
{
return mbSize;
}
public void reflect(Vector2D nor)
{
mbVel.reflect(nor);
}
public void move()
{
mbLoc.add(mbVel);
// hit a wall?
if(mbLoc.getX() >= (bWidth - mbSize/2))
{
// hit right wall
Vector2D nor = new Vector2D(-1, 0, true);
reflect(nor);
}
if(mbLoc.getY() >= (bHeight - mbSize/2))
{
Vector2D nor = new Vector2D(0, -1, true);
reflect(nor);
}
}
public void drawBall(Graphics gfx)
{
int x = (int) Math.round(mbLoc.getX() - mbSize/2);
int y = (int) Math.round(mbLoc.getY() - mbSize/2);
gfx.setColor(bColor);
gfx.fillOval(x, y, mbSize, mbSize);
}
private Vector2D mbLoc;
private Vector2D mbVel;
private int mbSize;
private Color bColor = Color.black;
int bWidth;
int bHeight;
}
Shape.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
import java.awt.Color;
import java.awt.Graphics;
/**
*
* #author PC
*/
abstract public class Shape {
public Shape(int x, int y, Color c)
{
s_X = x;
s_Y = y;
s_Color = c;
}
public static void setSize(int sz)
{
s_Size = sz;
}
abstract public boolean collide(MovingBall ball);
abstract public void drawShape(Graphics gfx);
protected int s_X;
protected int s_Y;
protected Color s_Color;
protected static int s_Size;
}
Square.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
import static collisiondetection.Shape.s_Size;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
/**
*
* #author PC
*/
public class Square extends Shape {
public Square(int x, int y, Color c)
{
super(x, y, c);
}
public boolean collide(MovingBall ball)
{
Rectangle r1 = new Rectangle(s_X - s_Size / 2, s_Y - s_Size, s_Size, s_Size);
Rectangle r2 = ball.getRect();
Rectangle r3 = r1.intersection(r2);
if (r3.isEmpty())
{
// no collision
// note thatr3 is not null
return false;
}
if (r3.getWidth() < r3.getHeight())
{
// hit horizontally
if (ball.getX() < s_X) {
// hit the left side
Vector2D nor = new Vector2D(-1, 0, true);
ball.reflect(nor);
} else {
Vector2D nor = new Vector2D(1, 0, true);
ball.reflect(nor);
}
} else {
if (ball.getY() < s_Y) {
// hit the top
Vector2D nor = new Vector2D(0, -1, true);
ball.reflect(nor);
} else {
Vector2D nor = new Vector2D(0, 1, true);
ball.reflect(nor);
}
}
return true;
}
public void drawShape(Graphics gfx)
{
gfx.setColor(s_Color);
gfx.fillRect(s_X - s_Size/2, s_Y - s_Size/2, s_Size, s_Size);
}
}
Cirlce.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package collisiondetection;
import java.awt.Color;
import java.awt.Graphics;
/**
*
* #author PC
*/
public class Circle extends Shape{
public Circle(int x, int y, Color c)
{
super(x, y, c);
}
public boolean collide(MovingBall ball)
{
double deltaX = ball.getX() - s_X;
double deltaY = ball.getY() - s_Y;
double centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if(centerDistance * 2 > s_Size + ball.getSize())
{
// no collision
// size is the diameter, not radius
return false;
}
Vector2D nor = new Vector2D(deltaX, deltaY, true);
ball.reflect(nor);
return true;
}
public void drawShape(Graphics gfx)
{
gfx.setColor(s_Color);
gfx.fillOval(s_X - s_Size/2, s_Y - s_Size/2, s_Size, s_Size);
}
}
You're importing the wrong ActionEvent.
import javafx.event.ActionEvent;
should be
import java.awt.event.ActionEvent;
... and welcome to this site, and thanks for providing the offending code, the error message, and the line that causes the error. I predict that you will go far with your coding.

Drawing curved line arrow on a bar graph using affine transformation

I am trying to draw a curved Line arrow on a stacked bar graph.I have been able to draw the curved line and arrow.But i am not able to connect the arrow to the end of the curved line.I am using affine transformation to draw the curved line.The below link describes the curved line and arrow that i have been able to draw http://i58.tinypic.com/2m422hy.png.Can anyone guide me as to how to connect the arrow to the end of the curved line.
Here is the code
package Stack;
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author OSPL-B4
/
/
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.jfree.chart.annotations.CategoryAnnotation;
import org.jfree.chart.axis.CategoryAnchor;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.AnnotationChangeListener;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
//import java.awt.Font;
/**
* A line annotation that can be placed on a
* {#link org.jfree.chart.plot.CategoryPlot}.
*/
public class CategoryLineAnnotation_demo1 implements CategoryAnnotation,
Cloneable, Serializable {
/** The category for the start of the line. */
private Comparable category1;
/** The value for the start of the line. */
private double value1;
/** The category for the end of the line. */
private Comparable category2;
/** The value for the end of the line. */
private double value2;
private final int ARR_SIZE = 4;
/** The line color. */
private transient Paint paint = Color.black;
/** The line stroke. */
private transient Stroke stroke = new BasicStroke(1.0f);
/**
* Creates a new annotation that draws a line between (category1, value1)
* and (category2, value2).
*
* #param category1 the category (<code>null</code> not permitted).
* #param value1 the value.
* #param category2 the category (<code>null</code> not permitted).
* #param value2 the value.
*/
public CategoryLineAnnotation_demo1(Comparable category1, double value1,
Comparable category2, double value2,
Paint paint, Stroke stroke) {
if (category1 == null) {
throw new IllegalArgumentException("Null 'category1' argument.");
}
if (category2 == null) {
throw new IllegalArgumentException("Null 'category2' argument.");
}
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
if (stroke == null) {
throw new IllegalArgumentException("Null 'stroke' argument.");
}
this.category1 = category1;
System.out.println("First Category value is "+category1);
this.value1 = value1;
this.category2 = category2;
System.out.println("Second Category value is "+category2);
this.value2 = value2;
this.paint = paint;
this.stroke = stroke;
}
/**
* Returns the category for the start of the line.
*
* #return The category for the start of the line (never <code>null</code>).
*/
public Comparable getCategory1() {
return this.category1;
}
/**
* Sets the category for the start of the line.
*
* #param category the category (<code>null</code> not permitted).
*/
public void setCategory1(Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.category1 = category;
}
/**
* Returns the y-value for the start of the line.
*
* #return The y-value for the start of the line.
*/
public double getValue1() {
return this.value1;
}
/**
* Sets the y-value for the start of the line.
*
* #param value the value.
*/
public void setValue1(double value) {
this.value1 = value;
}
/**
* Returns the category for the end of the line.
*
* #return The category for the end of the line (never <code>null</code>).
*/
public Comparable getCategory2() {
return this.category2;
}
/**
* Sets the category for the end of the line.
*
* #param category the category (<code>null</code> not permitted).
*/
public void setCategory2(Comparable category) {
if (category == null) {
throw new IllegalArgumentException("Null 'category' argument.");
}
this.category2 = category;
}
/**
* Returns the y-value for the end of the line.
*
* #return The y-value for the end of the line.
*/
public double getValue2() {
return this.value2;
}
/**
* Sets the y-value for the end of the line.
*
* #param value the value.
*/
public void setValue2(double value) {
this.value2 = value;
}
/**
* Returns the paint used to draw the connecting line.
*
* #return The paint (never <code>null</code>).
*/
public Paint getPaint() {
return this.paint;
}
/**
* Sets the paint used to draw the connecting line.
*
* #param paint the paint (<code>null</code> not permitted).
*/
public void setPaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.paint = paint;
}
/**
* Returns the stroke used to draw the connecting line.
*
* #return The stroke (never <code>null</code>).
*/
public Stroke getStroke() {
// System.out.println("In Stacked bar Stroke is "+getStroke());
return this.stroke;
}
/**
* Sets the stroke used to draw the connecting line.
*
* #param stroke the stroke (<code>null</code> not permitted).
*/
public void setStroke(Stroke stroke) {
if (stroke == null) {
throw new IllegalArgumentException("Null 'stroke' argument.");
}
this.stroke = stroke;
}
/**
* Draws the annotation.
*
* #param g2 the graphics device.
* #param plot the plot.
* #param dataArea the data area.
* #param domainAxis the domain axis.
* #param rangeAxis the range axis.
*/
public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
CategoryAxis domainAxis, ValueAxis rangeAxis) {
CategoryDataset dataset = plot.getDataset();
int catIndex1 = dataset.getColumnIndex(this.category1);
int catIndex2 = dataset.getColumnIndex(this.category2);
int catCount = dataset.getColumnCount();
double lineX1 = 0.0f;
double lineY1 = 0.0f;
double lineX2 = 0.0f;
double lineY2 = 0.0f;
PlotOrientation orientation = plot.getOrientation();
RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
plot.getDomainAxisLocation(), orientation);
RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
plot.getRangeAxisLocation(), orientation);
if (orientation == PlotOrientation.HORIZONTAL) {
lineY1 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
domainEdge);
lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
lineY2 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
domainEdge);
lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
}
else if (orientation == PlotOrientation.VERTICAL) {
lineX1 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
domainEdge);
lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
lineX2 = domainAxis.getCategoryJava2DCoordinate(
CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
domainEdge);
lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
}
g2.setPaint(this.paint);
g2.setStroke(this.stroke);
drawArrow(g2,(int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
}
void drawArrow(Graphics g1, int x1, int y1, int x2, int y2) {
Graphics2D g = (Graphics2D) g1.create();
double dx = x2 - x1, dy = y2 - y1;
System.out.println("Value of DX "+dx);
System.out.println("Value of DY "+dy);
double angle = Math.atan2(dy, dx);
System.out.println("Getting angle "+angle);
int len = (int) Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.concatenate(AffineTransform.getRotateInstance(angle));
g.transform(at);
System.out.println("Affine transform X co-ordinate value is "+at.getScaleX());
System.out.println("Affine transform Y co-ordinate value is "+at.getScaleY());
float center1=(x1+x2)/2-40;
float center2= (y1+y2)/2-40;
QuadCurve2D q=new QuadCurve2D.Float(0,0,center1,center2,x2,y2);
g.draw(q);
g.setColor(Color.RED);
System.out.println("Length of arrow is "+len);
System.out.println("Get Start point 2D "+q.getP1());
System.out.println("Get End point 2D "+q.getP2());
g.fillPolygon(new int[] {len, len-ARR_SIZE, len-ARR_SIZE-10, len-60},
new int[] {0, -ARR_SIZE, ARR_SIZE-20, 5}, 4);
}
public void paintComponent(Graphics g) {
for (int x = 15; x < 200; x += 16)
drawArrow(g, x, x, x, 150);
drawArrow(g, 30, 300, 300, 190);
}
/**
* Tests this object for equality with another.
*
* #param obj the object (<code>null</code> permitted).
*
* #return <code>true</code> or <code>false</code>.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CategoryLineAnnotation_demo1)) {
return false;
}
CategoryLineAnnotation_demo1 that = (CategoryLineAnnotation_demo1) obj;
if (!this.category1.equals(that.getCategory1())) {
return false;
}
if (this.value1 != that.getValue1()) {
return false;
}
if (!this.category2.equals(that.getCategory2())) {
return false;
}
if (this.value2 != that.getValue2()) {
return false;
}
if (!PaintUtilities.equal(this.paint, that.paint)) {
return false;
}
if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
return false;
}
return true;
}
/**
* Returns a hash code for this instance.
*
* #return A hash code.
*/
public int hashCode() {
// TODO: this needs work
return this.category1.hashCode() + this.category2.hashCode();
}
/**
* Returns a clone of the annotation.
*
* #return A clone.
*
* #throws CloneNotSupportedException this class will not throw this
* exception, but subclasses (if any) might.
*/
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* Provides serialization support.
*
* #param stream the output stream.
*
* #throws IOException if there is an I/O error.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
SerialUtilities.writePaint(this.paint, stream);
SerialUtilities.writeStroke(this.stroke, stream);
}
/**
* Provides serialization support.
*
* #param stream the input stream.
*
* #throws IOException if there is an I/O error.
* #throws ClassNotFoundException if there is a classpath problem.
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
this.paint = SerialUtilities.readPaint(stream);
this.stroke = SerialUtilities.readStroke(stream);
}
#Override
public void addChangeListener(AnnotationChangeListener al) {
}
#Override
public void removeChangeListener(AnnotationChangeListener al) {
}
}
You can create the arrow head based on the last line segment (which might already be transformed using an AffineTransform)
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ArrowPainter
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new ArrowPaintPanel();
f.getContentPane().add(panel);
f.setSize(500,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ArrowPaintPanel extends JPanel implements MouseMotionListener
{
private Point2D startPoint = null;
private Point2D endPoint = null;
ArrowPaintPanel()
{
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
if (startPoint == null)
{
startPoint = new Point(getWidth()/2, getHeight()/2);
}
if (endPoint == null)
{
return;
}
Line2D line = new Line2D.Double(startPoint, endPoint);
Shape arrowHead = createArrowHead(line, 30, 20);
g.draw(line);
g.fill(arrowHead);
}
#Override
public void mouseDragged(MouseEvent e)
{
endPoint = e.getPoint();
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
endPoint = e.getPoint();
repaint();
}
private static Shape createArrowHead(Line2D line, double length, double width)
{
Point2D p0 = line.getP1();
Point2D p1 = line.getP2();
double x0 = p0.getX();
double y0 = p0.getY();
double x1 = p1.getX();
double y1 = p1.getY();
double dx = x1 - x0;
double dy = y1 - y0;
double invLength = 1.0 / Math.sqrt(dx*dx+dy*dy);
double dirX = dx * invLength;
double dirY = dy * invLength;
double ax = x1 - length * dirX;
double ay = y1 - length * dirY;
double offsetX = width * -dirY * 0.5;
double offsetY = width * dirX * 0.5;
double c0x = ax + offsetX;
double c0y = ay + offsetY;
double c1x = ax - offsetX;
double c1y = ay - offsetY;
Path2D arrowHead = new Path2D.Double();
arrowHead.moveTo(x1, y1);
arrowHead.lineTo(c0x, c0y);
arrowHead.lineTo(c1x, c1y);
arrowHead.closePath();
return arrowHead;
}
}
EDIT: Update for the above EDIT and the comments: That's a lot of code, but still nothing that can be tested easily. What happens when you replace your line
drawArrow(g2,(int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
with
g.fill(createArrowHead(new Line2D.Double(lineX1, lineY1, lineX2, lineY2), 30, 20));
?

Java call to create new Area doesn't create shape?

I was working on an issue where I was getting a cast problem trying to cast from Shape to Area (see previous post cast exception question). Now it seems that my shape that is create is not getting created correctly. Instead of posting all of my source code here I am attaching a link to all the source files here.
Essentially I create the shape as follows with a standard call of
YingYang shape = new YingYang();
shape = shape.moveTo(x, y);
shape = shape.scaleBy(size);
shape.setColor(getNextColor());
and the calls to the Area Class are:
public YingYang()
{
Area mainCircle = new Area(new Ellipse2D.Double(...)
...
yingYang.add(mainCircle);
}
The MoveTo call:
public YingYang moveTo(double x, double y)
{
at.translate(x, y);
at.setToTranslation(x, y);
yingYang.transform(at);
return new YingYang(at.createTransformedShape(yingYang));
}
The ScaleBy:
public YingYang scaleBy(double scale)
{
double cx = this.getBounds2D().getCenterX();
double cy = this.getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.scale(scale, scale);
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(yingYang));
}
When I call the paintComponent() in my drawing panel:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for(YingYang s : shapes)
{
System.out.println(s.getBounds2D());
g2.setColor(s.getColor());
g2.fill(s);
}
}
The print statement prints out:
java.awt.geom.Rectangle2D$Double[x=0.0,y=0.0,w=0.0,h=0.0]
I'm at a loss... Any Ideas?
It looks like you have combined both my recommendations into one piece of code. If you are going to use your variable yingYang then you should implement the shape on the class. However if you are going to extend the area you need to remove the yingYang variable and use the class as the area eg: yingYang.add(mainCircle); becomes add(mainCircle);... essentially remove all references of the yingYang variable.
So instead of the "yingYang" variable you are using "this". heres is a modified version of your YingYang class with the references removed.
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class YingYang extends Area
{
AffineTransform at = new AffineTransform();
private boolean movingRight = true;
private boolean movingUp = true;
private Color color = Color.BLACK;
private int dx = 10, dy = 10;
public YingYang(Shape shape)
{
super(shape);
}
public YingYang()
{
// Construct the Outer Circle & Lower Dot
Area mainCircle = new Area(new Ellipse2D.Double(-210, -210, 420, 420));
Area lowerDot = new Area(new Ellipse2D.Double(-10, 90, 40, 40));
mainCircle.subtract(lowerDot);
// Begin Construction of the whit side of symbol
Area whiteSide = new Area(new Ellipse2D.Double(-200, -200, 400, 400));
Area rect = new Area(new Rectangle2D.Double(0, -200, 200, 400));
whiteSide.subtract(rect);
// Construct the upper white Circle
Area upperCircle = new Area(new Ellipse2D.Double(-100, -200, 200, 200));
whiteSide.add(upperCircle);
// Construct the Upper Dot
Area upperDot = new Area(new Ellipse2D.Double(-10, -110, 40, 40));
whiteSide.subtract(upperDot);
// Remove the lower circle portion
Area lowerCircle = new Area(new Ellipse2D.Double(-100, 0, 200, 200));
whiteSide.subtract(lowerCircle);
// Add Main Circle
add(mainCircle);
// Subtract the white side
subtract(whiteSide);
}
//------------------------ Methods -----------------------------------------
/**
* Sets this shapes color
* (must call getColor before drawing this shape)
* #param color
*/
public void setColor(Color color)
{
this.color = color;
}
/**
* Gets this shapes current color
* #return color
*/
public Color getColor()
{
return this.color;
}
/**
* Determines if the shape is moving left to right
* #return - boolean
*/
public boolean isMovingRight()
{
return movingRight;
}
/**
* Determines if the shape is moving from down to up
* #return - boolean
*/
public boolean isMovingUp()
{
return movingUp;
}
/**
* Changes the Horizontal Path that this shape is traveling
*/
public void changeHorizonalMovement()
{
if(isMovingRight())
{
movingRight = false;
}
else
{
movingRight = true;
}
}
/**
* Changes the Vertical Path that this shape is traveling
*/
public void changeVerticalMovement()
{
if(isMovingUp())
{
movingUp = false;
}
else
{
movingUp = true;
}
}
/**
* Sets the direction of the Horizontal Path of this shape
* true = left to right : false = right to left
* #param dir - boolean
*/
public void setHorizonalMovement(boolean dir)
{
this.movingRight = dir;
}
/**
* Sets the direction of the Vertical Path of this shape
* true = down to up : false = up to down
* #param dir - boolean
*/
public void setVerticalMovement(boolean dir){
this.movingUp = dir;
}
/**
* Moves the current shape by the amount x,y
* #param x - double
* #param y - double
*/
public YingYang moveTo(double x, double y)
{
at.translate(x, y);
at.setToTranslation(x, y);
transform(at);
return new YingYang(at.createTransformedShape(this));
}
/**
* Rotate this shape
* #param theta - amount to rotate shape by
* #return
*/
public YingYang rotate(double theta)
{
double cx = getBounds2D().getCenterX();
double cy = getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.rotate(Math.toRadians(theta));
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
public YingYang moveToAndRotate(double x, double y, double theta)
{
double cx = getBounds2D().getCenterX();
double cy = getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.translate(x, y);
at.rotate(Math.toRadians(theta));
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
/**
* Scales this shape uniformly by the amount of scale
* about the origin
* #param scale - double
*/
public YingYang scaleBy(double scale)
{
double cx = this.getBounds2D().getCenterX();
double cy = this.getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.scale(scale, scale);
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
/**
* Rotates this shape theta degrees about the origin
*/
public YingYang rotate(Double theta)
{
double cx = this.getBounds2D().getCenterX();
double cy = this.getBounds2D().getCenterY();
at.translate(cx, cy);
at.setToTranslation(cx, cy);
at.rotate(Math.toRadians(theta));
at.translate(-cx, -cy);
return new YingYang(at.createTransformedShape(this));
}
public int getDx()
{
return this.dx;
}
public void setDx(int x)
{
this.dx = x;
}
public int getDy()
{
return this.dy;
}
public void setDy(int y)
{
this.dy = y;
}
}

Categories

Resources