From 9e1c341955fb016f4a62339e4f11ac42d9572e95 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 May 2023 16:39:38 -0400
Subject: render cleanup for phys vector arrows.

---
 .../nodes/PhysicsBox/PhysicsSimulationWeight.tsx   | 288 ++++++++-------------
 1 file changed, 112 insertions(+), 176 deletions(-)

(limited to 'src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx')

diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index 98b0e3f04..f533df109 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -116,9 +116,20 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         };
     }
 
-    componentDidMount() {
-        // Timer for animating the simulation
-        setInterval(() => this.setState({ timer: this.state.timer + 1 }), 50);
+    _timer: NodeJS.Timeout | undefined;
+
+    componentWillUnmount() {
+        this._timer && clearTimeout(this._timer);
+    }
+    componentWillUpdate(nextProps: Readonly<IWeightProps>, nextState: Readonly<IState>, nextContext: any): void {
+        if (nextProps.simulationType !== this.props.simulationType) setTimeout(() => this.setState({ timer: this.state.timer + 1 }));
+        if (nextProps.paused) {
+            this._timer && clearTimeout(this._timer);
+            this._timer = undefined;
+        } else if (this.props.paused) {
+            this._timer && clearTimeout(this._timer);
+            this._timer = setInterval(() => this.setState({ timer: this.state.timer + 1 }), 50);
+        }
     }
 
     // Constants
@@ -154,7 +165,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
 
     // Helper function to go between display and real values
     getDisplayYPos = (yPos: number) => this.props.yMax - yPos - 2 * this.props.radius + 5;
-
+    gravityForce = (): IForce => ({
+        description: 'Gravity',
+        magnitude: this.props.mass * this.props.gravity,
+        directionInDegrees: 270,
+    });
     // 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.props.setPosition(xPos, this.getDisplayYPos(yPos));
@@ -210,7 +225,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
             if (this.props.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));
+                    maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * this.props.gravity);
                 }
                 if (maxYPos < 0) maxYPos = 0;
 
@@ -247,51 +262,41 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                 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)),
+                    magnitude: this.props.mass * 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,
                 };
                 const 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)),
+                    magnitude: this.props.mass * this.props.coefficientOfKineticFriction * 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,
                 };
                 // reduce magnitude of friction force if necessary such that block cannot slide up plane
                 // prettier-ignore
-                const yForce = -Math.abs(this.props.gravity) +
+                const yForce = - this.props.gravity +
                         normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) +
                         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);
+                    frictionForce.magnitude = (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + 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,
-                };
                 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)),
+                    magnitude: this.props.mass * 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,
                 };
                 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)),
+                    magnitude: this.props.mass * 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,
                 };
                 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)),
+                    magnitude: this.props.mass * 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,
                 };
-                const gravityForce: IForce = {
-                    description: 'Gravity',
-                    magnitude: this.props.mass * Math.abs(this.props.gravity),
-                    directionInDegrees: 270,
-                };
-                const kineticFriction = this.props.coefficientOfKineticFriction != 0;
-                this.props.setForcesUpdated([gravityForce, normalForce, ...(kineticFriction ? [frictionForce] : [])]);
-                this.props.setComponentForces([normalForceComponent, gravityParallel, gravityPerpendicular, ...(kineticFriction ? [frictionForceComponent] : [])]);
+                const kineticFriction = this.props.coefficientOfKineticFriction != 0 ? [frictionForce] : [];
+                this.props.setForcesUpdated([this.gravityForce(), normalForce, ...kineticFriction]);
+                this.props.setComponentForces([normalForceComponent, gravityParallel, gravityPerpendicular, ...kineticFriction]);
             }
         }
 
@@ -317,13 +322,12 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         }
 
         // Update wedge coordinates
-        if (this.state.coordinates === '' || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) {
+        if (!this.state.coordinates || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) {
             const left = this.props.xMax * 0.25;
             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 });
