JLabel duplicates itself, if window gets resized - java

I'm programming a simple diagram, with what you can display some points on x, y axes.
public class GraphPlotter extends JPanel {
private static final long serialVersionUID = 1L;
/** Default frame size X for frame in pixels */
private final int DEFAULT_FRAME_SIZE_X = 800;
/** Default frame size Y for frame in pixels */
private final int DEFAULT_FRAME_SIZE_Y = 600;
/** Padding to Frame */
private final int PAD = 30;
/** Radius of dot */
private final int DOT_RADIUS = 3;
/** Padding of label */
private final int LABEL_PAD = 10;
/** Height of label */
private final int LABEL_HEIGHT = 10;
/** Width of label */
private final int LABEL_WIDTH = 100;
/** Max value for x to print */
private int maxValueForX;
/** Scale factor depending to y*/
private int maxValueForY;
/** Label for the x axis */
private String labelForX = "time";
/** Label for the y axis */
private String labelForY;
/**
* List with points to draw. It holds the y coordinates of points. x
* coordinates are spaced
*/
private List<Integer> dataPoints = new ArrayList<>();
/**
*
* Constructor of this class
*
*/
public GraphPlotter(ArrayList<Integer> dataPoints, String labelForY) {
this.dataPoints = dataPoints;
this.maxValueForX = dataPoints.size();
this.maxValueForY = Collections.max(dataPoints);
this.labelForY = labelForY;
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(this);
f.setSize(this.DEFAULT_FRAME_SIZE_X + PAD, this.DEFAULT_FRAME_SIZE_Y + PAD);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
/** method that draws the points and lines between the points */
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth();
int h = getHeight();
// add labels
JLabel jLabelY = new JLabel(labelForY);
JLabel jLabelX = new JLabel(labelForX);
jLabelY.setSize(LABEL_WIDTH, LABEL_HEIGHT);
jLabelY.setLocation(LABEL_PAD, LABEL_PAD);
add(jLabelY);
jLabelX.setSize(LABEL_WIDTH, LABEL_HEIGHT);
jLabelX.setLocation(w - LABEL_WIDTH - LABEL_PAD, h - LABEL_HEIGHT - LABEL_PAD);
jLabelX.setHorizontalAlignment(SwingConstants.RIGHT);
add(jLabelX);
// add axis
g2.drawLine(PAD, PAD, PAD, h - PAD);
g2.drawLine(PAD, h - PAD, w - PAD, h - PAD);
double xScale = (w - 2 * PAD) / (dataPoints.size() + 1);
double yScale = (h - 2 * PAD) / this.maxValueForY;
int x0 = PAD;
int y0 = h - PAD;
// draw the points as small circles
g2.setPaint(Color.blue);
for (int i = 0; i < dataPoints.size(); i++) {
int x = x0 + (int) (xScale * (i + 1));
int y = y0 - (int) (yScale * dataPoints.get(i));
g2.fillOval(x - DOT_RADIUS, y - DOT_RADIUS, 2 * DOT_RADIUS,
2 * DOT_RADIUS);
}
}
/** Size of List */
private int getSizeOfDataPoints() {
return dataPoints.size();
}
/** Deletes last DataPoint in List */
private void deleteLastDataPoint() {
dataPoints.remove(0);
}
/**
* Ad point and repaint graph.
*
* #param point to draw (y-coord)
*/
public void add(int point) {
if (getSizeOfDataPoints() > this.maxValueForX) {
deleteLastDataPoint();
}
dataPoints.add(point);
this.repaint();
}
public static void main(String[] args) {
ArrayList<Integer> scores = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 50; i++) {
scores.add(random.nextInt(10));
}
String labelForY = "throughput";
GraphPlotter graphPlotter = new GraphPlotter(scores, labelForY);
graphPlotter.add(5);
}
The labels are displayed at start of the application. If I resize the window of the application, the labels will be displayed all over the window. (see screenshots)
before resizing
after resizing
How can I avoid the dublication of the labels?
I assume, that the program is adding the same labels in the panel, after the window is repainted. If I write
add(jLabelY);
repaint();
in the paintComponent() method, this error occures at startup of the application.
I also tried to put the panel into a FlowLayout, but didn't make any changes.

