TrajectoryGraph.java

package com.hacks.trajectoryCalculator; // maven build

// import the graphs
import org.jfree.data.function.Function2D;
import org.jfree.data.general.*;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYDataset;

// import the other files
import com.hacks.trajectoryCalculator.*;

// import swing and awt
import javax.swing.JFrame;
import java.awt.Dimension;

public class TrajectoryGraph extends JFrame {
    // Constructor of the graph that will be displayed
    public TrajectoryGraph() {
        InputInitial newInputs = new InputInitial(); // start input object from separate file
        newInputs.spawnInputs(); // start the process of collecting user input
       drawGraph(InputInitial.initialVelocity, InputInitial.initialDegrees, InputInitial.initialHeight); // call method to draw the graph with the user input taken
    }

    // graph drawer
    public void drawGraph(double velocity, double degrees, double height) {
        Function2D test = new TrajectoryMath(velocity, degrees, height); // TrajectoryMath implements function2d, so use those values create new function2d object
        TrajectoryMath testGetter = new TrajectoryMath(velocity, degrees, height); // creates TrajectoryMath object from the other file to call custom methods
        XYDataset dataset = DatasetUtils.sampleFunction2D(test, 0.0, testGetter.getRoot(), 50, "Function"); // generates the dataset of xy values with the function
        final JFreeChart chart = ChartFactory.createXYLineChart("Trajectory Equation", "X Position (meters)", "Y Position (meters)", dataset, PlotOrientation.VERTICAL, true, true, false); // creates the actual graph with attributes
        
        // initializing the display
        ChartPanel cp = new ChartPanel(chart) {

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(500, 500); // set initial dimension
            }
        };

        // allow mouse wheel scrolling
        cp.setMouseWheelEnabled(true);
        add(cp);

        // Finalize the building of the graph
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
    }
    public static void main(String[] args) {
    
        // runs the creation of the graph with a queue in a different thread and posts the gui after events are processed
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                // initialize new TrajectoryGraph object, setvisible to display the graph
                new TrajectoryGraph().setVisible(true);
            }
        });
        
    }
}

TrajectoryMath.java

package com.hacks.trajectoryCalculator;

import org.jfree.data.function.Function2D;

/*
 * Actual math for calculating trajectory
 * 
 * Calculates equation as a string and returns it
 * 
 * 
 */

 public class TrajectoryMath implements Function2D {

    // Initialize variables
    private double velocity;
    private double degrees;
    private double height;
    private String equation;
    private double coefficientA;
    private double coefficientB;
    private double coefficientC;

    
    // Constructor
    public TrajectoryMath(double velocity, double degrees, double height) {
        this.velocity = velocity;
        this.degrees = degrees;
        this.height = height;

        this.calculateEquation();
    }

    // Getters and setters
    public double getVelocity() {
        return this.velocity;
    }

    public double getDegrees() {
        return this.degrees;
    }

    public double getHeight() {
        return this.height;
    }

    public String getEquation() {
        return this.equation;
    }

    public double getCoefficientA() {
        return this.coefficientA;
    }

    public double getCoefficientB() {
        return this.coefficientB;
    }

    public double getCoefficientC() {
        return this.coefficientC;
    }

    public double getRoot() {
        double rootOne = (-this.coefficientB + Math.sqrt(Math.pow(coefficientB, 2) - 4 * (-this.coefficientC) * this.coefficientA)) / (2 * (-this.coefficientC));
        double rootTwo = (-this.coefficientB - Math.sqrt(Math.pow(coefficientB, 2) - 4 * (-this.coefficientC) * this.coefficientA)) / (2 * (-this.coefficientC));

        if (rootOne > 0) {
            return rootOne;
        } else if (rootTwo > 0) {
            return rootTwo;
        } else {
            return 0.0;
        }
    }

    public void setVelocity(double velocity) {
        this.velocity = velocity;
    }

    public void setDegrees(double degrees) {
        this.degrees = degrees;
    }
    
    public void setHeight(double height) {
        this.height = height;
    }

    // Array should be in order of velocity, degrees, and height
    public void setParameters(double[] values) {
        this.velocity = values[0];
        this.degrees = values[1];
        this.height = values[2];
    }

    // Math for equation
    private void calculateEquation() {

        this.coefficientA = height;
        this.coefficientB = Math.tan(degrees * Math.PI/180);
        this.coefficientC = 9.8 / (2 * Math.pow(velocity, 2) * Math.pow(Math.cos(degrees * Math.PI/180), 2));

        String precheckedEquation = "y = " + String.valueOf(coefficientA) + " + " + String.valueOf(coefficientB) + "x - " +
        String.valueOf(coefficientC) + "x^2";
        // Fix double negative, but later
        //if (precheckedEquation.contains("- -")) {
        //}

        //Add threshold
        this.equation = precheckedEquation;
    }

    public double getValue(double v) {
        return coefficientA + coefficientB * v - coefficientC * Math.pow(v, 2);
    }

    /*
     * public static void main(String[] args) {
        TrajectoryMath example = new TrajectoryMath(15, 60, 4);
        System.out.println(example.getRoot());
     }
     */
     
   
 }

