Hello I wonder if anyone can help me , I have declared 2 values x1, y1 both as 120 and trying to use these in the following method:
private void drawDot(int x, int y, Graphics2D twoD) {
twoD.fillOval(x-50, y-50, 100, 100);
}
However when I use drawDot(120,120,twoD) it paints the filled oval in the wrong place compared to when I manually use
twoD.fillOval(70,70,100,100);
Shouldn't these 2 statements do the exact same thing? Is there something I am missing? I have added an image to show the issue, the oval on the left is the oval drawn by my drawDot method and the oval on the right is the oval in the correct place as it should be. If anyone has any advice it would be greatly appreciated. Thanks
click this link to see how both ovals are drawn
the entire class:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class DieFace extends JPanel {
int x1,y1 = 120;
int x2,y2 = 300;
int x3,y3 = 480;
private BufferedImage bufferedImage;
public DieFace() {
this.init();
this.frameInit();
updateVal(1);
}
private void init() {
this.setPreferredSize(new Dimension(600,600));
}
private void frameInit() {
JFrame window = new JFrame("Dice Simulation");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setContentPane(this);
window.pack();
window.setVisible(true);
window.setLocationRelativeTo(null);
}
public void paintComponent(Graphics g) {
Graphics2D twoD = (Graphics2D) g;
twoD.drawImage(bufferedImage,0,0,null);
}
private void drawDot(int x, int y, Graphics2D twoD) {
twoD.fillOval(x-50, y-50, 100, 100);
}
public void updateVal(int dieRoll) {
bufferedImage = new BufferedImage(600,600,BufferedImage.TYPE_INT_ARGB);
Graphics2D twoD = bufferedImage.createGraphics();
twoD.setColor(Color.WHITE);
twoD.fillRect(0, 0, 600, 600);
twoD.setColor(Color.BLACK);
if(dieRoll==1) {
drawDot(x1,y1,twoD);
twoD.fillOval(70, 70, 100, 100);
}
repaint();
}
}
Shouldn't these 2 statements do the exact same thing? Is there something I am missing?
Because you made a mistake in your variable initializations on x1, y1:
int x1,y1 = 120; //This means x1 = 0, y1 = 120
What you actually wanted is:
int x1 = 120, y1 = 120;
Since x1 is not 120, when you invoke
drawDot(x1,y1,twoD);
drawDot(0, 120, twoD); is being invoked
Hence both elipses will appear on the same y-axis, but different on the x-axis.
Related
I am new to Java programming and I am trying to rotate an image using the following code but nothing seems to be working, I searched a lot online but nothing helped. I saw people doing it using BufferedImage but don't want to use that. This code is rotating entire 2d object instead of just image which i want to rotate. I found out this by displaying rectangle as images were not aligned on top of each other. Thanks for your help.
package package3;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rotate extends JPanel {
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 1000);
frame.setVisible(true);
}
class MyDrawPanel extends JPanel{
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Image image = new ImageIcon(
getClass()
.getResource("wheel.png"))
.getImage();
g2d.drawImage(image, 0, 0, 500, 500, this);
int x = image.getHeight(this);
int y = image.getWidth(this);
g2d.rotate(1, x/2, y/2);
g2d.setBackground(Color.black);
g2d.drawImage(image, 0, 0, 500, 500, this);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
}
}
}
here's what output looks like
First of all, when rotating an image with a Graphics context, the rotation will occur at a "anchor" point (the top/left is default position if I recall).
So, in order to rotate an image around it's center, you need to set the anchor point to the center of the image WITHIN the context of it's container.
This would mean that the rotate call should be something like...
g2d.rotate(radians, xOffset + (image.getWidth() / 2), yOffset + (image.getHeight() / 2));
Then when you draw the image at xOffset/yOffset, the image will "appear" rotated around the anchor point (or the center of the image).
Second, transformations are compounding. That is, when you transform a graphics context, all subsequent paint operations will be transformed. If you then transform it again, the new transformation will be add to the old one (so if you rotated by 45 degrees and then rotate again by 45 degrees, the transformation would now be 90 degrees).
It's typically "easiest" to create a "copy" of the Graphics state first, apply your transformations and paint operations and then dispose of the copy, which will leave the original context in it's original (transformed) state (all painting operations applied), this way you don't to spend time trying to figure out how to undo the mess
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new MyDrawPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
class MyDrawPanel extends JPanel {
private BufferedImage image;
public MyDrawPanel() throws IOException {
image = ImageIO.read(getClass().getResource("/images/MegaTokyo.png"));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (image == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
drawImageTopLeft(g2d);
drawImageBottomRight(g2d);
drawImageMiddle(g2d);
g2d.rotate(Math.toRadians(45), getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, this.getWidth(), this.getHeight());
g2d.dispose();
}
protected void drawImageTopLeft(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = 0;
int y = 0;
g2d.rotate(Math.toRadians(135), image.getWidth() / 2, image.getHeight() / 2);
g2d.drawImage(image, x, y, this);
g2d.dispose();
}
protected void drawImageBottomRight(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = (getWidth() - image.getWidth());
int y = (getHeight() - image.getHeight());
g2d.rotate(Math.toRadians(-45), getWidth() - (image.getWidth() / 2), getHeight() - (image.getHeight() / 2));
g2d.drawImage(image, x, y, this);
g2d.dispose();
}
protected void drawImageMiddle(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = (getWidth() - image.getWidth()) / 2;
int y = (getHeight() - image.getHeight()) / 2;
g2d.rotate(Math.toRadians(45), getWidth() / 2, getHeight() / 2);
g2d.drawImage(image, x, y, this);
g2d.dispose();
}
}
}
i am trying to do a roulette casino game, so for this i made my roulette using the Arc2D package.
My code below
package roulette;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.AffineTransform;
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RouletteInterface extends JPanel{
public int spinValue = 0;
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
paintRoulette(g2d);
}
public void paintRoulette(Graphics2D g2d) {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
AffineTransform at = AffineTransform.getTranslateInstance(10, 10);
at.rotate(spinValue, 10, 10);
double angle = 360 / 36.9;
double startAngle = 0;
int color = 0;
for(int i = 0; i < 37; i++) {
if(i == 0) {
g2d.setColor(Color.GREEN);
} else {
if(color == 0) {
g2d.setColor(Color.BLACK);
color = 1;
} else {
g2d.setColor(Color.RED);
color = 0;
}
}
g2d.fill(new Arc2D.Double(100, 100, 300, 300, startAngle, angle, Arc2D.PIE));
startAngle += angle;
}
g2d.transform(at);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
timer.start();
}
}
In short i am not using a generalpath because i want to fill each arc with color red/green or black like the original roulette, and for the rotation i tried using a timer to increase the spinValue (this worked for me but when i use a generalpath) for the AfinneTransformation, but when i run the code, well, nothing happens. It shows me only the roulette without animation. What can i do?
Thanks in advance.
Painting and graphics in general are quite advanced topics, Java/Swing does a good job to "commonalise" the APIs into something which is reasonable easy to use, but still takes time and effort to learn and understand fully.
I would highly recommend having Performing Custom Painting, Painting in AWT and Swing and 2D Graphics and the JavaDocs booked marked, as you will be coming back to them on a regular bases (I still do)
There are lots of issues, which are compounding to make your life difficult.
Starting with...
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
paintRoulette(g2d);
}
You should favour overriding paintComponent instead of paint, paint is a complicated process and you need to choose your entry point into carefully. Also, you should always call the paint methods super method, unless you are absolutely, positively prepared to take over its core functionality yourself.
In your case, you should also be making a copy of the Graphics context before passing it to paintRoulette, as Graphics is a shared resource and the transformations you are applying will cause issues for anything which is painted after your component.
Transformations...
AffineTransform at = AffineTransform.getTranslateInstance(10, 10);
at.rotate(spinValue, 10, 10);
This is somewhat interesting. You're creating translation of 10x10 which will move the origin point of the Graphics context. You then apply a rotation, which is anchored to 10x10.
The reason I mention it is because you then do...
g2d.fill(new Arc2D.Double(100, 100, 300, 300, startAngle, angle, Arc2D.PIE));
This means that the arc is offset by 110x110 from the corner of the component (add in your translation) and you'll be rotating about a point 20x20 from the component's top/left corner (add in your translation) ... this is weird to me because the centre of the of wheel is actually at 250x250 (from the component's top/left corner) which is going to make for one very weird affect.
Finally, you apply the transformation AFTER the painting is done AND then create a Timer inside the paint method...
Painting is done in serial. So one operation will effect the next, this will mean you will need to apply the transformation BEFORE you paint something (that you want transformed)
You also need to understand that you don't control the paint process, this means that your component may be painted for any number of reason at any time without your interaction. This means you could an infinite number of Timers, over a very small period of time.
Instead, your timer should be controlled externally from the paint process.
One other thing that took me some time to work out is...
public int spinValue = 0;
//...
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
You declare spinValue as int, but are adding a floating point value to it, this will have the effect of the decimal component been truncated, so the value will ALWAYS be 0.
Also, AffineTransform#rotate expects angles to be in radians, not degrees. Not sure if it's important, but you should be aware of it.
Runnable example...
Okay, so after applying the above, the code "might" look something like...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new RoulettePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RoulettePane extends JPanel {
private double spinValue = 0;
private Timer timer;
public RoulettePane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
spin();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintRoulette(g2d);
g2d.dispose();
}
protected void spin() {
if (timer != null && timer.isRunning()) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
timer.start();
}
protected void paintRoulette(Graphics2D g2d) {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
int width = getWidth();
int height = getHeight();
int dimeter = Math.min(width, height);
AffineTransform at = AffineTransform.getRotateInstance(spinValue, dimeter / 2, dimeter / 2);
g2d.transform(at);
double angle = 360 / 36.9;
double startAngle = 0;
int color = 0;
for (int i = 0; i < 37; i++) {
if (i == 0) {
g2d.setColor(Color.GREEN);
} else {
if (color == 0) {
g2d.setColor(Color.BLACK);
color = 1;
} else {
g2d.setColor(Color.RED);
color = 0;
}
}
g2d.fill(new Arc2D.Double(0, 0, dimeter, dimeter, startAngle, angle, Arc2D.PIE));
startAngle += angle;
}
}
}
}
nb: I took the translation out for the time been as I wanted to focus on making the output more dynamic based on the actual width/height of the component
For an assignment we were asked to modify existing code to include more than one shape, more than one color, and to have the shapes move. I got a little carried away and started adding in more things but now we have an issue. All was going well until I added the left arm(as viewed on the screen). I am having a problem with a piece of the arm remaining in the previous place when moving the figure to the right. Up, down, and left are all working well, it is only to the right where I hit the problem. I have added a screenshot of what it looks like after I move the figures to the right two times. Thanks for any assistance you may be able to provide.
roboMan output
import javax.swing.JFrame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.Color;
import java.awt.Polygon;
import java.util.Scanner;
import java.awt.geom.Ellipse2D;
public class SwingBot1 {
public static void main(String[] args) {
// contruction of new JFrame object
JFrame frame = new JFrame();
// mutators
frame.setSize(400,400);
frame.setTitle("SwingBot");
// program ends when window closes
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Robot r = new Robot();
frame.add(r);
// voila!
frame.setVisible(true);
// your Scanner-based command loop goes here
Scanner in = new Scanner(System.in);
boolean active = true;
while(active){
System.out.println("Enter \"up\", \"down\", \"left\", or \"right\" "
+ "to move.\nClose the window to quit.");
String direction = in.nextLine();
switch(direction){
case "up":
r.moveBot(0, -20);
break;
case "down":
r.moveBot(0, 20);
break;
case "right":
r.moveBot(20, 0);
break;
case "left":
r.moveBot(-20, 0);
break;
}
}
}
public static class Robot extends JComponent
{
private Rectangle rect = new Rectangle(45, 30, 20, 20);
int x = 30;
int y = 50;
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
// set the color
g2.setColor(Color.RED);
// draw the shape`
g2.fill(rect);
Graphics2D g3 = (Graphics2D) g;
g3.setColor(Color.BLUE);
g3.draw(new Ellipse2D.Double(30, 50, 50, 100));
g3.fillOval(30, 50, 50, 100);
Graphics2D g4 = (Graphics2D) g;
g4.setColor(Color.GREEN);
g4.draw(new Line2D.Double(70, 141, 90, 190));
g4.draw(new Line2D.Double(39, 141, 30, 191));
Graphics2D g5 = (Graphics2D) g;
g5.setColor(Color.RED);
g5.draw(new Line2D.Double(80, 85, 125, 70));
//arm, left as viewed
g5.draw(new Line2D.Double(0, 70, 30, 85));
}
public void moveBot(int x, int y)
{
setX(getX()+x);
setY(getY()+y);
repaint();
}
public int getX()
{
return x;
}
public void setX(int x)
{
this.x = x;
}
public int getY()
{
return y;
}
public void setY(int y)
{
this.y = y;
}
/* public void moveBot(int x, int y)
{
// move the rectangle
rect.translate(x,y);
// redraw the window
poly.translate(x, y);
repaint();
}*/
}
}
It looks like you need to repaint the frame.
I am currently experimenting with the Graphics2D and the KeyListener, and I am currently testing out rotating objects (in this case a pentagon). Now, it's working in the sense that it rotates, except it is supposed to rotate by 1 radian each time VK_RIGHT or VK_LEFT are pressed.
However, currently it does that for the first key press only. From then on, it creates a pattern of rotating it by 1, 2, 3, 4, 5... and so on radians each time (the nth keypress rotates it by nth radians) instead of just 1 radian per keypress.
Creating the JFrame:
import javax.swing.JFrame;
public class Main {
public Main() {
JFrame window = new JFrame("Rotating Hexagons");
window.setSize(800,600);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.setContentPane(new RotatingHexagon());
window.pack();
window.setVisible(true);
}
public static void main(String[]args) {
new Main();
}
}
RotatingHexagon Class:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class RotatingHexagon extends JPanel implements KeyListener {
private Polygon poly;
private int[] xpoints = { 0, -10, -7, 7, 10 };
private int[] ypoints = { -10, -2, 10, 10, -2 };
private int rotation = 0;
public RotatingHexagon() {
setPreferredSize(new Dimension(800,600));
setFocusable(true);
requestFocus();
}
public void init() {
poly = new Polygon(xpoints, ypoints, xpoints.length);
addKeyListener(this);
}
public void paint(Graphics g) {
init();
Graphics2D g2d = (Graphics2D) g;
int width = getSize().width;
int height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.WHITE);
g2d.drawString(rotation + " radians", 10, 20);
g2d.translate(width / 2, height / 2);
g2d.scale(20, 20);
g2d.rotate(Math.toRadians(rotation));
g2d.setColor(new Color(255, 100, 100));
g2d.fill(poly);
g2d.setColor(Color.WHITE);
g2d.draw(poly);
}
public void keyPressed(KeyEvent k) {
switch(k.getKeyCode()) {
case KeyEvent.VK_LEFT:
rotation--;
if (rotation < 0) rotation = 359;
repaint();
break;
case KeyEvent.VK_RIGHT:
rotation++;
if (rotation > 360) rotation = 0;
repaint();
break;
}
}
public void keyReleased(KeyEvent k) {}
public void keyTyped(KeyEvent k) {}
}
I really don't have any idea why it isn't just rotating 1 radian each time, so any help is appreciated.
Thanks.
the reason is calling the init() function in the paint() method again and again. So the KeyListener is added multiple times, causing that it is called multiple times, incrementing your counter more every time you press the key.
Move it to the constructor:
public RotatingHexagon() {
setPreferredSize(new Dimension(800,600));
setFocusable(true);
requestFocus();
addKeyListener(this);
}
public void init() {
poly = new Polygon(xpoints, ypoints, xpoints.length);
}
Andy
You should probally just use a persistant AffineTransform to do the rotation. They are a lot more powerfull.
I also saw several issues in your code, you are calling the init method each frame - this could be 60 times per second. In this you are adding a new keylistener each frame. You are also creating a new polygon which would slow down performance.
I've made some changes to your code and and i've used AffineTransforms as an example. Have a look and see if this helps.
package com.joey.testing;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class AffineTransformTest extends JPanel implements KeyListener {
private Polygon poly;
private int[] xpoints = { 0, -10, -7, 7, 10 };
private int[] ypoints = { -10, -2, 10, 10, -2 };
private int rotation = 0;
AffineTransform transform;
AffineTransform rotationTransform;
AffineTransform translateTransform;
public AffineTransformTest() {
setPreferredSize(new Dimension(800,600));
setFocusable(true);
requestFocus();
//Do Init here - no point in creating new polygon each frame.
//It also adds the key listener each time
init();
updateTransforms();
}
public void init() {
poly = new Polygon(xpoints, ypoints, xpoints.length);
transform = new AffineTransform();
rotationTransform = new AffineTransform();
translateTransform = new AffineTransform();
addKeyListener(this);
}
//Use Paint Compoent
#Override
public void paintComponent(Graphics g) {
//Always call super to clear the screen
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getSize().width;
int height = getSize().height;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, width, height);
g2d.setColor(Color.WHITE);
g2d.drawString(rotation + " radians", 10, 20);
//Store old transform so we can apply it
AffineTransform old = g2d.getTransform();
//Add Transform and move polygon
g2d.setTransform(transform);
g2d.setColor(new Color(255, 100, 100));
g2d.fill(poly);
g2d.setColor(Color.WHITE);
g2d.draw(poly);
g2d.setTransform(old);
}
public void keyPressed(KeyEvent k) {
switch(k.getKeyCode()) {
case KeyEvent.VK_LEFT:
rotation--;
if (rotation < 0) rotation = 359;
repaint();
break;
case KeyEvent.VK_RIGHT:
rotation++;
if (rotation > 360) rotation = 0;
repaint();
break;
}
updateTransforms();
}
public void updateTransforms(){
//Resets transform to rotation
rotationTransform.setToRotation(Math.toRadians(rotation));
translateTransform.setToTranslation(getWidth()/2, getHeight()/2);
//Chain the transforms (Note order matters)
transform.setToIdentity();
transform.concatenate(translateTransform);
transform.concatenate(rotationTransform);
}
public void keyReleased(KeyEvent k) {}
public void keyTyped(KeyEvent k) {}
public static void main(String input[]){
JFrame f= new JFrame("AffineTransform");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(100, 100);
f.setResizable(true);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(new AffineTransformTest());
f.show();
}
}
I'm working on a simple little swing component, and I'm tearing out my hair trying to figure out why my paint method isn't working.
The idea behind this component is that it's a small JPanel with a label. The background (behind the label) is supposed to be white, with a colored rectangle on the left-hand side indicating the ratio of two measurements: "actual" and "expected".
If you had a bunch of these components aligned vertically, they'd form a bar chart, composed of horizontal bars.
This kind of thing should be super-simple.
Anyhow, here's the code:
package com.mycompany.view;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class BarGraphPanel extends JPanel {
private static final Color BACKGROUND = Color.WHITE;
private static final Color FOREGROUND = Color.BLACK;
private static final Color BORDER_COLOR = new Color(229, 172, 0);
private static final Color BAR_GRAPH_COLOR = new Color(255, 255, 165);
private int actual = 0;
private int expected = 1;
private JLabel label;
public BarGraphPanel() {
super();
label = new JLabel();
label.setOpaque(false);
label.setForeground(FOREGROUND);
super.add(label);
super.setOpaque(true);
}
public void setActualAndExpected(int actual, int expected) {
this.actual = actual;
this.expected = expected;
}
#Override
public void paint(Graphics g) {
double proportion = (expected == 0) ? 0 : ((double) actual) / expected;
Rectangle bounds = super.getBounds();
g.setColor(BACKGROUND);
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
g.setColor(BAR_GRAPH_COLOR);
g.fillRect(bounds.x, bounds.y, (int) (bounds.width * proportion), bounds.height);
g.setColor(BORDER_COLOR);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
label.setText(String.format("%s of %s (%.1f%%)", actual, expected, proportion * 100));
super.paint(g);
g.dispose();
}
}
And here's the simple test harness:
package com.mycompany.view;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
public class MyFrame extends JFrame {
public MyFrame() {
super();
super.setLayout(new GridLayout(3, 1));
super.setPreferredSize(new Dimension(300, 200));
BarGraphPanel a = new BarGraphPanel();
BarGraphPanel b = new BarGraphPanel();
BarGraphPanel c = new BarGraphPanel();
a.setActualAndExpected(75, 100);
b.setActualAndExpected(85, 200);
c.setActualAndExpected(20, 300);
super.add(a);
super.add(b);
super.add(c);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Throwable t) { }
MyFrame frame = new MyFrame();
frame.pack();
frame.setVisible(true);
}
}
The test harness creates a simple frame and then adds three of these controls.
The labels are all rendered correctly, which indicates to me that the paint() method is actually being called, but the rectangles aren't being drawn to the Graphics object.
What am I doing wrong?
And why does Swing programming suck so much?
Here's my final code. Thanks, everyone, for your help!
public void paintComponent(Graphics g) {
double proportion = (expected == 0) ? 0 : ((double) actual) / expected;
Rectangle bounds = super.getBounds();
g.setColor(BACKGROUND);
g.fillRect(0, 0, bounds.width, bounds.height);
g.setColor(BAR_GRAPH_COLOR);
g.fillRect(0, 0, (int) (bounds.width * proportion), bounds.height);
g.setColor(BORDER_COLOR);
g.drawRect(0, 0, bounds.width - 1, bounds.height - 1);
FontMetrics metrics = g.getFontMetrics();
String label = String.format("%s of %s (%.1f%%)", actual, expected, proportion * 100);
Rectangle2D textBounds = metrics.getStringBounds(label, g);
g.setColor(FOREGROUND);
g.drawString(label, 5, (int) ((bounds.height + textBounds.getHeight()) / 2));
}
I think you've almost answered your own question in the comments, along with David's answer. Change paint(Graphics g) to paintComponent(Graphics g) and remove the last two lines of the method, and you should be fine.
EDIT: Oddly, this only works for the first bar of the three. More testing in progress...
By the way, you have an off-by-one error in the border-painting code. It should be:
g.setColor(BORDER_COLOR);
g.drawRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
EDIT2: OK, got it. Your full paintComponent method should be as follows:
#Override
public void paintComponent(Graphics g) {
double proportion = (expected == 0) ? 0 : ((double) actual) / expected;
Rectangle bounds = super.getBounds();
g.setColor(BACKGROUND);
g.fillRect(0, 0, bounds.width, bounds.height);
g.setColor(BAR_GRAPH_COLOR);
g.fillRect(0, 0, (int) (bounds.width * proportion), bounds.height);
g.setColor(BORDER_COLOR);
g.drawRect(0, 0, bounds.width-1, bounds.height-1);
label.setText(String.format("%s of %s (%.1f%%)", actual, expected, proportion * 100));
}
Note that the coordinates given to g.fillRect() and g.drawRect() are relative to the component, so they must start at (0,0).
And no, I can't help you with your last question, though I feel your pain. :)
In your JPanel, you called super.setOpaque(true). The JPanel is going to completely fill in the background when you call super.paint() and overwrite your retangles.
Not sure if this is the source of your problem, but in Swing you're supposed to override paintComponent(Graphics2D) instead...
If anything, I think you should be calling super.paint(g); at the TOP of your method, not at the very bottom. It's possible the superclass is drawing on top of your stuff.