Rescale a drawing in Java (JFrame) - java

I need the simplest way to rescale a drawing in java (for example a rectangle...). I found a way to "stretch" them:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.Dimension;
public class Stretch extends JFrame {
int originalHeight = 600;
int originalWidth = 600;
public Stretch() {
super("Stretch");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(originalWidth, originalHeight);
}
public static void main(String[] args) {
Stretch s = new Stretch();
s.setVisible(true);
}
public void paint(Graphics g) {
Dimension size = this.getBounds().getSize();
int rectWidth = 100;
int rectHeight = 130;
g.setColor(Color.white);
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.black);
g.drawRect(100, 100, rectWidth + size.width - originalWidth, rectHeight + size.height - originalHeight);
}
}
As you can see the formula is like that:
g.drawRect(100, 100, rectWidth + size.width - originalWidth, rectHeight + size.height - originalHeight);
Now how can I rescale the drawing? Width and height have to maintain the same proportion.
Thank you.

There are bascially two different approaches for this:
You can "scale the whole Graphics"
You can scale the actual shapes
The difference may be subtle in some cases, but can be important: When you scale the whole Graphics, then everything will be scaled. Particularly, when you scale it about 2.0, and then draw a line with a width of 1.0, the line will be drawn 2 pixels wide. Whether or not this is desired depends on the application case.
In order to scale the actual shapes, you can not use the drawRect method. Instead you will have to create Shape instances that represent the geometric shapes. You can then create scaled versions of these shapes with AffineTransform#createTransformedShape.
Here is an example that compares both approaches (and corrects some of the other issues that have been in your code). In both cases, the same rectangle with an original size of (10,13) is painted, scaled by a factor of 5.0.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScalingDrawnObjects
{
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);
ScalingDrawnObjectsPanel p = new ScalingDrawnObjectsPanel();
f.getContentPane().add(p);
f.setSize(600,400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ScalingDrawnObjectsPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
Shape rectangle = new Rectangle2D.Double(2, 2, 10, 13);
g.setColor(Color.RED);
drawWithScaledGraphics(g, rectangle);
g.translate(100, 0);
drawScaledObject(g, rectangle);
}
private static void drawWithScaledGraphics(Graphics2D g, Shape shape)
{
AffineTransform oldAt = g.getTransform();
g.scale(5.0, 5.0);
g.draw(shape);
g.setTransform(oldAt);
}
private static void drawScaledObject(Graphics2D g, Shape shape)
{
AffineTransform at = AffineTransform.getScaleInstance(5.0, 5.0);
g.draw(at.createTransformedShape(shape));
}
}
EDIT In response to the comment
The code that I posted is not "complicated". It is as compilcated as it has to be, but not more. You should not extend JFrame and you should not override paint. You should create the GUI on the EDT. You should ...
However, you should not use code like the following, but maybe this is what you're looking for
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.Dimension;
public class Stretch extends JFrame {
int originalHeight = 600;
int originalWidth = 600;
public Stretch() {
super("Stretch");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(originalWidth, originalHeight);
}
public static void main(String[] args) {
Stretch s = new Stretch();
s.setVisible(true);
}
public void paint(Graphics g) {
Dimension size = this.getBounds().getSize();
int rectWidth = 100;
int rectHeight = 130;
g.setColor(Color.white);
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.black);
int w = rectWidth + size.width - originalWidth;
int h = rectHeight + size.height - originalHeight;
double sx = (double)w / rectWidth;
double sy = (double)h / rectHeight;
double s = Math.min(sx, sy);
int fw = (int)(s * rectWidth);
int fh = (int)(s * rectHeight);
g.drawRect(100, 100, fw, fh);
}
}

How about multiplying the width and the height by the scale you want?
So if you want to scale it by 2:
g.drawRect(100, 100, 2 * (rectWidth + size.width - originalWidth), 2 * (rectHeight + size.height - originalHeight));

Related

Draw sine wave curve