InputInitial.java

package com.hacks.trajectoryCalculator;

import javax.swing.JOptionPane; // library to display options
import javax.swing.JTextField; // library to create a text field to render on GUI

public class InputInitial {
    // instance variables to be used
    public static Double initialVelocity;
    public static Double initialDegrees;
    public static Double initialHeight;

    // create the GUI element that users input into
    public void spawnInputs() {
        // while the values have not changed yet, keep going (for error handling)
        while (initialVelocity == null || initialDegrees == null || initialHeight == null) {
            // text field initialization
            JTextField inputVelocity = new JTextField();
            JTextField inputDegrees = new JTextField();
            JTextField inputHeight = new JTextField();

            // organizing the input text to display + the text field in object
            Object[] inputs = {
                "Initial Velocity (m/s):", inputVelocity,
                "Initial Degrees:", inputDegrees,
                "Initial Height (m):", inputHeight
            };

            JOptionPane.showConfirmDialog(null, inputs, "Input the initial values for your object (numbers only):", JOptionPane.OK_CANCEL_OPTION); // creates the option menu with the 3 inputs

            // take the input, assign it to the public variables
            initialVelocity = parseInput(inputVelocity);
            initialDegrees = parseInput(inputDegrees);
            initialHeight = parseInput(inputHeight);
        }
        
    }

    // change JTextField into Double, also error handling
    public Double parseInput(JTextField inputValue) {
        String placeholder = inputValue.getText(); // get the string out of the input
        
        // error handling + edge cases
        try {
            double initialValue = Double.parseDouble(placeholder);

            // if negative, cannot be valid so throw error --> reinput values bc still null
            if (initialValue < 0.0) {
                JOptionPane.showMessageDialog(null, "Inputs must be greater than 0", "Invalid Input", JOptionPane.WARNING_MESSAGE);
                return null;
            } else { // if everything ok, return the value
                return initialValue;
            }
            
        } catch (Exception e) { // if cannot be cased as a double, throw error --> reinput values
            JOptionPane.showMessageDialog(null, "There was an invalid input for " + placeholder + ", please try again. " + e, "Unwanted Input", JOptionPane.WARNING_MESSAGE);
            return null;
        }
    }
}

Project Description

In this project, I worked with Bailey to create this program. The code is split into 3 files: one to draw the graph, one to gather user input, and one to calculate the trajectory equation. The overall code structure flows like so:

  • initialize TrajectoryGraph object, and call the user input method
  • the user inputs the initial velocity, initial height, and initial degrees into a JOptionsPane
  • the data is fed into the calculator, which returns height given a x value
  • the graph is initialized, and drawn using Function2D
  • the graph is then presented to the user as a GUI element

