0

How it looks

The stickman is connected to the bubble by a line. When I move for example, Jimmy, I want the line that connects Jimmy to the Fruits he sells to be maintained. The same goes when I drag Fruit.

But somehow this is not working. When I drag the stickman or the bubble, the lines got disjointed. enter image description here

Here are my codes if anyone would like to try and run it. I've tried to include only the relevant stuffs.

Example class

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Example extends JPanel {

private static List<Person> persons;
private static List<Fruit> fruits;
private static List<LineTest> lines;
private Point mousePt;
private static Font setFont;
private Random randomGenerator;
private Person person;
private Fruit bubble;
private LineTest line;
private static final int W = 640;    
private static final int H = 480; 

public Example() {
    persons = new ArrayList<Person>();  // Stores the person's names & coords
    fruits = new ArrayList<Fruit>(); // Stores the person's name and what fruits he sells & coords
    lines = new ArrayList<LineTest>(); // Stores the person's name, fruits he sells & coords
    randomGenerator = new Random();
    setFont = new Font("Sans Serif", Font.BOLD, 12);

    String person1 = "Jimmy";
    String person2 = "Sally";

    person = new Person(person1, 50,50);
    addPerson(person);
    person = new Person(person2, 50,150);
    addPerson(person);

    String fruit1 = "Banana";
    String fruit2 = "Apple";
    String fruit3 = "Orange";
    String fruit4 = "Watermelon";
    String fruit5 = "Pineapple";
    String fruit6 = "Grapes";

    bubble = new Fruit(person1, fruit1, setFont, 150, 50);
    addFruit(bubble);
    bubble = new Fruit(person1, fruit2, setFont, 150, 100);
    addFruit(bubble);
    bubble = new Fruit(person1, fruit3, setFont, 150, 150);
    addFruit(bubble);
    bubble = new Fruit(person2, fruit4, setFont, 150, 200);
    addFruit(bubble);
    bubble = new Fruit(person2, fruit5, setFont, 150, 250);
    addFruit(bubble);
    bubble = new Fruit(person2, fruit6, setFont, 150, 300);
    addFruit(bubble);

    for (int i=0; i<persons.size();i++) {
        for (int j=0; j<fruits.size();j++) {
            // If the same person in the person's list can be found in the fruits list
            // draw a line between the Person and the Fruit
            if (persons.get(i).getPerson().equals((fruits.get(j).getPerson()))) {
                int personX = persons.get(i).getCoorX();
                int personY = persons.get(i).getCoorY();
                int fruitX = fruits.get(j).getCoorX();
                int fruitY = fruits.get(j).getCoorY();
                line = new LineTest(persons.get(i).getPerson(), fruits.get(j).getFruit(), personX, personY, fruitX, fruitY);
                addLine(line);
            }
        }
    } 

    this.setFont(setFont);    
    this.addMouseListener(new MouseAdapter() {    
        @Override    
        public void mousePressed(MouseEvent e) {    
            mousePt = e.getPoint();  
            for (Person p:persons) {  
                p.select(mousePt.x, mousePt.y);
            }  

            for (Fruit f:fruits) {
                f.select(mousePt.x, mousePt.y);
            }
        }  

        public void mouseReleased(MouseEvent e) {  
            for (Person p:persons) {
                p.unselect();
            }

            for(Fruit f:fruits) {
                f.unselect();
            }
        }  
    });    
    this.addMouseMotionListener(new MouseMotionAdapter() {    

        @Override    
        public void mouseDragged(MouseEvent e) {    
            mousePt = e.getPoint();  
            for (Person s:persons) {
                s.move(mousePt.x, mousePt.y);
                int personX = mousePt.x;
                int personY = mousePt.y;
                for(int k=0; k<lines.size(); k++) {
                    // If the same person in the person's list can be found in the fruits list
                    // move the point on the Person to a new coords
                    if(s.person.equals(lines.get(k).person)) {
                        lines.get(k).move(personX, personY);
                    }
                }
            }

            for(Fruit f:fruits) {
                f.move(mousePt.x, mousePt.y);
                int fruitX = mousePt.x;
                int fruitY = mousePt.y;
                for(int k=0; k<lines.size(); k++) {
                    if(f.person.equals(lines.get(k).person)) {
                        lines.get(k).move(fruitX, fruitY);
                    }
                }
            }
            repaint();    
        }    
    }); 
}

public void addPerson(Person person) {    
    persons.add(person);  
    repaint();    
} 

public void addFruit (Fruit fruit) {    
    fruits.add(fruit);    
    repaint();    
} 

public void addLine(LineTest line) {
    lines.add(line);
    repaint();
}

@Override    
public void paintComponent(Graphics g) {    
    super.paintComponent(g);  
    Graphics2D g2 = (Graphics2D)g.create();

    for (Person p:persons) {    
        p.paint(g2); 
    }   
    for (LineTest l:lines) {
        l.paint(g2);
    }
    for (Fruit f:fruits) {    
        f.paint(g2);
    } 
}    

@Override    
public Dimension getPreferredSize() {    
    return new Dimension(W, H);    
}    

public static void main(String[] args) {    
    EventQueue.invokeLater(new Runnable() {    
        @Override    
        public void run() {    
            JFrame f = new JFrame();    
            f.add(new Example());    
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);    
            f.pack();    
            f.setLocationRelativeTo(null);    
            f.setVisible(true);     
        }    
    });    
}    
}