Output is a straight line, but should be a curve.
Not sure if the problem is maths or code.
I know the problem is in the bit where it says (Math.sin(sum3.getValue()) however I can't work out what it should be...
I have for some reason got a post it note that say I(V)=SIN(V); which I imagine is what I am trying to implement to get an I(V) curve.
Sum3 is (V) from a different class.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class GraphApp extends JFrame {
int x,y;
int ax,by;
IVChar sum3 = new IVChar();
//create a window in which the graph will be shown
public GraphApp(){
setTitle("Graph App");
setSize(700,700);
setResizable(true);
setVisible(true);
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
x = 30;
y = 300;
}
// create the axis
#Override
public void paint(Graphics g){
g.drawLine(300, 30, 300, 600); // y axis
g.drawLine(30, 300, 600, 300); // x axis
g.setColor(Color.blue);//colour of drawLine
g.fillOval(x, y, 3, 3);
g.drawString("I", 310, 40);
g.drawString("V'", 600, 314);
run();
repaint(); //makes it run again
}
// implement and draw graphical functions
public void run(){
try{
Thread.sleep(10); //speed line is drawn
float ax,by;
ax = x-300;
by = y-300;
//specify the function
by = (float) Math.sin(sum3.getValue());//makes a sin wave
x = (int) (ax + 300);
y = (int) (300 - by);
x++;
}catch(Exception e){
System.out.println("Error!");
}
}
public static void main(String[]args){
new GraphApp();
}
}
You can use javax.swing.Timer to animate drawing a curve:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class GraphApp extends JFrame {
public GraphApp(){
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
add(new SineWave());
pack();
setVisible(true);
}
public static void main(String[]args){
SwingUtilities.invokeLater(()->new GraphApp());
}
}
class SineWave extends JPanel{
//use constants for better readability
private static final double W = 600, H = 700, AMPLITUDE = H/3;
private static final int MARGIN =30, GAP = 15,DOT_SIZE = 3, SPEED = 10;
//starting point
private double x = MARGIN;
private final double y = H/2;
private final int dX = 1; //x increment
//you need to use doubles to avoid rounding error and have use non integer coordinates
private final List<Point2D.Double> points;
private final Timer timer;
public SineWave() {
setPreferredSize(new Dimension((int)W, (int)H));
points = new ArrayList<>();
timer = new Timer(SPEED, e->addPoints()); //timer to add sine points
timer.start();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
Shape xAxis = new Line2D.Double(MARGIN, H/2, W-MARGIN, H/2);
g2.draw(xAxis);
Shape yAxis = new Line2D.Double(W/2, MARGIN, W/2, H-MARGIN);
g2.draw(yAxis);
g.drawString("I", (int)(W/2-GAP),MARGIN);
g.drawString("V'", (int)(W-GAP), (int)(H/2+GAP));
g2.setColor(Color.blue);//colour of graph
for(Point2D.Double p : points){
Shape point = new Ellipse2D.Double(p.getX(), p.getY(),DOT_SIZE , DOT_SIZE);
g2.draw(point);
}
}
private void addPoints() {
double angel = - Math.PI + 2* Math.PI * ((x-MARGIN)/(W- 2*MARGIN));//angle in radians
double newY = y + AMPLITUDE * Math.sin(angel);
points.add(new Point2D.Double(x, newY));
x += dX;
if(x >= W-MARGIN) {
timer.stop();
}
repaint();
}
}

make image point toward specific location in java

BEFORE YOU MARK IT AS DUPLICATE
I have searched a lot in the internet for that and tried every solution, but no one does it the same way I do it. In my case the rotation is in a sperate class.
I have created a java class that inherits JLabel class, in my class I have an arrow BufferedImage which I draw using the paintComponent(Graphics g) method.
I am trying to make the arrow point to a specific point (which I get from a different method) but something goes wrong and the arrow rotates to the wrong direction.
I THINK: it doesn't calculate correctly because the imageLocation is relative to the label.
Here is my code:
package pkg1;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public final class ImageLabel extends JLabel {
private float angle = 0.0f; // in radians
private Point imageLocation = new Point();
private File imageFile = null;
private Dimension imageSize = new Dimension(50, 50);
private BufferedImage bi;
private BufferedImage resizeImage(BufferedImage originalImage, int img_width, int img_height) {
int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
BufferedImage resizedImage = new BufferedImage(img_width, img_height, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, img_width, img_height, null);
g.dispose();
return resizedImage;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (bi == null) {
return;
}
imageLocation = new Point(getWidth() / 2 - bi.getWidth() / 2, getHeight() / 2 - bi.getHeight() / 2);
Graphics2D g2 = (Graphics2D) g;
g2.rotate(angle, imageLocation.x + bi.getWidth() / 2, imageLocation.y + bi.getHeight() / 2);
g2.drawImage(bi, imageLocation.x, imageLocation.y, null);
}
public void rotateImage(float angle) { // rotate the image to specific angle
this.angle = (float) Math.toRadians(angle);
repaint();
}
public void pointImageToPoint(Point target) {
calculateAngle(target);
repaint();
}
private void calculateAngle(Point target) {
// calculate the angle from the center of the image
float deltaY = target.y - (imageLocation.y + bi.getHeight() / 2);
float deltaX = target.x - (imageLocation.x + bi.getWidth() / 2);
angle = (float) Math.atan2(deltaY, deltaX);
if (angle < 0) {
angle += (Math.PI * 2);
}
}
}
Okay, so two things jump out at me...
If you take a Point from outside the context of the label, you will have to translate the point into the components coordinate context
The calculateAngle seems wrong
So starting with...
private void calculateAngle(Point target) {
// calculate the angle from the center of the image
float deltaY = target.y - (imageLocation.y + bi.getHeight() / 2);
float deltaX = target.x - (imageLocation.x + bi.getWidth() / 2);
angle = (float) Math.atan2(deltaY, deltaX);
if (angle < 0) {
angle += (Math.PI * 2);
}
}
angle = (float) Math.atan2(deltaY, deltaX); should be angle = (float) Math.atan2(deltaX, deltaY); (swap the deltas)
You will find that you need to adjust the result by 180 degrees in order to get the image to point in the right direction
angle = Math.toRadians(Math.toDegrees(angle) + 180.0);
Okay, I'm an idiot, but it works :P
I'd also make use of a AffineTransform to translate and rotate the image - personally, I find it easier to deal with.
In the example, I've cheated a little. I set the translation of the AffineTransform to the centre of the component, I then rotate the context around the new origin point (0x0). I then paint the image offset by half it's height/width, thus making it appear as the if the image is been rotated about it's centre - It's late, I'm tired, it works :P
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private ImageLabel label;
public TestPane() {
setLayout(new GridBagLayout());
label = new ImageLabel();
add(label);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
label.pointImageToPoint(e.getPoint(), TestPane.this);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public final class ImageLabel extends JLabel {
private double angle = 0;
private Point imageLocation = new Point();
private File imageFile = null;
private Dimension imageSize = new Dimension(50, 50);
private BufferedImage bi;
public ImageLabel() {
setBorder(new LineBorder(Color.BLUE));
bi = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setColor(Color.RED);
g2d.drawLine(25, 0, 25, 50);
g2d.drawLine(25, 0, 0, 12);
g2d.drawLine(25, 0, 50, 12);
g2d.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(bi.getWidth(), bi.getHeight());
}
protected Point centerPoint() {
return new Point(getWidth() / 2, getHeight() / 2);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (bi == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = g2d.getTransform();
Point center = centerPoint();
at.translate(center.x, center.y);
at.rotate(angle, 0, 0);
g2d.setTransform(at);
g2d.drawImage(bi, -bi.getWidth() / 2, -bi.getHeight() / 2, this);
g2d.dispose();
}
public void rotateImage(float angle) { // rotate the image to specific angle
this.angle = (float) Math.toRadians(angle);
repaint();
}
public void pointImageToPoint(Point target, JComponent fromContext) {
calculateAngle(target, fromContext);
repaint();
}
private void calculateAngle(Point target, JComponent fromContext) {
// calculate the angle from the center of the image
target = SwingUtilities.convertPoint(fromContext, target, this);
Point center = centerPoint();
float deltaY = target.y - center.y;
float deltaX = target.x - center.x;
angle = (float) -Math.atan2(deltaX, deltaY);
angle = Math.toRadians(Math.toDegrees(angle) + 180.0);
repaint();
}
}
}
I just want to add that using a JLabel for this purpose is overkill, a simple JPanel or JComponent would do the same job and carry a lot less overhead with it, just saying

Draw ring with given thickness, position, and radius. (Java2D)

I need to draw a ring, with given thickness, that looks something like this:
The center must be transparent, so that it doesn't cover previously drawn shapes. (or other rings) I've tried something like this:
//g is a Graphics2D object
g.setColor(Color.RED);
g.drawOval(x,y,width,height);
g.setColor(Color.WHITE);
g.drawOval(x+thickness,y+thickness,width-2*thickness,height-2*thickness);
which draws a satisfactory ring, but it covers other shapes; the interior is white, not transparent. How can I modify/rewrite my code so that it doesn't do that?
You can create an Area from an Ellipse2D that describes the outer circle, and subtract the ellipse that describes the inner circle. This way, you will obtain an actual Shape that can either be drawn or filled (and this will only refer to the area that is actually covered by the ring!).
The advantage is that you really have the geometry of the ring available. This allows you, for example, to check whether the ring shape contains a certain point, or to fill it with a Paint that is more than a single color:
Here is an example, the relevant part is the createRingShape method:
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RingPaintTest
{
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);
RingPaintTestPanel p = new RingPaintTestPanel();
f.getContentPane().add(p);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class RingPaintTestPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.RED);
g.drawString("Text", 100, 100);
g.drawString("Text", 300, 100);
Shape ring = createRingShape(100, 100, 80, 20);
g.setColor(Color.CYAN);
g.fill(ring);
g.setColor(Color.BLACK);
g.draw(ring);
Shape otherRing = createRingShape(300, 100, 80, 20);
g.setPaint(new GradientPaint(
new Point(250, 40), Color.RED,
new Point(350, 200), Color.GREEN));
g.fill(otherRing);
g.setColor(Color.BLACK);
g.draw(otherRing);
}
private static Shape createRingShape(
double centerX, double centerY, double outerRadius, double thickness)
{
Ellipse2D outer = new Ellipse2D.Double(
centerX - outerRadius,
centerY - outerRadius,
outerRadius + outerRadius,
outerRadius + outerRadius);
Ellipse2D inner = new Ellipse2D.Double(
centerX - outerRadius + thickness,
centerY - outerRadius + thickness,
outerRadius + outerRadius - thickness - thickness,
outerRadius + outerRadius - thickness - thickness);
Area area = new Area(outer);
area.subtract(new Area(inner));
return area;
}
}
You can use the Shape and Area classes to create interesting effects:
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.net.*;
public class Subtract extends JPanel
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int size = 100;
int thickness = 10;
int innerSize = size - (2 * thickness);
Shape outer = new Ellipse2D.Double(0, 0, size, size);
Shape inner = new Ellipse2D.Double(thickness, thickness, innerSize, innerSize);
Area circle = new Area( outer );
circle.subtract( new Area(inner) );
int x = (getSize().width - size) / 2;
int y = (getSize().height - size) / 2;
g2d.translate(x, y);
g2d.setColor(Color.CYAN);
g2d.fill(circle);
g2d.setColor(Color.BLACK);
g2d.draw(circle);
g2d.dispose();
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("Subtract");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Subtract());
frame.setLocationByPlatform( true );
frame.setSize(200, 200);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Using an Area you can also add multiple Shapes together or get the intersection of multiple Shapes.
You could use graphics.setStroke(...) for this. This way the center will be fully transparent and therefore won't cover previously drawn shapes. In my example I had to do some additional calculations because of this method though, to make sure the displayed x and y coordinates are actually the same as the ones of the Ring instance:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example {
public Example() {
ArrayList<Ring> rings = new ArrayList<Ring>();
rings.add(new Ring(10, 10, 100, 20, Color.CYAN));
JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gg = (Graphics2D) g.create();
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Ring ring : rings) {
// Previously drawn
gg.setColor(Color.BLACK);
String str = "Hello!";
gg.drawString(str, ring.getX() + (ring.getWidth() - gg.getFontMetrics().stringWidth(str)) / 2,
ring.getY() + ring.getHeight() / 2 + gg.getFontMetrics().getAscent());
// The actual ring
ring.draw(gg);
}
gg.dispose();
}
};
JFrame frame = new JFrame();
frame.setContentPane(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example();
}
});
}
}
class Ring {
private int x, y, width, height, thickness;
private Color color;
public Ring(int x, int y, int width, int height, int thickness, Color color) {
setX(x);
setY(y);
setWidth(width);
setHeight(height);
setThickness(thickness);
setColor(color);
}
public Ring(int x, int y, int radius, int thickness, Color color) {
this(x, y, radius * 2, radius * 2, thickness, color);
}
public void draw(Graphics2D gg) {
Stroke oldStroke = gg.getStroke();
Color oldColor = gg.getColor();
gg.setColor(Color.BLACK);
gg.setStroke(new BasicStroke(getThickness()));
gg.drawOval(getX() + getThickness() / 2, getY() + getThickness() / 2, getWidth() - getThickness(),
getHeight() - getThickness());
gg.setColor(getColor());
gg.setStroke(new BasicStroke(getThickness() - 2));
gg.drawOval(getX() + getThickness() / 2, getY() + getThickness() / 2, getWidth() - getThickness(),
getHeight() - getThickness());
gg.setStroke(oldStroke);
gg.setColor(oldColor);
}
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 int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getThickness() {
return thickness;
}
public void setThickness(int thickness) {
this.thickness = thickness;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}

Getting a graph to auto adjust with the screen

I have created a graph in a Java applet and I'm trying to get the g.fillRect to auto adjust with the screen. I want the first bar to be a third of the screen and then to halve in size for each other bar.
g.fillRect(xpos, 550, width, hight);
I seem to have a problem with getting a gap in between each bar. Could you give me a hand with this problem? Thanks in advance.
You have to compute the width of each individual bar, which is
barWidth = availableWidth / numberOfBars - 1
The "-1" will be the space that will be left between the bars, and thus, has to be added again when computing the actual coordinates. You could spend some time with the details there: When the numbers are not "nicely divisble", then the bars either have to have different widths, or you have to add a small margin at the left and right side to compensate for the odd size.
However, here is a quick sketch of one possible solution:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class BarChart
{
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);
f.getContentPane().setLayout(new BorderLayout());
final JSlider slider = new JSlider(1, 50, 3);
final BarChartPanel barChartPanel = new BarChartPanel();
slider.addChangeListener(new ChangeListener()
{
#Override
public void stateChanged(ChangeEvent e)
{
barChartPanel.setNumberOfBars(slider.getValue());
}
});
f.getContentPane().add(slider, BorderLayout.NORTH);
f.getContentPane().add(barChartPanel, BorderLayout.CENTER);
f.pack();
f.setSize(600,600);
f.setVisible(true);
}
}
class BarChartPanel extends JPanel
{
private int numberOfBars = 3;
void setNumberOfBars(int n)
{
this.numberOfBars = n;
repaint();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.BLUE);
Random random = new Random(0);
int barWidth = getWidth() / numberOfBars - 1;
int barsWidth = numberOfBars * (barWidth+1);
int offsetX = (getWidth() - barsWidth) / 2;
for (int b=0; b<numberOfBars; b++)
{
int x = offsetX + b * (barWidth + 1);
int barHeight = random.nextInt(500);
int y = getHeight() - barHeight;
g.fillRect(x, y, barWidth, barHeight);
}
}
}