Key Requirements

Using Math

getRoot()

public double getRoot() {
    double rootOne = (-this.coefficientB + Math.sqrt(Math.pow(coefficientB, 2) - 4 * (-this.coefficientC) * this.coefficientA)) / (2 * (-this.coefficientC));
    double rootTwo = (-this.coefficientB - Math.sqrt(Math.pow(coefficientB, 2) - 4 * (-this.coefficientC) * this.coefficientA)) / (2 * (-this.coefficientC));

    if (rootOne > 0) {
        return rootOne;
    } else if (rootTwo > 0) {
        return rootTwo;
    } else {
        return 0.0;
    }
}

In this section from the TrajectoryMath.java file, the Math class is used to calculate the quadratic formula. This can be seen with the Math.sqrt(Math.pow(coefficientB, 2) - 4 * (-this.coefficientC) * this.coefficientA)), which uses the class methods of square root and exponents.

calculateEquation()

private void calculateEquation() {

    this.coefficientA = height;
    this.coefficientB = Math.tan(degrees * Math.PI/180);
    this.coefficientC = 9.8 / (2 * Math.pow(velocity, 2) * Math.pow(Math.cos(degrees * Math.PI/180), 2));

    String precheckedEquation = "y = " + String.valueOf(coefficientA) + " + " + String.valueOf(coefficientB) + "x - " +
    String.valueOf(coefficientC) + "x^2";
    // Fix double negative, but later
    //if (precheckedEquation.contains("- -")) {
    //}

    //Add threshold
    this.equation = precheckedEquation;
}

In this section from the TrajectoryMath.java file, the Math class is used again to calculate coefficientC, with both exponent and using the trig function of cosine.

Different Constructor Signatures

// TrajectoryGraph.java
public TrajectoryGraph() {
    InputInitial newInputs = new InputInitial(); // start input object from separate file
    newInputs.spawnInputs(); // start the process of collecting user input
   drawGraph(InputInitial.initialVelocity, InputInitial.initialDegrees, InputInitial.initialHeight); // call method to draw the graph with the user input taken
}

// TrajectoryMath.java
public TrajectoryMath(double velocity, double degrees, double height) {
    this.velocity = velocity;
    this.degrees = degrees;
    this.height = height;

    this.calculateEquation();
}

As seen above, these are two different constructors from different files. One of them initiates a graph object, while the other is used to calculate the function to display.

Wrapper Class + Static Data

// InputInitial.java
public static Double initialVelocity;
public static Double initialDegrees;
public static Double initialHeight;

The wrapper class of Double defines the static data of the initialValues used in InputInitial. I used the wrapper class in order to be able to use the default null value to do error handling, as without being a wrapper class the double has to be a number.

Example Outputs:

Example user input

generated graph

Organization

Working as a Team

To complete this, me and my partner Bailey Say split up the work in order to be more efficient. Bailey worked on creating the TrajectoryMath.java file, which took care of getting and setting the variables to create the function. It also created the function itself, and he did research to find the formula to calculate a trajectory.

As for myself, I worked on getting GUI to display as well as creating dialog boxes for the user to input. For this, I worked on the TrajectoryGraph.java as well as InputInitial.java. I also worked on error handling in input, cutting off negative inputs as well as non double inputs.

In addition to working on GUI, I also attempted to use spring boot and maven to build the project. Even though I could not exactly figure out how to put it on a website, I at least resolved issues with building through maven. I learned that the package in front of the files has to correspond with the specific folder in order for maven to know what to import, and that allowed us to use these multiple files to run a single program.

Conclusion

Overall, this project helped me and Bailey to understand more about how projects in Java are built, having to figure out how to use tools like maven. It also allowed us to experiment with multi file java programs, finding out how calling methods from other files works as well as classes. In addition to being good learning for CSA, it also contributes to me and Bailey's experience in AP Physics.