Person class

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;

public class Person extends Rectangle {    
String person;
int x,y;  
int tx, ty;  
boolean isSelected = false;

public Person(String person, int x, int y) {  
    this.person = person;
    this.x = x;  
    this.y = y; 
    this.setBounds(x-10,y-10,40,90);

    isSelected = true;  
    move(x, y);  
    isSelected = false;  
}  

public void select(int x, int y){
    if(this.contains(x,y)) {
        isSelected=true; 
    } 
}  

public void unselect(){  
    isSelected = false;  
}

public void move(int x, int y) {  
    if(isSelected) {  
        LineTest.isPersonMoved = true;
        LineTest.isFruitMoved = false;
        tx = x;  
        ty= y;  
        this.translate(tx-this.x, ty-this.y); 
        this.x = tx;  
        this.y = ty;  
    }  
}  

public void paint(Graphics g) {  
    Graphics2D g2 = (Graphics2D)g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.drawOval(x, y, 20, 20); // head
    g2.drawLine(x+10,y+20,x+10,y+50); // body
    g2.drawLine(x+10,y+20,x+25,y+40); // right hand
    g2.drawLine(x+10,y+20,x-5,y+40); // left hand
    g2.drawLine(x+10,y+50,x-5,y+70); // left leg
    g2.drawLine(x+10,y+50,x+25,y+70); // right leg
    g2.drawString(person, tx-15, ty+85);
}  

public String getPerson() {
    return person;
}

public int getCoorX() {
    return x;
}

public int getCoorY() {
    return y;
}
} 

Fruit class

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;

public class Fruit extends Rectangle {

private static final long serialVersionUID = 1L;
String fruit, person;
Font _font;
int x, y, tx, ty;
public static int height, width, ovalWidth, ovalHeight;
boolean isSelected;
public static FontMetrics getMetrics;
public static Graphics2D g2;

public Fruit(String person, String fruit, Font font, int x, int y) {
    this.person = person;
    this.fruit = fruit;
    this._font = font;
    this.x = x;
    this.y = y;
    this.setBounds(x, y, ovalWidth, ovalHeight);

    isSelected = true;  
    move(x, y);  
    isSelected = false; 

}

public void select(int x, int y){
    if(this.contains(x,y)) {
        isSelected=true; 
    } 
} 

public void unselect(){  
    isSelected = false;  
}

public void move(int x, int y) {  
    if(isSelected) {  
        LineTest.isPersonMoved = false;
        LineTest.isFruitMoved = true;

        tx = x;  
        ty= y;  
        this.translate(tx-this.x, ty-this.y);
        this.x = tx;  
        this.y = ty;  
    }  
} 

public void paint(Graphics g) {
    g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    getMetrics = g2.getFontMetrics(_font);
    height = getMetrics.getHeight();
    width = getMetrics.stringWidth(fruit);

    ovalWidth = width+25;
    ovalHeight = height+25;
    g2.setColor(Color.WHITE);
    g2.fillOval(x, y, ovalWidth, ovalHeight);
    g2.setColor(Color.BLACK);
    g2.drawOval(x, y, ovalWidth, ovalHeight);

    int centreX = x + ovalWidth/2;
    int centreY = y + ovalHeight/2;
    g2.drawString(fruit, (int) (centreX - width/2), (int) (centreY + height/4));

    this.setBounds(x, y, ovalWidth, ovalHeight);
}

public String getPerson() {
    return person;
}

public String getFruit() {
    return fruit;
}

public int getCoorX() {
    return x;
}

public int getCoorY() {
    return y;
}

}