paintComponent() can be called at almost arbitrary times by Swing (whenever the component needs to be redrawn; e.g. on resize). Therefore, it should usually be side-effect free. However, you use paintComponent to add() labels as children of your panel, which you never remove. Therefore, each time your panel gets redrawn, two labels get added to its children. super.paintComponent() will then paint them all.
One solution to this would be to keep the two labels as fields of your panel and only update their location in paintComponent (before calling super.paintComponent())

Related

mouse event not working with GCompound objects

I am learning Java following class CS106A and am having issues that GCompound object is not interacting with mouse events.
I noticed the issue while doing programming exercises in "the Art and Science of Java" and couldn't figure out the reason. So i tried to copy the sample code of the book and tried to run it in eclipse, and they didn't work either.
Below is GFace class defined in the book that extends GCompound.
import acm.graphics.*;
public class GFace extends GCompound{
/** Constants specifying feature size as a fraction of the head size*/
private static final double EYE_WIDTH = 0.15;
private static final double EYE_HEIGHT = 0.15;
private static final double NOSE_WIDTH = 0.15;
private static final double NOSE_HEIGHT = 0.10;
private static final double MOUTH_WIDTH = 0.50;
private static final double MOUTH_HEIGHT = 0.03;
/** Construct a new GFace object with the specified dimensions. */
public GFace (double width, double height){
head = new GOval (width, height);
leftEye = new GOval (EYE_WIDTH * width, EYE_HEIGHT * height);
rightEye = new GOval (EYE_WIDTH * width, EYE_HEIGHT * height);
nose = createNose (NOSE_WIDTH * width, NOSE_HEIGHT * height);
mouth = new GRect(MOUTH_WIDTH * width, MOUTH_HEIGHT * height);
add(head, 0, 0);
add(leftEye, 0.25 * width - EYE_WIDTH * width / 2,
0.25 * height - EYE_HEIGHT * height / 2);
add(rightEye, 0.75 * width - EYE_WIDTH * width / 2,
0.25 * height - EYE_HEIGHT * height / 2);
add(nose, 0.50 * width, 0.50 * height);
add(mouth, 0.50 * width - MOUTH_WIDTH * width / 2,
0.75 * height - MOUTH_HEIGHT * height / 2);
}
/** creates a triangle for the nose*/
private GPolygon createNose(double width, double height){
GPolygon poly = new GPolygon();
poly.addVertex(0, -height/2);
poly.addVertex(width / 2, height / 2);
poly.addVertex(-width / 2, height / 2);
return poly;
}
/*instance variables*/
private GOval head;
private GOval leftEye, rightEye;
private GPolygon nose;
private GRect mouth;
}
and then below program is supposed to create a GFace object on Canvas so that user can drag it around with mouse. But, when i ran the program, the GFace object on canvas does not respond to my mouse, while GRect object can ne dragged on canvas. I've also added a rectangle to see if by clicking on the GFace, the program returns null, as suggested in the comment(thanks for the advice!), and it does. It just seems the GCompound object cannot be selected.
import acm.graphics.*;
import acm.program.*;
import java.awt.event.*;
import java.awt.*;
import acm.util.*;
public class DragFace extends GraphicsProgram{
/** Width of the face */
private static final double FACE_WIDTH = 200;
/** Height of the face */
private static final double FACE_HEIGHT = 300;
/** Runs the program */
public void run(){
/*added a filled rectangle to the screen.
* if no object selected and returns null,
* rectangle changes color randomly*/
add(temp);
temp.setFillColor(Color.RED);
temp.setFilled(true);
GFace face = new GFace(FACE_WIDTH, FACE_HEIGHT);
double x1 = (getWidth() - FACE_WIDTH) / 2;
double y1 = (getHeight() - FACE_HEIGHT) / 2;
add(face, x1, y1);
GObject rect = new GRect(FACE_WIDTH, FACE_HEIGHT);
double x = (getWidth() - FACE_WIDTH) / 2;
double y = (getHeight() - FACE_HEIGHT) / 2;
add(rect, x, y);
rect.setColor(Color.RED);
addMouseListeners();
}
/** called on mouse press to record the coordinates of the click */
public void mousePressed(MouseEvent e){
lastX = e.getX();
lastY = e.getY();
gobj = getElementAt(lastX, lastY);
/* if no object selected and returns null,
* rectangle changes color randomly each time */
if(gobj == null){
temp.setFillColor(rgen.nextColor());
}
}
/** called on mouse drag to reposition the object*/
public void mouseDragged(MouseEvent e){
if(gobj != null){
gobj.move(e.getX() - lastX, e.getY() - lastY);
lastX = e.getX();
lastY = e.getY();
}
}
/** called on mouse click to move this object to the front*/
public void mouseClicked(MouseEvent e){
if(gobj != null) gobj.sendToFront();
}
/*private state*/
private GObject gobj; /*the object being dragged*/
private double lastX; /*the last mouse X position*/
private double lastY; /*the last mouse Y position*/
private GRect temp = new GRect(0,0,100,100);/* private rectangle*/
private RandomGenerator rgen = new RandomGenerator();
}
MY question is
does GCompound object works as a unit like a GRect? If so, can can anyone point out the cause of the issue in the code above? I don't know if it's my software that's causing the issue, or if I am missing something with the use of GCompound class.
the Gface is supposed to be centered on Canvas, but somehow it shows up on the button right corner. The x- and y- coordinate of the Gface is always twice of the coordinate specified in the add call. Any idea how this might have happened?
Any help will be greatly appreciated.

