Related
here i'm trying to draw a circle using drawOval method and I want to move it on the screen with a specific velocity. but i have a problem with double variables for the velocity.for example when vx=0.25 and vy=0 the circle is just stuck on its place.
sorry for my bad English though.
here is the java code that i'm using
int x=0 , y=0;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
move();
g.drawOval(x, y, 10, 10);
repaint();
}
public void move() {
x+=0.25;
y+=0.25;
}
You should not call move from the paintComponent method! You never know when this method will be called, and thus, you cannot control the movement speed properly.
You should not call repaint from the paintComponent method! Never. This will send the painting system into an endless cycle of repaint operations!
Regarding the question:
There is a method for drawing arbitrary shapes based on double coordinates. This is also covered and explained extensively in the 2D Graphics Tutorial. The key is to use the Shape interface. For your particular example, the relevant part of the code is this:
private double x = 0;
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
double radius = 5;
g.draw(new Ellipse2D.Double(
x - radius, y - radius, radius * 2, radius * 2));
}
That is, you create an Ellipse2D instance, and then just draw it.
Here is an MVCE, showing what you're probably trying to accomplish:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PaintWithDouble
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PaintWithDoublePanel p = new PaintWithDoublePanel();
f.getContentPane().add(p);
startMoveThread(p);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static void startMoveThread(PaintWithDoublePanel p)
{
Thread t = new Thread(() -> {
while (true)
{
p.move();
p.repaint();
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
});
t.setDaemon(true);
t.start();
}
}
class PaintWithDoublePanel extends JPanel
{
private double x = 0;
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double radius = 5;
g.draw(new Ellipse2D.Double(
x - radius, y - radius, radius * 2, radius * 2));
g.drawString("At " + x + ", " + y, 10, 30);
}
public void move()
{
x += 0.05;
y += 0.05;
}
}
Edited in response to the comment (and to clarify some things that have been said in other answers) :
While it is technically correct to say that there are "only whole pixels", and there "is no pixel with coordinates (0.3, 1.8)", this does not mean that fractional coordinates will not affect the final appearance of the rendered output. Every topic becomes a science when you're studying it long enough. Particularly, a lot of research went into the question of how to improve the visual appearance of rendered output, going beyond what you can achieve with a trivial Bresenham or so. An entry point for further research could be the article about subpixel rendering.
In many cases, as usual, there are trade-offs between the appearance and the drawing performance. As for Java and its 2D drawing capabilities, these trade-offs are mostly controlled via the RenderingHints class. For example, there is the RenderingHints#VALUE_STROKE_PURE that enables subpixel rendering. The effect is shown in this screen capture:
The slider is used to change the y-offset of the rightmost point of a horizontal line by -3 to +3 pixels. In the upper left, you see a line, rendered as-it-is. In the middle, you see the line magnified by a factor of 8, to better show the effect: The pixels are filled with different opacities, depending on how much of the pixel is covered by an idealized, 1 pixel wide line.
While it's certainly the case that this is not relevant for most application cases, it might be worth noting here.
The following is an MCVE that was used for the screen capture:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class PaintWithDoubleMagnified
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
PaintWithDoubleMagnifiedPanel p = new PaintWithDoubleMagnifiedPanel();
f.getContentPane().add(p, BorderLayout.CENTER);
JSlider slider = new JSlider(0, 100, 50);
slider.addChangeListener(e -> {
int value = slider.getValue();
double relative = -0.5 + value / 100.0;
p.setY(relative * 6);
});
f.getContentPane().add(slider, BorderLayout.SOUTH);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class PaintWithDoubleMagnifiedPanel extends JPanel
{
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.drawString("At " + y, 10, 20);
paintLine(g);
BufferedImage image = paintIntoImage();
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.scale(8.0, 8.0);
g.drawImage(image, 0, 0, null);
}
public void setY(double y)
{
this.y = y;
repaint();
}
private void paintLine(Graphics2D g)
{
g.setColor(Color.BLACK);
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
Line2D line = new Line2D.Double(
10, 30, 50, 30 + y);
g.draw(line);
}
private BufferedImage paintIntoImage()
{
BufferedImage image = new BufferedImage(
100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
paintLine(g);
g.dispose();
return image;
}
}
First notice that rendering system is using int as arguments for each pixel.
So if p1 is near p2 on x axis then
p1(x,y) and p2(x+1,y)
eg:(0,0) and (1,0)
You do not have something in the middle like (0.5,1) since no pixels.
That why Graphics api is using int for (x,y) coordinates.
Graphics api
If you wanted to consider also double you have to adapt the default coordinates systems to fit your needs.(cannot render all double there in individual pixels, need to group them in categories)
Eg. say want to place x_points : 0, 0.5, 1
So 0->0, 0.5(double)->1(int) , 1->2
Other pixels could map as 0.2->1, 0.7->2 , -0.9->0
One rule map consider all double range (better say in (-0.5,1])
can be -0.5<d<=0 -> 0,0<d<=0.5 -> 1, 0.5<d<=1 -> 2 where d=input_x(double)
That means you adjust the coordinates systems to fit your needs
is there a method in java for drawing a circle with double variables for its center?
NO(using standard Graphics api). Have just what api is provided, but you could render what ever input you wanted (even based on double) by adjusting coordinates system.
class MyPaint extends JPanel
{
private double x = 0, y=0;
private int width = 30, height = 30;
//adjust coordinates system
//for x in [0,1] have [0,0.1,0.2,0.3 ..]
//from no pixel between (0,1) to 9 pixels (0,0.1, ..,1)
//0->0,0.1->1,0.2->2,0.9->9,1->10
//in that way you have full control of rendering
private double scale_x = 0.1;
//same on y as x
private double scale_y = 0.1;
//pixel scaled on x,y
//drawing with
private int xs,ys;
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
xs = (int) (x/scale_x);
ys = (int) (y/scale_y);
g.drawString("Draw At: " + xs + ", " + ys + " From:" + x+","+y, 10, 30);
g.drawOval(xs, ys, (int) (width/scale_x), (int) (height/scale_y));
}
public void move()
{
//adjustments is better to be >= then scale(x or y) seen as absolute value
//if need 0.01 to be display on individual pixel on x
//then modify scale_x = 0.01 (or even 0.001)
x+=0.1;
y+=0.5;
}
}
I am having trouble getting my histogram to fill correctly.
I was given a large data file full of doubles that represented GPAs, about 5500 of them. I created a method to calculate the count, mean, and standard deviation of the data, however my last problem is to graph the data.
I am to make a histogram for each of the possible grades (12 of them) and graph them about the total of each grade.
I think I have coded the total for each grade correctly, but when it comes to actually drawing the histogram I cannot figure out the 4 arguments needed for fillRect.
I've been playing around with different variables, but nothing seems to get me close.
Any help is appreciated.
private static int[] gradeCounts(double[] stats) throws Exception{
double stdv = 0;
double sum = 0;
double sum2 = 0;
double variance = 0;
Scanner fsc = new Scanner(new File("introProgGrades.txt"));
while (!fsc.hasNextDouble())
fsc.nextLine();
int[] binCounts = new int[NUM_OF_GRADE_CATEGORIES];
double x = 0;
while (fsc.hasNextDouble()){
stats[2]++;
x = fsc.nextDouble();
sum += x;
sum2 += x * x;
if (x == 0.0)
binCounts[0]++;
else if (x == 0.6666667)
binCounts[1]++;
else if (x == 1.0)
binCounts[2]++;
else if (x == 1.3333333)
binCounts[3]++;
else if (x == 1.6666667)
binCounts[4]++;
else if (x == 2.0)
binCounts[5]++;
else if (x == 2.3333333)
binCounts[6]++;
else if (x == 2.6666667)
binCounts[7]++;
else if (x == 3.0)
binCounts[8]++;
else if (x == 3.3333333)
binCounts[9]++;
else if (x == 3.6666667)
binCounts[10]++;
else
binCounts[11]++;
}
stats[0] = sum/stats[2];
variance = (stats[2] * sum2 - sum * sum) / (stats[2]*(stats[2]-1));
stdv = Math.sqrt(variance);
stats[1] = stdv;
return binCounts;
}
What I am having trouble with:
private static void plotHistogram(int[] binCounts){
int max = Arrays.stream(binCounts).max().getAsInt();
DrawingPanel panel = new DrawingPanel (800,800);
Graphics2D g = panel.getGraphics();
g.fillRect(0, 0, 800/binCounts.length,max);
}
I think I have to iterate through the data with a for loop, but it's the parameters of fillRect that I am clueless on.
but when it comes to actually drawing the histogram I cannot figure out the 4 arguments needed for fillRect.
The JavaDocs are quite explicit in the properties and their meanings, it's just a box, with a position (x/y) and size (width/height)
public abstract void fillRect​(int x,
int y,
int width,
int height)
Fills the specified rectangle. The left and right edges of the rectangle are at x and x +
width - 1. The top and bottom edges are at y and y + height - 1. The
resulting rectangle covers an area width pixels wide by height pixels
tall. The rectangle is filled using the graphics context's current
color. Parameters: x - the x coordinate of the rectangle to be filled.
y - the y coordinate of the rectangle to be filled. width - the width
of the rectangle to be filled. height - the height of the rectangle to
be filled.
I've been playing around with different variables, but nothing seems to get me close.
So, two things come to mind, the first comes from the JavaDocs above...
The rectangle is filled using the graphics context's current
color
This is something that's easy to forget
The second is it seems that you misunderstand how painting works in Swing.
Painting in Swing has a very specific and well documented workflow. The first thing you should do is go read Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how painting works and how you should work with it.
There is never a good reason to call JComponent#getGraphics, apart from been able to return null, this is just a snapshot of the last paint cycle and will be wiped clean on the next paint pass (which may occur at any time for any number of reasons).
Instead, you will need a custom component and override it's paintComponent method instead.
You should then have a read through the 2D Graphics trail to get a better understanding of how the API works and what features/functionality it can provide.
For example....
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Random rnd = new Random();
int[] binCounts = new int[10];
for (int index = 0; index < binCounts.length; index++) {
binCounts[index] = rnd.nextInt(100);
}
JFrame frame = new JFrame();
frame.add(new DrawingPanel(binCounts));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DrawingPanel extends JPanel {
private int[] binCounts;
private int max;
public DrawingPanel(int[] binCounts) {
this.binCounts = binCounts;
max = Arrays.stream(binCounts).max().getAsInt();
System.out.println(max);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int barWidth = 800 / binCounts.length;
for (int i = 0; i < binCounts.length; i++) {
int barHeight = (int)((binCounts[i] / (double)max) * getHeight());
// I personally would cache this until the state of the component
// changes, but for brevity
Rectangle rect = new Rectangle(i * barWidth, getHeight() - barHeight, barWidth, barHeight);
g2d.setColor(Color.BLUE);
g2d.fill(rect);
g2d.setColor(Color.BLACK);
g2d.draw(rect);
}
g2d.dispose();
}
}
}
Okay so I'm trying to make things snap to a grid I have.
This is how I snap to the screen itself:
int finalCalcX = (mouseX / Handler.gridSpace32) * Handler.gridSpace32;
int finalCalcY = (mouseY / Handler.gridSpace32) * Handler.gridSpace32;
The variable names speak for themselves I think.
Mouse coordinates divided by my tiles being 32x32 pixels, times that again to get the snap-to-grid functionality.
Now this works fine for the screen, but when I want to add it to the "map" itself, I can't just add my map x and y offsets to that, it gets messed up.
I've played around with it for about two days, and I also got it to snap to the map itself, but when I'm say, halfway in the map on both axis, the mouseX and mouseY messes the grid thing up.
It's kind of hard for me to explain, but the offset from the 0, 0 (every origins position, even the screen) PLUS the maps offset when you move away from the origin, gets added to the distance between the cursor itself and the transparent snap-to-grid tile that I'm using to test.
Basically the offset between the maps origin, and the camera, is for some reason the same offset between the cursor and the transparent tile. So the further into the map i move, the further away the tile gets from the cursor, and eventually moves outside the screen width and height...
When I move further into the map, I want the snap-to-grid functionality to stay corret, no matter where on the map I am.
Render method:
for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) {
gridSpace(graphics, (int) (x * Handler.gridSpace32 - handler.getCamera().getOffsetX()),
(int) (y * Handler.gridSpace32 - handler.getCamera().getOffsetY()));
checkHighlight(graphics);
}
}
The gridSpace is the grid itself.
Here is what's in the highlight at the moment:
int finalCalcX = (mouseX / Handler.gridSpace32) * Handler.gridSpace32;
int finalCalcY = (mouseY / Handler.gridSpace32) * Handler.gridSpace32;
graphics.setColor(new Color(100, 200, 100, 3));
graphics.fillRect(finalCalcX, finalCalcY, Handler.gridSpace32, Handler.gridSpace32);
Sorry for my terrible explanation skills, but that's the best I can do.
What am I doing wrong?
I think I recreated what you are getting, and the problem lies with usage of incorrect sign in calculations of camera position and translation. Executing code below you should get similar behavior to what you described, while uncommenting commented lines (while commenting out the ones that follow them) should give you correct behavior.
package test;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.*;
public class SnapToGrid extends JPanel implements MouseMotionListener{
private int[] camera;
private int[] mouse;
private final int gridSize = 16;
SnapToGrid() {
camera = new int[2];
mouse = new int[2];
setFocusable(true);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//g2.translate(-camera[0], -camera[1]);
g2.translate(camera[0], camera[1]);
//draw background
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
{
Color c = ((j*9) + i) % 2 == 0 ? Color.black : Color.white;
g2.setColor(c);
g2.fillRect(i*gridSize, j*gridSize, gridSize, gridSize);
}
g2.setColor(Color.blue);
int[] snappedPos = getSnappedMousePosition();
g2.fillRect(snappedPos[0], snappedPos[1], gridSize, gridSize);
}
private int[] getSnappedMousePosition() {
return new int[] {
camera[0] + mouse[0] - ((camera[0] + mouse[0]) % gridSize),
camera[1] + mouse[1] - ((camera[1] + mouse[1]) % gridSize)
};
}
public static void main (String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SnapToGrid());
frame.pack();
frame.setVisible(true);
}
#Override
public void mouseDragged(MouseEvent e) {
//camera[0] -= e.getX() - mouse[0];
//camera[1] -= e.getY() - mouse[1];
camera[0] += e.getX() - mouse[0];
camera[1] += e.getY() - mouse[1];
mouse[0] = e.getX();
mouse[1] = e.getY();
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
mouse[0] = e.getX();
mouse[1] = e.getY();
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(gridSize * 18, gridSize * 18);
}
}
One thing that I really don't like with your code, is that you are not using transformations. Using transformations allows you to separate world from viewport, submits to far easier debugging of issues like this here, and most importantly, if you want to add things like scaling or rotating later on, you only need to add few lines, as opposed to rewriting half of your render method.
Already tried searching, but couldn't find anything.
I'm trying to draw multiple 2D Ellipses using an array, and a for loop, I'm repainting the frame every second. The thing is, I only get one Ellipse everytime I repaint, can somebody tell me what's wrong with me code, please?
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class MovingDot extends JFrame{
static int posX = (int)Math.round(Math.random()*780);
static int posY = (int)Math.round(Math.random()*780);
static int width = (int)Math.round(Math.random()*780);
static int height = (int)Math.round(Math.random()*780);
static int dots = 0;
public static Timer timer;
public MovingDot(){
super("Moving Dot");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(800, 800);
Dot2 dot = new Dot2();
add(dot);
setVisible(true);
timer = new Timer((int)Math.round((1000)), timerAction);
timer.start();
}
private ActionListener timerAction = new ActionListener(){
#Override
public void actionPerformed(ActionEvent ae){
posX = (int)Math.round(Math.random()*780);
posY = (int)Math.round(Math.random()*780);
width = (int)Math.round(Math.random()*780);
height = (int)Math.round(Math.random()*780);
float r = (float)Math.random();
float g = (float)Math.random();
float b = (float)Math.random();
Color col = new Color(r,g,b);
setBackground(col);
dots++;
repaint();
}
};
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run()
{
new MovingDot();
}
});
}
}
class Dot2 extends JPanel{
#Override
public void paintComponent(Graphics c2){
int x = MovingDot.posX;
int y = MovingDot.posY;
int w = MovingDot.width;
int h = MovingDot.height;
float r,g,b;
Color col;
Graphics2D c = (Graphics2D) c2;
c.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Ellipse2D.Float[] e = new Ellipse2D.Float[10];
for (int i = 0; i < 10; i++) {
if (i == 0)
r = (float)Math.random();
else
r = 0.163F;
g = (float)Math.random();
b = (float)Math.random();
col = new Color(r,g,b);
c.setColor(col);
e[i] = new Ellipse2D.Float(x, y, w, h);
c.fill(e[i]);
}
}
}
Found out what was wrong myself, I had to make x, y, w and h random in my paintComponent. And no, this is not for a school assignment, I'm trying to teach myself Java using a book.
And about making my methods static, I was planning on using them in my JPanel, but I realised I don't need them, so I'm going to delete them. Thanks for your advice!
You shouldn't be creating your Ellipse array inside of paintComponent, makes no sense.
Instead create the array in the class.
Your JPanel's paintComponent method should not have any program logic in it. It should only have code that paints the ellipses. That is, it should iterate through your array with a for loop, and if the item in the array is not null draw it.
You'd be even better off using an ArrayList<Ellipse2D> and not an array. That way you wouldn't have to check for nulls.
In the Timer's ActionListener, if your counter is < 10, you'd add an Ellipse2D to the array and call repaint.
If the counter >= 10 you'd stop the Timer
Also, none of your static variables should be static, and having them as static suggests that the program design is off. If this is for a school assignment, that could lead to deduction of your grade.
Trying to figure out the best way to do this (And without crossing any specifics DO NOTs that I don't know about).
I'm working on visually displaying a graph (Various nodes, with edges connecting them) with circles and lines to represent such. Each node will be added during runtime and I can't hardcode this. From what I understand, all painting needs to be done in the paint(Graphics g) method - which isn't that helpful, since I can't be change the parameters and it seems this is only called during the initial creation?
Right now I was thinking about having it call various other methods, passing the Graphics object, and depending on other variables - I'll decide whether that's what I even want to call (Since the paint() method is the only one I can call).
Am I going about this completely wrong? Never bothered with this before.
To give you a better idea of what I want to end up with: I want to be able to pass the coordinates of the shape I want to add for the node, and then add it to whatever I have on the graph so far. And then same with the edges, I want to be able to pass the beginning and end point of the line to repaint on top of whatever is existing at that time.
Not exactly what I want right now - but you'll get the idea from what I patched together so far:
import java.awt.*;
import javax.swing.*;
public class MyCanvas extends Canvas
{
public MyCanvas()
{
}
public void paint(Graphics graphics)
{
// Keep this until I figured out if it's painted on load or not.
graphics.drawLine(10, 20, 350, 380);
}
public static void main(String[] args)
{
MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
frame.setSize(canvasSize, canvasSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(canvas);
frame.setVisible(true);
}
public void drawNode(int x, int y, Graphics g)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g.setColor(Color.white);
g.fillOval(xLoc, yLoc, 8, 8);
g.drawOval(xLoc, yLoc, 8, 8);
}
public void drawArc(int x, int y, int xx, int yy, Graphics g)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
Edit: (Response for Andrew)
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MyCanvas extends JPanel
{
public MyCanvas() {
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
public static void main(String[] args)
{
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
JFrame frame = new JFrame();
JLabel label = new JLabel();
BufferedImage bImage = new BufferedImage(canvasSize, canvasSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bImage.createGraphics();
g2d.drawLine(50, 50, 300, 300);
ImageIcon iIcon = new ImageIcon(bImage);
label.setIcon(iIcon);
frame.add(label);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
g2d = drawNode(1,1,g2d);
label.repaint();
}
public static Graphics2D drawNode(int x, int y,Graphics2D g2d)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g2d.setColor(Color.white);
g2d.fillOval(xLoc, yLoc, 8, 8);
g2d.drawOval(xLoc, yLoc, 8, 8);
return g2d;
}
public static void drawArc(int x, int y, int xx, int yy)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
// g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
There are various strategies you might pursue for this.
If the objects are never removed from the drawing once done, use a BufferedImage, put it in a (ImageIcon in a) JLabel. When it comes time to update:
Get the graphics instance of the image and draw the new element.
Dispose of the graphics object.
Call repaint() on the label.
Keep a list of the drawn elements. In the paint method, paint them all. When a new element is added, call repaint() on the rendering component.
Here is an example of the 1st technique:
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class MyCanvas
{
JLabel view;
BufferedImage surface;
Random random = new Random();
public MyCanvas()
{
surface = new BufferedImage(600,400,BufferedImage.TYPE_INT_RGB);
view = new JLabel(new ImageIcon(surface));
Graphics g = surface.getGraphics();
g.setColor(Color.ORANGE);
g.fillRect(0,0,600,400);
g.setColor(Color.BLACK);
// Keep this until I figured out if it's painted on load or not.
g.drawLine(10, 20, 350, 380);
g.dispose();
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
addNewElement();
}
};
Timer timer = new Timer(200, listener);
timer.start();
}
public void addNewElement() {
boolean drawArc = random.nextBoolean();
int x = random.nextInt(60);
int y = random.nextInt(40);
Graphics g = surface.getGraphics();
if (drawArc) {
g.setColor(Color.BLUE);
int xx = random.nextInt(60);
int yy = random.nextInt(40);
drawArc(x,y,xx,yy,g);
} else {
drawNode(x,y,g);
}
g.dispose();
view.repaint();
}
public static void main(String[] args)
{
MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
frame.setSize(canvasSize, canvasSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(canvas.view);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void drawNode(int x, int y, Graphics g)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g.setColor(Color.white);
g.fillOval(xLoc, yLoc, 8, 8);
g.drawOval(xLoc, yLoc, 8, 8);
}
public void drawArc(int x, int y, int xx, int yy, Graphics g)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
Further tip
You might notice that the lines look quite 'jagged' & ugly. Both the BufferedImage or a JComponent has access to the more useful Graphics2D object (for the JComponent it is necessary to cast it in paintComponent()). A Graphics2D instance accepts rendering hints that can be used to smooth (dither) the elements drawn.