Thursday, August 14, 2014

Drawing a Sine Wave using Java Graphics [Swing]

Drawing a sine wave using Swing: The number of cycles is adjustable. Code Source: Thinking In Java 4th Edition.


Code :

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*; 

class SineDraw extends JPanel {

    //Scalefactor manipulates the number of points on the screen 
    //Joining these points forms our wave. It will represent the clarity 
    //of the display but low values may distort the wave
    private static final int SCALEFACTOR = 200;
    //The number of complete waves to display on the screen
    private int cycles;
    //Total number of points that will be joined to form the wave
    private int points;
    //The actual sine values calculated. Drawing will be done relative to
    //the height (actual vertical coordinates calculated from sine values). These aren't used as some coordinates.
    private double[] sines;
    //The actual coordinates (vertical) calculated based on the sine values
    private int[] pts;

    public SineDraw() {
        //Constructor Sets the number of cycles to display on the screen
        setCycles(1);
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        //The current width of the Panel
        int maxWidth = getWidth();
        //hstep mean the horizontal distance between two points on the screen
        double hstep = (double) maxWidth / (double) points;
        //The maximum height
        int maxHeight = getHeight();
        pts = new int[points];
        for (int i = 0; i < points; ++i) {
            /*
             This is for calculating the vertical coordinates of the
             points to be joined to form the wave. Horizontal coordinates are calculated
             based on hstep ( horizontal distance between consecutive points )
             eg.
             if sine[i] is 1, maxHeight = 500
             pts[i] = 1 * 250 * 0.95 (which means 95%) + maxHeight / 2 
             = 237.5 + 250
             = 487.5
             This is the bottom of the wave [*0.95 sets a 95% margin on display]
            
             Now if sine[i] is -1, maxHeight = 500
             pts[i] = -1 * 250 * 0.95 (which means 95%) + maxHeight / 2
             = -237.5 + 250
             = 12.5
             This is the top of the display. Note that +maxHeight/2 is necessary
            
             ( * maxHeight / 2 ) calculates vertical coordinate values relative to sine values
             as sines are positive, the graph will rise, and if sine is negative, the
             graph will fall
             */
            pts[i] = (int) (sines[i] * maxHeight / 2 * 0.95 + maxHeight / 2);
        }
        g.setColor(Color.RED);
        for (int i = 1; i < points; ++i) {
            /*
             Lets say first point gets 
             x1 = 0 * hstep = 0
             x2 = 0 * hstep = 0
            
             as expected.
            
             Second one 
             x1 = 1 * hstep = Lets say the points have a horizontal distance of 0.1
             x2 = 2 * hstep = Add the same horizontal distance to the second point two times
             as the values are caculated from the left most edge of the panel
            
            
             and so on...
             */
            int x1 = (int) ((i - 1) * hstep);
            int x2 = (int) (i * hstep);

            //The vertical values have already been calculated
            int y1 = pts[i - 1];
            int y2 = pts[i];

            //Now draw a line based on these calculated coordinates based on sine values
            g.drawLine(x1, y1, x2, y2);
        }
    }

    public void setCycles(int newCycles) {
        cycles = newCycles;

        /*Total number of points = SCALEFACTOR ( here this is manipulating the sharpness of display 
         by changing the total number of points ) * cycles ( Lets say 1 cycle ) so that 
         by multiplying it with SCALEFACTOR is becomes 200 cycles in this case.
         We need both negative values and positive values to draw. So the number of
         points is multiplied by 2. [One cycle is represented by an upper + lower curve
         on the graph]. If you remove the (*2) and set the slider to 1, notice that
         the upper curve isn't drawn.
        
         Before the first half that is before the effect of *2, sines have +ve values
         After the first half, negative sin values begin.
         */
        points = SCALEFACTOR * cycles * 2;
        sines = new double[points];
        for (int i = 0; i < points; ++i) {
            if(i == points/2){
                System.out.println("***** Rest half *****");
            }
            /*
            FYI, 1 radian is the angle subtended by an arc of the circle that
            has the same length as the radius of the circle. And PI is the angle 
            subtended by half the circle on the center = 180 Degrees.
            Hers's a nice GIF explanation : http://en.wikipedia.org/wiki/Radian
            
            Multiply Math.PI by i (to get reltively increasing radian values
            and divide by SCALEFACTOR for getting values relative to the number
            of points.
            
            Angle is getting increased by 180deg (Math.PI) every time and then divided by SCALEFACTOR
            to get small increasing sine values based on these slowly increasing radian values
            */
            double radians = (Math.PI / SCALEFACTOR) * i; //Notice that last iteration (in case scalefactor = 200, cycles = 2, points = 800
            //radians = PI * 800 / 200 = PI * 4 = 180 degrees 4 times which gets you 2 complete cycles.

            //Now calculate the actual sin values based on radians
            sines[i] = Math.sin(radians);
            System.out.println("Radian = " + radians + " sines = " + sines[i]);
        }
        repaint();
    }
}

public class SineWave extends JFrame {

    private SineDraw sines = new SineDraw();
    private JSlider adjustCycles = new JSlider(1, 30, 1);

    public SineWave() {
        add(sines);
        adjustCycles.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                //Slider sets the number of cycles
                sines.setCycles(
                        ((JSlider) e.getSource()).getValue());
            }
        });
        add(BorderLayout.SOUTH, adjustCycles);
        setSize(700, 400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        //Nimbus look and feel. Looks nice to me.
        try {
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception e) {
            // If Nimbus is not available, you can set the GUI to another look and feel.
        }
        new SineWave();
    }
}

No comments:

Post a Comment