+            this.setState({ coordinates: coordinatePair1 + coordinatePair2 + coordinatePair3 });
         }
 
         if (this.state.xPosition != prevState.xPosition || this.state.yPosition != prevState.yPosition) {
@@ -395,11 +399,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         const yPosPlus = yPos - this.props.springRestLength > 0;
         const yPosMinus = yPos - this.props.springRestLength < 0;
         return [
-            {
-                description: 'Gravity',
-                magnitude: Math.abs(this.props.gravity) * this.props.mass,
-                directionInDegrees: 270,
-            },
+            this.gravityForce(),
             {
                 description: 'Spring Force',
                 magnitude: this.props.springConstant * (yPos - this.props.springRestLength) * (yPosPlus ? 1 : yPosMinus ? -1 : 0),
@@ -412,10 +412,8 @@ export default class Weight extends React.Component<IWeightProps, IState> {
     getNewPendulumForces = (xPos: number, yPos: number, xVel: number, yVel: number): IForce[] => {
         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;
-        }
+        const angle = (ang => (ang < 0 ? ang + 180 : ang))((Math.atan(y / x) * 180) / Math.PI);
+
         let oppositeAngle = 90 - angle;
         if (oppositeAngle < 0) {
             oppositeAngle = 90 - (180 - angle);
@@ -424,14 +422,10 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         const pendulumLength = Math.sqrt(x * x + y * y);
         this.props.setPendulumAngle(oppositeAngle, undefined);
 
-        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 mag = this.props.mass * this.props.gravity * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;
 
         return [
-            {
-                description: 'Gravity',
-                magnitude: Math.abs(this.props.gravity) * this.props.mass,
-                directionInDegrees: 270,
-            },
+            this.gravityForce(),
             {
                 description: 'Tension',
                 magnitude: mag,
@@ -471,11 +465,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
             this.state.walls.forEach(wall => {
                 if (wall.angleInDegrees == 0 && wall.yPos > 0.4) {
                     const groundY = (wall.yPos / 100) * this.props.panelHeight();
-                    const gravity: IForce = {
-                        description: 'Gravity',
-                        magnitude: Math.abs(this.props.gravity) * this.props.mass,
-                        directionInDegrees: 270,
-                    };
+                    const gravity = this.gravityForce();
                     if (maxY > groundY) {
                         this.setState({ yPosition: groundY - 2 * this.props.radius - 0.01 });
                         if (this.props.elasticCollisions) {
@@ -536,11 +526,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
     getForces = (xPos: number, yPos: number, xVel: number, yVel: number) => {
         // prettier-ignore
         switch (this.props.simulationType) {
-                case 'Pendulum':        return this.getNewPendulumForces(xPos, yPos, xVel, yVel);
-                case 'Spring'  :        return this.getNewSpringForces(yPos);
-                case 'Circular Motion': return this.getNewCircularMotionForces(xPos, yPos);
-                default:                return this.props.forcesUpdated();
-            }
+            case 'Pendulum':        return this.getNewPendulumForces(xPos, yPos, xVel, yVel);
+            case 'Spring'  :        return this.getNewSpringForces(yPos);
+            case 'Circular Motion': return this.getNewCircularMotionForces(xPos, yPos);
+            default:                return this.props.forcesUpdated();
+        }
     };
 
     // Update position, velocity using RK4 method
@@ -569,13 +559,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         // make sure harmonic motion maintained and errors don't propagate
         switch (this.props.simulationType) {
             case 'Spring':
+                const equilibriumPos = this.props.springRestLength + (this.props.mass * this.props.gravity) / this.props.springConstant;
+                const amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
                 if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) {
-                    const equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
-                    const amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
                     yPos = equilibriumPos - amplitude;
                 } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) {
-                    const equilibriumPos = this.props.springRestLength + (this.props.mass * Math.abs(this.props.gravity)) / this.props.springConstant;
-                    const amplitude = Math.abs(equilibriumPos - this.props.springStartLength);
                     yPos = equilibriumPos + amplitude;
                 }
                 break;
@@ -620,32 +608,30 @@ export default class Weight extends React.Component<IWeightProps, IState> {
 
             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,
+                magnitude: this.props.mass * this.props.gravity * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength,
                 directionInDegrees: angle,
             };
             const gravityParallel: IForce = {
                 description: 'Gravity Parallel Component',
-                magnitude: Math.abs(this.props.gravity) * Math.cos(((90 - angle) * Math.PI) / 180),
+                magnitude: this.props.gravity * Math.cos(((90 - angle) * Math.PI) / 180),
                 directionInDegrees: 270 - (90 - angle),
             };
             const gravityPerpendicular: IForce = {
                 description: 'Gravity Perpendicular Component',
-                magnitude: Math.abs(this.props.gravity) * Math.sin(((90 - angle) * Math.PI) / 180),
+                magnitude: this.props.gravity * Math.sin(((90 - angle) * Math.PI) / 180),
                 directionInDegrees: -(90 - angle),
             };
-            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));
+            if (this.props.gravity * Math.sin(((90 - angle) * Math.PI) / 180) < 0) {
+                gravityPerpendicular.magnitude = Math.abs(this.props.gravity * Math.sin(((90 - angle) * Math.PI) / 180));
                 gravityPerpendicular.directionInDegrees = 180 - (90 - angle);
             }
             this.props.setComponentForces([tensionComponent, gravityParallel, gravityPerpendicular]);
         }
     };
 
-    renderForce = (force: IForce, index: number, asComponent: boolean) => {
+    renderForce = (force: IForce, index: number, asComponent: boolean, color = '#0d0d0d') => {
         if (force.magnitude < this.epsilon) return;
 
         const angle = (force.directionInDegrees * Math.PI) / 180;
@@ -654,8 +640,6 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         const arrowEndY = arrowStartY - Math.abs(force.magnitude) * Math.sin(angle) - this.props.radius * Math.sin(angle);
         const arrowEndX = arrowStartX + Math.abs(force.magnitude) * Math.cos(angle) + this.props.radius * Math.cos(angle);
 
-        const color = '#0d0d0d';
-
         let labelTop = arrowEndY + (force.directionInDegrees >= 0 && force.directionInDegrees < 180 ? 40 : -40);
         let labelLeft = arrowEndX + (force.directionInDegrees > 90 && force.directionInDegrees < 270 ? -120 : 30);
 
@@ -696,6 +680,40 @@ export default class Weight extends React.Component<IWeightProps, IState> {
         );
     };
 
+    renderVector = (id: string, magX: number, magY: number, color: string, label: string) => {
+        const mag = Math.sqrt(magX * magX + magY * magY);
+        return (
+            <div className="showvecs" style={{ zIndex: 6 }}>
+                <svg width={this.panelWidth} height={this.panelHeight}>
+                    <defs>
+                        <marker id={id} 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>
+                    <line
+                        x1={this.state.xPosition + this.props.radius + (magX / mag) * this.props.radius}
+                        y1={this.state.yPosition + this.props.radius + (magY / mag) * this.props.radius}
+                        x2={this.state.xPosition + this.props.radius + (magX / mag) * this.props.radius + magX}
+                        y2={this.state.yPosition + this.props.radius + (magY / mag) * this.props.radius + magY}
+                        stroke={color}
+                        strokeWidth="5"
+                        markerEnd={`url(#${id})`}
+                    />
+                </svg>
+                <div
+                    style={{
+                        pointerEvents: 'none',
+                        position: 'absolute',
+                        left: this.state.xPosition + this.props.radius + 2 * (magX / mag) * this.props.radius + magX + 'px',
+                        top: this.state.yPosition + this.props.radius + 2 * (magY / mag) * this.props.radius + magY + 'px',
+                        lineHeight: 1,
+                    }}>
+                    {/* <p>{label}</p> */}
+                </div>
+            </div>
+        );
+    };
+
     // Render weight, spring, rod(s), vectors
     render() {
         return (
@@ -784,7 +802,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                                 const deltaY = this.state.yPosition + this.props.radius;
                                 const dir1T = Math.PI - Math.atan(deltaY / deltaX1);
                                 const dir2T = Math.atan(deltaY / deltaX2);
-                                const tensionMag2 = (this.props.mass * Math.abs(this.props.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
+                                const tensionMag2 = (this.props.mass * this.props.gravity) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
                                 const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
                                 const tensionForce1: IForce = {
                                     description: 'Tension',
@@ -796,12 +814,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                                     magnitude: tensionMag2,
                                     directionInDegrees: (dir2T * 180) / Math.PI,
                                 };
-                                const grav: IForce = {
-                                    description: 'Gravity',
-                                    magnitude: this.props.mass * Math.abs(this.props.gravity),
-                                    directionInDegrees: 270,
-                                };
-                                this.props.setForcesUpdated([tensionForce1, tensionForce2, grav]);
+                                this.props.setForcesUpdated([tensionForce1, tensionForce2, this.gravityForce()]);
                             }
                         }
                     }}>
@@ -810,22 +823,15 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                     </div>
                 </div>
                 {this.props.simulationType == 'Spring' && (
-                    <div
-                        className="spring"
-                        style={{
-                            pointerEvents: 'none',
-                            position: 'absolute',
-                            left: 0,
-                            top: 0,
-                        }}>
+                    <div className="spring">
                         <svg width={this.panelWidth} height={this.panelHeight}>
-                            {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(val => {
+                            {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(val => {
                                 const count = 10;
                                 const xPos1 = this.state.xPosition + this.props.radius + (val % 2 === 0 ? -20 : 20);
+                                const xPos2 = this.state.xPosition + this.props.radius + (val === 10 ? 0 : val % 2 === 0 ? 20 : -20);
                                 const yPos1 = (val * this.state.yPosition) / count;
-                                const xPos2 = this.state.xPosition + this.props.radius + (val % 2 === 0 ? 20 : -20);
-                                const yPos2 = ((val + 1) * this.state.yPosition) / count;
-                                return <line key={val} x1={xPos1} y1={yPos1} x2={xPos2} y2={yPos2} stroke={'#808080'} strokeWidth="10" />;
+                                const yPos2 = val === 10 ? this.state.yPosition + this.props.radius : ((val + 1) * this.state.yPosition) / count;
+                                return <line key={val} x1={xPos1} strokeLinecap="round" y1={yPos1} x2={xPos2} y2={yPos2} stroke={'#808080'} strokeWidth="10" />;
                             })}
                         </svg>
                     </div>
@@ -846,14 +852,7 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                     </div>
                 )}
                 {this.props.simulationType == 'Pulley' && (
-                    <div
-                        className="wheel"
-                        style={{
-                            pointerEvents: 'none',
-                            position: 'absolute',
-                            left: 0,
-                            top: 0,
-                        }}>
+                    <div className="wheel">
                         <svg width={this.panelWidth} height={this.panelHeight}>
                             <circle cx={(this.props.xMax + this.props.xMin) / 2} cy={this.props.radius} r={this.props.radius * 1.5} fill={'#808080'} />
                         </svg>
@@ -895,7 +894,6 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                                 />
                             </svg>
                         </div>
-
                         <p
                             style={{
                                 position: 'absolute',
@@ -956,12 +954,11 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                 )}
                 {this.props.simulationType == 'Inclined Plane' && (
                     <div>
-                        <div style={{ position: 'absolute', left: '0', top: '0' }}>
+                        <div className="wedge">
                             <svg width={this.panelWidth} height={this.props.yMax + 'px'}>
                                 <polygon points={this.state.coordinates} style={{ fill: 'burlywood' }} />
                             </svg>
                         </div>
-
                         <p
                             style={{
                                 position: 'absolute',
@@ -972,85 +969,24 @@ export default class Weight extends React.Component<IWeightProps, IState> {
                         </p>
                     </div>
                 )}
-                {!this.state.dragging && this.props.showAcceleration && (
-                    <div>
-                        <div
-                            style={{
-                                pointerEvents: 'none',
-                                position: 'absolute',
-                                left: 0,
-                                top: 0,
-                            }}>
-                            <svg width={this.panelWidth} height={this.panelHeight}>
-                                <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.forcesUpdated()) * 15}
-                                    y2={this.state.yPosition + this.props.radius + this.getNewAccelerationY(this.props.forcesUpdated()) * 15}
-                                    stroke={'green'}
-                                    strokeWidth="5"
-                                    markerEnd="url(#accArrow)"
-                                />
-                            </svg>
-                            <div
-                                style={{
-                                    pointerEvents: 'none',
-                                    position: 'absolute',
-                                    left: this.state.xPosition + this.props.radius + this.state.xAccel * 15 + 25 + 'px',
-                                    top: this.state.yPosition + this.props.radius + this.state.yAccel * 15 + 70 + 'px',
-                                    lineHeight: 1,
-                                }}>
-                                <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',
-                                left: 0,
-                                top: 0,
-                            }}>
-                            <svg width={this.panelWidth} height={this.panelHeight}>
-                                <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 * 7}
-                                    y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 7}
-                                    stroke={'blue'}
-                                    strokeWidth="5"
-                                    markerEnd="url(#velArrow)"
-                                />
-                            </svg>
-                            <div
-                                style={{
-                                    pointerEvents: 'none',
-                                    position: 'absolute',
-                                    left: this.state.xPosition + this.props.radius + this.state.xVelocity * 7 + 25 + 'px',
-                                    top: this.state.yPosition + this.props.radius + this.state.yVelocity * 7 + 'px',
-                                    lineHeight: 1,
-                                }}>
-                                <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.showAcceleration &&
+                    this.renderVector(
+                        'accArrow',
+                        this.getNewAccelerationX(this.props.forcesUpdated()),
+                        this.getNewAccelerationY(this.props.forcesUpdated()),
+                        'green',
+                        `${Math.round(100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel)) / 100} m/s^2`
+                    )}
+                {!this.state.dragging &&
+                    this.props.showVelocity &&
+                    this.renderVector(
+                        'velArrow',
+                        this.state.xVelocity,
+                        this.state.yVelocity,
+                        'blue',
+                        `${Math.round(100 * Math.sqrt(this.props.displayXVelocity * this.props.displayXVelocity + this.props.displayYVelocity * this.props.displayYVelocity)) / 100} m/s`
+                    )}
                 {!this.state.dragging && this.props.showComponentForces && this.props.componentForces().map((force, index) => this.renderForce(force, index, true))}
                 {!this.state.dragging && this.props.showForces && this.props.forcesUpdated().map((force, index) => this.renderForce(force, index, false))}
             </div>
-- 
cgit v1.2.3-70-g09d2

