Creating labels along with drawlines - java

I have asked a question regarding custom widget but confused to whether I need it and how should proceed.
I have currently this class
public class GUIEdge {
public Node node1;
public Node node2;
public int weight;
public Color color;
public GUIEdge(Node node1, Node node2 , int cost) {
this.node1 = node1;
this.node2 = node2;
this.weight = cost;
this.color = Color.darkGray;
}
public void draw(Graphics g) {
Point p1 = node1.getLocation();
Point p2 = node2.getLocation();
g.setColor(this.color);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
Currently this draws an edge between two points but now I want that a label for the cost also gets created along with it.
I have already added handling for dragging of node and edges so what is the best way to create the label
Do I need to make a custom widget for that ? Could anyone explain that suppose making a component by extending from JComponent then I'll call it by g.mixed() where mixed is that new widget...?

Tool tips are certainly worth a look. Other choices include drawString(), translate(), or TextLayout. There are many examples available.
Addendum: The example below shows both drawString() and setToolTipText(), as suggested by #Catalina Island. For simplicity, the endpoints are relative to the component's size, so you can see the result of resizing the window.
Addendum: This use of setToolTipText() merely demonstrates the approach. As #camickr notes here, you should override getToolTipText(MouseEvent) and update the tip when the mouse is over the line or when the line is selected.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JComponent;
import javax.swing.JFrame;
/** #see https://stackoverflow.com/questions/5394364 */
public class LabeledEdge extends JComponent {
private static final int N = 20;
private Point n1, n2;
public LabeledEdge(int w, int h) {
this.setPreferredSize(new Dimension(w, h));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
this.n1 = new Point(N, N);
this.n2 = new Point(getWidth() - N, getHeight() - N);
g.drawLine(n1.x, n1.y, n2.x, n2.y);
double d = n1.distance(n2);
this.setToolTipText(String.valueOf(d));
g.drawString(String.valueOf((int) d),
(n1.x + n2.x) / 2, (n1.y + n2.y) / 2);
}
private static void display() {
JFrame f = new JFrame("EdgeLabel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new LabeledEdge(320, 240));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
}

I think you can use GUIEdge extends JComponent. That way you'd get tool tip labels automatically.

Related

What should I have in paintComponent to drag and rotate an image?

I have worked with the school assignment for quite some time now. But I can not really understand what I should do. The assignment is due tomorrow and I feel quite stressed.
The task is, I'll get some pictures, have them in a window, then be able to move around them and also be able to rotate.
The big problem is that I do not know how I'll manage paintComponent().
What I read is that it should be called automatic "when needed" and when you call repaint(). I find it hard to get it to work.
The main class
import java.awt.*;
import javax.swing.*;
import java.util.*;
public class JFrameC extends JFrame{
JPanel panel;
ArrayList <ThePhoto> oneArray = new <ThePhoto> ArrayList();
public JFrameC(){
super("This window");
setLayout(new BorderLayout());
panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.setLayout(null);
add(panel);
setSize(500,500);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void addPicture(String name){
oneArray.add(new ThePhoto(name, this));
panel.add(oneArray.get(oneArray.size()-1).getJPanel());
}
public void draw(JPanel p){
//One of the tasks is that the image is pressed to end up on top.
//I thought that if I sort of an ArrayList so I can keep track of which one
//is on top. Then be able to paint them in order.
for(ThePhoto x : oneArray){
if(x.getJPanel() == p && oneArray.indexOf(x) != 0){
int i = oneArray.indexOf(x);
for(;i > 0; i--){
ThePhoto temp = oneArray.get(i);
oneArray.set(i, oneArray.get(i-1));
oneArray.set(i-1, temp);
}
break;
}
}
panel.validate();//I can not get this to work
panel.repaint();
getContentPane().validate();//Or this.
getContentPane().repaint();
}
public void paintComponent(Graphics g){
//Is this right?
//What should I write here?
}
public static void main(String[] args) {
JFrameC j = new JFrameC();
j.addPicture("one.gif");
j.addPicture("two.gif");
j.addPicture("three.gif");
j.addPicture("four.gif");
}
}
Class
import javax.swing.*;
import java.awt.*;
public class ThePhoto{
ImageIcon onePicture;
JLabel l;
JPanel p;
JFrameC k;
int posX = 10;
int posY = 10;
public ThePhoto(String name, JFrameC k){
this.k = k;
onePicture = new ImageIcon(name);
l = new JLabel(onePicture);
p = new JPanel();
p.setLayout(new CardLayout());
p.setBorder(null);
p.setBackground(null);
p.add(l);
p.setBounds(posX, posY, 100, 100);
p.addMouseListener(new HandleMouse(k, this));
p.addMouseMotionListener(new HandleMouse(k, this));
}
public void setX(int x){posX = x;}
public void setY(int y){posY = y;}
public JPanel getJPanel(){return p;}
public void paintComponent(Graphics g){
//Is this right?
//What should I write here?
}
}
MouseEvent Class
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.*;
import javax.swing.*;
public class HandleMouse extends MouseAdapter implements MouseMotionListener{
JFrame k;
ThePhoto b;
public HandleMouse(JFrameC k, ThePhoto b){
this.k = k;
this.b = b;
}
public void mouseClicked (MouseEvent e) {
k.draw((JPanel)e.getComponent());
}
public void mouseDragged (MouseEvent e) {
e.translatePoint(e.getComponent().getLocation().x, e.getComponent().getLocation().y);
e.getComponent().setLocation(e.getX(), e.getY());
b.setX(e.getX());
b.setY(e.getY());
}
public void mouseReleased(MouseEvent e) {
k.draw((JPanel)e.getComponent());
}
}
To summarize the issues clearer:
1.Is it best to call repaint() to Frame or Panel? As, I understand it is in both cases everything 'in' the container that will be repainted. And if so, should JFrame be preferable?
2.Is there any routine/usual/rule on what should be in the paintComponent()?
3.What advice and criticism whatsoever is very welcome. But please write so that a beginner understands and no unnecessary insults.
I understand that nobody wants to do my homework. But I only ask for some advice so that I can get better. I also want to write that I am a novice and therefore looks like my code to be written by a novice.
Solve the problem for a single image before trying for multiple images. Starting from this example, use ImageIO.read() to initialize an image and use drawImage() to render it in paintComponent().
private final BufferedImage image = getImage();
private BufferedImage getImage() {
try {
return ImageIO.read(new URL(
"http://i.imgur.com/kxXhIH1.jpg"));
} catch (IOException e) {
e.printStackTrace(System.err);
}
return null;
}
…
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image,
textPt.x - image.getWidth() / 2,
textPt.y - image.getHeight() / 2, this);
}
You can rotate the graphics context as shown here.

Successive drawing in a JFrame

As far as I can see, drawing in JFrame works like this:
Extends a JPanel and override its paint(Graphics g) method
add(panel) it to your JFrame
Is it possible to
Draw a line
Display that line
Then later add a second line
Show the new resulting image
...
Yes, you need to use
repaint()
every time after you drew something new, to display the modifications on the screen. On a repaint, the whole screen is redrawn, therefore you need to ensure you draw again everything you want to keep.
With respect to preserving previous content, this question might help you out.
AuroMetal pointed me to this tutorial and I extracted, that JPanel has no idea, what it has already drawn, so you have to maintain an ArrayList of everything to be drawn and cycle it through on every repaint():
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Canvas extends JPanel implements LinePrinter {
private static final long serialVersionUID = 1L;
private ArrayList<Line> lines = new ArrayList<Line>();
public Canvas(String title) {
// Just generating a JFrame to display this JPanel
JFrame frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 425);
frame.setVisible(true);
frame.add(this);
}
#Override
public void addLine(Line line) {
lines.add(line);
this.repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Line line : lines) {
g.drawLine(line.A.x, line.A.y, line.B.x, line.B.y);
}
}
}
where my custom Line class is
public class Line {
public final Point A, B;
public Line(Point A, Point B) {
this.A = A;
this.B = B;
}
}
and my custom Point class is
public class Point {
public final int x;
public final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

Graphics not appearing in Java Jframe Canvas

I am trying to draw a vine within a Jframe however the graphics are not appearing when I run the program. The idea is that a "Vine" is randomly generated and can randomly branch out into other "Vines". If anyone can help that would be much appreciated.
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Vine extends JPanel {
public void paint(Graphics g, int a, int b, int c)
{
super.paint(g);
g.setColor(Color.BLACK);
g.drawLine(10, 10, 100, 100);
grow(g, a, b, c);
}
public Boolean TF(){
Boolean A;
int B = ((int)Math.random()+1);
if (B==1){
A = true;
} else {
A = false;
}
return A;
}
public void grow(Graphics g, int a, int b, int c){
int x = a;
int y = b;
int age = c;
for (int i=0; i<= age; i++) {
if (TF()) {
if (TF()) {
grow(g, x, y, ((age-i)/2));
}
}
if (TF()){
if (TF()){
g.drawLine(x, y, (x+1), y);
x++;
} else {
g.drawLine(x, y, (x-1), y);
x++;
}
} else {
g.drawLine(x, y, x, (y+1));
y++;
}
}
}
public static void main(String[] args)
{
JFrame f = new JFrame("Vine");
f.setBounds(300, 300, 200, 120);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Vine panel = new Vine();
Container c = f.getContentPane();
panel.setBackground(Color.WHITE);
c.add(panel);
f.setResizable(true);
f.setVisible(true);
}
}
There are several issues with this code:
Nobody is calling the paint(Graphics g, int a, int b, int c) method. When you inherit from a Swing component like this, there are several methods that are invoked "automatically" (among them a paint(Graphics g) method). But in order to perform custom painting, you should usually override the paintComponent(Graphics g) method.
You are generating random numbers while you are painting. This will have some odd effects. The most obvious one: When you resize the frame (or something else happens that causes the frame to be repainted), a new random vine will appear. It will have nothing in common with the previous one, causing a flickering mess, in the best case.
In order to create reporoducible results that are still "random", I generally recommend to not use Math.random(). (In fact, I never use this, at all). The java.util.Random class is usually preferable. It allows creating reproducible random number sequences (which is helpful for debugging), and it offers convenient methods like Random#nextBoolean(), which has exactly the effect that you probably wanted to achieve with the (somewhat oddly implemented) TF() method. This leads to the next point...:
Use better variable- and method names. Naming variables like the c in your example, or methods TF(), will make the code unreadable. You may call them x and y if they refer to screen coordinates, but the c should probably be called age, and the TF() method ... heck, I don't know how this should be called, maybe something like shouldBranch()?
If you have to perform "extensive" computations (like the random branching, in your example), it is usually better to pull this computation out of the painting process. You can assemble the desired lines and paintings in various ways. For the given example, a simple Path2D should be sufficient.
So far, the technical things. Apart from that: The algorithm itself will not lead to "nice" results. The random branching for each pixel will cause the lines to clob together to a black, fuzzy spot.
In fact, it can be pretty hard to tweak this to create "nice" results. It is hard to exactly say how the "randomness" should influence the overall appearance. The branching should be random, the angles should be random, the branch lengths should be random. Additionally, it will always look a bit odd when all the lines are drawn with the same thickness. The thickness of the lines should probably decrease at each branch, and along the branches in general.
In some practical applications, this generation of random plant-like structures is done with Lindenmayer systems - this may be a starting point for further research.
However, here is a very simple example of how simple lines can be assembled, somewhat randomly, to create something that resembles a plant:
Of course, this looks like cr*p compared to what one could do, given enough time and incentive. But it consists only of a few lines of code that randomly assemble a few branching lines, so this is as good as it gets without considering all the possible improvements.
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class VinePainting
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
private void createAndShowGUI()
{
JFrame f = new JFrame("Vine");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
VinePanel panel = new VinePanel();
Container c = f.getContentPane();
panel.setBackground(Color.WHITE);
c.add(panel);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
class VinePanel extends JPanel
{
private static final Random RANDOM = new Random(0);
private Path2D vine;
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
if (vine == null)
{
vine = createVine(getWidth()/2, getHeight());
}
g.setColor(Color.BLACK);
g.draw(vine);
}
private Path2D createVine(int x, int y)
{
Path2D path = new Path2D.Double();
double angleRad = Math.toRadians(-90);
grow(path, x, y, angleRad, 10.0, 0, 30);
return path;
}
private static void grow(Path2D path,
double x, double y, double angleRad,
double stepSize, int step, int steps)
{
if (step == steps)
{
return;
}
path.moveTo(x, y);
double dirX = Math.cos(angleRad);
double dirY = Math.sin(angleRad);
double distance = random(stepSize, stepSize + stepSize);
double newX = x + dirX * distance;
double newY = y + dirY * distance;
path.lineTo(newX, newY);
final double angleRadDeltaMin = -Math.PI * 0.2;
final double angleRadDeltaMax = Math.PI * 0.2;
double progress = (double)step / steps;
double branchProbability = 0.3;
double branchValue = RANDOM.nextDouble();
if (branchValue + 0.1 < branchProbability)
{
double angleRadDelta = random(angleRadDeltaMin, angleRadDeltaMax);
double newAngleRad = angleRad + angleRadDelta;
double newStepSize = (1.0 - progress * 0.1) * stepSize;
grow(path, newX, newY, newAngleRad, newStepSize, step+1, steps);
}
double angleRadDelta = random(angleRadDeltaMin, angleRadDeltaMax);
double newAngleRad = angleRad + angleRadDelta;
double newStepSize = (1.0 - progress * 0.1) * stepSize;
grow(path, newX, newY, newAngleRad, newStepSize, step+1, steps);
}
private static double random(double min, double max)
{
return min + RANDOM.nextDouble() * (max - min);
}
}
A side note: This is somewhat similiar to this question. But there, randomness did not play a role, so the recursion is done while painting the tree. (Therefore, it allows playing around with some sliders, to modify the parameters and observe the effects).
Here's a GUI I created using your code. I didn't understand how your grow method worked, so I pretty much left it alone.
Here's what I did.
Created a GrowVine method to hold the line segments to draw. I also created a LineSegment class to hold one line segment. I did this so that I could keep the model data separate from the view. By separating concerns, I could focus on one part of the problem at a time.
I started the Swing application with a call to the SwingUtilities invoke later method. This ensures that the Swing components are created and used on the Event Dispatch thread.
I created a DrawingPanel from a JPanel, and overrode the paintComponent method. Notice that my override code does nothing but draw. All of the calculations are done in the GrowVine class.
I greatly simplified your TF method and renamed it to coinFlip. coinFlip better indicates to future readers of the code (including yourself) that the boolean should be true half the time and false half the time.
I left your grow method alone. I removed the drawLine methods and had the grow method write line segments to the List.
Once you get your grow method working, you can run the GrowVine class in a separate thread to animate the drawing of the vine.
Here's the code. I hope this helps you.
package com.ggl.testing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Vine implements Runnable {
#Override
public void run() {
JFrame frame = new JFrame("Vine");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DrawingPanel panel = new DrawingPanel(400, 400);
frame.add(panel);
frame.pack();
frame.setVisible(true);
new GrowVine(panel, 400, 400).run();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Vine());
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -8460577623396871909L;
private List<LineSegment> lineSegments;
public DrawingPanel(int width, int height) {
this.setPreferredSize(new Dimension(width, height));
this.lineSegments = new ArrayList<>();
}
public void setLineSegments(List<LineSegment> lineSegments) {
this.lineSegments = lineSegments;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
for (int i = 0; i < lineSegments.size(); i++) {
LineSegment ls = lineSegments.get(i);
Point s = ls.getStartPoint();
Point e = ls.getEndPoint();
g.drawLine(s.x, s.y, e.x, e.y);
}
}
}
public class GrowVine implements Runnable {
private int width;
private int height;
private DrawingPanel drawingPanel;
private List<LineSegment> lineSegments;
public GrowVine(DrawingPanel drawingPanel, int width, int height) {
this.drawingPanel = drawingPanel;
this.lineSegments = new ArrayList<>();
lineSegments.add(new LineSegment(10, 10, width - 10, height - 10));
this.width = width;
this.height = height;
}
#Override
public void run() {
grow(width / 2, height / 2, 200);
drawingPanel.setLineSegments(lineSegments);
}
public void grow(int a, int b, int c) {
int x = a;
int y = b;
int age = c;
for (int i = 0; i <= age; i++) {
if (coinFlip()) {
if (coinFlip()) {
grow(x, y, ((age - i) / 2));
}
}
if (coinFlip()) {
if (coinFlip()) {
lineSegments.add(new LineSegment(x, y, (x + 1), y));
x++;
} else {
lineSegments.add(new LineSegment(x, y, (x - 1), y));
x++;
}
} else {
lineSegments.add(new LineSegment(x, y, x, (y + 1)));
y++;
}
}
}
private boolean coinFlip() {
return Math.random() < 0.50D;
}
}
public class LineSegment {
private final Point startPoint;
private final Point endPoint;
public LineSegment(int x1, int y1, int x2, int y2) {
this.startPoint = new Point(x1, y1);
this.endPoint = new Point(x2, y2);
}
public Point getStartPoint() {
return startPoint;
}
public Point getEndPoint() {
return endPoint;
}
}
}
There are lots of implementation issues here. You can override the paintComponent of the JPanel to paint in it. Here is a quick fix to demonstrate how this is done. I changed your implementation a lot to fix the issues so you can get an idea.
Note: This was a quick fix. So the code quality is low and some OOP concepts were ignored. Just go through this and understand how this work and implement your own code.
Explanation:
You have to call repaint method of JPanel class to make it repaint itself. It will paint all Lines in the LinkedList to the panel too(refer the implementation). After repainting, create a new random line and add it to the LinkedList. It will be painted next time.
Then we have to animate this. So I implemented runnable interface in the Vine class. run method will be called when we add this vine object(panel) to a Thread and start() the thread. We need to run it forever. So add a loop in the run method. Then repaint the panel every time run method is called. This animation is too fast, so add a Thread.sleep() to slow down the animation.
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.util.LinkedList;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Line {//Line class to store details of lines
int x1, y1, x2, y2;
public Line(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
}
class Vine extends JPanel implements Runnable {//implements runnable to animate it
LinkedList<Line> lines = new LinkedList<>();//LinkedList to store lines
int x = 10;
int y = 10;
Line line;
public Boolean TF() {
Boolean A;
int B = (int) (Math.random() * 2 + 1);//logical error fixed
if (B == 1) {
A = true;
} else {
A = false;
}
return A;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
for (Line line : lines) {//paint all lines in the LinkedList
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
//Create and add a next line
if (TF()) {
if (TF()) {
line = new Line(x, y, (x + 1), y);
lines.add(line);
x++;
} else {
line = new Line(x, y, (x - 1), y);
lines.add(line);
x++;
}
} else {
line = new Line(x, y, x, (y + 1));
lines.add(line);
y++;
}
}
private static Vine panel;
public static void main(String[] args) {
JFrame f = new JFrame("Vine");
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new Vine();
Container c = f.getContentPane();
panel.setBackground(Color.WHITE);
c.add(panel);
f.setResizable(true);
f.setVisible(true);
panel.start();//start the animation(thread)
}
private void start() {
Thread thread = new Thread(this);
thread.start();
}
#Override
public void run() {
while (true) {
try {
Thread.sleep(100);//slow down the animation
panel.repaint();//then repaint the panel
} catch (InterruptedException ex) {
}
}
}
}

Java Graphics not displaying on successive function calls, why?

I'm making a visualization for a BST implementation (I posted another question about it the other day). I've created a GUI which displays the viewing area and buttons. I've added code to the BST implementation to recursively traverse the tree, the function takes in coordinates along with the Graphics object which are initially passed in by the main GUI class. My idea was that I'd just have this function re-draw the tree after every update (add, delete, etc...), drawing a rectangle over everything first to "refresh" the viewing area. This also means I could alter the BST implementation (i.e by adding a balance operation) and it wouldn't affect the visualization.
The issue I'm having is that the draw function only works the first time it is called, after that it doesn't display anything. I guess I don't fully understand how the Graphics object works since it doesn't behave the way I'd expect it to when getting passed/called from different functions. I know the getGraphics function has something to do with it.
Relevant code:
private void draw(){
Graphics g = vPanel.getGraphics();
tree.drawTree(g,ORIGIN,ORIGIN);
}
vPanel is what I'm drawing on
private void drawTree(Graphics g, BinaryNode<AnyType> n, int x, int y){
if( n != null ){
drawTree(g, n.left, x-10,y+10 );
if(n.selected){
g.setColor(Color.blue);
}
else{
g.setColor(Color.gray);
}
g.fillOval(x,y,20,20);
g.setColor(Color.black);
g.drawString(n.element.toString(),x,y);
drawTree(g,n.right, x+10,y+10);
}
}
It is passed the root node when it is called by the public function. Do I have to have:
Graphics g = vPanel.getGraphics();
...within the drawTree function? This doesn't make sense!!
Thanks for your help.
This is not the right way of doing it. If you want a component that displays the tree, you should make your own JComponent and override the paintComponent-method.
Whenever the model (the tree / current node etc) changes, you invoke redraw() which will trigger paintComponent.
I actually don't think you are allowed to fetch the Graphics object from anywhere else than the argument of the paintComponent method.
Try out the following program
import java.awt.Graphics;
public class FrameTest {
public static void main(String[] args) {
final JFrame f = new JFrame("Frame Test");
f.setContentPane(new MyTreeComponent());
f.setSize(400, 400);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
new Thread() {
public void run() {
for (int i = 0; i < 10; i++) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.repaint();
}
}
}.start();
}
}
class MyTreeComponent extends JComponent {
public void paintComponent(Graphics g) {
// Draw your tree. (Using random here to visualize the updates.)
g.drawLine(30, 30, 50, 30 + new Random().nextInt(20));
g.drawLine(30, 30, 50, 30 - new Random().nextInt(20));
}
}
The best starting point is probably http://java.sun.com/docs/books/tutorial/uiswing/painting/index.html
#aioobe's approach is sound and the example is compelling. In addition to the cited tutorial, Performing Custom Painting, I would add that drawing should take place on the Event Dispatch Thread (EDT). In the variation below, note how the GUI is built using EventQueue.invokeLater. Similarly, the actionPerformed() method of javax.swing.Timer invokes repaint() on the EDT to display recently added nodes. A more elaborate example may be found here.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
public class StarPanel extends JPanel implements ActionListener {
private static final Random rnd = new Random();
private final Timer t = new Timer(100, this);
private final List<Node> nodes = new ArrayList<Node>();
private static class Node {
private Point p;
private Color c;
public Node(Point p, Color c) {
this.p = p;
this.c = c;
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Star Topology");
f.add(new StarPanel());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
});
}
public StarPanel() {
this.setPreferredSize(new Dimension(400, 400));
t.start();
}
#Override // Respond to the Timer
public void actionPerformed(ActionEvent e) {
int w = this.getWidth();
int h = this.getHeight();
nodes.add(new Node(
new Point(rnd.nextInt(w), rnd.nextInt(h)),
new Color(rnd.nextInt())));
this.repaint();
}
#Override
public void paintComponent(Graphics g) {
int w2 = this.getWidth() / 2;
int h2 = this.getHeight() / 2;
for (Node n : nodes) {
g.setColor(n.c);
int x = n.p.x;
int y = n.p.y;
g.drawLine(w2, h2, x, y);
g.drawLine(w2, h2, x, y);
g.drawRect(x - 2, y - 2, 4, 4);
}
}
}

How to make a curve to join two objects editable?

I'm involved in a project in which we're doing a visual editor (written in Java). Now, I'm trying to make curves that join two different objects that I'm painting in a class that extends JPanel (this class is what I'm using to paint, inside a JFrame, overriding the method paintComponent). I'm in troubles because I'm using the class QuadCurve2D to make this, but I cannot make it clickable (I'm using the method contains, but it doesn't work everytime), make it editable (for example, setting a square in its middle point to modify its curvature. The point that is used on the middle of the QuadCurve2D when the constructor is called is outside the curve) or something (method, variable, iterator, etc) that could tell me which Points are in the QuadCurve2D.
After looking for all of that some time, I have no answer, so I'm trying posting it here to find a solution. Is there anyway to make it with the QuadCurve2D class, or do I have to try with some external library?
First of all sorry for the long reply. I am now posting a complete answer to your question. I am sub classing the QuadCurve2D.Double class and with a little math now you define the curve with a start,end and a middle point instead of a control point. Also i have created a new method that checks whether a point is on the curve. The intersects method checks if the convex hull of the shape intersects with the provided shape so in the case of the concave curve this is functional but not accurate. Note that my implementation of the method to check whether a point is on the curve is rather computationally expensive and not 100% accurate since i am checking along the curve length with a specified resolution (0 is the beginning of the curve, 1 is the end. So in the example provided i am checking with a resolution of 0.01 meaning 100 checks are made along the curve). For that matter make sure that the provided step in the resolution is a divider of 0.5 (the middle point) so that you may be able to select it. If that makes no sense don't pay attention it doesn't really matter, you can use my example out of the box. Note that i also provide a check box to switch between the intersects method and my own for checking whether the mouse is on the curve. And when using my new method, i also provide a slider to specify the resolution so that you may see the effects of various values. Here are the classes.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
#SuppressWarnings("serial")
public class CurvePanel extends JPanel implements MouseListener,MouseMotionListener{
Point2D startPoint = new Point2D.Double(50, 50);
Point2D middlePoint = new Point2D.Double(100,80);
Point2D endPoint = new Point2D.Double(200, 200);
Point2D[] points = new Point2D[] {startPoint,middlePoint,endPoint};
QuadCurveWithMiddlePoint curve;
private Point2D movingPoint;
private boolean dragIt = false;
private boolean showControls = false;
JCheckBox useNewMethod;
JSlider resolution;
public CurvePanel() {
setPreferredSize(new Dimension(300,300));
addMouseListener(this);
addMouseMotionListener(this);
curve = new QuadCurveWithMiddlePoint();
useNewMethod = new JCheckBox("Use new \"contains\" method");
resolution = new JSlider(JSlider.HORIZONTAL,1,10,1);
useNewMethod.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
resolution.setEnabled(useNewMethod.isSelected());
}
});
useNewMethod.setSelected(false);
resolution.setEnabled(false);
setCurve();
}
private void setCurve() {
curve.setCurveWithMiddlePoint(startPoint, middlePoint, endPoint);
}
public static void main(String[] args) {
JFrame f = new JFrame("Test");
CurvePanel panel = new CurvePanel();
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(panel.useNewMethod,BorderLayout.NORTH);
f.getContentPane().add(panel,BorderLayout.CENTER);
f.getContentPane().add(panel.resolution,BorderLayout.SOUTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
for (Point2D point : points) {
if (e.getPoint().distance(point) <= 2) {
movingPoint = point;
dragIt = true;
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
dragIt = false;
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragIt) {
movingPoint.setLocation(e.getPoint());
setCurve();
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
if (useNewMethod.isSelected())
showControls = curve.pointOnCurve(e.getPoint(), 2, resolution.getValue()/100.0);
else
showControls = curve.intersects(e.getX()-2, e.getY()-2, 4, 4);
repaint();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.white);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(Color.black);
g2.draw(curve);
if (showControls)
for (Point2D point : points) {
g2.setPaint(Color.black);
g2.drawOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
g2.setPaint(Color.red);
g2.fillOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
}
}
}
And also :
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D.Double;
#SuppressWarnings("serial")
public class QuadCurveWithMiddlePoint extends Double {
private Point2D middlePoint = new Point2D.Double();
private final double L = 0.5;
public QuadCurveWithMiddlePoint(double x1,double y1, double xm, double ym, double x2, double y2) {
super(x1,y1,xm,ym,x2,y2);
setMiddlePoint(xm, ym);
}
public QuadCurveWithMiddlePoint() {
this(0,0,0,0,0,0);
}
public Point2D getMiddlePoint() {
calculateMiddlePoint();
return middlePoint;
}
public void setMiddlePoint(double middleX, double middleY) {
setCurve(getP1(), getControlPointByMiddle(middleX, middleY), getP2());
calculateMiddlePoint();
}
public void setMiddlePoint(Point2D middle) {
setMiddlePoint(middle.getX(),middle.getY());
}
private Point2D getControlPointByMiddle(double middleX,double middleY) {
double cpx = (middleX-(L*L-2*L+1)*x1-(L*L)*x2)/(-2*L*L+2*L);
double cpy = (middleY-(L*L-2*L+1)*y1-(L*L)*y2)/(-2*L*L+2*L);
return new Point2D.Double(cpx,cpy);
}
private Point2D calculatePoint(double position) {
if (position<0 || position>1)
return null;
double middlex = (position*position-2*position+1)*x1+(-2*position*position+2*position)*ctrlx+(position*position)*x2;
double middley = (position*position-2*position+1)*y1+(-2*position*position+2*position)*ctrly+(position*position)*y2;
return new Point2D.Double(middlex,middley);
}
public void calculateMiddlePoint() {
middlePoint.setLocation(calculatePoint(L));
}
public void setCurveWithMiddlePoint(double xx1,double yy1, double xxm, double yym, double xx2, double yy2) {
setCurve(xx1, yy1, xxm, yym, xx2, yy2);
setMiddlePoint(xxm,yym);
}
public void setCurveWithMiddlePoint(Point2D start, Point2D middle, Point2D end) {
setCurveWithMiddlePoint(start.getX(),start.getY(),middle.getX(),middle.getY(),end.getX(),end.getY());
}
public boolean pointOnCurve(Point2D point, double accuracy, double step) {
if (accuracy<=0)
return false;
if (step<=0 || step >1)
return false;
boolean oncurve = false;
double current = 0;
while (!oncurve && current <= 1) {
if (calculatePoint(current).distance(point)<accuracy)
oncurve = true;
current += step;
}
return oncurve;
}
}
If you want to know how i made the class, do a search for basic linear algebra and also search Wikipedia for Bézier curves.

Categories

Resources