LineTest class

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;

/*
 * Draw line that connect between Person and Fruit
 */
public class LineTest {
int x1, y1, x2, y2, tx, ty;
String fruit, person;
public static boolean isPersonMoved, isFruitMoved;

public LineTest(String person, String fruit, int x1, int y1, int x2, int y2) {
    this.person = person;
    this.fruit = fruit;

    // Get x, y coordinates from person bound
    this.x1 = x1+35;
    this.y1 = y1+35;

    // Get x, y coordinates from fruit bound
    this.x2 = x2+30;
    this.y2 = y2+30;  
}

public void move(int x, int y) {
    if (isPersonMoved) {
        System.out.println("LineTest - isPersonMoved: " + isPersonMoved);
        tx = x;
        ty = y;
        this.x1 = tx+35;
        this.y1 = ty+35;
    } else if (isFruitMoved) {
        System.out.println("LineTest - isFruitMoved: " + isFruitMoved);
        tx = x;
        ty = y;
        this.x2 = tx+30;
        this.y2 = ty+30;
    }
}

public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D)g.create();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.drawLine(x1, y1, x2, y2);
}

public String getPerson() {
    return person;
}

public String getFruit() {
    return fruit;
}

public Point getFstCoor() {
    return new Point (x1, y1);
}

public Point getSndCoor() {
    return new Point(x2, y2);
}
}
sw2
  • 357
  • 6
  • 13
  • 1
    What I would do is provide some kind of relationship between your objects, so given one or the other, you can determine what object(s) relate to it, from this, you can determine the source and target of the line. You could create a Line class which takes two objects and simply maintains a connection between them. In either case, u til you need to draw the line, you should not be maintaining any information about its position – MadProgrammer Jul 31 '15 at 22:23
  • I would also add custom controls to the panel, rather than keeping the objects separate and being drawing rules on the JPanel. Make them draw themselves, not having the JPanel draw everything. – durron597 Jul 31 '15 at 22:29
  • 1
    Some graph editors are suggested [here](http://stackoverflow.com/a/15702152/230513). – trashgod Jul 31 '15 at 22:54

2 Answers2

3

The basic idea is, you need to generate some kind of relationship between the objects you want to link. You could make this relationship implicit (Person contains Fruit) or non-implicit, where the relationship is stored/managed externally, which you do is up to you (I like the implicit approach as it sets out your intentions)

Your codes a bit, odd, sorry, but it is, so I've made some modifications. I did think of using a Path2D for a lot of it, but that would mean everything code painted in the same color. The main point is to define some kind of commonality between the objects, while strictly speaking not required, because almost all the work between them is the same, why not...

public interface Paintable {
    public void paint(JComponent parent, Graphics2D g2d);
    public boolean contains(Point p);
    public void moveTo(Point2D p);
    public Rectangle2D getBounds();
}

Then I created a class to manage the relationships...

public class Relationship {

    private Paintable parent;
    private Paintable child;

    public Relationship(Paintable parent, Paintable child) {
        this.parent = parent;
        this.child = child;
    }

    public Paintable getChild() {
        return child;
    }

    public Paintable getParent() {
        return parent;
    }

}

Now, this means that your fruits could belong to more then one person (or other fruits), so if this goes against your rules, you will need to devise a different relationship algorithm (maybe something more implicit)

Now, when ever you update the UI, you simply paint the relationships and the objects...

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();

    for (Relationship relationship : relationships) {

        Point2D p1 = new Point2D.Double(relationship.getParent().getBounds().getCenterX(), relationship.getParent().getBounds().getCenterY());
        Point2D p2 = new Point2D.Double(relationship.getChild().getBounds().getCenterX(), relationship.getChild().getBounds().getCenterY());

        g2.draw(new Line2D.Double(p1, p2));

    }

    for (Person p : persons) {
        p.paint(this, g2);
    }
    for (Fruit f : fruits) {
        f.paint(this, g2);
    }

    g2.dispose();
}