How do i iterate through my ArrayList of objects to create a condition?

I am trying to have my last IF loop to iterate through the three objects within
the PinballTypeOne list, only these three objects inside the ArrayList should change their former color to orange once they've hit the left wall. Collision does work, but objects keep bouncing off without being changed to orange so i think i might have a problem with either how the ArrayList was made or how i'm attempting to iterate through the ArrayList. No compilation errors.
public class PinballObject
{
private int currentXLocation;
private int currentYLocation;
private int speedXTravel;
private int speedYTravel;
private Color colour;
private int radius;
private Machine machine;
private final int leftWallPosition;
private final int bottomWallPosition;
private final int topWallPosition;
private final int rightWallPosition;
private ArrayList<PinballObject> PinballTypeOneList = new ArrayList<PinballObject>();
/**
* Constructor for objects of class Pinball_Obj
*
* #param xPos the horizontal coordinate of the object
* #param yPos the vertical coordinate of the object
* #param xVel the horizontal speed of the object
* #param yVel the vertical speed of the object
* #param objectRadius the radius (in pixels) of the object
* #param objectColor the color of the object
* #param theMachine the machine this object is in
*/
public PinballObject(int xPos, int yPos, int xVel, int yVel, Color objectColor, int objectRadius, Machine theMachine)
{
currentXLocation = xPos;
currentYLocation = yPos;
speedXTravel = xVel;
speedYTravel = yVel;
colour = objectColor;
radius = objectRadius;
machine = theMachine;
leftWallPosition = machine.getLeftWall();
bottomWallPosition = machine.getBottomWall();
topWallPosition = machine.getTopWall();
rightWallPosition = machine.getRightWall();
}
public void makeType1()
{
PinballObject firstTypeOne = new PinballObject(50, 200, -5, 3, Color.RED, 10, machine);
PinballTypeOneList.add(firstTypeOne);
PinballObject secondTypeOne = new PinballObject(100, 300, 1, 2, Color.BLUE, 55, machine);
PinballTypeOneList.add(secondTypeOne);
PinballObject thirdTypeOne = new PinballObject(450, 125, -1, -1, Color.YELLOW, 40, machine);
PinballTypeOneList.add(thirdTypeOne);
}
/**
* Move this object according to its position and speed and redraw.
**/
public void move()
{
// remove from universe at the current position
machine.erase(this);
// compute new position
currentYLocation += speedYTravel;
currentXLocation += speedXTravel;
// check if it has hit the leftwall + change color to orange only for the objects IN the arraylist
if(currentXLocation <= (leftWallPosition + radius))
{
currentXLocation = leftWallPosition + radius;
speedXTravel = -speedXTravel;
if(this.equals(PinballTypeOneList)){
colour = Color.ORANGE;
}
// draw again at new position
machine.draw(this);
}
}
For your function move you should do so:
public void move()
{
machine.erase(this);
currentYLocation += speedYTravel;
currentXLocation += speedXTravel;
if(currentXLocation <= (leftWallPosition + radius))
{
currentXLocation = leftWallPosition + radius;
speedXTravel = -speedXTravel;
for(PinballObject po : PinballTypeOneList) { //Iterating through list of PinballObject's
if(this.equals(po)) { // If this PinballObject is in list
colour = Color.ORANGE; // Or use po.colour
}
}
machine.draw(this);
}
}
and you need to override equals and hashCode methods because you using your own defined class(PinballObject), see this question.
And i have question about your PinballTypeOneList why it is in PinballObject class? This means every PinballObject has it, this sounds bad... You need to make PinballTypeOneList global and then store in it your PinballObjects, or you have particular reason for doing so?
After this I believe your code should work.(though i didn't see most code).

How can I edit JPanel Graphics in one class?

So I'm trying to program snake on a JFrame and doing all graphical stuff (moving the 'snake', random food generation, etc.) on a JPanel. I'm in the beginning stages so all I'm trying to do right now is move a black square around on my frame using arrow keys. My while loop in the Panel class won't get interrupted by a key press in the Snake class, so is there a way to edit JPanel graphics from the same class with all my other code?
Here's all the code. My Panel class at the bottom follows the template I found here.
public class Snake {
// panel width and height
static int pW;
static int pH;
static int x = 10;
static int y = 10;
static int k;
static JFrame frame = new JFrame("SNAKE");
// getters for panel class
public int getPW() { return pW; }
public int getPH() { return pH; }
public int getX() { return x; }
public int getY() { return y; }
public static void main(String[] args) {
// get screen dimensions
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int sH = (int) screenSize.getHeight();
int sW = (int) screenSize.getWidth();
pW = (int) sW/2;
pH = (int) sH/2;
// initialize frame
frame.setSize (pW/1,pH/1);
frame.setLocation(pW/2,pH/2);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent e) {
k = e.getKeyCode();
switch(k) {
case 38: /* y -= square size */ break; // up
case 40: /* y += square size */ break; // down
case 37: /* x -= square size */ break; // left
case 39: /* x += square size */ break; // right
case 27: System.exit(0);
}
}
});
Panel panel = new Panel();
frame.add(panel);
frame.setVisible(true);
}
}
class Panel extends JPanel {
Snake snake = new Snake();
//square size and separation between squares
int sep = 0;
int size = 50;
// initial location of square on the panel/frame
int x = sep + size;
int y = sep + size;
// holding values to check if x or y have changed
int xH = x;
int yH = x;
public void paint(Graphics g) {
int pW = snake.getPW();
int pH = snake.getPH();
int i; int o;
Color on = Color.BLACK;
Color off = Color.GRAY;
// gray background
g.setColor(Color.GRAY);
g.fillRect(0,0,pW,pH);
// black square initialization
g.setColor(Color.BLACK);
g.fillRect(x, y, size, size);
/* this loop is supposed to check if the black
* rectangle has moved by repeatedly grabbing x & y
* values from the Snake class. When a key is pressed
* and the values change, a gray rectangle is placed at the old location
* and a black one is placed at the new location.
*
* When I run the program, I get stuck in this while loop.
* If I had the while loop in the same class I check for keys,
* I don't think I would have this problem
*/
while(true) {
x = snake.getX();
y = snake.getY();
if(x != xH || y != yH) {
g.setColor(off);
g.fillRect(xH, yH, size, size);
g.setColor(on);
g.fillRect(snake.getX(), snake.getY(), size, size);
xH = x;
yH = y;
}}
}
}
You should never have a while(true) loop in a painting method. This will just cause an infinite loop and your GUI will not be able to respond to events.
Instead you need to add methods to your snake class to move the snake. So when one of the arrow keys is pressed you update the starting position of the snake. Then the method will invoke repaint() and the snake will repaint itself when the paintComponent() method is invoked by Swing.
So your painting code should override paintComponent() not paint() and you should invoke super.paintComponent(g) as the first statement in the method.
Don't call your custom class "Panel", there is an AWT class with that name. Make your class name more descriptive.

