Search code examples
javastddraw

Making a sine graph move


public class SimpleHarmonic {
    public static void main(String[] args) {
        StdDraw.setXscale(0,900);
        StdDraw.setYscale(0,700);

        while (true) {
            StdDraw.setPenColor(StdDraw.BLACK);
            StdDraw.line(0,350,900,350); // x-axis
            StdDraw.line(450,0,450,900); // y-axis
            StdDraw.setPenColor(StdDraw.RED);

            for (double x = -450; x <= 450; x += 0.5) {
                double y = 50 * Math.sin(x * (Math.PI / 180));
                int Y = (int) y;
                int X = (int) x;
                StdDraw.line(450 + X, 350 - Y, 450 + X, 350 - Y);
            }
            StdDraw.clear();

        }
    }
}

In this code I am attempting to simulate simple harmonic motion. However, I have only been able to draw a static graph, but I need it to move continously.

I believe I need to use a loop to contionusly redraw the points, but I am not sure how to do that.

How can I make my current sine graph move contionusly?

Edit: Voted to close as non-programming? what?


Solution

  • I took a look at the StdDraw class you are using and it looks like what you want is the

    StdDRaw.show(int) method, this method comment states:

    /**
     * Display on screen, pause for t milliseconds, and turn on
     * <em>animation mode</em>: subsequent calls to
     * drawing methods such as {@code line()}, {@code circle()}, and {@code square()}
     * will not be displayed on screen until the next call to {@code show()}.
     * This is useful for producing animations (clear the screen, draw a bunch of shapes,
     * display on screen for a fixed amount of time, and repeat). It also speeds up
     * drawing a huge number of shapes (call {@code show(0)} to defer drawing
     * on screen, draw the shapes, and call {@code show(0)} to display them all
     * on screen at once).
     * @param t number of milliseconds
     */
    

    In this library any time you call a draw method such as line or circle it conditionally repaints the frame. By passing the int param to the draw method it will turn all painting methods into "animation mode" and defer repainting the frame until you call draw() (no params).


    To make it animate you must make each iteration of your while loop 1 animation frame, each frame will need to differ from the previous one. You can do this by using a variable outside your loop to offset each frame by a small ammount. Ill call this offset

    With this information you can alter your loop to look like:

        double offset = 0;
        while (true) {
            offset+=1; // move the frame slightly
            StdDraw.show(10); // defer repainting for 10 milisecoinds
    
            StdDraw.clear(); // clear before painting
    
            StdDraw.setPenColor(StdDraw.BLACK);
            StdDraw.line(0,350,900,350); // x-axis
            StdDraw.line(450,0,450,900); // y-axis
            StdDraw.setPenColor(StdDraw.RED);
    
            for (double x = -450; x <= 450; x += 0.5) {
                // apply the offset inside of calculation of Y only such that it 
                // slowly "moves" the sin wave
                double y = 50 * Math.sin((offset+x) * (Math.PI / 180));
                int Y = (int) y;
                int X = (int) x;
                StdDraw.line(450 + X, 350 - Y, 450 + X, 350 - Y);
            }
    
            StdDraw.show(); // end animation frame. force a repaint 
        }
    


    A few improvements in your code

    1 Inside your loop where you draw each "dot" you are increnting by .5. Because that X value is literally 1 pixel you arent gaining anything by going to .5 instead of 1. 1 is quite literally the smallest you can visually see in this enviroment. I recommend making it at least be x+=1

    for (double x = -450; x <= 450; x += 1)
    

    2 You are using the .line method but drawing to the same point. You could significantly speed up your program by only calculating every 3rd pixels Y value and connecting the dots. For instance

    double prevX = -450;
    double prevY = 50 * Math.sin((prevX+offset) * (Math.PI / 180)); // seed the previous Y to start
    for (double x = 0; x <= 450; x += 3) {
        double y = 50 * Math.sin((x+offset) * (Math.PI / 180));
        StdDraw.line(450 + (int)prevX, 350 - (int)prevY, 450 + (int)x, 350 - (int)y);
        prevX = x;
        prevY = y;
    }
    

    3 This isnt your code but in the StdDraw.init method you can set some rendering hints to allow for cleaner lines. This should make it look alot nicer

    offscreen.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
                               RenderingHints.VALUE_STROKE_PURE);
    

    Combining all those things heres what I wrote

    public static void main(String[] args) {
        StdDraw.setXscale(0,900);
        StdDraw.setYscale(0,700);
    
        double offset = 0;
        while (true) {
            StdDraw.show(10);
            StdDraw.clear();
            offset-=1;
    
            StdDraw.setPenColor(StdDraw.BLACK);
            StdDraw.line(0,350,900,350); // x-axis
            StdDraw.line(450,0,450,900); // y-axis
            StdDraw.setPenColor(StdDraw.RED);
    
    
            double prevX = 0;
            double prevY = 50 * Math.sin((prevX+offset) * (Math.PI / 180)); // seed the previous Y to start
            StdDraw.filledCircle(450 + prevX, 350 - prevY, 5);
    
            for (double x = 0; x <= 450; x += 3) {
                double y = 50 * Math.sin((x+offset) * (Math.PI / 180));
                StdDraw.line(450 + (int)prevX, 350 - (int)prevY, 450 + (int)x, 350 - (int)y);
                prevX = x;
                prevY = y;
            }
            StdDraw.show();
    
        }
    }
    

    I dont have an animation recorder so heres a picture Screenshot