import { Doc } from '../../../../fields/Doc';
import React = require('react');
import { IWallProps } from "./PhysicsSimulationWall";
import "./PhysicsSimulationWeight.scss";

export interface IForce {
  description: string;
  magnitude: number;
  directionInDegrees: number;
  component: boolean;
}
export interface IWeightProps {
  dataDoc: Doc;
  layoutDoc: Doc;
  adjustPendulumAngle: { angle: number; length: number };
  circularMotionRadius: number;
  coefficientOfKineticFriction: number;
  color: string;
  componentForces: IForce[];
  displayXVelocity: number;
  displayYVelocity: number;
  elasticCollisions: boolean;
  gravity: number;
  mass: number;
  mode: string;
  noMovement: boolean;
  paused: boolean;
  pendulumAngle: number;
  pendulumLength: number;
  radius: number;
  reset: boolean;
  showAcceleration: boolean;
  showComponentForces: boolean;
  showForceMagnitudes: boolean;
  showForces: boolean;
  showVelocity: boolean;
  simulationSpeed: number;
  simulationType: string;
  springConstant: number;
  springRestLength: number;
  springStartLength: number;
  startForces: IForce[];
  startPendulumAngle: number;
  startPosX: number;
  startPosY: number;
  startVelX: number;
  startVelY: number;
  timestepSize: number;
  updateDisplay: { xDisplay: number; yDisplay: number };
  updatedForces: IForce[];
  wedgeHeight: number;
  wedgeWidth: number;
  xMax: number;
  xMin: number;
  yMax: number;
  yMin: number;
}

interface IState {
  angleLabel: number,
  clickPositionX: number,
  clickPositionY: number,
  coordinates: string,
  dragging: boolean,
  kineticFriction: boolean,
  maxPosYConservation: number,
  timer: number,
  update: boolean,
  updatedStartPosX: number,
  updatedStartPosY: number,
  walls: IWallProps[],
  xPosition: number,
  xVelocity: number,
  yPosition: number,
  yVelocity: number,
  xAccel: number,
  yAccel: number,
}

export default class Weight extends React.Component<IWeightProps, IState> {

  constructor(props: any) {
    super(props)
    this.state = {
      angleLabel: 0,
      clickPositionX: 0,
      clickPositionY: 0,
      coordinates: "",
      dragging: false,
      kineticFriction: false,
      maxPosYConservation: 0,
      timer: 0,
      updatedStartPosX: this.props.dataDoc['startPosX'],
      updatedStartPosY: this.props.dataDoc['startPosY'],
      walls: [],
      xPosition: this.props.dataDoc['startPosX'],
      xVelocity: this.props.startVelX ? this.props.startVelX: 0,
      yPosition: this.props.dataDoc['startPosY'],
      yVelocity: this.props.startVelY ? this.props.startVelY: 0,
      xAccel: 0,
      yAccel: 0,
    }
  }

  componentDidMount() {
    // Timer for animating the simulation
    setInterval(() => {
      this.setState({timer: this.state.timer + 1});
    }, 50);
  }

  // Constants
  const draggable =
    this.props.dataDoc['simulationType'] != "Inclined Plane" &&
    this.props.dataDoc['simulationType'] != "Pendulum" &&
    this.props.dataDoc['mode'] == "Freeform";
  const epsilon = 0.0001;
  const labelBackgroundColor = `rgba(255,255,255,0.5)`;

  // Variables
  weightStyle = {
    alignItems: "center",
    backgroundColor: this.props.color,
    borderColor: "black",
    borderRadius: 50 + "%",
    borderStyle: "solid",
    display: "flex",
    height: 2 * this.props.radius + "px",
    justifyContent: "center",
    left: this.props.dataDoc['startPosX'] + "px",
    position: "absolute" as "absolute",
    top: this.props.dataDoc['startPosY'] + "px",
    touchAction: "none",
    width: 2 * this.props.radius + "px",
    zIndex: 5,
  };

  // Helper function to go between display and real values
  getDisplayYPos = (yPos: number) => {
    return this.props.yMax - yPos - 2 * this.props.radius + 5;
  };
  getYPosFromDisplay = (yDisplay: number) => {
    return this.props.yMax - yDisplay - 2 * this.props.radius + 5;
  };

  // Set display values based on real values
  setYPosDisplay = (yPos: number) => {
    const displayPos = this.getDisplayYPos(yPos);
    this.props.dataDoc['positionYDisplay'] = Math.round(displayPos * 100) / 100
  };
  setXPosDisplay = (xPos: number) => {
    this.props.dataDoc['positionXDisplay'] = Math.round(xPos * 100) / 100;
  };
  setYVelDisplay = (yVel: number) => {
    this.props.dataDoc['velocityYDisplay'] = (-1 * Math.round(yVel * 100)) / 100;
  };
  setXVelDisplay = (xVel: number) => {
    this.props.dataDoc['velocityXDisplay'] = Math.round(xVel * 100) / 100;
  };

  // Update display values when simulation updates
  setDisplayValues = (
    xPos: number = this.state.xPosition,
    yPos: number = this.state.yPosition,
    xVel: number = this.state.xVelocity,
    yVel: number = this.state.yVelocity
  ) => {
    this.setYPosDisplay(yPos);
    this.setXPosDisplay(xPos);
    this.setYVelDisplay(yVel);
    this.setXVelDisplay(xVel);
    this.props.dataDoc['accelerationYDisplay'] = 
      (-1 * Math.round(this.getNewAccelerationY(this.props.dataDoc['updatedForces']) * 100)) / 100
    ;
    this.props.dataDoc['accelerationXDisplay'] = 
      Math.round(this.getNewAccelerationX(this.props.dataDoc['updatedForces']) * 100) / 100
    ;
  };

  componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void {
    // Change pendulum angle from input field
    if (prevProps.adjustPendulumAngle != this.props.adjustPendulumAngle) {
      let length = this.props.adjustPendulumAngle.length;
      const x =
        length * Math.cos(((90 - this.props.adjustPendulumAngle.angle) * Math.PI) / 180);
      const y =
        length * Math.sin(((90 - this.props.adjustPendulumAngle.angle) * Math.PI) / 180);
      const xPos = this.props.xMax / 2 - x - this.props.radius;
      const yPos = y - this.props.radius - 5;
      this.setState({xPosition: xPos})
      this.setState({yPosition: yPos})
      this.setState({updatedStartPosX: xPos})
      this.setState({updatedStartPosY: yPos})
      this.props.dataDoc['pendulumAngle'] = this.props.adjustPendulumAngle.angle
      this.props.dataDoc['pendulumLength'] = this.props.adjustPendulumAngle.length
    }

    // When display values updated by user, update real values
    if (prevProps.updateDisplay != this.props.updateDisplay) {
      if (this.props.updateDisplay.xDisplay != this.state.xPosition) {
        let x = this.props.updateDisplay.xDisplay;
        x = Math.max(0, x);
        x = Math.min(x, this.props.xMax - 2 * this.props.radius);
        this.setState({updatedStartPosX: x})
        this.setState({xPosition: x})
        this.props.dataDoc['positionXDisplay'] = x
      }
  
      if (this.props.updateDisplay.yDisplay != this.getDisplayYPos(this.state.yPosition)) {
        let y = this.props.updateDisplay.yDisplay;
        y = Math.max(0, y);
        y = Math.min(y, this.props.yMax - 2 * this.props.radius);
        let coordinatePosition = this.getYPosFromDisplay(y);
        this.setState({updatedStartPosY: coordinatePosition})
        this.setState({yPosition: coordinatePosition})
        this.props.dataDoc['positionYDisplay'] = y
      }
  
      if (this.props.displayXVelocity != this.state.xVelocity) {
        let x = this.props.displayXVelocity;
        this.setState({xVelocity: x})
        this.props.dataDoc['velocityXDisplay'] = x
      }
  
      if (this.props.displayYVelocity != -this.state.yVelocity) {
        let y = this.props.displayYVelocity;
        this.setState({yVelocity: -y})
        this.props.dataDoc['velocityYDisplay'] = y
      }
    }
    
    // Prevent bug when switching between sims
    if (prevProps.startForces != this.props.startForces) {
      this.setState({xVelocity: this.props.startVelX})
      this.setState({yVelocity: this.props.startVelY})
      this.setDisplayValues();
    }

    // Make sure weight doesn't go above max height
    if (prevState.updatedStartPosY != this.state.updatedStartPosY || prevProps.startVelY != this.props.startVelY) {
      if (this.props.dataDoc['simulationType'] == "One Weight") {
        let maxYPos = this.state.updatedStartPosY;
        if (this.props.startVelY != 0) {
          maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * Math.abs(this.props.gravity));
        }
        if (maxYPos < 0) {
          maxYPos = 0;
        }
        this.setState({maxPosYConservation: maxYPos})
      }
    }