Move me

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Example extends JPanel {

    private List<Person> persons;
    private List<Fruit> fruits;
    private Point2D offset;
    private static Font baseFont;
    private Random randomGenerator;
    private Person person;
    private Fruit bubble;
    private static final int W = 640;
    private static final int H = 480;

    private Paintable selectedShape;

    private List<Relationship> relationships;

    public Example() {
        persons = new ArrayList<>();  // Stores the person's names & coords
        fruits = new ArrayList<>(); // Stores the person's name and what fruits he sells & coords
        relationships = new ArrayList<>(25);

        randomGenerator = new Random();
        baseFont = new Font("Sans Serif", Font.BOLD, 12);

        String person1 = "Jimmy";
        String person2 = "Sally";


        String fruit1 = "Banana";
        String fruit2 = "Apple";
        String fruit3 = "Orange";
        String fruit4 = "Watermelon";
        String fruit5 = "Pineapple";
        String fruit6 = "Grapes";

        Person person = new Person(person1, 50, 50);
        addPerson(person);

        Fruit bubble = new Fruit(fruit1, baseFont, 150, 50);
        addFruit(bubble);
        relate(person, bubble);
        bubble = new Fruit(fruit2, baseFont, 150, 100);
        addFruit(bubble);
        relate(person, bubble);
        bubble = new Fruit(fruit3, baseFont, 150, 150);
        addFruit(bubble);
        relate(person, bubble);

        person = new Person(person2, 50, 150);
        addPerson(person);

        bubble = new Fruit(fruit4, baseFont, 150, 200);
        addFruit(bubble);
        relate(person, bubble);
        bubble = new Fruit(fruit5, baseFont, 150, 250);
        addFruit(bubble);
        relate(person, bubble);
        bubble = new Fruit(fruit6, baseFont, 150, 300);
        addFruit(bubble);
        relate(person, bubble);

        this.setFont(baseFont);
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                for (Paintable p : getShapes()) {
                    if (p.contains(e.getPoint())) {
                        // Selected
                        selectedShape = p;
                        offset = new Point2D.Double(e.getX() - p.getBounds().getX(), e.getY() - p.getBounds().getY());
                        break;
                    }
                }
            }

            public void mouseReleased(MouseEvent e) {
                selectedShape = null;
            }
        });
        this.addMouseMotionListener(new MouseMotionAdapter() {

            @Override
            public void mouseDragged(MouseEvent e) {
                if (selectedShape != null) {

                    Point2D p = new Point2D.Double(e.getX() - offset.getX(), e.getY() - offset.getX());

                    selectedShape.moveTo(p);
                }
                repaint();
            }
        });
    }

    protected List<Paintable> getShapes() {
        ArrayList<Paintable> shapes = new ArrayList<>(fruits);
        shapes.addAll(persons);
        return shapes;
    }

    public void addPerson(Person person) {
        persons.add(person);
        repaint();
    }

    public void addFruit(Fruit fruit) {
        fruits.add(fruit);
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g.create();

        for (Relationship relationship : relationships) {

            Point2D p1 = new Point2D.Double(relationship.getParent().getBounds().getCenterX(), relationship.getParent().getBounds().getCenterY());
            Point2D p2 = new Point2D.Double(relationship.getChild().getBounds().getCenterX(), relationship.getChild().getBounds().getCenterY());

            g2.draw(new Line2D.Double(p1, p2));

        }

        for (Person p : persons) {
            p.paint(this, g2);
        }
        for (Fruit f : fruits) {
            f.paint(this, g2);
        }

        g2.dispose();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(W, H);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame();
                f.add(new Example());
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }

    protected void relate(Person person, Fruit bubble) {
        relationships.add(new Relationship(person, bubble));
    }

    public class Relationship {

        private Paintable parent;
        private Paintable child;

        public Relationship(Paintable parent, Paintable child) {
            this.parent = parent;
            this.child = child;
        }

        public Paintable getChild() {
            return child;
        }

        public Paintable getParent() {
            return parent;
        }

    }

    public interface Paintable {

        public void paint(JComponent parent, Graphics2D g2d);

        public boolean contains(Point p);

        public void moveTo(Point2D p);

        public Rectangle2D getBounds();

    }

    public class Fruit implements Paintable {

        private static final long serialVersionUID = 1L;
        String fruit;
        Font font;

        private Ellipse2D bounds;

        public Fruit(String fruit, Font font, int x, int y) {
            this.fruit = fruit;
            this.font = font;
            bounds = new Ellipse2D.Double(x, y, 40, 90);
        }

        public String getFruit() {
            return fruit;
        }

        @Override
        public boolean contains(Point p) {
            return bounds.contains(p);
        }

        @Override
        public void moveTo(Point2D p) {
            bounds = new Ellipse2D.Double(p.getX(), p.getY(), 40, 90);
        }

        @Override
        public void paint(JComponent parent, Graphics2D g) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setFont(font);
            FontMetrics fm = g2.getFontMetrics();
            int height = fm.getHeight();
            int width = fm.stringWidth(fruit);

            g2.setColor(Color.WHITE);
            g2.fill(bounds);
            g2.setColor(Color.BLACK);
            g2.draw(bounds);

            double centreX = bounds.getX() + bounds.getWidth() / 2d;
            double centreY = bounds.getY() + bounds.getHeight() / 2d;
            g2.drawString(fruit, (int) (centreX - width / 2), (int) (centreY + height / 4));

            g2.dispose();
        }

        @Override
        public Rectangle2D getBounds() {
            return bounds.getBounds2D();
        }

    }

    public class Person implements Paintable {

        String person;
        private Rectangle2D bounds;

        public Person(String person, int x, int y) {
            this.person = person;
            bounds = new Rectangle2D.Double(x, y, 40, 90);
        }

        @Override
        public void paint(JComponent parent, Graphics2D g) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.translate(bounds.getX(), bounds.getY());
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.drawOval(0, 0, 20, 20); // head
            g2.drawLine(10, 20, 10, 50); // bodbounds.getY()
            g2.drawLine(10, 20, 25, 40); // right hand
            g2.drawLine(10, 20, 0 - 5, 40); // left hand
            g2.drawLine(10, 50, 0 - 5, 70); // left leg
            g2.drawLine(10, 50, 25, 70); // right leg
            g2.drawString(person, 0 - 15, 85);
            g2.dispose();
        }

        public String getPerson() {
            return person;
        }

        @Override
        public boolean contains(Point p) {
            return bounds.contains(p);
        }

        @Override
        public void moveTo(Point2D p) {
            bounds = new Rectangle2D.Double(p.getX(), p.getY(), 40, 90);
        }

        @Override
        public Rectangle2D getBounds() {
            return bounds.getBounds2D();
        }
    }

}

I would encourage you to have a look at Path2D, at least for the stick figure, it will make life eaiser

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you! May I know which part of my code is odd? If it's the stick figure, I will look at Path2D and try to implement it. Thanks. – sw2 Aug 02 '15 at 13:03
  • 1
    From my perspective, it starts with you extending from Recntagle, Graphics2D can actually paint shapes, so it's odd that you then have a paint method. Because you generally need more then one colour of each shape, it's not possible to just make use of a Shape based class. Also, the bounds of the stick figure don't actually match the actual bounds either – MadProgrammer Aug 02 '15 at 21:55
0

Save the positions of a the stick man within itself and the position of the fruits within their objects. Relate each objects which are together. When a repaint() is started get the position information of the stick man and the position information of the fruit and draw a line between these two points. If a move is done then a repaint of the line should done also.

Kami
  • 459
  • 1
  • 6
  • 27