Draw series of concentric circles with random settings

I'm really stuck on how to go about programming this. Need to draw a series of 8 concentric circles using Java drawArc method with following conditions
using import java.util.Random library
Provide for starting the drawings at random location (i.e., the x-y
cooridinate must be calculated randomly).
Provide a random color for each circle
Provide a random diameter for each circle
My current code is able to get random random color for each circle but not clear how to meet other random conditions
// Exercise 12.6 Solution: CirclesJPanel.java
// This program draws concentric circles
import java.awt.Graphics;
import javax.swing.JPanel;
public class ConcentricCircles extends JPanel
{
// draw eight Circles separated by 10 pixels
public void paintComponent( Graphics g )
{
Random random = new Random();
super.paintComponent( g );
// create 8 concentric circles
for ( int topLeft = 0; topLeft < 80; topLeft += 10 )
{
int radius = 160 - ( topLeft * 2 );
int r = random.nextInt(255);
int gr = random.nextInt(255);
int b = random.nextInt(255);
Color c = new Color(r,gr,b);
g.setColor(c);
g.drawArc( topLeft + 10, topLeft + 25, radius, radius, 0, 360 );
} // end for
}
}
// This program draws concentric circles
import javax.swing.JFrame;
public class ConcentricCirclesTest extends JFrame {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame frame=new JFrame("Concentric Circles");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ConcentricCircles cCirclesJPanel = new ConcentricCircles();
frame.add(cCirclesJPanel);
frame.setSize(200,250);
frame.setVisible(true);
}//end main
}
Several points are key to such an exercise:
Start with constants for the number of circles and step size; the random number generator in particular only needs to be created once.
private static final int N = 8;
private static final int S = 32;
private static Random random = new Random();
Choose a random point whose coordinates fall within the drawing area.
// a random point inset by S
int x = random.nextInt(getWidth() - S * 2) + S;
int y = random.nextInt(getHeight() - S * 2) + S;
For each circle, find the diameter as a function of S, adding a random fraction of a step, and render the arc at the chosen point offset by the radius.
for (int i = 0; i < N; i++) {
g2d.setColor(…);
int d = (i + 1) * S + random.nextInt(S / 2);
int r = d / 2;
g2d.drawArc(x - r, y - r, d, d, 0, 360);
}
Resize the enclosing frame, which forces a repaint(), to see the effect.
As random colors are not always appealing, consider Collections.shuffle() on a List<Color>.
private final List<Color> clut = new ArrayList<Color>();
…
for (int i = 0; i < N; i++) {
clut.add(Color.getHSBColor((float) i / N, 1, 1));
}
…
Collections.shuffle(clut);
…
g2d.setColor(clut.get(i));
Override getPreferredSize() to establsh the drawing panel's size.
private static final int W = S * 12;
private static final int H = W;
…
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
See also Initial Threads.

