I'm programming a java applet.
In this applet I need to draw on an image some markers (as a red circle) and some lines.
I have succesfully implemented the markes, as an extension of JComponent, and I also put on this some mouse listeners.
I'm having big problems with the line object. I created another object extending JComponent, and, aside that I'm having some problems with the coordinates system, the setDimension creates troubles. For example it intercepts all the marker's click.
It isn't a method to make an object "dimension" more tight to the line, because I can't draw only verical or horizontal lines...
Thank to all of you.
EDIT
public class Path extends JComponent {
...
// stroke of the line
private Stroke spessore = new BasicStroke(SPESSORE);
// coordinates
private double x, y, x_2, y_2;
// ZoomManager is an object. In this project I can zoom in and zoom out the
// image, so this object convert coordinates get on the superior JPanel in
// coordinates on the image real-sized.
public Path(double x, double y, ZoomManager zoom) {//, double x_2, double y_2, ZoomManager zoom) {
super();
// this function return the coordinates on the real-sized image
Point a = DrawableObjects.getScaledCoordinates(x, y, zoom);
this.x = a.x;
this.y = a.y;
this.x_2 = a.x;
this.y_2 = a.y;
updateBoundsAndSize(zoom);
// this was only for test...
this.addMouseListener(new MouseListener(){
#Override
public void mouseClicked(MouseEvent arg0) {
System.out.println("CLICK!");
}
...
});
}
// this function is called during the mouse dragging for drow the line.
// it gets the coordinates, convert them, save them and update the bounds and
// size of the object
public void setArrivePoint(Point a, ZoomManager zoom) {
Point p = DrawableObjects.getScaledCoordinates(a.x, a.y, zoom);
this.x_2 = p.x;
this.y_2 = p.y;
updateBoundsAndSize(zoom);
}
// update the bounds of the object, the origin point of the rectangle is the
// top-left coordinate build with the original coordinates. The width and height of the rectangle are obtained by subtraction.
private void updateBoundsAndSize(ZoomManager zoom) {
Point p = DrawableObjects.getPanelCoordinates(x, y, zoom);
Point a = DrawableObjects.getPanelCoordinates(x_2, y_2, zoom);
int min_x = (int)Math.min(p.x, a.x) - SPESSORE;
int min_y = (int)Math.min(p.y, a.y) - SPESSORE;
if (min_x < 0)
min_x =0;
if (min_y < 0)
min_y = 0;
int w = (int) (Math.max(a.x, p.x) - min_x) + SPESSORE;
int h = (int) (Math.max(a.y, p.y) - min_y) + SPESSORE;
setBounds(new Rectangle(min_x, min_y, w, h));
repaint();
}
// drawing function
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D antiAlias = (Graphics2D) g;
antiAlias.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// get ZoomManager from the superior object
ZoomManager zoom = ((JPanelImmagine)this.getParent()).zoom;
antiAlias.setColor(DEFAULT_COLOR);
antiAlias.setStroke(spessore);
Point[] coordinates = updateCoordinates(zoom);
Line2D line = new Line2D.Double(coordinates[0], coordinates[1]);
antiAlias.draw(line);
}
// translate coordinates from superior jpanel to this object
private Point[] updateCoordinates(ZoomManager zoom) {
Point[] output = new Point[2];
Point p = DrawableObjects.getScaledCoordinates(x, y, zoom);
Point a = DrawableObjects.getScaledCoordinates(x_2, y_2, zoom);
double o_x = this.getBounds().getCenterX();
double o_y = this.getBounds().getCenterY();
Point origin = new Point ((int)o_x, (int)o_y);
output[0] = calculateCoordinates(p, origin);
output[1] = calculateCoordinates(a, origin);
return output;
}
private Point calculateCoordinates(Point p, Point origin) {
double new_x = p.x - origin.x;
double new_y = p.y - origin.y;
return new Point((int)new_x, (int)new_y);
}
Resolved using this method!
I had to change totally approach to the problem.
Related
I want to make a visual representation of the Pythagoras tree using Java, the code outputs a PNG fixed image.
I started by defining Vector class which starting from two vector components (x,y) can rotate the vector, scale it or add it to another vector.
public class Vector {
public double x;
public double y;
public Vector(double x, double y) {
this.x = x;
this.y = y;
}
public Vector rotated(double alpha) {
double x1 = Math.cos(alpha) * x - Math.sin(alpha) * y;
double y1 = Math.sin(alpha) * x + Math.cos(alpha) * y;
Vector vRotated = new Vector(x1, y1);
return vRotated;
}
public Vector scaled(double s) {
double x1 = x * s;
double y1 = y * s;
Vector vScaled = new Vector(x1, y1);
return vScaled;
}
public Vector added(Vector v) {
double x1 = this.x+v.x;
double y1 = this.y+v.y;
Vector vAdded = new Vector(x1,y1);
return vAdded;
}
}
I have also writen the method for creating the initial image and background and saving it to the desired path
public static void createPythagorasTreeImage(int startSize) throws IOException {
// Creation of the image object
int height = 5 * startSize;
int width = 8 * startSize;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Create a Graphics2D object from the image and set a white background
Graphics2D g = image.createGraphics();
g.setColor(new Color(255, 255, 255));
g.fillRect(0, 0, width, height);
// Initial position and orientation of the first segment
Vector startPos = new Vector(width / 2, startSize);
Vector up = new Vector(0, 1);
// Start the recursion.
drawSegment(g, startPos, up, startSize, height);
// Save the image as PNG
String OS = System.getProperty("os.name").toLowerCase(); // different for win and unix
String filePath = System.getProperty("user.dir") + (OS.indexOf("win") >= 0 ? "\\" : "/") + "pythagorasTree.png";
System.out.println("Writing pythagoras-tree image to: " + filePath);
ImageIO.write(image, "png", new File(filePath));
}
I have read on wikipedia on how to the tree works, and want to now implement the algorithm.
What I need help with is implementing these two methods using Graphics2D (which I'm not very familiar with):
public static void drawRotatedRect(Graphics2D g, Vector pos, Vector up, int a, int height) {
}
This method should Draw a square using Graphics2D (maybe using g.fillPolygon()?), at position pos, up the vector that indicates the rotation of the square by indicating which direction is up for the square, a is the side of the square and height is the height of the drawing space.
public static void drawSegment(Graphics2D g, Vector pos, Vector up, int a, int height) {
}
This method should draw the first square using the previous method, than compute the positions and rotations of the two new squares and draw them, repeat this recursively until a square has a very small side length (2px).
This is my understanding for the Pythagoras tree, I managed to write the majority of the code and it seems that the idea is correct, only if I get the two missing methods to work.
You can work with the Graphics2D context by drawing a Path2D with floating point (or double) precision. I reccoment this, since you will notice that using int precision might give you weird effects.
To draw a path, do:
Path2D.Double rectangle = new Path2D.Double();
rectangle.moveTo(0, 0);
// ... basically draw the four points of the rectangle here.
rectangle.closePath();
g.setColor(yourColorOfChoice);
g.fill(rectangle);
Notice that you need to draw the rectangular shapes manually, since they need ot be rotated, and Graphics2D does not do well with rotations. You could try using inherent rotations, but you will pixelate your context, and you won't like it.
I am very much looking forward to your results. Could you paste the final image into your question, once you are done :)?
I'm drawing two shapes (circles) in a JPanel and I need to connect them with a line. I was doing this by just getting the middle point of the circle and connecting each other, easy.
The problem is that now I need to make single-direction lines, which has an "arrow" at the end, to point out which direction the line goes. So now I can't use the middle point of the circle because I need to connect each other from border to border, so the "arrow' can appear correctly.
On my last try that was the result, nothing good:
PS: In the screenshot I'm not filling the circles just to see the exact position of the line, but normally I would fill it.
I'm having trouble to calculate the exact position of the border I need to start/end my line. Anyone has any idea on how to do this?
EDIT: The circles are movable, they could be in any position, so the line should work in any case.
Okay, so basically, we can break down the problem to basic issues:
Get the angle between the two circles
Draw a line from circumference of one circle to another along this angle
Both these issues aren't hard to solve (and any time spent searching the internet would provide solutions - because that's where I got them from ;))
So, the angle between two points could be calculated using something like...
protected double angleBetween(Point2D from, Point2D to) {
double x = from.getX();
double y = from.getY();
// This is the difference between the anchor point
// and the mouse. Its important that this is done
// within the local coordinate space of the component,
// this means either the MouseMotionListener needs to
// be registered to the component itself (preferably)
// or the mouse coordinates need to be converted into
// local coordinate space
double deltaX = to.getX() - x;
double deltaY = to.getY() - y;
// Calculate the angle...
// This is our "0" or start angle..
double rotation = -Math.atan2(deltaX, deltaY);
rotation = Math.toRadians(Math.toDegrees(rotation) + 180);
return rotation;
}
And the point on a circle can be calculated using something like...
protected Point2D getPointOnCircle(Point2D center, double radians, double radius) {
double x = center.getX();
double y = center.getY();
radians = radians - Math.toRadians(90.0); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
double yPosy = Math.round((float) (y + Math.sin(radians) * radius));
return new Point2D.Double(xPosy, yPosy);
}
Just beware, there's some internal modifications of the results to allow for the difference between the mathematical solution and the way that the Graphics API draws circles
Okay, so big deal you say, how does that help me? Well, I great deal actually.
You'd calculate the angle between the to circles (both to and from, you might be able to simple inverse one angle, but I have the calculation available so I used it). From that, you can calculate the point on each circle where the line will intersect and then you simply need to draw it, something like...
double from = angleBetween(circle1, circle2);
double to = angleBetween(circle2, circle1);
Point2D pointFrom = getPointOnCircle(circle1, from);
Point2D pointTo = getPointOnCircle(circle2, to);
Line2D line = new Line2D.Double(pointFrom, pointTo);
g2d.draw(line);
Runnable Example
Because I've distilled much of the calculations down to communalised properties, I've provided my test code as a runnable example. All the calculations are based on dynamic values, nothing is really hard coded. For example, you can change the size and positions of the circles and the calculations should continue to work...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
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.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
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 Ellipse2D circle1;
private Ellipse2D circle2;
private Point2D drawTo;
public TestPane() {
circle1 = new Ellipse2D.Double(10, 10, 40, 40);
circle2 = new Ellipse2D.Double(100, 150, 40, 40);
//addMouseMotionListener(new MouseAdapter() {
// #Override
// public void mouseMoved(MouseEvent e) {
// drawTo = new Point2D.Double(e.getPoint().x, e.getPoint().y);
// repaint();
// }
//});
}
protected Point2D center(Rectangle2D bounds) {
return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
}
protected double angleBetween(Shape from, Shape to) {
return angleBetween(center(from.getBounds2D()), center(to.getBounds2D()));
}
protected double angleBetween(Point2D from, Point2D to) {
double x = from.getX();
double y = from.getY();
// This is the difference between the anchor point
// and the mouse. Its important that this is done
// within the local coordinate space of the component,
// this means either the MouseMotionListener needs to
// be registered to the component itself (preferably)
// or the mouse coordinates need to be converted into
// local coordinate space
double deltaX = to.getX() - x;
double deltaY = to.getY() - y;
// Calculate the angle...
// This is our "0" or start angle..
double rotation = -Math.atan2(deltaX, deltaY);
rotation = Math.toRadians(Math.toDegrees(rotation) + 180);
return rotation;
}
protected Point2D getPointOnCircle(Shape shape, double radians) {
Rectangle2D bounds = shape.getBounds();
// Point2D point = new Point2D.Double(bounds.getX(), bounds.getY());
Point2D point = center(bounds);
return getPointOnCircle(point, radians, Math.max(bounds.getWidth(), bounds.getHeight()) / 2d);
}
protected Point2D getPointOnCircle(Point2D center, double radians, double radius) {
double x = center.getX();
double y = center.getY();
radians = radians - Math.toRadians(90.0); // 0 becomes th?e top
// Calculate the outter point of the line
double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
double yPosy = Math.round((float) (y + Math.sin(radians) * radius));
return new Point2D.Double(xPosy, yPosy);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.draw(circle1);
g2d.draw(circle2);
// This was used for testing, it will draw a line from circle1 to the
// drawTo point, which, if enabled, is the last known position of the
// mouse
//if (drawTo != null) {
// Point2D pointFrom = center(circle1.getBounds2D());
// g2d.setColor(Color.RED);
// g2d.draw(new Line2D.Double(drawTo, pointFrom));
//
// double from = angleBetween(pointFrom, drawTo);
// System.out.println(NumberFormat.getNumberInstance().format(Math.toDegrees(from)));
//
// Point2D poc = getPointOnCircle(circle1, from);
// g2d.setColor(Color.BLUE);
// g2d.draw(new Line2D.Double(poc, drawTo));
//}
double from = angleBetween(circle1, circle2);
double to = angleBetween(circle2, circle1);
Point2D pointFrom = getPointOnCircle(circle1, from);
Point2D pointTo = getPointOnCircle(circle2, to);
g2d.setColor(Color.RED);
Line2D line = new Line2D.Double(pointFrom, pointTo);
g2d.draw(line);
g2d.dispose();
}
}
}
Arrow head
The intention is to treat the arrow head as a separate entity. The reason is because it's just simpler that way, you also get a more consistent result regardless of the distance between the objects.
So, to start with, I define a new Shape...
public class ArrowHead extends Path2D.Double {
public ArrowHead() {
int size = 10;
moveTo(0, size);
lineTo(size / 2, 0);
lineTo(size, size);
}
}
Pretty simple really. It just creates two lines, which point up, meeting in the middle of the available space.
Then in the paintComponent method, we perform some AffineTransform magic using the available information we already have, namely
The point on our target circles circumference
The angle to our target circle
And transform the ArrowHead shape...
g2d.setColor(Color.MAGENTA);
ArrowHead arrowHead = new ArrowHead();
AffineTransform at = AffineTransform.getTranslateInstance(
pointTo.getX() - (arrowHead.getBounds2D().getWidth() / 2d),
pointTo.getY());
at.rotate(from, arrowHead.getBounds2D().getCenterX(), 0);
arrowHead.transform(at);
g2d.draw(arrowHead);
Now, because I'm crazy, I also tested the code by drawing an arrow pointing at our source circle, just to prove that the calculations would work...
// This just proofs that the previous calculations weren't a fluke
// and that the arrow can be painted pointing to the source object as well
g2d.setColor(Color.GREEN);
arrowHead = new ArrowHead();
at = AffineTransform.getTranslateInstance(
pointFrom.getX() - (arrowHead.getBounds2D().getWidth() / 2d),
pointFrom.getY());
at.rotate(to, arrowHead.getBounds2D().getCenterX(), 0);
arrowHead.transform(at);
g2d.draw(arrowHead);
Let the first circle center coordinates are AX, AY, radius AR, and BX, BY, BR for the second circle.
Difference vector
D = (DX, DY) = (BX - AX, BY - AY)
Normalized
d = (dx, dy) = (DX / Length(D), DY / Length(D))
Start point of arrow
S = (sx, sy) = (AX + dx * AR, AY + dy * AR)
End point
E = (ex, ey) = (BX - dx * BR, BY - dy * BR)
Example:
AX = 0 AY = 0 AR = 1
BX = 4 BY = 3 BR = 2
D = (4, 3)
Length(D) = 5
dx = 4/5
dy = 3/5
sx = 0.8 sy = 0.6
ex = 4 - 2 * 4/5 = 12/5 = 2.4
ey = 3 - 2 * 3/5 = 9/5 = 1.8
Looking at the Screenshot, I think you need to find the top right corner of circle A, and then add half of the total distance to the bottom to y. Next, find the top right corner of circle B, and add half of the distance to the top left corner to x. Finally, make a line connecting the two, and render an arrow on the end of it.
Like this:
private int x1, y1, x2, y2 width = 20, height = 20;
private void example(Graphics g) {
// Set x1, x2, y1, and y2 to something
g.drawOval(x1, y1, width, height);
g.drawOval(x2, y2, width, height);
g.drawLine(x1, y1 + (height/2), x2 + (width/2), y2);
g.drawImage(/*Image of an arrow*/, (x2 + width/2)-2, y2);
}
My trick:
Let the two centers be C0 and C1. Using complex numbers, you map these two points to a horizontal segment from the origin by the transformation
P' = (P - C0) (C1 - C0)* / L
where * denotes conjugation and L = |C1 - C0|. (If you don't like the complex number notation, you can express this with matrices as well.)
Now the visible part of the segment goes from (R0, 0) to (L - R1, 0). The two other vertices of the arrow are at (L - R1 - H, W) and (L - R1 - H, -W) for an arrowhead of height H and width 2W.
By applying the inverse transform you get the original coordinates,
P = C0 + L P' / (C1 - C0)*.
I have a set of two dimensions points. Their X and Y are greater than -2 and lesser than 2. Such point could be : (-0.00012 ; 1.2334 ).
I would want to display these points on a graph, using rectangles (a rectangle illustrates a point, and has its coordinates set to its point's ones - moreover, it has a size of 10*10).
Rectangles like (... ; Y) should be displayed above any rectangles like (... ; Y-1) (positive Y direction is up). Thus, I must set the graph's origin not at the top-left hand-corner, but somewhere else.
I'm trying to use Graphics2D's AffineTransform to do that.
I get the minimal value for all the X coordinates
I get the minimal value for all the Y coordinates
I get the maximal value for all the X coordinates
I get the maximal value for all the Y coordinates
I get the distance xmax-xmin and ymax-ymin
Then, I wrote the code I give you below.
Screenshots
Some days ago, using my own method to scale, I had this graph:
(so as I explained, Y are inverted and that's not a good thing)
For the moment, i.e., with the code I give you below, I have only one point that takes all the graph's place! Not good at all.
I would want to have:
(without lines, and without graph's axis. The important here is that points are correctly displayed, according to their coordinates).
Code
To get min and max coordinates value:
x_min = Double.parseDouble((String) list_all_points.get(0).get(0));
x_max = Double.parseDouble((String) list_all_points.get(0).get(0));
y_min = Double.parseDouble((String) list_all_points.get(0).get(1));
y_max = Double.parseDouble((String) list_all_points.get(0).get(1));
for(StorableData s : list_all_points) {
if(Double.parseDouble((String) s.get(0)) < x_min) {
x_min = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(0)) > x_max) {
x_max = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(1)) < y_min) {
y_min = Double.parseDouble((String) s.get(1));
}
if(Double.parseDouble((String) s.get(1)) > y_max) {
y_max = Double.parseDouble((String) s.get(1));
}
}
To draw a point:
int x, y;
private void drawPoint(Cupple storable_data) {
//x = (int) (storable_data.getNumber(0) * scaling_coef + move_x);
//y = (int) (storable_data.getNumber(1) * scaling_coef + move_y);
x = storable_data.getNumber(0).intValue();
y = storable_data.getNumber(1).intValue();
graphics.fillRect(x, y, 10, 10);
graphics.drawString(storable_data.toString(), x - 5, y - 5);
}
To paint the graph:
#Override
public void paint(Graphics graphics) {
this.graphics = graphics;
Graphics2D graphics_2d = ((Graphics2D) this.graphics);
AffineTransform affine_transform = graphics_2d.getTransform();
affine_transform.scale(getWidth()/(x_max - x_min), getHeight()/(y_max - y_min));
affine_transform.translate(x_min, y_min);
graphics_2d.transform(affine_transform);
for(StorableData storable_data : list_all_points) {
graphics_2d.setColor(Color.WHITE);
this.drawPoint((Cupple) storable_data);
}
I suggest you map each data point to a point on the screen, thus avoiding the following coordinate system pitfalls. Take your list of points and create from them a list of points to draw. Take into account that:
The drawing is pixel-based, so you will want to scale your points (or you would have rectangles 1 to 4 pixels wide...).
You will need to translate all your points because negative values will be outside the boundaries of the component on which you draw.
The direction of the y axis is reversed in the drawing coordinates.
Once that is done, use the new list of points for the drawing and the initial one for calculations. Here is an example:
public class Graph extends JPanel {
private static int gridSize = 6;
private static int scale = 100;
private static int size = gridSize * scale;
private static int translate = size / 2;
private static int pointSize = 10;
List<Point> dataPoints, scaledPoints;
Graph() {
setBackground(Color.WHITE);
// points taken from your example
Point p1 = new Point(-1, -2);
Point p2 = new Point(-1, 0);
Point p3 = new Point(1, 0);
Point p4 = new Point(1, -2);
dataPoints = Arrays.asList(p1, p2, p3, p4);
scaledPoints = dataPoints.stream()
.map(p -> new Point(p.x * scale + translate, -p.y * scale + translate))
.collect(Collectors.toList());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// draw a grid
for (int i = 0; i < gridSize; i++) {
g2d.drawLine(i * scale, 0, i * scale, size);
g2d.drawLine(0, i * scale, size, i * scale);
}
// draw the rectangle
g2d.setPaint(Color.RED);
g2d.drawPolygon(scaledPoints.stream().mapToInt(p -> p.x).toArray(),
scaledPoints.stream().mapToInt(p -> p.y).toArray(),
scaledPoints.size());
// draw the points
g2d.setPaint(Color.BLUE);
// origin
g2d.fillRect(translate, translate, pointSize, pointSize);
g2d.drawString("(0, 0)", translate, translate);
// data
for (int i = 0; i < dataPoints.size(); i++) {
Point sp = scaledPoints.get(i);
Point dp = dataPoints.get(i);
g2d.fillRect(sp.x, sp.y, pointSize, pointSize);
g2d.drawString("(" + dp.x + ", " + dp.y + ")", sp.x, sp.y);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setContentPane(new Graph());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And another:
You might want to have the points aligned on the grid intersections and not below and to the right of them. I trust you will figure this one out.
Also, I ordered the points so that drawPolygon will paint the lines in the correct order. If your points are arbitrarily arranged, look for ways to find the outline. If you want lines between all points like in your example, iterate over all combinations of them with drawLine.
I have a JPanel which displays an image. In a separate class, I'm reading from an xml file points. I am firstly creating an arraylist of triangles from these points. However I need to show the triangles on the image, i.e. draw them on! (yes this should be simple). But as these points and triangles are created in another class, I do not seem to be able to draw them on the already-displayed image within the GUI class. I have tried creating a ArrayList in the JPanel itself, which I update and then want to repaint, although it will not let me do this as shown below:
Class
triangles = clips.getTriangles();
tempPanel.setTriangles(triangles){
JPanel
public void settriangles(ArrayList<Point[]> t){
triangles = t;
repaint();
}
My only other idea is for the JPanel to have a listener waiting for when triangles are returned, updating the field and hence then repainting.
Any ideas?
Thanks
Edit: Code for Drawing
public void settriangles(ArrayList<Point[]> t){
triangles = t;
repaint();
}
public void paintComponent(Graphics g) {
System.out.println("in paint component");
if (g != null) {
Graphics2D graphics = (Graphics2D) g;
try {
Rectangle back_rect = new Rectangle(0, 0, getWidth(),
getHeight());
graphics.setColor(GuiComponentGenerator.GUI_BACKGROUND_COLOUR);
graphics.fill(back_rect);
if (image != null) {
int width = Math.round(image.getWidth() * magnification);
int height = Math.round(image.getHeight() * magnification);
Rectangle image_rect = new Rectangle(offset.x, offset.y,
width, height);
graphics.setColor(Color.BLACK);
graphics.draw(image_rect);
graphics.drawImage(image, offset.x, offset.y, width,
height, null);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for(int pos = 0; pos < triangles.size(); pos++){
Point[] current = triangles.get(pos);
ArrayList<Point> current_triangle = new ArrayList<Point>();
current_triangle.add(current[0]);
current_triangle.add(current[1]);
current_triangle.add(current[2]);
drawRegion(graphics, current_triangle);
}
}
}
finally {
graphics.dispose();
}
}
private void drawRegion(Graphics2D graphics, ArrayList<Point> points) {
graphics.setColor(trans_grey);
Area area = getArea(points);
graphics.fill(area);
graphics.setStroke(new BasicStroke(2));
graphics.setColor(Color.BLACK);
graphics.draw(area);
}
private Area getArea(ArrayList<Point> points) {
Area area = new Area(getPath(points, true));
return area;
}
private GeneralPath getPath(ArrayList<Point> points, boolean close_path) {
GeneralPath path = new GeneralPath();
Point current_screen_point = calculateScreenPoint(points.get(0));
path.moveTo(current_screen_point.x, current_screen_point.y);
for (int point_num = 1; point_num < points.size(); point_num++) {
current_screen_point = calculateScreenPoint(points.get(point_num));
path.lineTo(current_screen_point.x, current_screen_point.y);
}
if (close_path)
path.closePath();
return path;
}
public Point calculateScreenPoint(Point image_point) {
float h_proportion = (float) image_point.x / (float) image.getWidth();
float v_proportion = (float) image_point.y / (float) image.getHeight();
float image_width_in_panel = (float) image.getWidth() * magnification;
float image_height_in_panel = (float) image.getHeight() * magnification;
Point on_screen_point = new Point(0, 0);
on_screen_point.x = offset.x
+ Math.round(h_proportion * image_width_in_panel);
on_screen_point.y = offset.y
+ Math.round(v_proportion * image_height_in_panel);
return on_screen_point;
}
Your paintComponent leaves a little to be desired ;)
Firstly, you should never get a null graphics unless the paint method has been called in correctly, which in case they deserve for it to fail.
You should try and use Graphics.create to create a copy of the incoming Graphics context. This allows you to mess with the Graphics properties (such as transforms etc) without adversly effecting the original
I don't know what the image is all about, but basically, if its null, your triangles will never paint (don't know if this is what you want or not).
I don't know what the offset relates to, but just in case, the 0x0 point is always the top, left corner of your component.
public void paintComponent(Graphics g) {
// This is important, you will to have a very good reason not to call this
super.paintComponent(g);
System.out.println("in paint component");
// Should never need this. You should never call the paintComponent
// directly.
// if (g != null) {
// Create a copy of the graphics, with which you can play...
Graphics2D graphics = (Graphics2D) g.create();
try {
Rectangle back_rect = new Rectangle(0, 0, getWidth(),
getHeight());
graphics.setColor(Color.GREEN);
graphics.fill(back_rect);
// What's this trying to do...
// Where do you produce this???
// Because I didn't know where the image was been produced
// I commented out the code, but you should be aware
// That if the image is null, you triangles will never paint...
// if (image != null) {
// int width = Math.round(image.getWidth() * magnification);
// int height = Math.round(image.getHeight() * magnification);
//
// Rectangle image_rect = new Rectangle(offset.x, offset.y,
// width, height);
// graphics.setColor(Color.BLACK);
// graphics.draw(image_rect);
// graphics.drawImage(image, offset.x, offset.y, width,
// height, null);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int pos = 0; pos < triangles.size(); pos++) {
Point[] current = triangles.get(pos);
ArrayList<Point> current_triangle = new ArrayList<Point>(3);
current_triangle.add(current[0]);
current_triangle.add(current[1]);
current_triangle.add(current[2]);
drawRegion(graphics, current_triangle);
}
//} // From the image != null
} finally {
graphics.dispose();
}
}
I'd also suggest you have a read through
2D Graphics
Performing Custom Painting in Swing
If you haven't already ;)
This article will give you all the info you need http://java.sun.com/products/jfc/tsc/articles/painting/
but I think you are missing -
super.paintComponent(g);
public class MyPanel extends JPanel {
protected void paintComponent(Graphics g) {
// Let UI delegate paint first
// (including background filling, if I'm opaque)
super.paintComponent(g);
// paint my contents next....
}
}
I worked out how to draw triangles on the image, when passing through an arrayList, where each Point[] represents the points of the triangle.
Note that this is now in a single entire class which is passed the information, rather than trying to call repaint from another class.
public AnnotatedDisplayTriangles(BufferedImage image, String image_path, ArrayList<Point[]> triangles) {
this.image = image;
this.image_path = image_path;
this.triangles = triangles;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw image centered.
int x = (getWidth() - image.getWidth())/2;
int y = (getHeight() - image.getHeight())/2;
g.drawImage(image, x, y, this);
Stroke drawingStroke = new BasicStroke(2);
Graphics2D graph = (Graphics2D)g;
graph.setStroke(drawingStroke);
graph.setPaint(Color.black);
for(int p = 0; p < triangles.size(); p++){
Point[] current_triangles = triangles.get(p);
for(int triangle = 0; triangle < current_triangles.length; triangle++ ){
Point current = current_triangles[triangle];
Point next;
if(triangle == current_triangles.length -1 )
next = current_triangles[0];
else
next = current_triangles[triangle + 1];
Line2D line = new Line2D.Double(current.x, current.y, next.x, next.y);
graph.draw(line);
}
}
}
public static void main(String image_path,ArrayList<Point[]> triangles, String panel_name) throws IOException {
String path = image_path;
BufferedImage image = ImageIO.read(new File(path));
AnnotatedDisplayTriangles contentPane = new AnnotatedDisplayTriangles(image, path, triangles);
// You'll want to be sure this component is opaque
// since it is required for contentPanes. Some
// LAFs may use non-opaque components.
contentPane.setOpaque(true);
int w = image.getWidth();
int h = image.getHeight();
JFrame f = new JFrame(panel_name);
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(contentPane);
f.setSize(w,h);
f.setLocation(200,200);
f.setVisible(true);
}
I've got a JPanel I'm drawing onto and I'm trying to get it so that you can use the mouse wheel to "zoom" in and out by modifying the variable scale. Right now what happens is you can zoom in or out but all of the drawing shifts to the right and downwards when you zoom in and then back up and left when zooming out.
I want to make it so that it adjusts as if you were zooming in on the point at the center of the JPanel but I can't figure out how to calculate the right offset to translate by…
Anybody got an idea of how to do this, or even better a cleaner way of achieving this whole pan and zoom ability?
I am basically plotting a bunch of coordinates that came from a system where 0,0 is in the lower left corner and fall between the bounds:
xMin = 661208
xMax = 662618
yMin = 4291657
yMax = 4293285
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
double scale = details.getScale();
double xOffset = details.getxOffset();
double yOffset = details.getyOffset();
g2.scale(scale, -scale);
// Dividing offset by scale makes panning 1:1 with the cursor still. yMax to account for the
// fact we needed to flip around the y axis to make it right-side up.
g2.translate((-xMin + xOffset / scale), (-yMax + yOffset / scale));
// Code to draw stuff goes here. It uses coordinates between xMin-xMax and yMin-yMax to draw.
.
.
.
}
Here is an example with the wheel:
public class GraphicsOnly extends JPanel implements MouseWheelListener {
Shape[] shapes;
Dimension size;
double scale = 1.0;
private static int source = 100;
public GraphicsOnly() {
addMouseWheelListener(this);
size = new Dimension(10,10);
setBackground(new Color(240,200,200));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if(shapes == null) initShapes();
// Keep shapes centered on panel.
double x = (getWidth() - scale*size.width)/2;
double y = (getHeight() - scale*size.height)/2;
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
at.scale(scale, scale);
g2.setPaint(Color.blue);
g2.draw(at.createTransformedShape(shapes[0]));
g2.setPaint(Color.green.darker());
g2.draw(at.createTransformedShape(shapes[1]));
g2.setPaint(new Color(240,240,200));
g2.fill(at.createTransformedShape(shapes[2]));
g2.setPaint(Color.red);
g2.draw(at.createTransformedShape(shapes[2]));
}
public Dimension getPreferredSize() {
int w = (int)(scale*size.width);
int h = (int)(scale*size.height);
return new Dimension(w, h);
}
private void initShapes() {
shapes = new Shape[3];
int w = getWidth();
int h = getHeight();
shapes[0] = new Rectangle2D.Double(w/16, h/16, w*7/8, h*7/8);
shapes[1] = new Line2D.Double(w/16, h*15/16, w*15/16, h/16);
shapes[2] = new Ellipse2D.Double(w/4, h/4, w/2, h/2);
size.width = w;
size.height = h;
}
public static void main(String[] args) {
GraphicsOnly app = new GraphicsOnly();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(app));
f.setSize(400, 400);
f.setLocation(200,200);
f.setVisible(true);
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// TODO Auto-generated method stub
if(e.getWheelRotation() >0){
source = source-e.getScrollAmount();
}else{
source = source+e.getScrollAmount();
}
scale = source/100.0;
System.out.println(scale);
repaint();
revalidate();
}
}