    // Check for collisions and update
    if (prevState.timer != this.state.timer) {
      if (!this.props.paused && !this.props.noMovement) {
        let collisions = false;
        if (
          this.props.dataDoc['simulationType'] == "One Weight" ||
          this.props.dataDoc['simulationType'] == "Inclined Plane"
        ) {
          const collisionsWithGround = this.checkForCollisionsWithGround();
          const collisionsWithWalls = this.checkForCollisionsWithWall();
          collisions = collisionsWithGround || collisionsWithWalls;
        }
        if (this.props.dataDoc['simulationType'] == "Pulley") {
          if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) {
            collisions = true;
          }
        }
        if (!collisions) {
          this.update();
        }
        this.setDisplayValues();
      }
    }

    // Reset everything on reset button click
    if (prevProps.reset != this.props.reset) {
      this.resetEverything();
    }
    
    // Convert from static to kinetic friction if/when weight slips on inclined plane
    if (prevState.xVelocity != this.state.xVelocity) {
      if (
        this.props.dataDoc['simulationType'] == "Inclined Plane" &&
        Math.abs(this.state.xVelocity) > 0.1 &&
        this.props.dataDoc['mode'] != "Review" &&
        !this.state.kineticFriction
      ) {
        this.setState({kineticFriction: true})
        const normalForce: IForce = {
          description: "Normal Force",
          magnitude:
            this.props.mass *
            Math.abs(this.props.gravity) *
            Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
          directionInDegrees:
            180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
          component: false,
        };
        let frictionForce: IForce = {
          description: "Kinetic Friction Force",
          magnitude:
            this.props.mass *
            this.props.coefficientOfKineticFriction *
            Math.abs(this.props.gravity) *
            Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
          directionInDegrees:
            180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
          component: false,
        };
        // reduce magnitude of friction force if necessary such that block cannot slide up plane
        let yForce = -Math.abs(this.props.gravity);
        yForce +=
          normalForce.magnitude *
          Math.sin((normalForce.directionInDegrees * Math.PI) / 180);
        yForce +=
          frictionForce.magnitude *
          Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
        if (yForce > 0) {
          frictionForce.magnitude =
            (-normalForce.magnitude *
              Math.sin((normalForce.directionInDegrees * Math.PI) / 180) +
              Math.abs(this.props.gravity)) /
            Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
        }

        const frictionForceComponent: IForce = {
          description: "Kinetic Friction Force",

          magnitude:
            this.props.mass *
            this.props.coefficientOfKineticFriction *
            Math.abs(this.props.gravity) *
            Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
          directionInDegrees:
            180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
          component: true,
        };
        const normalForceComponent: IForce = {
          description: "Normal Force",
          magnitude:
            this.props.mass *
            Math.abs(this.props.gravity) *
            Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
          directionInDegrees:
            180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
          component: true,
        };
        const gravityParallel: IForce = {
          description: "Gravity Parallel Component",
          magnitude:
            this.props.mass *
            Math.abs(this.props.gravity) *
            Math.sin(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
          directionInDegrees:
            180 -
            90 -
            (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI +
            180,
          component: true,
        };
        const gravityPerpendicular: IForce = {
          description: "Gravity Perpendicular Component",
          magnitude:
            this.props.mass *
            Math.abs(this.props.gravity) *
            Math.cos(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)),
          directionInDegrees:
            360 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI,
          component: true,
        };
        const gravityForce: IForce = {
          description: "Gravity",
          magnitude: this.props.mass * Math.abs(this.props.gravity),
          directionInDegrees: 270,
          component: false,
        };
        if (this.props.coefficientOfKineticFriction != 0) {
          this.props.dataDoc['updatedForces'] = ([gravityForce, normalForce, frictionForce]);
          this.props.dataDoc['componentForces'] = ([
            frictionForceComponent,
            normalForceComponent,
            gravityParallel,
            gravityPerpendicular,
          ]);
        } else {
          this.props.dataDoc['updatedForces'] = ([gravityForce, normalForce]);
          this.props.dataDoc['componentForces'] = ([
            normalForceComponent,
            gravityParallel,
            gravityPerpendicular,
          ]);
        }
      }
    }

    // Add/remove walls when simulation type changes
    if (prevProps.simulationType != this.props.simulationType) {
      let w: IWallProps[] = [];
      if (this.props.dataDoc['simulationType'] == "One Weight" || this.props.dataDoc['simulationType'] == "Inclined Plane") {
        w.push({ length: 70, xPos: 0, yPos: 0, angleInDegrees: 0 });
        w.push({ length: 70, xPos: 0, yPos: 80, angleInDegrees: 0 });
        w.push({ length: 80, xPos: 0, yPos: 0, angleInDegrees: 90 });
        w.push({ length: 80, xPos: 69.5, yPos: 0, angleInDegrees: 90 });
      }
      this.setState({walls: w})
    }

    // Update x position when start pos x changes
    if (prevProps.startPosX != this.props.startPosX) {
      if (this.props.paused) {
        this.setState({xPosition: this.props.startPosX})
        this.setState({updatedStartPosX: this.props.startPosX})
        this.props.dataDoc['positionXDisplay'] = this.props.startPosX
      }
    }


    // Update y position when start pos y changes
    if (prevProps.startPosY != this.props.startPosY) {
       if (this.props.paused) {
        this.setState({yPosition: this.props.startPosY})
        this.setState({updatedStartPosY: this.props.startPosY})
        this.props.dataDoc['positionYDisplay'] = this.props.startPosY
      }
    }

    // Update wedge coordinates
    if (prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) {
      const left = this.props.xMax * 0.5 - 200;
      const coordinatePair1 = Math.round(left) + "," + this.props.yMax + " ";
      const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + "," + this.props.yMax + " ";
      const coordinatePair3 = Math.round(left) + "," + (this.props.yMax - this.props.wedgeHeight);
      const coord = coordinatePair1 + coordinatePair2 + coordinatePair3;
      this.setState({coordinates: coord})
    } 
  }

  // Reset simulation on reset button click
  resetEverything = () => {
    this.setState({kineticFriction: false})
    this.setState({xPosition: this.state.updatedStartPosX})
    this.setState({yPosition: this.state.updatedStartPosY})
    this.setState({xVelocity: this.props.startVelX ?? 0})
    this.setState({yVelocity: this.props.startVelY ?? 0})
    this.props.dataDoc['pendulumAngle'] = this.props.dataDoc['startPendulumAngle']
    this.props.dataDoc['updatedForces'] = (this.props.dataDoc['startForces'])
    this.props.dataDoc['accelerationXDisplay'] = 0
    this.props.dataDoc['accelerationYDisplay'] = 0
    this.props.dataDoc['positionXDisplay'] = this.state.updatedStartPosX
    this.props.dataDoc['positionYDisplay'] = this.state.updatedStartPosY
    this.props.dataDoc['velocityXDisplay'] = this.props.startVelX ?? 0
    this.props.dataDoc['velocityYDisplay'] = this.props.startVelY ?? 0
    this.setState({angleLabel: Math.round(this.props.dataDoc['pendulumAngle']* 100) / 100})
  };

  // Compute x acceleration from forces, F=ma
  getNewAccelerationX = (forceList: IForce[]) => {
    let newXAcc = 0;
    if (forceList) {
      forceList.forEach((force) => {
        newXAcc +=
          (force.magnitude *
            Math.cos((force.directionInDegrees * Math.PI) / 180)) /
          this.props.mass;
      });
    }
    return newXAcc;
  };


  // Compute y acceleration from forces, F=ma
  getNewAccelerationY = (forceList: IForce[]) => {
    let newYAcc = 0;
    if (forceList) {
      forceList.forEach((force) => {
        newYAcc +=
          (-1 *
            (force.magnitude *
              Math.sin((force.directionInDegrees * Math.PI) / 180))) /
            this.props.mass;
      });
    }
    return newYAcc;
  };

  // Compute uniform circular motion forces given x, y positions
  getNewCircularMotionForces = (xPos: number, yPos: number) => {
    let deltaX = (this.props.xMin + this.props.xMax) / 2 - (xPos + this.props.radius);
    let deltaY = yPos + this.props.radius - (this.props.yMin + this.props.yMax) / 2;
    let dir = (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
    const tensionForce: IForce = {
      description: "Centripetal Force",
      magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius,
      directionInDegrees: dir,
      component: false,
    };
    return [tensionForce];
  };

  // Compute spring forces given y position
  getNewSpringForces = (yPos: number) => {
    let springForce: IForce = {
      description: "Spring Force",
      magnitude: 0,
      directionInDegrees: 90,
      component: false,
    };
    if (yPos - this.props.springRestLength > 0) {
      springForce = {
        description: "Spring Force",
        magnitude: this.props.springConstant * (yPos - this.props.springRestLength),
        directionInDegrees: 90,
        component: false,
      };
    } else if (yPos - this.props.springRestLength < 0) {
      springForce = {
        description: "Spring Force",
        magnitude: this.props.springConstant * (this.props.springRestLength - yPos),
        directionInDegrees: 270,
        component: false,
      };
    }
    return [
      {
        description: "Gravity",
        magnitude: Math.abs(this.props.gravity) * this.props.mass,
        directionInDegrees: 270,
        component: false,
      },
      springForce,
    ];
  };

  // Compute pendulum forces given position, velocity
  getNewPendulumForces = (
    xPos: number,
    yPos: number,
    xVel: number,
    yVel: number
  ) => {
    const x = this.props.xMax / 2 - xPos - this.props.radius;
    const y = yPos + this.props.radius + 5;
    let angle = (Math.atan(y / x) * 180) / Math.PI;
    if (angle < 0) {
      angle += 180;
    }
    let oppositeAngle = 90 - angle;
    if (oppositeAngle < 0) {
      oppositeAngle = 90 - (180 - angle);
    }

    const pendulumLength = Math.sqrt(x * x + y * y);
    this.props.dataDoc['pendulumAngle'] = oppositeAngle;

    const mag =
      this.props.mass  * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) +
      (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;

    const forceOfTension: IForce = {
      description: "Tension",
      magnitude: mag,
      directionInDegrees: angle,
      component: false,
    };

    return [
      {
        description: "Gravity",
        magnitude: Math.abs(this.props.gravity) * this.props.mass,
        directionInDegrees: 270,
        component: false,
      },
      forceOfTension,
    ];
  };

  // Check for collisions in x direction
  const checkForCollisionsWithWall = () => {
    let collision = false;
    const minX = this.state.xPosition;
    const maxX = this.state.xPosition + 2 * this.props.radius;
    if (this.state.xVelocity != 0) {
      this.state.walls.forEach((wall) => {
        if (wall.angleInDegrees == 90) {
          const wallX = (wall.xPos / 100) * this.props.layoutDoc._width;
          if (wall.xPos < 0.35) {
            if (minX <= wallX) {
              this.setState({xPosition: wallX+0.01});
              if (this.props.elasticCollisions) {
                this.setState({xVelocity: -this.state.xVelocity});
              } else {
                this.setState({xVelocity: 0});
              }
              collision = true;
            }
          } else {
            if (maxX >= wallX) {
              this.setState({xPosition: wallX- 2 * this.props.radius-0.01});
              if (this.props.elasticCollisions) {
                this.setState({xVelocity: -this.state.xVelocity});
              } else {
                this.setState({xVelocity: 0});
              }
              collision = true;
            }
          }
        }
      });
    }
    return collision;
  };

  // Check for collisions in y direction
  checkForCollisionsWithGround = () => {
    let collision = false;
    const minY = this.state.yPosition;
    const maxY = this.state.yPosition + 2 * this.props.radius;
    if (this.state.yVelocity > 0) {
      this.state.walls.forEach((wall) => {
        if (wall.angleInDegrees == 0 && wall.yPos > 0.4) {
          const groundY = (wall.yPos / 100) * this.props.layoutDoc._height;
          if (maxY > groundY) {
            this.setState({yPosition: groundY- 2 * this.props.radius-0.01});
            if (this.props.elasticCollisions) {
              this.setState({yVelocity: -this.state.yVelocity});
            } else {
              this.setState({yVelocity: 0});
              if (this.props.dataDoc['simulationType'] != "Two Weights") {
                const forceOfGravity: IForce = {
                  description: "Gravity",
                  magnitude: Math.abs(this.props.gravity) * this.props.mass,
                  directionInDegrees: 270,
                  component: false,
                };
                const normalForce: IForce = {
                  description: "Normal force",
                  magnitude: Math.abs(this.props.gravity) * this.props.mass,
                  directionInDegrees: wall.angleInDegrees + 90,
                  component: false,
                };
                this.props.dataDoc['updatedForces'] = ([forceOfGravity, normalForce]);
                if (this.props.dataDoc['simulationType'] == "Inclined Plane") {
                  const forceOfGravityC: IForce = {
                    description: "Gravity",
                    magnitude: Math.abs(this.props.gravity) * this.props.mass,
                    directionInDegrees: 270,
                    component: true,
                  };
                  const normalForceC: IForce = {
                    description: "Normal force",
                    magnitude: Math.abs(this.props.gravity) * this.props.mass,
                    directionInDegrees: wall.angleInDegrees + 90,
                    component: true,
                  };
                  this.props.dataDoc['componentForces'] = ([forceOfGravityC, normalForceC]);
                }
              }
            }
            collision = true;
          }
        }
      });
    }
    if (this.state.yVelocity < 0) {
      this.state.walls.forEach((wall) => {
        if (wall.angleInDegrees == 0 && wall.yPos < 0.4) {
          const groundY = (wall.yPos / 100) * this.props.layoutDoc._height;
          if (minY < groundY) {
            this.setState({yPosition: groundY + 0.01});
            if (this.props.elasticCollisions) {
              this.setState({yVelocity: -this.state.yVelocity});
            } else {
              this.setState({yVelocity: 0});
            }
            collision = true;
          }
        }
      });
    }
    return collision;
  };

  // Called at each RK4 step
  evaluate = (
    currentXPos: number,
    currentYPos: number,
    currentXVel: number,
    currentYVel: number,
    deltaXPos: number,
    deltaYPos: number,
    deltaXVel: number,
    deltaYVel: number,
    dt: number
  ) => {
    const newXPos = currentXPos + deltaXPos * dt;
    const newYPos = currentYPos + deltaYPos * dt;
    const newXVel = currentXVel + deltaXVel * dt;
    const newYVel = currentYVel + deltaYVel * dt;
    const newDeltaXPos = newXVel;
    const newDeltaYPos = newYVel;
    let forces = this.props.dataDoc['updatedForces'];
    if (this.props.dataDoc['simulationType'] == "Pendulum") {
      forces = this.getNewPendulumForces(newXPos, newYPos, newXVel, newYVel);
    } else if (this.props.dataDoc['simulationType'] == "Spring") {
      forces = this.getNewSpringForces(newYPos);
    } else if (this.props.dataDoc['simulationType'] == "Circular Motion") {
      forces = this.getNewCircularMotionForces(newXPos, newYPos);
    }
    const newDeltaXVel = this.getNewAccelerationX(forces);
    const newDeltaYVel = this.getNewAccelerationY(forces);
    return {
      xPos: newXPos,
      yPos: newYPos,
      xVel: newXVel,
      yVel: newYVel,
      deltaXPos: newDeltaXPos,
      deltaYPos: newDeltaYPos,
      deltaXVel: newDeltaXVel,
      deltaYVel: newDeltaYVel,
    };
  };

  // Update position, velocity using RK4 method
  update = () => {
    let startXVel = this.state.xVelocity;
    let startYVel = this.state.yVelocity;
    let xPos = this.state.xPosition;
    let yPos = this.state.yPosition;
    let xVel = this.state.xVelocity;
    let yVel = this.state.yVelocity;
    let forces: IForce[] = this.props.dataDoc['updatedForces'];
    if (this.props.dataDoc['simulationType'] == "Pendulum") {
      forces = this.getNewPendulumForces(xPos, yPos, xVel, yVel);
    } else if (this.props.dataDoc['simulationType'] == "Spring") {
      forces = this.getNewSpringForces(yPos);
    } else if (this.props.dataDoc['simulationType'] == "Circular Motion") {
      forces = this.getNewCircularMotionForces(xPos, yPos);
    }
    const xAcc = this.getNewAccelerationX(forces);
    const yAcc = this.getNewAccelerationY(forces);
    for (let i = 0; i < this.props.simulationSpeed; i++) {
      const k1 = this.evaluate(xPos, yPos, xVel, yVel, xVel, yVel, xAcc, yAcc, 0);
      const k2 = this.evaluate(
        xPos,
        yPos,
        xVel,
        yVel,
        k1.deltaXPos,
        k1.deltaYPos,
        k1.deltaXVel,
        k1.deltaYVel,
        this.props.timestepSize * 0.5
      );
      const k3 = this.evaluate(
        xPos,
        yPos,
        xVel,
        yVel,
        k2.deltaXPos,
        k2.deltaYPos,
        k2.deltaXVel,
        k2.deltaYVel,
        this.props.timestepSize * 0.5
      );
      const k4 = this.evaluate(
        xPos,
        yPos,
        xVel,
        yVel,
        k3.deltaXPos,
        k3.deltaYPos,
        k3.deltaXVel,
        k3.deltaYVel,
        this.props.timestepSize
      );

      xVel +=
        ((this.props.timestepSize * 1.0) / 6.0) *
        (k1.deltaXVel + 2 * (k2.deltaXVel + k3.deltaXVel) + k4.deltaXVel);
      yVel +=
        ((this.props.timestepSize * 1.0) / 6.0) *
        (k1.deltaYVel + 2 * (k2.deltaYVel + k3.deltaYVel) + k4.deltaYVel);
      xPos +=
        ((this.props.timestepSize * 1.0) / 6.0) *
        (k1.deltaXPos + 2 * (k2.deltaXPos + k3.deltaXPos) + k4.deltaXPos);
      yPos +=
        ((this.props.timestepSize * 1.0) / 6.0) *
        (k1.deltaYPos + 2 * (k2.deltaYPos + k3.deltaYPos) + k4.deltaYPos);
    }
    // make sure harmonic motion maintained and errors don't propagate
    if (this.props.dataDoc['simulationType'] == "Spring") {
      if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) {
        let equilibriumPos =
        this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
        let amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
        yPos = equilibriumPos - amplitude;
      } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) {
        let equilibriumPos =
        this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
        let amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
        yPos = equilibriumPos + amplitude;
      }
    }
    if (this.props.dataDoc['simulationType'] == "Pendulum") {
      let startX = this.state.updatedStartPosX;
      if (startXVel <= 0 && xVel > 0) {
        xPos = this.state.updatedStartPosX;
        if (this.state.updatedStartPosX > this.props.xMax / 2) {
          xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius;
        }
        yPos = this.props.startPosY;
      } else if (startXVel >= 0 && xVel < 0) {
        xPos = this.state.updatedStartPosX;
        if (this.state.updatedStartPosX < this.props.xMax / 2) {
          xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius;
        }
        yPos = this.props.startPosY;
      }
    }
    if (this.props.dataDoc['simulationType'] == "One Weight") {
      if (yPos < this.state.maxPosYConservation) {
        yPos = this.state.maxPosYConservation;
      }
    }
    this.setState({xVelocity: xVel});
    this.setState({yVelocity: yVel});
    this.setState({xPosition: xPos});
    this.setState({yPosition: yPos});
    let forcesn = this.props.dataDoc['updatedForces']
    if (this.props.dataDoc['simulationType'] == "Pendulum") {
      forcesn = this.getNewPendulumForces(xPos, yPos, xVel, yVel);
    } else if (this.props.dataDoc['simulationType'] == "Spring") {
      forcesn = this.getNewSpringForces(yPos);
    } else if (this.props.dataDoc['simulationType'] == "Circular Motion") {
      forcesn = this.getNewCircularMotionForces(xPos, yPos);
    }
    this.props.dataDoc['updatedForces'] = (forcesn);

    // set component forces if they change
    if (this.props.dataDoc['simulationType'] == "Pendulum") {
      let x = this.props.xMax / 2 - xPos - this.props.radius;
      let y = yPos + this.props.radius + 5;
      let angle = (Math.atan(y / x) * 180) / Math.PI;
      if (angle < 0) {
        angle += 180;
      }
      let oppositeAngle = 90 - angle;
      if (oppositeAngle < 0) {
        oppositeAngle = 90 - (180 - angle);
      }

      const pendulumLength = Math.sqrt(x * x + y * y);

      const mag =
      this.props.mass * Math.abs(this.props.gravity) * Math.cos((oppositeAngle * Math.PI) / 180) +
        (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;

      const tensionComponent: IForce = {
        description: "Tension",
        magnitude: mag,
        directionInDegrees: angle,
        component: true,
      };
      const gravityParallel: IForce = {
        description: "Gravity Parallel Component",
        magnitude: Math.abs(this.props.gravity) * Math.cos(((90 - angle) * Math.PI) / 180),
        directionInDegrees: 270 - (90 - angle),
        component: true,
      };
      const gravityPerpendicular: IForce = {
        description: "Gravity Perpendicular Component",
        magnitude: Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180),
        directionInDegrees: -(90 - angle),
        component: true,
      };
      if (Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180) < 0) {
        gravityPerpendicular.magnitude = Math.abs(
          Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180)
        );
        gravityPerpendicular.directionInDegrees = 180 - (90 - angle);
      }
      this.props.dataDoc['componentForces'] = ([
        tensionComponent,
        gravityParallel,
        gravityPerpendicular,
      ]);
    }
  };

  // Render weight, spring, rod(s), vectors
  render () { 
    <div style={{ zIndex: -1000 }}>
      <div
        className="weightContainer"
        onPointerDown={(e) => {
          if (this.draggable) {
            e.preventDefault();
            this.props.dataDoc['paused'] = true;
            this.setState({dragging: true});
            this.setState({clickPositionX: e.clientX});
            this.setState({clickPositionY: e.clientY});
          } 
        }}
        onPointerMove={(e) => {
          e.preventDefault();
          if (this.state.dragging) {
            let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
            if (newY > this.props.yMax - 2 * this.props.radius - 10) {
              newY = this.props.yMax - 2 * this.props.radius - 10;
            } else if (newY < 10) {
              newY = 10;
            }

            let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
            if (newX > this.props.xMax - 2 * this.props.radius - 10) {
              newX = this.props.xMax - 2 * this.props.radius - 10;
            } else if (newX < 10) {
              newX = 10;
            }
            if (this.props.dataDoc['simulationType'] == "Suspension") {
              if (newX < (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15) {
                newX = (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15;
              } else if (newX > (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15) {
                newX = (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15;
              }
            }

            this.setState({yPosition: newY});
            this.props.dataDoc['positionYDisplay'] = 
              Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100
            if (this.props.dataDoc['simulationType'] != "Pulley") {
              this.setState({xPosition: newX});
              this.props.dataDoc['positionXDisplay'] = newX
            }
            if (this.props.dataDoc['simulationType'] != "Suspension") {
              if (this.props.dataDoc['simulationType'] != "Pulley") {
                this.setState({updatedStartPosX: newX});
              }
              this.setState({updatedStartPosY: newY});
            }
            this.setState({clickPositionX: e.clientX});
            this.setState({clickPositionY: e.clientY});
            this.setDisplayValues();
          }
        }}
        onPointerUp={(e) => {
          if (this.state.dragging) {
            e.preventDefault();
            if (
              this.props.dataDoc['simulationType'] != "Pendulum" &&
              this.props.dataDoc['simulationType'] != "Suspension"
            ) {
              this.resetEverything();
            }
            this.setState({dragging: false});
            let newY = this.state.yPosition + e.clientY - this.state.clickPositionY;
            if (newY > this.props.yMax - 2 * this.props.radius - 10) {
              newY = this.props.yMax - 2 * this.props.radius - 10;
            } else if (newY < 10) {
              newY = 10;
            }

            let newX = this.state.xPosition + e.clientX - this.state.clickPositionX;
            if (newX > this.props.xMax - 2 * this.props.radius - 10) {
              newX = this.props.xMax - 2 * this.props.radius - 10;
            } else if (newX < 10) {
              newX = 10;
            }
            if (this.props.dataDoc['simulationType'] == "Spring") {
              this.props.dataDoc.springStartLength = newY
            }
            if (this.props.dataDoc['simulationType'] == "Suspension") {
              let x1rod = (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200;
              let x2rod = (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius;
              let deltaX1 = this.state.xPosition + this.props.radius - x1rod;
              let deltaX2 = x2rod - (this.state.xPosition + this.props.radius);
              let deltaY = this.state.yPosition + this.props.radius;
              let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
              let dir2T = Math.atan(deltaY / deltaX2);
              let tensionMag2 =
                (this.props.mass * Math.abs(this.props.gravity)) /
                ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) +
                  Math.sin(dir2T));
              let tensionMag1 =
                (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
              dir1T = (dir1T * 180) / Math.PI;
              dir2T = (dir2T * 180) / Math.PI;
              const tensionForce1: IForce = {
                description: "Tension",
                magnitude: tensionMag1,
                directionInDegrees: dir1T,
                component: false,
              };
              const tensionForce2: IForce = {
                description: "Tension",
                magnitude: tensionMag2,
                directionInDegrees: dir2T,
                component: false,
              };
              const grav: IForce = {
                description: "Gravity",
                magnitude: this.props.mass * Math.abs(this.props.gravity),
                directionInDegrees: 270,
                component: false,
              };
              this.props.dataDoc['updatedForces'] = ([tensionForce1, tensionForce2, grav]);
            }
          }
        }}
      >
        <div className="weight" style={this.weightStyle}>
          <p className="weightLabel">{this.props.mass} kg</p>
        </div>
      </div>
      {this.props.dataDoc['simulationType'] == "Spring" && (
        <div
          className="spring"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((val) => {
              const count = 10;
              let xPos1;
              let yPos1;
              let xPos2;
              let yPos2;
              if (val % 2 == 0) {
                xPos1 = this.state.xPosition + this.props.radius - 20;
                xPos2 = this.state.xPosition + this.props.radius + 20;
              } else {
                xPos1 = this.state.xPosition + this.props.radius + 20;
                xPos2 = this.state.xPosition + this.props.radius - 20;
              }
              yPos1 = (val * this.state.yPosition) / count;
              yPos2 = ((val + 1) * this.state.yPosition) / count;
              return (
                <line
                  key={val}
                  x1={xPos1}
                  y1={yPos1}
                  x2={xPos2}
                  y2={yPos2}
                  stroke={"#808080"}
                  strokeWidth="10"
                />
              );
            })}
          </svg>
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Pulley" && (
        <div
          className="wheel"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -1,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            <circle
              cx={(this.props.xMax + this.props.xMin) / 2}
              cy={this.props.radius}
              r={this.props.radius * 1.5}
              fill={"#808080"}
            />
          </svg>
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Pulley" && (
        <div
          className="rod"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            <line
              x1={this.state.xPosition + this.props.radius}
              y1={this.state.yPosition + this.props.radius}
              x2={this.state.xPosition + this.props.radius}
              y2={this.props.yMin}
              stroke={"#deb887"}
              strokeWidth="10"
            />
          </svg>
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Suspension" && (
        <div
          className="rod"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            <line
              x1={this.state.xPosition + this.props.radius}
              y1={this.state.yPosition + this.props.radius}
              x2={(this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200}
              y2={this.props.yMin}
              stroke={"#deb887"}
              strokeWidth="10"
            />
          </svg>
          <p
            style={{
              position: "absolute",
              zIndex: 10,
              left: (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200 + 80 + "px",
              top: 10 + "px",
              backgroundColor: this.labelBackgroundColor,
            }}
          >
            {Math.round(
              ((Math.atan(
                (this.state.yPosition + this.props.radius) /
                  (this.state.xPosition +
                    this.props.radius -
                    ((this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200))
              ) *
                180) /
                Math.PI) *
                100
            ) / 100}
            °
          </p>
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Suspension" && (
        <div
          className="rod"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            <line
              x1={this.state.xPosition + this.props.radius}
              y1={this.state.yPosition + this.props.radius}
              x2={(this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius}
              y2={this.props.yMin}
              stroke={"#deb887"}
              strokeWidth="10"
            />
          </svg>

          <p
            style={{
              position: "absolute",
              zIndex: 10,
              left: (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - 80 + "px",
              top: 10 + "px",
              backgroundColor: this.labelBackgroundColor,
            }}
          >
            {Math.round(
              ((Math.atan(
                (this.state.yPosition + this.props.radius) /
                  ((this.props.xMax + this.props.xMin) / 2 +
                  this.props.yMin +
                    200 +
                    this.props.radius -
                    (this.state.xPosition + this.props.radius))
              ) *
                180) /
                Math.PI) *
                100
            ) / 100}
            °
          </p>
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Circular Motion" && (
        <div
          className="rod"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            <line
              x1={this.state.xPosition + this.props.radius}
              y1={this.state.yPosition + this.props.radius}
              x2={(this.props.xMin + this.props.xMax) / 2}
              y2={(this.props.yMin + this.props.yMax) / 2}
              stroke={"#deb887"}
              strokeWidth="10"
            />
          </svg>
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Pendulum" && (
        <div
          className="rod"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
            <line
              x1={this.state.xPosition + this.props.radius}
              y1={this.state.yPosition + this.props.radius}
              x2={this.props.xMax / 2}
              y2={-5}
              stroke={"#deb887"}
              strokeWidth="10"
            />
          </svg>
          {!this.state.dragging && (
            <div>
              <p
                style={{
                  position: "absolute",
                  zIndex: 5,
                  left: this.state.xPosition + "px",
                  top: this.state.yPosition - 70 + "px",
                  backgroundColor: this.labelBackgroundColor,
                }}
              >
                {Math.round(this.props.pendulumLength)} m
              </p>
              <p
                style={{
                  position: "absolute",
                  zIndex: -1,
                  left: this.props.xMax / 2 + "px",
                  top: 30 + "px",
                  backgroundColor: this.labelBackgroundColor,
                }}
              >
                {Math.round(this.props.pendulumAngle * 100) / 100}°
              </p>
            </div>
          )}
        </div>
      )}
      {this.props.dataDoc['simulationType'] == "Inclined Plane" && (
        <div>
          <div
            style={{ position: "absolute", left: "0", top: "0", zIndex: -5 }}
          >
            <svg width={this.props.xMax + "px"} height={this.props.yMax + "px"}>
              <polygon points={this.state.coordinates} style={{ fill: "burlywood" }} />
            </svg>
          </div>

          <p
            style={{
              position: "absolute",
              zIndex: 500,
              left: Math.round(this.props.xMax * 0.5 - 200 + this.props.wedgeWidth - 80) + "px",
              top: Math.round(this.props.layoutDoc._height * 0.8 - 40) + "px",
            }}
          >
            {Math.round(
              ((Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI) * 100
            ) / 100}
            °
          </p>
        </div>
      )}
      {!this.state.dragging && this.props.showAcceleration && (
        <div>
          <div
            style={{
              pointerEvents: "none",
              position: "absolute",
              zIndex: -1,
              left: 0,
              top: 0,
            }}
          >
            <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
              <defs>
                <marker
                  id="accArrow"
                  markerWidth="10"
                  markerHeight="10"
                  refX="0"
                  refY="3"
                  orient="auto"
                  markerUnits="strokeWidth"
                >
                  <path d="M0,0 L0,6 L9,3 z" fill="green" />
                </marker>
              </defs>
              <line
                x1={this.state.xPosition + this.props.radius}
                y1={this.state.yPosition + this.props.radius}
                x2={this.state.xPosition + this.props.radius + this.getNewAccelerationX(this.props.updatedForces) * 7}
                y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.updatedForces) * 7}
                stroke={"green"}
                strokeWidth="5"
                markerEnd="url(#accArrow)"
              />
            </svg>
            <div
              style={{
                pointerEvents: "none",
                position: "absolute",
                left: this.state.xPosition + this.props.radius + this.state.xAccel * 3 + 25 + "px",
                top: this.state.yPosition + this.props.radius + this.state.yAccel * 3 + 70 + "px",
                zIndex: -1,
                lineHeight: 0.5,
              }}
            >
              <p>
                {Math.round(
                  100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel)
                ) / 100}{" "}
                m/s
                <sup>2</sup>
              </p>
            </div>
          </div>
        </div>
      )}
      {!this.state.dragging && this.props.showVelocity && (
        <div>
          <div
            style={{
              pointerEvents: "none",
              position: "absolute",
              zIndex: -1,
              left: 0,
              top: 0,
            }}
          >
            <svg width={this.props.xMax + "px"} height={this.props.layoutDoc._height + "px"}>
              <defs>
                <marker
                  id="velArrow"
                  markerWidth="10"
                  markerHeight="10"
                  refX="0"
                  refY="3"
                  orient="auto"
                  markerUnits="strokeWidth"
                >
                  <path d="M0,0 L0,6 L9,3 z" fill="blue" />
                </marker>
              </defs>
              <line
                x1={this.state.xPosition + this.props.radius}
                y1={this.state.yPosition + this.props.radius}
                x2={this.state.xPosition + this.props.radius + this.state.xVelocity * 3}
                y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 3}
                stroke={"blue"}
                strokeWidth="5"
                markerEnd="url(#velArrow)"
              />
            </svg>
            <div
              style={{
                pointerEvents: "none",
                position: "absolute",
                left: this.state.xPosition + this.props.radius + this.state.xVelocity * 3 + 25 + "px",
                top: this.state.yPosition +  this.props.radius + this.state.yVelocity * 3 + "px",
                zIndex: -1,
                lineHeight: 0.5,
              }}
            >
              <p>
                {Math.round(
                  100 *
                    Math.sqrt(
                      this.props.displayXVelocity * this.props.displayXVelocity +
                      this.props.displayYVelocity * this.props.displayYVelocity
                    )
                ) / 100}{" "}
                m/s
              </p>
            </div>
          </div>
        </div>
      )}
      {!this.state.dragging &&
        this.props.showComponentForces &&
        this.props.componentForces.map((force, index) => {
          if (force.magnitude < this.epsilon) {
            return;
          }
          let arrowStartY: number = this.state.yPosition + this.props.radius;
          const arrowStartX: number = this.state.xPosition + this.props.radius;
          let arrowEndY: number =
            arrowStartY -
            Math.abs(force.magnitude) *
              20 *
              Math.sin((force.directionInDegrees * Math.PI) / 180);
          const arrowEndX: number =
            arrowStartX +
            Math.abs(force.magnitude) *
              20 *
              Math.cos((force.directionInDegrees * Math.PI) / 180);

          let color = "#0d0d0d";

          let labelTop = arrowEndY;
          let labelLeft = arrowEndX;
          if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
            labelLeft -= 120;
          } else {
            labelLeft += 30;
          }
          if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
            labelTop += 40;
          } else {
            labelTop -= 40;
          }
          labelTop = Math.min(labelTop, this.props.yMax + 50);
          labelTop = Math.max(labelTop, this.props.yMin);
          labelLeft = Math.min(labelLeft, this.props.xMax - 60);
          labelLeft = Math.max(labelLeft, this.props.xMin);

          return (
            <div key={index}>
              <div
                style={{
                  pointerEvents: "none",
                  position: "absolute",
                  zIndex: -1,
                  left: this.props.xMin,
                  top: this.props.yMin,
                }}
              >
                <svg
                  width={this.props.xMax - this.props.xMin + "px"}
                  height={this.props.layoutDoc._height + "px"}
                >
                  <defs>
                    <marker
                      id="forceArrow"
                      markerWidth="10"
                      markerHeight="10"
                      refX="0"
                      refY="3"
                      orient="auto"
                      markerUnits="strokeWidth"
                    >
                      <path d="M0,0 L0,6 L9,3 z" fill={color} />
                    </marker>
                  </defs>
                  {force.component == true && (
                    <line
                      x1={arrowStartX}
                      y1={arrowStartY}
                      x2={arrowEndX}
                      y2={arrowEndY}
                      stroke={color}
                      strokeWidth="5"
                      strokeDasharray="10,10"
                      markerEnd="url(#forceArrow)"
                    />
                  )}
                  {force.component == false && (
                    <line
                      x1={arrowStartX}
                      y1={arrowStartY}
                      x2={arrowEndX}
                      y2={arrowEndY}
                      stroke={color}
                      strokeWidth="5"
                      markerEnd="url(#forceArrow)"
                    />
                  )}
                </svg>
              </div>
              <div
                style={{
                  pointerEvents: "none",
                  position: "absolute",
                  left: labelLeft + "px",
                  top: labelTop + "px",
                  // zIndex: -1,
                  lineHeight: 0.5,
                  backgroundColor: this.labelBackgroundColor,
                }}
              >
                {force.description && <p>{force.description}</p>}
                {!force.description && <p>Force</p>}
                {this.props.showForceMagnitudes && (
                  <p>{Math.round(100 * force.magnitude) / 100} N</p>
                )}
              </div>
            </div>
          );
        })}
      {!this.state.dragging &&
        this.props.showForces &&
        this.props.updatedForces.map((force, index) => {
          if (force.magnitude < this.epsilon) {
            return;
          }
          let arrowStartY: number = this.state.yPosition + this.props.radius;
          const arrowStartX: number = this.state.xPosition + this.props.radius;
          let arrowEndY: number =
            arrowStartY -
            Math.abs(force.magnitude) *
              20 *
              Math.sin((force.directionInDegrees * Math.PI) / 180);
          const arrowEndX: number =
            arrowStartX +
            Math.abs(force.magnitude) *
              20 *
              Math.cos((force.directionInDegrees * Math.PI) / 180);

          let color = "#0d0d0d";

          let labelTop = arrowEndY;
          let labelLeft = arrowEndX;
          if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
            labelLeft -= 120;
          } else {
            labelLeft += 30;
          }
          if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
            labelTop += 40;
          } else {
            labelTop -= 40;
          }
          labelTop = Math.min(labelTop, this.props.yMax + 50);
          labelTop = Math.max(labelTop, this.props.yMin);
          labelLeft = Math.min(labelLeft, this.props.xMax - 60);
          labelLeft = Math.max(labelLeft, this.props.xMin);

          return (
            <div key={index}>
              <div
                style={{
                  pointerEvents: "none",
                  position: "absolute",
                  zIndex: -1,
                  left: this.props.xMin,
                  top: this.props.yMin,
                }}
              >
                <svg
                  width={this.props.xMax - this.props.xMin + "px"}
                  height={this.props.layoutDoc._height + "px"}
                >
                  <defs>
                    <marker
                      id="forceArrow"
                      markerWidth="10"
                      markerHeight="10"
                      refX="0"
                      refY="3"
                      orient="auto"
                      markerUnits="strokeWidth"
                    >
                      <path d="M0,0 L0,6 L9,3 z" fill={color} />
                    </marker>
                  </defs>
                  {force.component == true && (
                    <line
                      x1={arrowStartX}
                      y1={arrowStartY}
                      x2={arrowEndX}
                      y2={arrowEndY}
                      stroke={color}
                      strokeWidth="5"
                      strokeDasharray="10,10"
                      markerEnd="url(#forceArrow)"
                    />
                  )}
                  {force.component == false && (
                    <line
                      x1={arrowStartX}
                      y1={arrowStartY}
                      x2={arrowEndX}
                      y2={arrowEndY}
                      stroke={color}
                      strokeWidth="5"
                      markerEnd="url(#forceArrow)"
                    />
                  )}
                </svg>
              </div>
              <div
                style={{
                  pointerEvents: "none",
                  position: "absolute",
                  left: labelLeft + "px",
                  top: labelTop + "px",
                  lineHeight: 0.5,
                  backgroundColor: this.labelBackgroundColor,
                }}
              >
                {force.description && <p>{force.description}</p>}
                {!force.description && <p>Force</p>}
                {this.props.showForceMagnitudes && (
                  <p>{Math.round(100 * force.magnitude) / 100} N</p>
                )}
              </div>
            </div>
          );
        })}
    </div>
  }
};