Animated line graphs with primitive line graphic functions Java

In the effort to learn more about applets and Java, I am experimenting making wave applets by drawing lines (drawLine) and making animated line graphs.
I can make a static graph just fine. However I am struggling with the animated aspect of the graph: the axes of the graph should move from left to right, increasing and growing larger than 0.
My problem is translating my needs into a solution. Can anyone give me any pointers with my problem?
I have a multidimensional array indexed by points containing the x and y of a particular point. I have tried modifying my render function to decrease the Xs to make it appear as if it is moving left but this doesn't work right.
What approach am I looking to take? How different will my approach be if the values of Y could change due to user action or added data?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
/**
* A graph that should in the future represent a wave according to my inputs
*
* #authorImprofane
* #version 1
*/
public class Graph extends JFrame
{
private InnerGraph inner;
private int width;
private Random RNG;
private int height;
private int[][] series;
private int xoffset;
int prevx = 0;
int prevy = 0;
/**
* Constructor for objects of class Graph
*/
public Graph(int width, int height) throws InterruptedException
{
RNG = new Random();
setTitle(" Graph");
series = new int[width][2];
this.width = width;
this.height = height;
inner = new InnerGraph(width, height);
add(inner, BorderLayout.CENTER);
setVisible(true);
inner.preparePaint();
pack();
updateGraph();
}
public static void main(String[] args) throws InterruptedException
{
new Graph(300, 300);
}
public void updateGraph() throws InterruptedException
{
// virtual x is how far along on the x axis we are, I ignore the 'real' X axis
int vx = 0;
int point = 0;
int xdecay = 0;
int inc = 5;
// how many times we need to plot a point
int points = (int) java.lang.Math.floor( width / inc );
System.out.println(points);
inner.preparePaint();
// draw all points to graph
// make some junk data, a saw like graph
for (vx = 0 ; vx < points ; vx++) {
series[vx] = new int[] { vx*inc, ( (vx*inc) % 120 ) };
}
Thread.sleep(150);
int n = 5;
while(n > 0) {
System.out.println(xdecay);
inner.preparePaint();
for (vx = 0 ; vx < points ; vx++) {
inner.updateSeries(vx, xdecay);
inner.repaint();
Thread.sleep(50);
}
xdecay += inc;
// shift the data points to the left
int[][] nseries = new int[points][2];
// System.arraycopy(series, 1, nseries, 0, points-1);
n--;
}
}
public class InnerGraph extends JPanel
{
private Graphics g;
private Image img;
private int gwidth;
private int gheight;
Dimension size;
public InnerGraph(int width, int height)
{
gwidth = width;
gheight = height;
size = new Dimension(1, 1);
}
/**
* Try make panel the requested size.
*/
public Dimension getPreferredSize()
{
return new Dimension(gwidth, gheight);
}
/**
* Create an image and graphics context
*
*/
public void preparePaint()
{
size = getSize();
img = inner.createImage( (size.width | gwidth), (size.height | gheight) );
g = img.getGraphics();
}
/**
* Draw a point to the chart given the point to use and the decay.
* Yes this is bad coding style until I work out the mathematics needed
* to do what I want.
*/
public void updateSeries(int point, int decay)
{
g.setColor(Color.blue);
int nx = series[point][0];
series[point][0] -= decay;
if ( point-1 >= 0 ) {
series[point-1][0] -= decay;
}
int ny = series[point][1];
prevx -= decay;
g.drawLine(prevx-decay, prevy, nx-decay, ny );
prevx = nx-decay;
prevy = ny;
}
public void paintComponent(Graphics g)
{
g.drawImage(img, 0, 0, null);
}
}
}
First, it looks like you are subtracting the decay too often in the updateSeries method.
Below is a new version of that method.
public void updateSeries(int point, int decay)
{
g.setColor(Color.blue);
int nx = series[point][0];
series[point][0] -= decay;
if ( point-1 >= 0 ) {
series[point-1][0] -= decay;
}
int ny = series[point][1];
// prevx -= decay;
// g.drawLine(prevx-decay, prevy, nx-decay, ny );
g.drawLine(prevx, prevy, nx-decay, ny );
prevx = nx-decay;
prevy = ny;
}
With those changes, the lines draw better, but they draw so slowly that the animation is hard to see. You probably need to build a new graphics and swap the whole thing so that you don't have to watch each individual line segment being drawn.
For a starter you could check out the following example (and the ones related to this)
Scroll Chart #java2s.com

Categories