Search code examples
javaswinggraphicsplotreal-time

Graphical Components


I have done a program that numerically solves a set of differential equations which describes how an "arbitrary" illness move in an isolated and constant population, it was a programming assignment from a class I took a while ago. What I've done to extend it is to add some graphical components that can pause, reset and "play" the simulation, as well as some components that allows me to change some constants in the equations.

All this was an exercise in programming as I find it to be fun and exciting and want to become better.

However, at the moment I'm stuck, what I want to do now is to make a very simple form of animation of it. I want to visualize the data I get for the number of infected, susceptibles and resistants in a grid as points. I managed to create the grid and have an idea of how to place the dots.

The problem I have is how to draw the dots as the program is working, I can draw one dot in the grid but only as the grid is created, that's it. I need to be able to create a dot at a specific place in the grid, this goes on until the number of dots reaches a finite number, say 30. At that points I want to have the first dot, the one the left, removed, all the dots shifted to the left and place the new dot at the furthest right of the grid, the whole thing is then repeated.

I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason. I've read through my course literature on Java, but despite the extensive sections where he explains most of the different graphical components he does not say that much about those methods, only that you don't call for the paintComponent() method, it is done automatically.

If there is something unclear let me know and I'll try to clarify it.

Thanks in advance.

// Fox Mulder


Solution

  • I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason.

    Basically, say you create a custom component by extending JPanel. When you @Override the paintComponent() method, it get implicitly called for you, so you never have to call it. So what ever you paint inside the method, gets drawn on your surface. For example

    public class DrawingPanel extends JPanel {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.fillOval(x, y, 10, 10);
        }
    }
    

    When you call repaint() you are basically causing the paintComponent method to be call implicitly. So to answer your question, Yes you will need to call it if you want to animate, as you will need to update some kind of variable (like the x and y) in the paintComponent() method, to see any change in the drawing.

    You can see more at Performing Custom Painting

    Not to handle the actual animation, you'll want to use a javax.swing.Timer. You can see more at How to use Swing Timers. Here's the basic construct

    Timer ( int delayInMillis, ActionListener listener )
    

    where delayInMillis is the time to delay between ticks(in this case animations) and the ActionListener listens for "ticks". Each tick, the actionPerformed of the ActionListener is called. There, you can put the code to update any variables you use for animation.

    So for example you update the x and y, in the actionPerformed, then call repaint()

    public class DrawingPanel extends JPanel {
    
        int x = 50;
        int y = 50;
    
        public DrawingPanel() {
            Timer timer = new Timer(40, new ActionListener(){
                @Override
                public void actionPerformed(ActionEvent e) {
                    x += 5;
                    y += 5;
                    repaint();
                }
            });
            timer.start();
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.fillOval(x, y, 10, 10);
        }
    }
    

    Now this was just a simple example. But in your case you want to animate a scatter plot. So what you can do is have a list of Points and in the actionPerformed you can add pull points from that list and push them into another list that is to be drawn. So say you have this

    List<Point> originalPoints;
    List<Point> pointsToDraw;
    ...
    @Override
    protected void paintComponent(Grapchics g) {
        super.paintComponent(g);
        for (Point point : pointsToDraw) {
            g.fillOval(point.x - 5, point.y - 5, 10, 10);
        }
    }
    

    Basically all the points in pointsToDraw list will be drawn. Initially it will be empty. And in the timer, you can add to the list, until the originalPoints list is exhausted. For example.

    List<Point> originalPoints;
    List<point> pointsToDraw;
    
    private int currentIndex = 0;
    
    public DrawingPanel(List<Point> originalPoints) {
        this.originalPoints = originalPoints;
        pointsToDraw = new ArrayList<>();
    
        Timer timer = new Timer(40, new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                if (currentIndex == originalPoints.size() - 1) {
                    ((Timer)e.getSource()).stop();
                } else {
                    pointsToDraw.add(originalPoints.get(currentIndex));
                    currentIndex++;
                }
                repaint();
            }
        });
        timer.start();
    }
    

    So basicall you just keep a current index. When the index reaches the size of the original list, you stop the timer. Otherwise you just pop from the originalPoints and push to the pointsToDraw. For every point you add the pointsToDraw, a repaint() is called, and there will be another point for the paintComponent to draw a circle with.

    The END


    UDPATE

    I just reread your question, and I think I have have misunderstood it. If you want all the points drawn, then basically just have one list. And draw all the points initially. with each tick, just remove the first index, advance all the rest up an index, and add a new one to the end. Though this is the implementation of a LinkedList so you may just want to use that