Repository: UniversityOfSaskatchewanCMPT371/term-project-2025-team-1
Pull request #176: Id3 3d graph
Author: login: m-labrecque
Created: 2025-03-07T03:39:10Z | Merged: 2025-03-09T19:15:11Z

## Describe your changes
Created the beginnings of the embedded graph. Only a box with some text is currently displayed, just to show that it was created. But the vector calculation is implemented and points are stored and ready to be used to create the graph.

## Issue ticket number and link
There is no issue for this, this was a feature.
## Explanation of the testing process
There are unit tests for the vector calculation. And the system was run to confirm the expected ui appeared.
## Checklist before requesting a review

- [x] I have added one lead (Dev or Test), and another reviewer (**not** PM)
- [x] I have performed a self-review of my code
- [x] If it is a core feature, I have added thorough tests.

> [!TIP]
> The easier it is to understand what's changed, the quicker your PR will be approved!

> [!IMPORTANT]
> All comments and tweaks suggested by made by reviewers must be reconciled by each member before full approval



Diff:
diff --git a/csvTestFiles/indexedData.csv b/csvTestFiles/indexedData.csv
new file mode 100644
index 00000000..4a97e030
--- /dev/null
+++ b/csvTestFiles/indexedData.csv
@@ -0,0 +1,11 @@
+Time,X,B
+0,1,2
+1,2,4
+2,3,6
+3,4,8
+4,5,10
+5,6,12
+6,7,14
+7,8,16
+8,9,18
+9,10,20
\ No newline at end of file
diff --git a/projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx b/projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
new file mode 100644
index 00000000..2732f130
--- /dev/null
+++ b/projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
@@ -0,0 +1,29 @@
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
+            <EmbeddedGraph graph={graphObject}/>
+        </>
+    )
+}
\ No newline at end of file
diff --git a/projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx b/projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
new file mode 100644
index 00000000..87affd79
--- /dev/null
+++ b/projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
@@ -0,0 +1,144 @@
+import { sendError, sendLog } from "../../logger-frontend";
+import { EmbeddedInterface } from "../../types/EmbeddedInterface";
+import { CSVDataObject } from "../Csv_Components/CSVDataObject";
+import { GraphObject } from "./GraphObject";
+import { PointObject } from "./PointObject";
+
+/**
+ * EmbeddedGraphObject is a class that extends GraphObject and implements the EmbeddedInterface
+ * Holds attributes and methods required of a EmbeddedGraph
+ * @preconditions A valid CSVDataObject
+ * @postconditions Creates a new EmbeddedGraph Object
+ */
+export class EmbeddedGraphObject extends GraphObject implements EmbeddedInterface {
+
+    tao: number;
+
+    constructor(csv: CSVDataObject) {
+        super(csv);
+        this.tao = 1;
+    }
+
+    /**
+     * Adds embedded point vectors to the graph.
+     * pre-conditions: valid points exist in the csvDataObject of the graph
+     * post-conditions: PointObject's containing the vectors are stored in the points array attribute
+     */
+    addPoints(): void {
+        const data = this.csvData.getData();
+        data.forEach((line) => {
+            const newPoint = new PointObject();
+            // gets the time of the current line in the data set
+            const time = line[this.axes.xLabel as keyof typeof line] as unknown as number;
+
+            // calculates the vector and stores it in the position attribute of a new PointObject
+            const vectorPosition = this.calculateVectorPosition(time, data);
+            newPoint.setPosition(vectorPosition);
+            this.points.push(newPoint);
+        });
+        sendLog("info", "Points added to EmbeddedGraphObject (EmbeddedGraphObject.addPoints())");
+    }
+
+    /**
+     * Calculated the embedded time vector dimensions for the given time.
+     * Uses the data set selected in the csvDataObject of the graph
+     * Vector return is of the form [y[time], y[time - tao], y[time - 2*tao]] where y is the data set column selected
+     * pre-conditions: time >= 0, csvDataObject must contain valid data set and a valid data set much be selected
+     * post-conditions: none
+     * @param time - the index/time of the data set calculating the vector for
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns an array contaning the coordinates of the vector in the form [x, y, z]
+     */
+    calculateVectorPosition(time: number, csvData: {key: Record<string, string | number>}[]): [number, number, number] {
+        if (time < 0) {
+            const e = new Error("time must be >= 0");
+            sendError(e, `Error in calculateVectorPosition - got ${time}, expected: a number greater than or equal to 0`);
+            throw e;
+        }
+
+        const position: [number, number, number] = [0,0,0];
+        
+        // calculate the indexes for the 3 coordinates
+        const xIndex = time;
+        const yIndex = time - this.tao;
+        const zIndex = time - 2*this.tao;
+
+        // gets the value of the specified indices from the csvData set
+        position[0] = this.retreiveCoordinateValue(xIndex, csvData);
+        position[1] = this.retreiveCoordinateValue(yIndex, csvData);
+        position[2] = this.retreiveCoordinateValue(zIndex, csvData);
+        
+        sendLog("info", `vector position calculated for data at index/time ${time} (EmbeddedGraphObject.calculateVectorPosition())`)
+        return position;
+    }
+
+    /**
+     * gets the value of the currently seelcted column at the line specified in index
+     * pre-conditions: csvData contains valid data, and the graph has a column selected that exists in the csv file
+     * post-conditions: none
+     * @param index line in csv file that contains the coordniate value being retreived
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns if index is >=0, the value at the index (line) of the csv in the column currently selected
+     *          otherwise, 0
+     */
+    retreiveCoordinateValue(index: number, csvData: {key: Record<string, string | number>}[]): number {
+        if (index < 0) {
+            return 0;
+        } else {
+            const line: {key: Record<string, string | number>} = csvData[index];
+            const position = line[this.axes.yLabel as keyof typeof line] as unknown as number;
+            return position;
+        }
+    }
+
+    
+    /**
+     * gets the dimensions of the graph
+     * @preconditions none
+     * @returns the current dimensions of the graph
+     */
+    getDimensions(): { width: number; height: number; depth?: number; } {
+        return this.dimensions;
+    }
+
+    /**
+     * Sets the dimenstion of the graph to the new values
+     * @preconditions all given dimenstions must be valid numbers for the dimensions of a graph
+     * @postconditions the dimension attribute of the graphis updated to contain the given values
+     * @param width the new width of the graph
+     * @param height the new height of the graph
+     * @param depth thew new depth of the graph
+     */
+    setDimensions(width: number, height: number, depth?: number): void {
+        const newDimensions = {width: width, height: height, depth: depth};
+        this.dimensions = newDimensions;
+    }
+
+    /**
+     * Gets the value of tao
+     * pre-conditions: none
+     * post-conditions: returns the current value of tao
+     */
+    getTao(): number {
+        return this.tao;
+    }
+
+    /**
+     * Sets the value of tao
+     * @param newTao - a number greater than or eqaul to 1
+     * post-conditions: the value of tao is updated to newTao
+     */
+    setTao(newTao: number): void {
+        if (newTao < 1) {
+            const e = new TypeError('Tao must be greater than or equal to 1')
+            sendError(e, `Error in setTao, ${newTao} is not greater than or equal to 1`)
+            throw e;
+        }
+
+        this.tao = newTao;
+        sendLog("info", `value of tao in EmbeddedGraphObject updated to the value ${newTao}`)
+    }
+    
+}
\ No newline at end of file
diff --git a/projectApp/src/components/Graph_Components/TimeSeriesGraphObject.tsx b/projectApp/src/components/Graph_Components/TimeSeriesGraphObject.tsx
index 666ed68d..3f6a00c7 100644
--- a/projectApp/src/components/Graph_Components/TimeSeriesGraphObject.tsx
+++ b/projectApp/src/components/Graph_Components/TimeSeriesGraphObject.tsx
@@ -15,12 +15,13 @@ export class TimeSeriesGraphObject extends GraphObject implements TimeSeriesGrap
         super(csv);
     }   
 
