Registered User Registered User - 5 months ago 37
Java Question

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?

ug_ ug_
Answer

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