Drawing antialiased shapes and rotating them in Java

Background
Im total Java newbie, today I started learning it (with thenewboston.org). I already know how to make simple windows/forms/gui, how to draw lines etc.
My goal is to create in Java gauge like this:
This is gauge which I created in .NET C# WPF, and now I want to rewrite this to Java.
Main question:
How to create triangle or other shape with some transparency and rotate it?
I tried to draw something by using Graphics object like this:
public void paint(Graphics g){
g.drawLine(0, 0, 100, 100);
}
But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.
I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?
Edit:
I already know how to antialias (Hovercraft Full Of Eels already helped me in this - thanks).
Edit2:
My code actually looks like this:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainWindow extends JPanel {
private Point p1 = new Point(100, 100);
private Point p2 = new Point(740, 450);
public MainWindow() {
this.setPreferredSize(new Dimension(800, 600));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawLines(g);
}
private void drawLines(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.DARK_GRAY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private void display() {
JFrame f = new JFrame("Main Window");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
new MainWindow().display();
}
}
You state:
I tried to draw something by using Graphics object like this:
public void paint(Graphics g){
g.drawLine(0, 0, 100, 100);
}
But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.
I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?
Suggestions:
Don't hard-code your numbers. Use class fields (variables) instead so that your program can change the position of items drawn easily.
Don't override a component's paint(...) method. Instead override the paintComponent(Graphics g) method of an object that derives from JComponent or one of its children such as JPanel. This will give you the benefit of automatic double-buffering for smoother animation, and also will reduce the likelihood of erroneous drawing of a component's children or borders.
Cast your Graphics object to a Graphics2D object so that you can do more advanced drawing using classes that implement the Shape interface, including Rectangle2D, Ellipse2D, Line2D, Path2D, and many more.
Draw the background image as a BufferedImage using Graphics#drawImage(...) method, and then draw your moving images on top of this, again using the Graphics2D object and again changing the images drawn based on the state of the object (the values held by its fields).
Be careful when doing animations that you obey Swing threading rules, that you don't have any animation or game loops that tie up the Swing thread. A Swing Timer can allow you to create a quick and easy (albeit somewhat primitive) game loop.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DailAnimation extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 350;
private static final Point2D CENTER = new Point2D.Double(PREF_W / 2.0,
PREF_W / 2.0);
private static final double RADIUS = PREF_W / 2.0;
private static final Color LARGE_TICK_COLOR = Color.green;
private static final Color CENTER_HUB_COLOR = Color.LIGHT_GRAY;
private static final Stroke LARGE_TICK_STROKE = new BasicStroke(3f);
private static final int LRG_TICK_COUNT = 9;
private static final double TOTAL_LRG_TICKS = 12;
private static final double LRG_TICK_OUTER_RAD = 0.9;
private static final double LRG_TICK_INNER_RAD = 0.8;
private static final int START_TICK = 10;
private static final double CENTER_HUB_RADIUS = 10;
public static final int MAX_SPEED = 100;
private static final double INIT_SPEED = 0;
private static final double DIAL_INNER_RAD = 0.02;
private static final double DIAL_OUTER_RAD = 0.75;
private static final Color DIAL_COLOR = Color.DARK_GRAY;
private BufferedImage backgroundImg;
private double speed;
private double theta;
private double cosTheta;
private double sinTheta;
public DailAnimation() {
setBackground(Color.white);
backgroundImg = createBackgroundImg();
setSpeed(INIT_SPEED);
}
public void setSpeed(double speed) {
if (speed < 0) {
speed = 0;
} else if (speed > MAX_SPEED) {
speed = MAX_SPEED;
}
this.speed = speed;
this.theta = ((speed / MAX_SPEED) * LRG_TICK_COUNT * 2.0 + START_TICK)
* Math.PI / TOTAL_LRG_TICKS;
cosTheta = Math.cos(theta);
sinTheta = Math.sin(theta);
repaint();
}
private BufferedImage createBackgroundImg() {
BufferedImage img = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(LARGE_TICK_COLOR);
g2.setStroke(LARGE_TICK_STROKE);
for (int i = 0; i < LRG_TICK_COUNT; i++) {
double theta = (i * 2.0 + START_TICK) * Math.PI / TOTAL_LRG_TICKS;
double cosTheta = Math.cos(theta);
double sinTheta = Math.sin(theta);
int x1 = (int) (LRG_TICK_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
int y1 = (int) (LRG_TICK_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
int x2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
int y2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());
g2.drawLine(x1, y1, x2, y2);
}
g2.setColor(CENTER_HUB_COLOR);
int x = (int) (CENTER.getX() - CENTER_HUB_RADIUS);
int y = (int) (CENTER.getY() - CENTER_HUB_RADIUS);
int width = (int) (2 * CENTER_HUB_RADIUS);
int height = width;
g2.fillOval(x, y, width, height);
// g2.draw(ellipse);
g2.dispose();
return img;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, this);
}
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(DIAL_COLOR);
int x1 = (int) (DIAL_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
int y1 = (int) (DIAL_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
int x2 = (int) (DIAL_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
int y2 = (int) (DIAL_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());
g.drawLine(x1, y1, x2, y2);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
final DailAnimation mainPanel = new DailAnimation();
JFrame frame = new JFrame("DailAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
int delay = 100;
new Timer(delay, new ActionListener() {
int speed = 0;
#Override
public void actionPerformed(ActionEvent evt) {
speed ++;
if (speed > DailAnimation.MAX_SPEED) {
((Timer)evt.getSource()).stop();
}
mainPanel.setSpeed(speed);
}
}).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Drawing a line via Graphics.drawLine() writes pixels directly to whatever is backing the Graphics instance. If you want to rotate the line, you must calculate what its coordinates should be when rotated. This is the only way to draw things in AWT and Swing.
You could write a needle class that maintained its angle, and then have it handle its rendering every frame.

Categories

Resources