+    // TODO - refactor point methods to GraphObject as both TimeSeries and Embedded graphs will use same point objects
     /**
      * Adds a new point to the graph.
      * pre-codition: valid points in GraphObject
      * post-condition: a new PointInterface instance is added to the graph
      */
-    addPoint(): void{
+    addPoints(): void{
         this.csvData.getData().forEach((data) => {
             const newPoint = new PointObject();
             newPoint.setPosition([0,0,0.01])
@@ -215,7 +216,7 @@ export class TimeSeriesGraphObject extends GraphObject implements TimeSeriesGrap
 
         //Resetting points
         this.clearPoints();
-        this.addPoint();
+        this.addPoints();
         this.updatePoints();
 
         //Assigning new position values to the points
diff --git a/projectApp/src/controller/CSVController.tsx b/projectApp/src/controller/CSVController.tsx
index 8d855352..087737ed 100644
--- a/projectApp/src/controller/CSVController.tsx
+++ b/projectApp/src/controller/CSVController.tsx
@@ -1,4 +1,5 @@
 import { CSVDataObject } from "../components/Csv_Components/CSVDataObject";
+import { EmbeddedGraphObject } from "../components/Graph_Components/EmbeddedGraphObject";
 import { TimeSeriesGraphObject } from "../components/Graph_Components/TimeSeriesGraphObject";
 import { sendError, sendLog } from "../logger-frontend";
 import { CSVReaderModel } from "../models/CSVReaderModel";
@@ -37,10 +38,15 @@ export class CSVController implements ControllerInterface {
         for(const csv of this.model.getData()){
             if(csv.getDisplayBoard() == 1){
                 csv.setVRSelected(true);
-                const graph = new TimeSeriesGraphObject(csv);
-                graph.setName(csv.getName());
-                graph.addPoint();
-                mainController.getGraphController().pushDataToModel(graph)
+                const TSGraph = new TimeSeriesGraphObject(csv);
+                TSGraph.setName(csv.getName());
+                TSGraph.addPoints();
+
+                const emGraph = new EmbeddedGraphObject(csv);
+                emGraph.setName(csv.getName());
+                emGraph.addPoints();
+
+                mainController.getGraphController().pushDataToModel(TSGraph, emGraph)
                 console.log("Success on generate?")
                 sendLog("info","generate has pushed a new graph");
             }
diff --git a/projectApp/src/controller/GraphController.tsx b/projectApp/src/controller/GraphController.tsx
index fbfdfc39..dd39b661 100644
--- a/projectApp/src/controller/GraphController.tsx
+++ b/projectApp/src/controller/GraphController.tsx
@@ -1,4 +1,5 @@
 import { CSVDataObject } from "../components/Csv_Components/CSVDataObject";
+import { EmbeddedGraphObject } from "../components/Graph_Components/EmbeddedGraphObject";
 import { TimeSeriesGraphObject } from "../components/Graph_Components/TimeSeriesGraphObject";
 import { GraphModel } from "../models/GraphModel";
 import { ControllerInterface } from "../types/BaseInterfaces";
@@ -16,7 +17,6 @@ import mainController from "./MainController";
  *   - Graph objects are stored in the model's data array. Once a graph is added, it persists until it is removed.
  *  
  */
-
 export class GraphController implements ControllerInterface{
     model: GraphModel;
     constructor(){
@@ -29,12 +29,12 @@ export class GraphController implements ControllerInterface{
     * @param csv The CSV data object for which to generate or retrieve the graph.
     * @returns {TimeSeriesGraphClass} The generated or retrieved time series graph.
     * 
-    * Pre-Conditions:
+    * @preconditions:
     *    The `csv` parameter must be a valid `CSVDataObject`.
     *    The `model` property must be initialized and contain valid graph data.
     *    The `mainController` must be initialized and valid.
     * 
-    * Post-Conditions:
+    * @postconditions:
     *    If a graph with the same name as `csv` exists, its range is updated, a new graph is created and returned otherwise
     *    The mainController's main scene is updated.
     */
@@ -52,16 +52,45 @@ export class GraphController implements ControllerInterface{
     }
 
     /**
-     * Pushes a TimeSeriesGraphObject into the model's data collection.
+     * Generates an embedded graph for the given CSV data object.
+     * 
+     * @param csv The CSV data object for which to generate or retrieve the graph. 
+     * @returns {EmbeddedGraphClass} The generated or retrieved time series graph.
+     * 
+     * @precondition:
+     *    The `csv` parameter must be a valid `CSVDataObject`.
+     *    The `model` property must be initialized and contain valid graph data.
+     *    The `mainController` must be initialized and valid.
+     * 
+     * @postconditions:
+     *    If a graph with the same name as `csv` exists, its range is updated, a new graph is created and returned otherwise
+     *    The mainController's main scene is updated.
+     */
+    generateEmbeddedGraph(csv: CSVDataObject): EmbeddedGraphObject{
+        const result:EmbeddedGraphObject = new EmbeddedGraphObject(csv);
+
+        for(const graph of this.getModel().getEmbeddedGraphData()){
+            if(graph.getName() == csv.getName()){
+                return graph;
+            }
+        }
+        mainController.updateMainScene();
+        return result;
+    }
+
+    /**
+     * Pushes a TimeSeriesGraphObject and EmbeddedGraphObject into the model's data collection.
      *
-     * @precondition The provided 'graph' is a valid TimeSeriesGraphObject.
+     * @precondition The provided 'graph' is a valid TimeSeriesGraphObject and 'emGraph' is a valid EmbeddedGraphObject.
      *
-     * @postcondition The 'graph' is added to the model's data collection.
+     * @postcondition The 'graph' and 'emGraph' graphs are added to the model's data collection.
      *
      * @param graph The TimeSeriesGraphObject to add to the model.
+     * @param emGraph The EmbeddedGraphObject to add to the model.
      */
-    pushDataToModel(graph: TimeSeriesGraphObject): void{
-        this.getModel().getData().push(graph);
+    pushDataToModel(graph: TimeSeriesGraphObject, emGraph: EmbeddedGraphObject): void{
+        this.getModel().addTimeSeriesGraph(graph);
+        this.getModel().addEmbeddedGraph(emGraph);
     }
 
     /**
@@ -102,5 +131,31 @@ export class GraphController implements ControllerInterface{
     getDataLength(): number{
         return this.getModel().getData().length;
     }
+
+    /**
+     * Retrieves the array of EmbeddedGraphObject instances stored in the model.
+     *
+     * @precondition none
+     *
+     * @postcondition The array of EmbeddedGraphObject instances is returned.
+     *
+     * @returns The array of EmbeddedGraphObject instances.
+     */
+    getModelEmData(): EmbeddedGraphObject[]{
+        return this.model.getEmbeddedGraphData();
+    };
+
+    /**
+     * Returns the number of EmbeddedGraphObject instances stored in the model.
+     *
+     * @precondition none
+     *
+     * @postcondition The number of EmbeddedGraphObject instances is returned.
+     *
+     * @returns The number of EmbeddedGraphObject instances in the model
+     */
+    getEmDataLength(): number{
+        return this.getModel().getEmbeddedGraphData().length;
+    }
     
 }
diff --git a/projectApp/src/models/GraphModel.tsx b/projectApp/src/models/GraphModel.tsx
index f0317cf9..fe90c71e 100644
--- a/projectApp/src/models/GraphModel.tsx
+++ b/projectApp/src/models/GraphModel.tsx
@@ -1,3 +1,4 @@
+import { EmbeddedGraphObject } from "../components/Graph_Components/EmbeddedGraphObject";
 import { TimeSeriesGraphObject } from "../components/Graph_Components/TimeSeriesGraphObject";
 import { ModelInterface } from "../types/BaseInterfaces";
 import { CSVDataInterface } from "../types/CSVInterfaces";
@@ -14,9 +15,14 @@ import { CSVDataInterface } from "../types/CSVInterfaces";
  *   
  */
 export class GraphModel implements ModelInterface{
+    // TODO - change name of data to be timeSeriesData and update getters and setters
+
     data: TimeSeriesGraphObject[];
+    embeddedGraphData: EmbeddedGraphObject[];
+
     constructor(){
         this.data = [];
+        this.embeddedGraphData = [];
     }
 
     /**
@@ -57,4 +63,30 @@ export class GraphModel implements ModelInterface{
     getData(): TimeSeriesGraphObject[]{
         return this.data;
     }
+
+    /**
+     * Adds a EmbeddedGraphObject to the model.
+     *
+     * @preconditions The 'graph' parameter must be a valid EmbeddedGraphObject instance.
+     *
+     * @postconditions The provided 'graph' is appended to the internal 'embeddedGraphData' array.
+     *
+     * @param {EmbeddedGraphObject} graph - The EmbeddedGraphObject to add to the model.
+     */
+    addEmbeddedGraph(graph: EmbeddedGraphObject): void{
+        this.embeddedGraphData.push(graph);
+    }
+
+    /**
+     * Returns the EmbeddedGraphObject instances in the model.
+     *
+     * @preconditions none.
+     * 
+     * @postconditions Returns the current state of the 'embeddedGraphData' array, which includes all added EmbeddedGraphObject instances.
+     * 
+     * @returns {EmbeddedGraphObject[]} An array of EmbeddedGraphObject instances.
+     */
+    getEmbeddedGraphData(): EmbeddedGraphObject[]{
+        return this.embeddedGraphData;
+    }
 }
\ No newline at end of file
diff --git a/projectApp/src/pages/Graph_Elements/EmbeddedGraph.tsx b/projectApp/src/pages/Graph_Elements/EmbeddedGraph.tsx
new file mode 100644
index 00000000..1b6c959e
--- /dev/null
+++ b/projectApp/src/pages/Graph_Elements/EmbeddedGraph.tsx
@@ -0,0 +1,48 @@
+import { Text } from "@react-three/drei";
+import React from "react";
+import mainController from "../../controller/MainController";
+import { EmbeddedGraphObject } from "../../components/Graph_Components/EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ * This function will create an Embedded Graph in the VR environment using a EmbeddedGraphObject.
+ * This graph will be 3D.
+ * @preconditions A defined EmbeddedGraphObject.
+ * @postconditions Returns a React JSX Element that represents a 3D Embedded Graph.
+ */
+
+export function EmbeddedGraph({graph}:{graph: EmbeddedGraphObject}): React.JSX.Element{
+
+    //Used to update the graph
+    function UpdateGraph(): void{
+        mainController.updateMainScene();
+        sendLog("info", "an EmbeddedGraph object was updated (EmbeddedGraph.tsx)");
+    }
+
+    /**
+     * This Function creates the main graph of the Embedded Graph
+     * @postcondition Body of the graph
+     */
+    function GenerateGraph(): React.JSX.Element{
+        sendLog("info", "an EmbeddedGraph visualization is being created [not yet functioning] (EmbeddedGraph.tsx)");
+        return (
+            <>
+                <mesh position={[3, 1, 6]}>
+                    <boxGeometry args = {[6, 5.5, 2]}/>
+                    <meshBasicMaterial color="gray"/>
+                </mesh>
+
+                <mesh rotation = {[0, 3.14, 0]} position = {[3,2,4.8]}>
+                    <Text fontSize={0.35}>A 3D Graph will exist someday soon</Text>
+                </mesh>
+                <mesh rotation = {[0, 3.14, 0]} position={[3,1,4.8]}>
+                    <Text fontSize={0.35}>but here's a box in the meantime!</Text>
+                </mesh>
+            </>
+        )
+    }
+
+    return(
+        <GenerateGraph/>
+    )
+}
\ No newline at end of file
diff --git a/projectApp/src/pages/Scene/MainScene.tsx b/projectApp/src/pages/Scene/MainScene.tsx
index ef94cb1c..e3a9edff 100644
--- a/projectApp/src/pages/Scene/MainScene.tsx
+++ b/projectApp/src/pages/Scene/MainScene.tsx
@@ -1,9 +1,10 @@
-import { Text } from '@react-three/drei';
 import DropdownUI from "../UI/DropdownUI"
 import { CreateTimeSeries } from '../../components/Graph_Components/CreateTimeSeries';
 import mainController from '../../controller/MainController';
 import { useEffect, useRef, useState } from 'react';
 import { TimeSeriesGraphObject } from '../../components/Graph_Components/TimeSeriesGraphObject';
+import { EmbeddedGraphObject } from '../../components/Graph_Components/EmbeddedGraphObject';
+import { CreateEmbeddedGraph } from '../../components/Graph_Components/CreateEmbeddedGraph';
 
 /*
 * The main scene being used in the current program
@@ -15,6 +16,7 @@ export default function MainScene(): React.JSX.Element{
     //Then make it possible for the ui to  stay in view of the camera (maybe top left)
     const [updateGraph, setUpdateGraph] = useState(false);
     const [graph, setGraph] = useState<(TimeSeriesGraphObject)>();
+    const [emGraph, setEmGraph] = useState<(EmbeddedGraphObject)>();
     
     //Only runs on the begining, might keep graph on and update file on graph instead
     function updateScene(): void{
@@ -24,27 +26,25 @@ export default function MainScene(): React.JSX.Element{
     useEffect(() => {
         mainController.setSceneRef(sceneRef);
     },);
+
     useEffect(() => {
         if(updateGraph) {
             const vrSelected = mainController.getCSVController().getVRSelected();
             if(mainController.getGraphController().getDataLength() > 0){
                 const newGraph = mainController.getGraphController().generateTimeSeriesGraph(vrSelected);
                 setGraph(newGraph);
+                
+
+            }
+            if (mainController.getGraphController().getEmDataLength() > 0) {
+                const newEmGraph = mainController.getGraphController().generateEmbeddedGraph(vrSelected);
+                setEmGraph(newEmGraph);
             }
         }
         setUpdateGraph(false);
     });
     return (
         <>
-        {/* This block of code is the sign behind the user
-        A red box with the text Look Back */}
-        <mesh position = {[3,1,6]}>
-            <boxGeometry args = {[5, 2, 2]}/>
-            <meshBasicMaterial color = "red"/>
-        </mesh>
-        <mesh rotation = {[0, 3.14, 0]} position = {[3,1,4.8]}>
-            <Text>Look Back</Text>
-        </mesh>
 
         {/* This block of code is the sign in front of the user
         A red box with the text Front */}
@@ -65,6 +65,7 @@ export default function MainScene(): React.JSX.Element{
         {/* <CreateTimeSeries graphObject={graph}></CreateTimeSeries> */}
         <DropdownUI position={[-1.7, 1.5, -1]} xSize={4} ySize={3}></DropdownUI>
         {graph && <CreateTimeSeries graphObject={graph}></CreateTimeSeries>}
+        {emGraph && <CreateEmbeddedGraph graphObject={emGraph}/>}
         </>
     );
 };
diff --git a/projectApp/src/types/EmbeddedInterface.tsx b/projectApp/src/types/EmbeddedInterface.tsx
index 82ed3575..4a4eaf9a 100644
--- a/projectApp/src/types/EmbeddedInterface.tsx
+++ b/projectApp/src/types/EmbeddedInterface.tsx
@@ -7,6 +7,39 @@ export interface EmbeddedInterface extends GraphInterface{
         height: number;
         depth?: number;                  //for 3D graphs? Yes for 3D Graph
     };
+    tao: number;
+
+    /**
+     * Adds embedded point vectors to the graph.
+     * pre-conditions: valid points exist in the csvDataObject of the graph
+     * post-conditions: PointObject's containing the vectors are stored in the points array attribute
+     */
+    addPoints(): void;
+
+    /**
+     * Calculated the embedded time vector dimensions for the given time.
+     * Uses the data set selected in the csvDataObject of the graph
+     * Vector return is of the form [y[time], y[time - tao], y[time - 2*tao]] where y is the data set column selected
+     * pre-conditions: time >= 0, csvDataObject must contain valid data set and a valid data set much be selected
+     * post-conditions: none
+     * @param time - the index/time of the data set calculating the vector for
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns an array contaning the coordinates of the vector in the form [x, y, z]
+     */
+    calculateVectorPosition(time: number, csvData: {key: Record<string, string | number>}[]): [number, number, number];
+
+    /**
+     * gets the value of the currently seelcted column at the line specified in index
+     * pre-conditions: csvData contains valid data, and the graph has a column selected that exists in the csv file
+     * post-conditions: none
+     * @param index line in csv file that contains the coordniate value being retreived
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns if index is >=0, the value at the index (line) of the csv in the column currently selected
+     *          otherwise, 0
+     */
+    retreiveCoordinateValue(index: number, csvData: {key: Record<string, string | number>}[]): number;
 
     /**
      * Gets the graph's dimensions
@@ -21,4 +54,18 @@ export interface EmbeddedInterface extends GraphInterface{
      * post-condition: graph's dimensions are updated to the new values
      */
     setDimensions(width: number, height: number, depth?: number): void;
+
+    /**
+     * Gets the value of tao
+     * pre-conditions: none
+     * post-conditions: returns the current value of tao
+     */
+    getTao(): number;
+
+    /**
+     * Sets the value of tao
+     * @param newTao - a number greater than or eqaul to 1
+     * post-conditions: the value of tao is updated to newTao
+     */
+    setTao(newTao: number): void;
 }
\ No newline at end of file
diff --git a/projectApp/src/types/PointInterface.tsx b/projectApp/src/types/PointInterface.tsx
index 31529377..12aadafd 100644
--- a/projectApp/src/types/PointInterface.tsx
+++ b/projectApp/src/types/PointInterface.tsx
@@ -4,10 +4,10 @@
 */
 
 export interface PointInterface {
-    position: [number, number, number];
+    position: [number, number, number]; // for embedded/3d graph
     selected: boolean;
-    xData: string;
-    yData: number;
+    xData: string;                      // time value for 2d graph and where needed for 3d stuff
+    yData: number;                      // y data for time series 2d graph
 
     getPosition(): [number, number, number];
     getXData(): string;
diff --git a/projectApp/src/types/TimeSeriesGraphInterface.tsx b/projectApp/src/types/TimeSeriesGraphInterface.tsx
index 6f330081..6640a76e 100644
--- a/projectApp/src/types/TimeSeriesGraphInterface.tsx
+++ b/projectApp/src/types/TimeSeriesGraphInterface.tsx
@@ -11,7 +11,7 @@ export interface TimeSeriesGraphInterface extends GraphInterface {
      * pre-condition: none
      * post-condition: a new PointObject instance is added to the graph
      */
-    addPoint(): void;
+    addPoints(): void;
 
     /**
      * Finds a point based on given x and y data
diff --git a/projectApp/tests/unit_tests/EmbeddedGraph.test.ts b/projectApp/tests/unit_tests/EmbeddedGraph.test.ts
new file mode 100644
index 00000000..ba7ef83e
--- /dev/null
+++ b/projectApp/tests/unit_tests/EmbeddedGraph.test.ts
@@ -0,0 +1,83 @@
+import { beforeAll, beforeEach, describe, expect, test } from "vitest";
+import { EmbeddedGraphObject } from "../../src/components/Graph_Components/EmbeddedGraphObject";
+import mainController from "../../src/controller/MainController";
+
+describe('Embedded Graph test', () => {
+    let graph: EmbeddedGraphObject;
+    // TODO - update url when file being used is in main
+    const url = "https://raw.githubusercontent.com/UniversityOfSaskatchewanCMPT371/term-project-2025-team-1/refs/heads/ID3-3DGraph-VectorCalculation/csvTestFiles/indexedData.csv";
+
+    beforeAll( async() => {
+         await mainController.getCSVController().loadURLFile(url);
+    })
+
+    beforeEach(() => {
+        const csv = mainController.getCSVController().getModelData()[0];
+        graph = new EmbeddedGraphObject(csv);
+    });
+
+    test('points get added to points array', () => {
+        graph.addPoints();
+        expect(graph.getPoints().length).toBeGreaterThan(0);
+    });
+
+    // TODO - change data set names, when label isn't hardcoded
+    test('vectors gets calculated correctly for X data set', () => {
+        graph.addPoints();
+        const points = graph.getPoints();
+        expect(points[0].getPosition()).toStrictEqual([1,0,0]);
+        expect(points[1].getPosition()).toStrictEqual([2,1,0]);
+        expect(points[2].getPosition()).toStrictEqual([3,2,1]);
+        expect(points[3].getPosition()).toStrictEqual([4,3,2]);
+        expect(points[4].getPosition()).toStrictEqual([5,4,3]);
+        expect(points[5].getPosition()).toStrictEqual([6,5,4]);
+        expect(points[6].getPosition()).toStrictEqual([7,6,5]);
+        expect(points[7].getPosition()).toStrictEqual([8,7,6]);
+        expect(points[8].getPosition()).toStrictEqual([9,8,7]);
+        expect(points[9].getPosition()).toStrictEqual([10,9,8]);
+    });
+
+    test('vectors calculation for header row B', () => {
+        const axes = graph.getAxes();
+        const newAxes = {
+            xLabel: axes.xLabel,
+            yLabel: 'B',
+            xRange: axes.xRange,
+            yRange: axes.yRange
+        }
+        graph.setAxes(newAxes)
+        graph.addPoints();
+        const points = graph.getPoints();
+        expect(points[0].getPosition()).toStrictEqual([2,0,0]);
+        expect(points[1].getPosition()).toStrictEqual([4,2,0]);
+        expect(points[2].getPosition()).toStrictEqual([6,4,2]);
+        expect(points[3].getPosition()).toStrictEqual([8,6,4]);
+        expect(points[4].getPosition()).toStrictEqual([10,8,6]);
+        expect(points[5].getPosition()).toStrictEqual([12,10,8]);
+        expect(points[6].getPosition()).toStrictEqual([14,12,10]);
+        expect(points[7].getPosition()).toStrictEqual([16,14,12]);
+        expect(points[8].getPosition()).toStrictEqual([18,16,14]);
+        expect(points[9].getPosition()).toStrictEqual([20,18,16]);
+    });
+
+    test('vector calculation with tao=2', () => {
+        graph.setTao(2);
+        graph.addPoints();
+        const points = graph.getPoints();
+        expect(points[0].getPosition()).toStrictEqual([1,0,0]);
+        expect(points[1].getPosition()).toStrictEqual([2,0,0]);
+        expect(points[2].getPosition()).toStrictEqual([3,1,0]);
+        expect(points[3].getPosition()).toStrictEqual([4,2,0]);
+        expect(points[4].getPosition()).toStrictEqual([5,3,1]);
+        expect(points[5].getPosition()).toStrictEqual([6,4,2]);
+        expect(points[6].getPosition()).toStrictEqual([7,5,3]);
+        expect(points[7].getPosition()).toStrictEqual([8,6,4]);
+        expect(points[8].getPosition()).toStrictEqual([9,7,5]);
+        expect(points[9].getPosition()).toStrictEqual([10,8,6]);
+    })
+
+    test('setting tao to an invalid value', () => {
+
+        expect(() => {graph.setTao(-1)}).toThrowError("Tao must be greater than or equal to 1");
+    })
+});
\ No newline at end of file
diff --git a/projectApp/tests/unit_tests/TimeSeriesGraphClass.test.ts b/projectApp/tests/unit_tests/TimeSeriesGraphClass.test.ts
index 36e8576e..d275c8f0 100644
--- a/projectApp/tests/unit_tests/TimeSeriesGraphClass.test.ts
+++ b/projectApp/tests/unit_tests/TimeSeriesGraphClass.test.ts
@@ -23,7 +23,7 @@ describe( 'Time Series Graph Class', async () => {
      */
     test('Should Initialize Points to the number of data set loaded', () => {
         // graph.clearPoints();
-        graph.addPoint();
+        graph.addPoints();
         expect(graph.getPoints().length).toEqual(4);
 
         //Testing resetting points
@@ -31,7 +31,7 @@ describe( 'Time Series Graph Class', async () => {
         expect(graph.getPoints()).toEqual([]);
 
         //Repopulating the points
-        graph.addPoint();
+        graph.addPoints();
         expect(graph.getPoints().length).toEqual(4);
     });
 
@@ -68,7 +68,7 @@ describe( 'Time Series Graph Class', async () => {
 
         //Testing points now
         graph.clearPoints()
-        graph.addPoint();
+        graph.addPoints();
 
         //First point in the graph
         const point = graph.getPoints()[0];


Reviews:
user: login: how2020h
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T13:20:18Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:42:48Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:46:22Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:47:43Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T20:48:54Z
user: login: ona341
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:32:49Z
user: login: ona341
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:34:13Z
user: login: ona341
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:42:20Z
user: login: Alii279
body: Good work! I also think we should delete the TODO comments and the import calls that were commented out.
I have one comment about documentation: comment was "Pre-Conditions:" but changed to "@preconditions:" in GraphController.tsx which I think made the documentation more neat. but "post-conditions" is kept in EmbeddedGraph.tsx and wasn't changed. Why is that?
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:42:27Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T20:06:25Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T20:10:20Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T20:11:55Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T20:13:00Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T20:18:35Z
user: login: IslandBlueMana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T21:13:50Z
user: login: IslandBlueMana
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T21:16:05Z
user: login: how2020h
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-08T23:55:53Z
user: login: how2020h
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T00:02:32Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T00:16:18Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T00:20:37Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T00:42:16Z
user: login: m-labrecque
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T00:44:38Z
user: login: IslandBlueMana
body: everything seems good
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-09T01:57:21Z
user: login: randall-Github
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T03:35:43Z
user: login: loop1dev
body: linux build job has to complete successfully before merging
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-03-09T04:40:00Z
user: login: how2020h
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-09T16:35:24Z
user: login: ona341
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-09T18:39:53Z
user: login: randall-Github
body: If everything we expect from 3D graph is here, then its clear to merge for me.  Especially since it no longer has errors and conflicts.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-09T18:56:38Z
user: login: loop1dev
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-09T19:15:03Z

Review comments:
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: how2020h
body: Could you add invariants and history properties for the class EmbeddedGraphObject?

created_at: 2025-03-07T13:20:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+import { sendLog } from "../../logger-frontend";
+import { EmbeddedInterface } from "../../types/EmbeddedInterface";
+import { CSVDataObject } from "../Csv_Components/CSVDataObject";
+import { GraphObject } from "./GraphObject";
+import { PointObject } from "./PointObject";
+
+
+export class EmbeddedGraphObject extends GraphObject implements EmbeddedInterface {
+
+    tao: number;
+
+    constructor(csv: CSVDataObject) {
+        super(csv);
+        this.tao = 1;
+    }
+
+    /**
+     * Adds embedded point vectors to the graph.
+     * pre-conditions: valid points exist in the csvDataObject of the graph
+     * post-conditions: PointObject's containing the vectors are stored in the points array attribute
+     */
+    addPoints(): void {
+        const data = this.csvData.getData();
+        data.forEach((line) => {
+            const newPoint = new PointObject();
+            // gets the time of the current line in the data set
+            const time = line[this.axes.xLabel as keyof typeof line] as unknown as number;
+
+            // calculates the vector and stores it in the position attribute of a new PointObject
+            const vectorPosition = this.calculateVectorPosition(time, data);
+            newPoint.setPosition(vectorPosition);
+            this.points.push(newPoint);
+        });
+        sendLog("info", "Points added to EmbeddedGraphObject (EmbeddedGraphObject.addPoints())");
+    }
+
+    /**
+     * Calculated the embedded time vector dimensions for the given time.
+     * Uses the data set selected in the csvDataObject of the graph
+     * Vector return is of the form [y[time], y[time - tao], y[time - 2*tao]] where y is the data set column selected
+     * pre-conditions: time >= 0, csvDataObject must contain valid data set and a valid data set much be selected
+     * post-conditions: none
+     * @param time - the index/time of the data set calculating the vector for
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns an array contaning the coordinates of the vector in the form [x, y, z]
+     */
+    calculateVectorPosition(time: number, csvData: {key: Record<string, string | number>}[]): [number, number, number] {
+        const position: [number, number, number] = [0,0,0];
+        
+        // calculate the indexes for the 3 coordinates
+        const xIndex = time;
+        const yIndex = time - this.tao;
+        const zIndex = time - 2*this.tao;
+
+        // gets the value of the specified indices from the csvData set
+        position[0] = this.retreiveCoordinateValue(xIndex, csvData);
+        position[1] = this.retreiveCoordinateValue(yIndex, csvData);
+        position[2] = this.retreiveCoordinateValue(zIndex, csvData);
+        
+        sendLog("info", "vector position calculated for data at index/time ????? (EmbeddedGraphObject.calculateVectorPosition())")
+        return position;
+    }
+
+    /**
+     * gets the value of the currently seelcted column at the line specified in index
+     * pre-conditions: csvData contains valid data, and the graph has a column selected that exists in the csv file
+     * post-conditions: none
+     * @param index line in csv file that contains the coordniate value being retreived
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns if index is >=0, the value at the index (line) of the csv in the column currently selected
+     *          otherwise, 0
+     */
+    retreiveCoordinateValue(index: number, csvData: {key: Record<string, string | number>}[]): number {
+        if (index < 0) {
+            return 0;
+        } else {
+            const line: {key: Record<string, string | number>} = csvData[index];
+            const position = line[this.axes.yLabel as keyof typeof line] as unknown as number;
+            return position;
+        }
+    }
+
+    
+
+    getDimensions(): { width: number; height: number; depth?: number; } {
+        return this.dimensions;
+    }
+
+
+    setDimensions(width: number, height: number, depth?: number): void {
+        const newDimensions = {width: width, height: height, depth: depth};
+        this.dimensions = newDimensions;
+    }
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: randall-Github
body: Can we add quick documentations for these two methods?
created_at: 2025-03-07T20:42:48Z
author_association: COLLABORATOR
diff_hunk: @@ -14,9 +15,15 @@ import { CSVDataInterface } from "../types/CSVInterfaces";
  *   
  */
 export class GraphModel implements ModelInterface{
+    // TODO - add attribute for embedded graph and add getters and setters for it
+    // TODO - change name of data to be timeSeriesData and update getters and setters
+
     data: TimeSeriesGraphObject[];
+    embeddedGraphData: EmbeddedGraphObject[];
+
path: projectApp/src/models/GraphModel.tsx
user: login: randall-Github
body: I thought for GraphModel data, we would be using GraphObject? Rather than TimeSeriesGraphObject and EmbeddedGraphObject.
created_at: 2025-03-07T20:46:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+import { sendLog } from "../../logger-frontend";
+import { EmbeddedInterface } from "../../types/EmbeddedInterface";
+import { CSVDataObject } from "../Csv_Components/CSVDataObject";
+import { GraphObject } from "./GraphObject";
+import { PointObject } from "./PointObject";
+
+
+export class EmbeddedGraphObject extends GraphObject implements EmbeddedInterface {
+
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: randall-Github
body: Can we just have a quick class description for this.
created_at: 2025-03-07T20:47:43Z
author_association: COLLABORATOR
path: projectApp/src/pages/Graph_Elements/EmbeddedGraph.tsx
user: login: randall-Github
body: This one just needs a quick clean up of inline comments.
created_at: 2025-03-07T20:48:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: ona341
body: lagging TODO present (should be removed since its implemented)
created_at: 2025-03-07T22:32:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
+
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: ona341
body: Do we need the empty fragment If we are only returning a singly component
created_at: 2025-03-07T22:34:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+import { sendLog } from "../../logger-frontend";
+import { EmbeddedInterface } from "../../types/EmbeddedInterface";
+import { CSVDataObject } from "../Csv_Components/CSVDataObject";
+import { GraphObject } from "./GraphObject";
+import { PointObject } from "./PointObject";
+
+
+export class EmbeddedGraphObject extends GraphObject implements EmbeddedInterface {
+
+    tao: number;
+
+    constructor(csv: CSVDataObject) {
+        super(csv);
+        this.tao = 1;
+    }
+
+    /**
+     * Adds embedded point vectors to the graph.
+     * pre-conditions: valid points exist in the csvDataObject of the graph
+     * post-conditions: PointObject's containing the vectors are stored in the points array attribute
+     */
+    addPoints(): void {
+        const data = this.csvData.getData();
+        data.forEach((line) => {
+            const newPoint = new PointObject();
+            // gets the time of the current line in the data set
+            const time = line[this.axes.xLabel as keyof typeof line] as unknown as number;
+
+            // calculates the vector and stores it in the position attribute of a new PointObject
+            const vectorPosition = this.calculateVectorPosition(time, data);
+            newPoint.setPosition(vectorPosition);
+            this.points.push(newPoint);
+        });
+        sendLog("info", "Points added to EmbeddedGraphObject (EmbeddedGraphObject.addPoints())");
+    }
+
+    /**
+     * Calculated the embedded time vector dimensions for the given time.
+     * Uses the data set selected in the csvDataObject of the graph
+     * Vector return is of the form [y[time], y[time - tao], y[time - 2*tao]] where y is the data set column selected
+     * pre-conditions: time >= 0, csvDataObject must contain valid data set and a valid data set much be selected
+     * post-conditions: none
+     * @param time - the index/time of the data set calculating the vector for
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns an array contaning the coordinates of the vector in the form [x, y, z]
+     */
+    calculateVectorPosition(time: number, csvData: {key: Record<string, string | number>}[]): [number, number, number] {
+        const position: [number, number, number] = [0,0,0];
+        
+        // calculate the indexes for the 3 coordinates
+        const xIndex = time;
+        const yIndex = time - this.tao;
+        const zIndex = time - 2*this.tao;
+
+        // gets the value of the specified indices from the csvData set
+        position[0] = this.retreiveCoordinateValue(xIndex, csvData);
+        position[1] = this.retreiveCoordinateValue(yIndex, csvData);
+        position[2] = this.retreiveCoordinateValue(zIndex, csvData);
+        
+        sendLog("info", "vector position calculated for data at index/time ????? (EmbeddedGraphObject.calculateVectorPosition())")
+        return position;
+    }
+
+    /**
+     * gets the value of the currently seelcted column at the line specified in index
+     * pre-conditions: csvData contains valid data, and the graph has a column selected that exists in the csv file
+     * post-conditions: none
+     * @param index line in csv file that contains the coordniate value being retreived
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns if index is >=0, the value at the index (line) of the csv in the column currently selected
+     *          otherwise, 0
+     */
+    retreiveCoordinateValue(index: number, csvData: {key: Record<string, string | number>}[]): number {
+        if (index < 0) {
+            return 0;
+        } else {
+            const line: {key: Record<string, string | number>} = csvData[index];
+            const position = line[this.axes.yLabel as keyof typeof line] as unknown as number;
+            return position;
+        }
+    }
+
+    
+
+    getDimensions(): { width: number; height: number; depth?: number; } {
+        return this.dimensions;
+    }
+
+
+    setDimensions(width: number, height: number, depth?: number): void {
+        const newDimensions = {width: width, height: height, depth: depth};
+        this.dimensions = newDimensions;
+    }
+
+    /**
+     * Gets the value of tao
+     * pre-conditions: none
+     * post-conditions: returns the current value of tao
+     */
+    getTao(): number {
+        return this.tao;
+    }
+
+    /**
+     * Sets the value of tao
+     * @param newTao - a number greater than or eqaul to 1
+     * post-conditions: the value of tao is updated to newTao
+     */
+    setTao(newTao: number): void {
+        // TODO - add check for newTao here
+        this.tao = newTao;
+        sendLog("info", "value of tao in EmbeddedGraphObject updated to the value *****");
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: ona341
body: Why do we send "****" in the logs instead of the new tao value
created_at: 2025-03-07T22:42:20Z
author_association: COLLABORATOR
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: m-labrecque
body: What would the purpose of these be? I don't see at this point how they would be used.
created_at: 2025-03-08T20:06:25Z
author_association: COLLABORATOR
diff_hunk: @@ -14,9 +15,15 @@ import { CSVDataInterface } from "../types/CSVInterfaces";
  *   
  */
 export class GraphModel implements ModelInterface{
+    // TODO - add attribute for embedded graph and add getters and setters for it
+    // TODO - change name of data to be timeSeriesData and update getters and setters
+
     data: TimeSeriesGraphObject[];
+    embeddedGraphData: EmbeddedGraphObject[];
+
path: projectApp/src/models/GraphModel.tsx
user: login: m-labrecque
body: Currently we need to have both because if we create just a single GraphObject then we wouldn't have the TimeSeries or the Embedded graph object methods or anything related to them. I think we did discuss changing this but there was not enough time to do this during this deliverable.
In order to make just a single graphObject the inheritance of the classes would need to change, graphObject would need to inherit the embedded and time series stuff instead of the other way around which is what currently exists.
created_at: 2025-03-08T20:10:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
+
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: m-labrecque
body: Probably not. This was done to match the CreateTimeSeriesGraph, as it used this format as well.
created_at: 2025-03-08T20:11:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+import { sendLog } from "../../logger-frontend";
+import { EmbeddedInterface } from "../../types/EmbeddedInterface";
+import { CSVDataObject } from "../Csv_Components/CSVDataObject";
+import { GraphObject } from "./GraphObject";
+import { PointObject } from "./PointObject";
+
+
+export class EmbeddedGraphObject extends GraphObject implements EmbeddedInterface {
+
+    tao: number;
+
+    constructor(csv: CSVDataObject) {
+        super(csv);
+        this.tao = 1;
+    }
+
+    /**
+     * Adds embedded point vectors to the graph.
+     * pre-conditions: valid points exist in the csvDataObject of the graph
+     * post-conditions: PointObject's containing the vectors are stored in the points array attribute
+     */
+    addPoints(): void {
+        const data = this.csvData.getData();
+        data.forEach((line) => {
+            const newPoint = new PointObject();
+            // gets the time of the current line in the data set
+            const time = line[this.axes.xLabel as keyof typeof line] as unknown as number;
+
+            // calculates the vector and stores it in the position attribute of a new PointObject
+            const vectorPosition = this.calculateVectorPosition(time, data);
+            newPoint.setPosition(vectorPosition);
+            this.points.push(newPoint);
+        });
+        sendLog("info", "Points added to EmbeddedGraphObject (EmbeddedGraphObject.addPoints())");
+    }
+
+    /**
+     * Calculated the embedded time vector dimensions for the given time.
+     * Uses the data set selected in the csvDataObject of the graph
+     * Vector return is of the form [y[time], y[time - tao], y[time - 2*tao]] where y is the data set column selected
+     * pre-conditions: time >= 0, csvDataObject must contain valid data set and a valid data set much be selected
+     * post-conditions: none
+     * @param time - the index/time of the data set calculating the vector for
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns an array contaning the coordinates of the vector in the form [x, y, z]
+     */
+    calculateVectorPosition(time: number, csvData: {key: Record<string, string | number>}[]): [number, number, number] {
+        const position: [number, number, number] = [0,0,0];
+        
+        // calculate the indexes for the 3 coordinates
+        const xIndex = time;
+        const yIndex = time - this.tao;
+        const zIndex = time - 2*this.tao;
+
+        // gets the value of the specified indices from the csvData set
+        position[0] = this.retreiveCoordinateValue(xIndex, csvData);
+        position[1] = this.retreiveCoordinateValue(yIndex, csvData);
+        position[2] = this.retreiveCoordinateValue(zIndex, csvData);
+        
+        sendLog("info", "vector position calculated for data at index/time ????? (EmbeddedGraphObject.calculateVectorPosition())")
+        return position;
+    }
+
+    /**
+     * gets the value of the currently seelcted column at the line specified in index
+     * pre-conditions: csvData contains valid data, and the graph has a column selected that exists in the csv file
+     * post-conditions: none
+     * @param index line in csv file that contains the coordniate value being retreived
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns if index is >=0, the value at the index (line) of the csv in the column currently selected
+     *          otherwise, 0
+     */
+    retreiveCoordinateValue(index: number, csvData: {key: Record<string, string | number>}[]): number {
+        if (index < 0) {
+            return 0;
+        } else {
+            const line: {key: Record<string, string | number>} = csvData[index];
+            const position = line[this.axes.yLabel as keyof typeof line] as unknown as number;
+            return position;
+        }
+    }
+
+    
+
+    getDimensions(): { width: number; height: number; depth?: number; } {
+        return this.dimensions;
+    }
+
+
+    setDimensions(width: number, height: number, depth?: number): void {
+        const newDimensions = {width: width, height: height, depth: depth};
+        this.dimensions = newDimensions;
+    }
+
+    /**
+     * Gets the value of tao
+     * pre-conditions: none
+     * post-conditions: returns the current value of tao
+     */
+    getTao(): number {
+        return this.tao;
+    }
+
+    /**
+     * Sets the value of tao
+     * @param newTao - a number greater than or eqaul to 1
+     * post-conditions: the value of tao is updated to newTao
+     */
+    setTao(newTao: number): void {
+        // TODO - add check for newTao here
+        this.tao = newTao;
+        sendLog("info", "value of tao in EmbeddedGraphObject updated to the value *****");
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: m-labrecque
body: I forgot to go back and change that to the new tao value. I will fix that!
created_at: 2025-03-08T20:13:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+import { sendLog } from "../../logger-frontend";
+import { EmbeddedInterface } from "../../types/EmbeddedInterface";
+import { CSVDataObject } from "../Csv_Components/CSVDataObject";
+import { GraphObject } from "./GraphObject";
+import { PointObject } from "./PointObject";
+
+
+export class EmbeddedGraphObject extends GraphObject implements EmbeddedInterface {
+
+    tao: number;
+
+    constructor(csv: CSVDataObject) {
+        super(csv);
+        this.tao = 1;
+    }
+
+    /**
+     * Adds embedded point vectors to the graph.
+     * pre-conditions: valid points exist in the csvDataObject of the graph
+     * post-conditions: PointObject's containing the vectors are stored in the points array attribute
+     */
+    addPoints(): void {
+        const data = this.csvData.getData();
+        data.forEach((line) => {
+            const newPoint = new PointObject();
+            // gets the time of the current line in the data set
+            const time = line[this.axes.xLabel as keyof typeof line] as unknown as number;
+
+            // calculates the vector and stores it in the position attribute of a new PointObject
+            const vectorPosition = this.calculateVectorPosition(time, data);
+            newPoint.setPosition(vectorPosition);
+            this.points.push(newPoint);
+        });
+        sendLog("info", "Points added to EmbeddedGraphObject (EmbeddedGraphObject.addPoints())");
+    }
+
+    /**
+     * Calculated the embedded time vector dimensions for the given time.
+     * Uses the data set selected in the csvDataObject of the graph
+     * Vector return is of the form [y[time], y[time - tao], y[time - 2*tao]] where y is the data set column selected
+     * pre-conditions: time >= 0, csvDataObject must contain valid data set and a valid data set much be selected
+     * post-conditions: none
+     * @param time - the index/time of the data set calculating the vector for
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns an array contaning the coordinates of the vector in the form [x, y, z]
+     */
+    calculateVectorPosition(time: number, csvData: {key: Record<string, string | number>}[]): [number, number, number] {
+        const position: [number, number, number] = [0,0,0];
+        
+        // calculate the indexes for the 3 coordinates
+        const xIndex = time;
+        const yIndex = time - this.tao;
+        const zIndex = time - 2*this.tao;
+
+        // gets the value of the specified indices from the csvData set
+        position[0] = this.retreiveCoordinateValue(xIndex, csvData);
+        position[1] = this.retreiveCoordinateValue(yIndex, csvData);
+        position[2] = this.retreiveCoordinateValue(zIndex, csvData);
+        
+        sendLog("info", "vector position calculated for data at index/time ????? (EmbeddedGraphObject.calculateVectorPosition())")
+        return position;
+    }
+
+    /**
+     * gets the value of the currently seelcted column at the line specified in index
+     * pre-conditions: csvData contains valid data, and the graph has a column selected that exists in the csv file
+     * post-conditions: none
+     * @param index line in csv file that contains the coordniate value being retreived
+     * @param csvData - the data contained in the csvDataObject of the graph
+     *                - this is passed in instead of calling the method to obtain it to avoid uneccessary calls to the methods for every point being calculated
+     * @returns if index is >=0, the value at the index (line) of the csv in the column currently selected
+     *          otherwise, 0
+     */
+    retreiveCoordinateValue(index: number, csvData: {key: Record<string, string | number>}[]): number {
+        if (index < 0) {
+            return 0;
+        } else {
+            const line: {key: Record<string, string | number>} = csvData[index];
+            const position = line[this.axes.yLabel as keyof typeof line] as unknown as number;
+            return position;
+        }
+    }
+
+    
+
+    getDimensions(): { width: number; height: number; depth?: number; } {
+        return this.dimensions;
+    }
+
+
+    setDimensions(width: number, height: number, depth?: number): void {
+        const newDimensions = {width: width, height: height, depth: depth};
+        this.dimensions = newDimensions;
+    }
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: m-labrecque
body: Will do!
created_at: 2025-03-08T20:18:35Z
author_association: COLLABORATOR
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: IslandBlueMana
body: i thought our docs were precond-postcond style?
created_at: 2025-03-08T21:13:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
+
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: IslandBlueMana
body: from what i learned \<\> empty tags are ways to return multiple \<elements\> without needing a \<div\> or Container
created_at: 2025-03-08T21:16:05Z
author_association: COLLABORATOR
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: how2020h
body: > What would the purpose of these be? I don't see at this point how they would be used.
For class EmbeddedGraphObject 
[projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx] line 8
created_at: 2025-03-08T23:55:53Z
author_association: COLLABORATOR
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: how2020h
body: > i thought our docs were precond-postcond style?

In Slides, Lecture 7 -- Contracts and Specifications v2.pdf, methods are always precond-postcond style, and the class needs invariants and history properties. 
created_at: 2025-03-09T00:02:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
+
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: randall-Github
body: Yeah empty tags <> would just act as a "Parent" container, without it we get flagged errors.
created_at: 2025-03-09T00:16:18Z
author_association: COLLABORATOR
diff_hunk: @@ -14,9 +15,15 @@ import { CSVDataInterface } from "../types/CSVInterfaces";
  *   
  */
 export class GraphModel implements ModelInterface{
+    // TODO - add attribute for embedded graph and add getters and setters for it
+    // TODO - change name of data to be timeSeriesData and update getters and setters
+
     data: TimeSeriesGraphObject[];
+    embeddedGraphData: EmbeddedGraphObject[];
+
path: projectApp/src/models/GraphModel.tsx
user: login: randall-Github
body: Yeah that's fine.  We can leave that refactoring for the next id.
created_at: 2025-03-09T00:20:37Z
author_association: COLLABORATOR
path: projectApp/src/components/Graph_Components/EmbeddedGraphObject.tsx
user: login: m-labrecque
body: I hadn't see that, I don't know if i can determine those properties for this class and we are needing to change around the inheritance of the graph classes so i think it would be best to leave this as is for the time being and deal with adding those into this class and all the other classes that don't have them going forward.
created_at: 2025-03-09T00:42:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
+
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: m-labrecque
body: So does this file still need the <> because it just returns the single component?
created_at: 2025-03-09T00:44:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+// TODO - make a create function similar to CreateTimeSeries but for embedded graph
+
+import { EmbeddedGraph } from "../../pages/Graph_Elements/EmbeddedGraph";
+import { EmbeddedGraphObject } from "./EmbeddedGraphObject";
+import { sendLog } from "../../logger-frontend";
+
+/**
+ *   The CreateEmbeddedGraph component is a functional component that creates an EmbeddedGraph. 
+ *   It is responsible for:
+ *     - Logging the creation of an EmbeddedGraph.
+ *     - Rendering an EmbeddedGraph using the provided EmbeddedGraphObject.
+ *
+ * @param {Object} graphObject - An object with a single property "graphObject" of type EmbeddedGraphObject.
+ * @returns {React.JSX.Element} - A React JSX element containing the EmbeddedGraph component.
+ * 
+ * @precondition graphObject must be a valid instance of EmbeddedGraphObject. The logging system must be configured and available.
+ *
+ * @postcondition 
+ * - An informational log entry is recorded indicating that an EmbeddedGraph has been created. 
+ * - A valid React JSX element is returned that renders the EmbeddedGraph component.
+ *  
+ */
+
+export function CreateEmbeddedGraph({graphObject}:{graphObject: EmbeddedGraphObject}): React.JSX.Element{
+    sendLog("info", "an EmbeddedGraph has been created [not yet functional] (CreateEmbeddedGraph.tsx)");
+    return(
+        <>
path: projectApp/src/components/Graph_Components/CreateEmbeddedGraph.tsx
user: login: randall-Github
body: Yes, without it it just tell use that the single component requires a parent and result in an error.  There is no need to worry at all about the <> its more or less just a placeholder, I believe it will have no effect.
created_at: 2025-03-09T03:35:42Z
author_association: COLLABORATOR

PR comments:
user: login: randall-Github
created_at: 2025-03-07T20:41:15Z
body: Would it be possible to remove these TODO lines before we merge these feature branches?
author_association: COLLABORATOR
user: login: randall-Github
created_at: 2025-03-07T22:18:20Z
body: Will assertions and error handling be added here?
author_association: COLLABORATOR
user: login: m-labrecque
created_at: 2025-03-08T20:10:49Z
body: > Will assertions and error handling be added here?

Where is this in reference too??
author_association: COLLABORATOR
user: login: m-labrecque
created_at: 2025-03-08T20:15:24Z
body: > Good work! I also think we should delete the TODO comments and the import calls that were commented out. I have one comment about documentation: comment was "Pre-Conditions:" but changed to "@preconditions:" in GraphController.tsx which I think made the documentation more neat. but "post-conditions" is kept in EmbeddedGraph.tsx and wasn't changed. Why is that?

The TODO's will be deleted that was an oversight and will be fixed. 
The post-condition's don't match as multiple people have been working on the documentation and we have never come to a consensus on what should be used. This isn't just something that exists in the changes made here, it exists throughout the entire code base. We need to decide what to use and change them to all be them same through all files.
author_association: COLLABORATOR
user: login: randall-Github
created_at: 2025-03-09T00:18:32Z
body: > > Will assertions and error handling be added here?
> 
> Where is this in reference too??

For the methods, mainly checking/handling for pre and post conditions. Was just wondering if those will get added, or for next ID.
author_association: COLLABORATOR
user: login: m-labrecque
created_at: 2025-03-09T01:10:52Z
body: > > > Will assertions and error handling be added here?
> > 
> > 
> > Where is this in reference too??
> 
> For the methods, mainly checking/handling for pre and post conditions. Was just wondering if those will get added, or for next ID.

Yeah we can add some I meant to do that but didn't get around to it.
author_association: COLLABORATOR
user: login: loop1dev
created_at: 2025-03-09T04:38:02Z
body: hold on folks, this is not making it through the linter. you can't merge yet.
author_association: COLLABORATOR
user: login: m-labrecque
created_at: 2025-03-09T04:38:59Z
body: > hold on folks, this is not making it through the linter. you can'y merge yer

yeah im fixing it rn
author_association: COLLABORATOR