diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000000000000000000000000000000000000..02b915b85f9fb22b6e51491729131d93c18d906e
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GitToolBoxProjectSettings">
+    <option name="commitMessageIssueKeyValidationOverride">
+      <BoolValueOverride>
+        <option name="enabled" value="true" />
+      </BoolValueOverride>
+    </option>
+    <option name="commitMessageValidationEnabledOverride">
+      <BoolValueOverride>
+        <option name="enabled" value="true" />
+      </BoolValueOverride>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/README.md b/README.md
index 4721102682913c91c821ece7b3c8de75376f065d..658d1b75f714bc120e9169c0d56e0044f2ae1759 100644
--- a/README.md
+++ b/README.md
@@ -1,93 +1,88 @@
 # IDATT2003 Mappevurdering
+***
+# ChaosGame
 
+## Description
+This project is a JavaFX application designed for creating a chaos game.
+Users can add points to a canvas and connect them with lines to set up the game. 
+They can then select a starting point and specify the number of iterations for the chaos game. 
+During each iteration, a point will be drawn on the canvas at a random location between 
+the starting point and one of the user-added points. 
+Additionally, users can save and load chaos games from files.
 
 
-## Getting started
+***
 
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
+## Installation Manual
+**Prerequisites:**
+- **Java Development Kit (JDK) 21 or later**
+- **Maven**
+- **Git**
+- **IntelliJ IDEA (Optional if you want to run it with the run button in IntelliJ IDEA)**
 
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
+**N.B. Make sure that you download it for the correct operating systems (Windows, Mac, Linux etc...)**
 
-## Add your files
+**Link to download JDK 21:**
+https://www.oracle.com/java/technologies/downloads/#java11
 
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
+**Link to download Maven:**
+https://maven.apache.org/download.cgi
 
-```
-cd existing_repo
-git remote add origin https://gitlab.stud.idi.ntnu.no/tamml/idatt2003-mappevurdering.git
-git branch -M main
-git push -uf origin main
-```
 
-## Integrate with your tools
+**Link to download IntelliJ IDEA (Optional):**
+https://www.jetbrains.com/idea/download/?section=windows
 
-- [ ] [Set up project integrations](https://gitlab.stud.idi.ntnu.no/tamml/idatt2003-mappevurdering/-/settings/integrations)
+**Link to download Javafx (Optional, to use the run button in intelliJ):**
+https://openjfx.io/
 
-## Collaborate with your team
 
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
+## How to clone the project:
+1. **Open the terminal to the directory where you want to clone the repository. Then run the following command:**
 
-## Test and Deploy
+<pre>git clone https://gitlab.stud.idi.ntnu.no/tamml/mappevurderingprog2.git </pre> 
 
-Use the built-in continuous integration in GitLab.
 
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
+2. **Switch to the correct branch**
 
-***
+The default branch should be master, if not you can write this in the terminal:
+<pre>git checkout master</pre>
 
-# Editing this README
 
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
+3. **Build the project with Maven**
 
-## Suggestions for a good README
+Navigate to the root directory of the project (the directory containing the pom.xml file) and run the following command:
 
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
+<pre>mvn clean install </pre>
 
-## Name
-Choose a self-explaining name for your project.
+This command cleans any previous builds, compiles the code, runs any tests, and installs the packaged code to your local Maven repository.
 
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
 
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
+## How to run the project:
 
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
+**With maven:**
 
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
+Make sure that you have cloned the project if not follow the instructions above under the (How to clone the project).
 
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
+To run your project with Maven, you can use the mvn javafx:run command. This command is part of the JavaFX Maven plugin, which should be included in your pom.xml file. Open a terminal, navigate to your project directory (the one containing the pom.xml file), and run the command:
+<pre>mvn javafx:run</pre>
 
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
+---
 
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
+**With the run button in IntelliJ IDEA:**
 
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
+Make sure that you have cloned the project if not follow the instructions above under the (How to clone the project).
 
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
+To run the program with the run button in IntelliJ you need to make sure that you have all the prerequisites downloaded.
 
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
+**Open your project in IntelliJ IDEA.**
 
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
+1. **Go to Run -> Edit Configurations... in the menu.**
+2. **Click on the + button to add a new configuration.**
+3. **Select Application.**
+4. **In the Main class field, enter:**
+<pre>edu.ntnu.idatt2003.mappevurderingprog2.MyApp</pre>  
+5. **In the Use classpath of module field, select your project.**
+6. **Click OK to save the configuration.**
 
-## License
-For open source projects, say how it is licensed.
 
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+Now, you can run your project by clicking the green run button in the toolbar or by pressing Shift + F10.  Please note that you need to have the JavaFX SDK installed on your machine to run the JavaFX application.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index dc444fafeb54673e8bf2d361881a38fd89c6db45..51886ca7d81e6f90195ebcb45a168113d0f461be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,19 +1,23 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- Project Object Model (POM) file for Maven build configuration -->
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
+    <!-- Basic project identifiers -->
     <groupId>edu.ntnu.idatt2003</groupId>
     <artifactId>mappevurderingprog2</artifactId>
     <version>1.0-SNAPSHOT</version>
     <name>mappevurderingprog2</name>
 
+    <!-- Project properties -->
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <junit.version>5.9.2</junit.version>
+        <junit.version>5.10.1</junit.version>
     </properties>
 
+    <!-- List of project dependencies -->
     <dependencies>
         <dependency>
             <groupId>org.openjfx</groupId>
@@ -29,7 +33,7 @@
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
-            <version>5.10.1</version>
+            <version>${junit.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -40,6 +44,7 @@
         </dependency>
     </dependencies>
 
+    <!-- Build configuration -->
     <build>
         <plugins>
             <plugin>
@@ -66,7 +71,7 @@
                         <id>default-cli</id>
                         <configuration>
                             <mainClass>
-                                edu.ntnu.idatt2003.mappevurderingprog2/edu.ntnu.idatt2003.mappevurderingprog2.HelloApplication
+                                edu.ntnu.idatt2003.mappevurderingprog2.MyApp
                             </mainClass>
                             <launcher>app</launcher>
                             <jlinkZipName>app</jlinkZipName>
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/MyApp.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/MyApp.java
new file mode 100644
index 0000000000000000000000000000000000000000..9bb35a5aade0065bbb852bb45c5aa907e63baded
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/MyApp.java
@@ -0,0 +1,45 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.CanvasController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGame;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.View;
+import java.io.FileNotFoundException;
+import javafx.application.Application;
+import javafx.stage.Stage;
+
+/**
+ * The MyApp class represents the main application class.
+ * It extends the Application class from JavaFX.
+ */
+public class MyApp extends Application {
+
+  /**
+   * Starts the application.
+   *
+   * @param primaryStage The primary stage of the application.
+   * @throws FileNotFoundException If the file is not found.
+   */
+  @Override
+  public void start(Stage primaryStage) throws FileNotFoundException {
+    GameController gameController = new GameController();
+    CanvasController canvasController = new CanvasController();
+    FileController fileController = new FileController();
+    View view = new View(gameController, canvasController, fileController);
+    ChaosGame.getInstance().addObserver(view);
+    primaryStage.setScene(view.createScene());
+    primaryStage.show();
+  }
+
+  /**
+   * Launches the application.
+   *
+   * @param args The arguments passed to the application.
+   */
+  public static void main(String[] args) {
+    launch(args);
+  }
+}
+
+
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/CanvasController.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/CanvasController.java
new file mode 100644
index 0000000000000000000000000000000000000000..cecb931f97c3b130137f710af540f836a9a1d901
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/CanvasController.java
@@ -0,0 +1,112 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.controllers;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGame;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.paint.Color;
+
+/**
+ * The CanvasController class is a controller class that handles the logic for the Canvas.
+ * The class is responsible for updating the canvas display and setting the canvas width and height.
+ */
+
+public class CanvasController {
+
+  // Private integer for the green threshold.
+  private int greenThreshold;
+
+  // Private integer for the blue threshold.
+  private int blueThreshold;
+
+  /**
+   * Calculates the quantiles for the hits.
+   */
+  public void calculateQuantiles() {
+    List<Integer> hitCounts = new ArrayList<>();
+    for (int[] row : ChaosGame.getInstance().getCanvas().getCanvasArray()) {
+      for (int hit : row) {
+        if (hit > 0) {
+          hitCounts.add(hit);
+        }
+      }
+    }
+
+    if (hitCounts.isEmpty()) {
+      return;
+    }
+
+    Collections.sort(hitCounts);
+    int n = hitCounts.size();
+    greenThreshold = hitCounts.get((int) (n * 0.33));
+    blueThreshold = hitCounts.get((int) (n * 0.66));
+  }
+
+  /**
+   * Returns the color for the hits.
+   *
+   * @param hits the number of hits.
+   * @return the color for the hits.
+   */
+  private Color getColorForHits(int hits) {
+    if (hits == 0) {
+      return Color.WHITE;
+    } else if (hits <= greenThreshold) {
+      return Color.GREEN;
+    } else if (hits <= blueThreshold) {
+      return Color.BLUE;
+    } else {
+      return Color.RED;
+    }
+  }
+
+  /**
+   * Updates the canvas display.
+   *
+   * @param mainCanvas the main canvas.
+   * @return the updated canvas.
+   */
+  public Canvas updateCanvasDisplay(Canvas mainCanvas) {
+    GraphicsContext gc = mainCanvas.getGraphicsContext2D();
+    int[][] canvasArray = ChaosGame.getInstance().getCanvas().getCanvasArray();
+    double canvasWidth = mainCanvas.getWidth() - 20;
+    double canvasHeight = mainCanvas.getHeight() - 20;
+    double pixelWidth = canvasWidth / canvasArray[0].length;
+    double pixelHeight = canvasHeight / canvasArray.length;
+
+    gc.clearRect(0, 0, mainCanvas.getWidth(), mainCanvas.getHeight());
+    for (int i = 0; i < canvasArray.length; i++) {
+      for (int j = 0; j < canvasArray[i].length; j++) {
+        double x = j * pixelWidth + 3;
+        double y = i * pixelHeight + 3;
+        gc.setFill(getColorForHits(canvasArray[i][j]));
+        gc.fillRect(x, y, pixelWidth, pixelHeight);
+      }
+    }
+    return mainCanvas;
+  }
+
+  /**
+   * Sets the canvas width.
+   *
+   * @param canvas the canvas.
+   * @param width  the width.
+   */
+  public void setCanvasWidth(Canvas canvas, double width) {
+    canvas.setWidth(width);
+    updateCanvasDisplay(canvas);
+  }
+
+  /**
+   * Sets the canvas height.
+   *
+   * @param canvas the canvas.
+   * @param height the height.
+   */
+  public void setCanvasHeight(Canvas canvas, double height) {
+    canvas.setHeight(height);
+    updateCanvasDisplay(canvas);
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/FileController.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/FileController.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c1c8ee93563ae3b0ef7ede9f12856a7a28c9748
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/FileController.java
@@ -0,0 +1,53 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.controllers;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGame;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameFileHandler;
+import java.util.List;
+
+/**
+ * The FileController class is a controller class that handles the logic for reading and writing
+ * ChaosGame transformations to and from files.
+ */
+public class FileController {
+
+  /**
+   * Reads ChaosGame transformations from a file.
+   *
+   * @param name the name of the file to read from
+   * @throws Exception if an error occurs during reading
+   */
+  public void saveFractalToFile(String name) throws Exception {
+    ChaosGameFileHandler.writeTransformationsToFile(ChaosGame.getInstance().getDescription(), name);
+  }
+
+  /**
+   * Reads ChaosGame transformations from a file.
+   *
+   * @param name the name of the file to read from
+   * @throws Exception if an error occurs during reading
+   */
+  public void readFractalFromFile(String name) throws Exception {
+    ChaosGameDescription description = ChaosGameFileHandler.readTransformationsFromFile(name);
+    ChaosGame.getInstance().setDescription(description);
+  }
+
+  /**
+   * Checks if a file with the specified name exists.
+   *
+   * @param name the name of the file to check
+   * @return true if the file exists, false otherwise
+   */
+  public boolean doesFileExist(String name) {
+    return ChaosGameFileHandler.checkFileExists(name);
+  }
+
+  /**
+   * Lists all transformation file names.
+   *
+   * @return a list of transformation file names
+   */
+  public List<String> listTransformationFileNames() {
+    return ChaosGameFileHandler.listTransformationFileNames();
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/GameController.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/GameController.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bcf9dc56080c5491be82ff9e94fe50bc903e41b
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/controllers/GameController.java
@@ -0,0 +1,226 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.controllers;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.JuliaTransform;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGame;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescriptionFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The GameController class is a controller class that handles the logic for the ChaosGame.
+ * The class is responsible for creating and updating the ChaosGameDescription,
+ * and running the ChaosGame.
+ * The class also handles saving and reading ChaosGameDescriptions to and from files.
+ */
+public class GameController {
+
+  /**
+   * Creates a Sierpinski triangle ChaosGameDescription
+   * and sets it as the current ChaosGameDescription.
+   */
+  public void createSierpinskiTriangle() {
+    ChaosGame.getInstance().setDescription(ChaosGameDescriptionFactory.createSierpinskiTriangle());
+  }
+
+  /**
+   * Creates a Barnsley Fern ChaosGameDescription and sets it as the current ChaosGameDescription.
+   */
+  public void createBarnsleyFern() {
+    ChaosGame.getInstance().setDescription(ChaosGameDescriptionFactory.createBarnsleyFern());
+  }
+
+  /**
+   * Creates a standard Julia transformation ChaosGameDescription
+   * and sets it as the current ChaosGameDescription.
+   */
+  public void createJuliaTransformation() {
+    ChaosGame.getInstance().setDescription(
+        ChaosGameDescriptionFactory.createStandardJuliaTransformation());
+  }
+
+  /**
+   * Updates the canvas coordinates of the current ChaosGameDescription.
+   *
+   * @param minCoords the minimum coordinates of the canvas
+   * @param maxCoords the maximum coordinates of the canvas
+   */
+  public void updateChaosCanvasCoordinates(Vector2D minCoords, Vector2D maxCoords) {
+    ChaosGameDescription description = ChaosGame.getInstance().getDescription();
+    description.setMaxCoords(maxCoords);
+    description.setMinCoords(minCoords);
+    ChaosGame.getInstance().setDescription(description);
+  }
+
+  /**
+   * Gets the current number of steps of the ChaosGame.
+   *
+   * @return the current number of steps
+   */
+  public int getCurrentSteps() {
+    return ChaosGame.getInstance().getSteps();
+  }
+
+  /**
+   * Gets the current minimum coordinates of the canvas.
+   *
+   * @return the current minimum coordinates
+   */
+  public Vector2D getCurrentMaxCoords() {
+    return ChaosGame.getInstance().getDescription().getMaxCoords();
+  }
+
+  /**
+   * Gets the current maximum coordinates of the canvas.
+   *
+   * @return the current maximum coordinates
+   */
+  public Vector2D getCurrentMinCoords() {
+    return ChaosGame.getInstance().getDescription().getMinCoords();
+  }
+
+  /**
+   * Gets the current Julia point of the ChaosGame.
+   *
+   * @return the current Julia point
+   */
+  public Complex getCurrentJuliaPoint() {
+    List<Transform2D> transforms = ChaosGame.getInstance().getDescription().getTransforms();
+    JuliaTransform julia = (JuliaTransform) transforms.get(0);
+    return julia.getPoint();
+  }
+
+  /**
+   * Gets the current affine transformations of the ChaosGame.
+   *
+   * @return the current affine transformations
+   */
+  public List<Transform2D> getAffineTransformations() {
+    return ChaosGame.getInstance().getDescription().getTransforms();
+  }
+
+
+  /**
+   * Sets the ChaosGame canvas.
+   */
+  public void setChaosCanvas() {
+    ChaosGame.getInstance().setCanvas();
+  }
+
+  /**
+   * Sets the number of steps of the ChaosGame.
+   *
+   * @param steps the number of steps to set
+   */
+  public void setChaosGameSteps(int steps) {
+    ChaosGame.getInstance().setSteps(steps);
+  }
+
+  /**
+   * Runs the ChaosGame transformations.
+   */
+  public void runTransformation() {
+    ChaosGame.getInstance().runSteps();
+  }
+
+  /**
+   * Gets the count of out-of-bounds occurrences.
+   *
+   * @return the count of out-of-bounds occurrences
+   */
+  public int getOutOfBoundsCount() {
+    return ChaosGame.getInstance().getOutOfBoundsCount();
+  }
+
+  /**
+   * Sets the Julia transformation with specified parameters.
+   *
+   * @param point     the Julia point to set
+   * @param minCoords the minimum coordinates of the canvas
+   * @param maxCoords the maximum coordinates of the canvas
+   */
+  public void setJuliaTransformation(Complex point, Vector2D minCoords, Vector2D maxCoords) {
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new JuliaTransform(point, -1));
+    transforms.add(new JuliaTransform(point, 1));
+    ChaosGame.getInstance().setDescription(
+        new ChaosGameDescription(transforms, minCoords, maxCoords));
+  }
+
+  /**
+   * Sets the affine transformation with specified parameters.
+   *
+   * @param transforms the affine transformations to set
+   * @param minCoords  the minimum coordinates of the canvas
+   * @param maxCoords  the maximum coordinates of the canvas
+   */
+  public void setAffineTransformation(
+      List<Transform2D> transforms, Vector2D minCoords, Vector2D maxCoords) {
+    ChaosGame.getInstance().setDescription(
+        new ChaosGameDescription(transforms, minCoords, maxCoords));
+  }
+
+  /**
+   * Checks if the ChaosGame is empty.
+   *
+   * @return true if the ChaosGame is empty, false otherwise
+   */
+  public boolean isChaosGameEmpty() {
+    boolean empty = false;
+    if (ChaosGame.getInstance().getDescription() == null) {
+      empty = true;
+    }
+    return empty;
+  }
+
+  /**
+   * Checks if the current transformation is an affine transformation.
+   *
+   * @return true if the current transformation is an affine transformation, false otherwise
+   */
+  public boolean isAffineTransformation() {
+    List<Transform2D> transforms = ChaosGame.getInstance().getDescription().getTransforms();
+    if (transforms.isEmpty()) {
+      return false;
+    }
+    return transforms.get(0) instanceof AffineTransform2D;
+  }
+
+  /**
+   * Checks if the current transformation is a Julia transformation.
+   *
+   * @return true if the current transformation is a Julia transformation, false otherwise
+   */
+  public boolean isJuliaTransformation() {
+    List<Transform2D> transforms = ChaosGame.getInstance().getDescription().getTransforms();
+    if (transforms.isEmpty()) {
+      return false;
+    }
+    return transforms.get(0) instanceof JuliaTransform;
+  }
+
+  /**
+   * Empties the ChaosGame.
+   */
+  public void emptyChaosGame() {
+    ChaosGame.getInstance().setDescription(null);
+  }
+
+  /**
+   * Resets canvas coordinates to initial values.
+   */
+  public void resetChaosCanvasCoordinates() {
+    ChaosGameDescription description = ChaosGame.getInstance().getDescription();
+    Vector2D initialMin = description.getInitialMinCoords();
+    Vector2D initialMax = description.getInitialMaxCoords();
+
+    description.setMinCoords(initialMin);
+    description.setMaxCoords(initialMax);
+    ChaosGame.getInstance().setDescription(description);
+  }
+}
+
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/AffineTransform2D.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/AffineTransform2D.java
index 0e0b92877bb8831074bd8dae05e43f8261410098..793ef435cdb1ec91ea02dee000c83a0c21e5c8d1 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/AffineTransform2D.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/AffineTransform2D.java
@@ -5,15 +5,18 @@ package edu.ntnu.idatt2003.mappevurderingprog2.models;
  * It applies a linear transformation to a 2D vector, and then adds a translation vector.
  */
 public class AffineTransform2D implements Transform2D {
+  // The linear transformation matrix
   private Matrix2x2 matrix;
-  private Vector2D vector;
 
-    /**
-     * Constructs an AffineTransform2D object with the given matrix and vector.
+  // The translation vector
+  private Vector2D vector;
 
-     * @param matrix the linear transformation matrix
-     * @param vector the translation vector
-     */
+  /**
+   * Constructs an AffineTransform2D object with the given matrix and vector.
+   *
+   * @param matrix the linear transformation matrix
+   * @param vector the translation vector
+   */
   public AffineTransform2D(Matrix2x2 matrix, Vector2D vector) {
     if (matrix == null) {
       throw new IllegalArgumentException("Matrix cannot be null");
@@ -25,21 +28,31 @@ public class AffineTransform2D implements Transform2D {
     this.vector = vector;
   }
 
+  /**
+   * Returns the linear transformation matrix.
+   *
+   * @return the linear transformation matrix
+   */
   public Matrix2x2 getMatrix() {
     return matrix;
   }
 
+  /**
+   * Returns the translation vector.
+   *
+   * @return the translation vector
+   */
   public Vector2D getVector() {
     return vector;
   }
 
-    /**
-     * Transforms the given 2D vector by applying the linear transformation
-     * and adding the translation vector.
-
-     * @param point the 2D vector to be transformed
-     * @return the transformed 2D vector
-     */
+  /**
+   * Transforms the given 2D vector by applying the linear transformation
+   * and adding the translation vector.
+   *
+   * @param point the 2D vector to be transformed
+   * @return the transformed 2D vector
+   */
   @Override
   public Vector2D transform(Vector2D point) {
     if (point == null) {
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Complex.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Complex.java
index 607be63038c5f88e85addb3d8c4fd1abbe20430f..d993cda5a4e1c68123e0c2f9765c2c0f15868e3b 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Complex.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Complex.java
@@ -7,24 +7,39 @@ package edu.ntnu.idatt2003.mappevurderingprog2.models;
  */
 public class Complex extends Vector2D {
 
-    /**
-     * Constructor for the complex number
-
-     * @param realPart the real part of the complex number
-     * @param imaginaryPart the imaginary part of the complex number
-     */
+  /**
+   * Constructor for the complex number.
+   *
+   * @param realPart      the real part of the complex number
+   * @param imaginaryPart the imaginary part of the complex number
+   */
   public Complex(double realPart, double imaginaryPart) {
     super(realPart, imaginaryPart);
   }
 
+  /**
+   * Method to get the real part of the complex number.
+   *
+   * @return the real part of the complex number.
+   */
   public double getRealPart() {
     return getX0();
   }
 
+  /**
+   * Method to get the imaginary part of the complex number.
+   *
+   * @return the imaginary part of the complex number.
+   */
   public double getImaginaryPart() {
     return getX1();
   }
 
+  /**
+   * Method to calculate the magnitude of the complex number.
+   *
+   * @return the magnitude of the complex number.
+   */
   @Override
   public Complex add(Vector2D other) {
     if (!(other instanceof Complex)) {
@@ -33,6 +48,11 @@ public class Complex extends Vector2D {
     return new Complex(getX0() + other.getX0(), getX1() + other.getX1());
   }
 
+  /**
+   * Method to calculate the magnitude of the complex number.
+   *
+   * @return the magnitude of the complex number
+   */
   @Override
   public Complex subtract(Vector2D other) {
     if (!(other instanceof Complex)) {
@@ -42,17 +62,16 @@ public class Complex extends Vector2D {
   }
 
   /**
-   * Method to calculate the square root of the complex number
-
+   * Method to calculate the square root of the complex number.
+   *
    * @return the square root of the complex number
    */
   public Complex sqrt() {
     double r = Math.sqrt(Math.pow(getX0(), 2) + Math.pow(getX1(), 2));
-    double theta = Math.atan2(getX1(), getX0());
-    return new Complex(Math.sqrt(r) * Math.cos(theta / 2), Math.sqrt(r) * Math.sin(theta / 2));
-  }
-
-  public double magnitudeSquared() {
-    return getX0() * getX0() + getX1() * getX1();
+    double theta = Math.atan2(getX1(), getX0()) / 2.0;
+    double sqrtMag = Math.sqrt(r);
+    double realPart = sqrtMag * Math.cos(theta);
+    double imaginaryPart = sqrtMag * Math.sin(theta);
+    return new Complex(realPart, imaginaryPart);
   }
 }
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/JuliaTransform.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/JuliaTransform.java
index 205838327c1c858f688b274e06ebd269c634905f..f8c05b1480d202e197b33f18e745d5ce36224e98 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/JuliaTransform.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/JuliaTransform.java
@@ -1,12 +1,17 @@
 package edu.ntnu.idatt2003.mappevurderingprog2.models;
 
 /**
- * The JuliaTransform class implements the Transform2D interface and represents a Julia set transformation.
+ * The JuliaTransform class implements the Transform2D interface and
+ * represents a Julia set transformation.
  * The transformation involves substituting the complex number with the square root of the
  * difference between the complex number and a given point.
  */
 public class JuliaTransform implements Transform2D {
+
+  // The point used in the transformation
   private final Complex point;
+
+  // The sign used in the transformation
   private final int sign;
 
   /**
@@ -23,10 +28,20 @@ public class JuliaTransform implements Transform2D {
     this.sign = sign;
   }
 
+  /**
+   * Gets the point used in the transformation.
+   *
+   * @return the point used in the transformation
+   */
   public Complex getPoint() {
     return point;
   }
 
+  /**
+   * Gets the sign used in the transformation.
+   *
+   * @return the sign used in the transformation
+   */
   public int getSign() {
     return sign;
   }
@@ -53,7 +68,6 @@ public class JuliaTransform implements Transform2D {
     if (sign == -1) {
       subtracted = new Complex(-subtracted.getX0(), -subtracted.getX1());
     }
-
     return subtracted;
   }
 }
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Matrix2x2.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Matrix2x2.java
index 72625fad178b5898d8a3b5c1f932724968392dcc..c65ad0a3b610d558caa354196c1e3505ab493e5a 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Matrix2x2.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Matrix2x2.java
@@ -1,7 +1,8 @@
 package edu.ntnu.idatt2003.mappevurderingprog2.models;
 
 /**
- * This class represents a 2x2 matrix and provides a method for multiplying a 2D vector with the matrix.
+ * This class represents a 2x2 matrix and
+ * provides a method for multiplying a 2D vector with the matrix.
  * The matrix is defined by the four elements a00, a01, a10, and a11.
  */
 public class Matrix2x2 {
@@ -20,7 +21,7 @@ public class Matrix2x2 {
 
   /**
    * Constructs a Matrix2x2 object with the specific elements.
-
+   *
    * @param a00 the element in the first row and first column
    * @param a01 the element in the first row and second column
    * @param a10 the element in the second row and first column
@@ -35,7 +36,7 @@ public class Matrix2x2 {
 
   /**
    * Returns the value of the element in the first row and first column.
-
+   *
    * @return the element in the first row and first column
    */
   public double getA00() {
@@ -44,34 +45,34 @@ public class Matrix2x2 {
 
   /**
    * Returns the value of the element in the first row and second column.
-
+   *
    * @return the element in the first row and second column
    */
   public double getA01() {
-      return a01;
+    return a01;
   }
 
   /**
    * Returns the value of the element in the second row and first column.
-
+   *
    * @return the element in the second row and first column
    */
   public double getA10() {
-      return a10;
+    return a10;
   }
 
   /**
    * Returns the value of the element in the second row and second column.
-
+   *
    * @return the element in the second row and second column
    */
   public double getA11() {
-      return a11;
+    return a11;
   }
 
   /**
    * Multiplies the given 2D vector with the matrix.
-
+   *
    * @param vector the 2D vector to be multiplied with the matrix
    * @return a new 2D vector that is the result of the multiplication
    */
@@ -79,6 +80,7 @@ public class Matrix2x2 {
     if (vector == null) {
       throw new IllegalArgumentException("Vector cannot be null");
     }
-    return new Vector2D(a00 * vector.getX0() + a01 * vector.getX1(), a10 * vector.getX0() + a11 * vector.getX1());
+    return new Vector2D(a00 * vector.getX0() + a01 * vector.getX1(),
+        a10 * vector.getX0() + a11 * vector.getX1());
   }
 }
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Transform2D.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Transform2D.java
index c3191d65e8659b0c8c23f56f3962d76434ec7a92..4cd79c3be7372bbf4cf8b5bfbab3e96ec07288fe 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Transform2D.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Transform2D.java
@@ -4,6 +4,6 @@ package edu.ntnu.idatt2003.mappevurderingprog2.models;
  * This class is the interface for 2D transformations.
  */
 public interface Transform2D {
-    public Vector2D transform(Vector2D point);
+  public Vector2D transform(Vector2D point);
 }
 
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Vector2D.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Vector2D.java
index be6567d9516fe6379d2647b7ea4ca10c6b9ca898..ea4fa766e95d087b455ae105757179cddbd496fd 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Vector2D.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/Vector2D.java
@@ -2,7 +2,8 @@ package edu.ntnu.idatt2003.mappevurderingprog2.models;
 
 /**
  * This class represents a 2D vector and provides methods for vector addition and subtraction.
- * The class encapsulates the x0 and x1 components of the vector, representing the x and y coordinates of the vector.
+ * The class encapsulates the x0 and x1 components of the vector,
+ * representing the x and y coordinates of the vector.
  */
 public class Vector2D {
 
@@ -14,7 +15,7 @@ public class Vector2D {
 
   /**
    * Constructs a Vector2D object with the given x0 and x1 components.
-
+   *
    * @param x0 The component along the x-axis
    * @param x1 The component along the y-axis
    */
@@ -25,7 +26,7 @@ public class Vector2D {
 
   /**
    * Returns the value of the x0 component of the vector.
-
+   *
    * @return the x0 component of the vector
    */
   public double getX0() {
@@ -34,7 +35,7 @@ public class Vector2D {
 
   /**
    * Returns the value of the x1 component of the vector.
-
+   *
    * @return the x1 component of the vector
    */
   public double getX1() {
@@ -43,7 +44,7 @@ public class Vector2D {
 
   /**
    * Adds another 2D vector to this vector and returns the result as a new vector.
-
+   *
    * @param other the vector to be added to this vector
    * @return a new vector that is the result of the addition
    */
@@ -56,7 +57,7 @@ public class Vector2D {
 
   /**
    * Subtracts another 2D vector from this vector and returns the result as a new vector.
-
+   *
    * @param other the vector to be subtracted from this vector
    * @return a new vector that is the result of the subtraction
    */
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosCanvas.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosCanvas.java
index 62168e6de68baf7615babff369d5395b3733b14f..d8b7bef8660c8c353ef6c7cd9ec0d63e8bbd0c6f 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosCanvas.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosCanvas.java
@@ -3,123 +3,129 @@ package edu.ntnu.idatt2003.mappevurderingprog2.models.chaos;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
-import javafx.scene.transform.Affine;
 
 /**
  * The ChaosCanvas class represent a 2D canvas with the ability to store and retrieve pixels.
  * The class can apply transformations to obtain pixel values from a given point.
  */
-public class ChaosCanvas{
-
-    //The canvas is represented as a 2D array of integers, where each integer represents a color.
-    private int [][] canvas;
-
-    // The width of the canvas
-    private int width;
-
-    // The height of the canvas
-    private int height;
-
-    // The minimum coordinates of the canvas
-    private Vector2D minCoords;
-
-    // The maximum coordinates of the canvas
-    private Vector2D maxCoords;
-
-    // The transformation from coordinates to indices
-    private AffineTransform2D transformCoordsToIndices;
-
-    /**
-     * Constructs a ChaosCanvas object with the given width, height, minimum coordinates, and maximum coordinates.
-
-     * @param width the width of the canvas
-     * @param height the height of the canvas
-     * @param minCoords the minimum coordinates of the canvas
-     * @param maxCoords the maximum coordinates of the canvas
-     */
-   public ChaosCanvas(int width, int height, Vector2D minCoords, Vector2D maxCoords) {
-        this.width = width;
-        this.height = height;
-        this.minCoords = minCoords;
-        this.maxCoords = maxCoords;
-        this.canvas = new int[width][height];
-        setUpTransformation();
+public class ChaosCanvas {
+
+  //The canvas is represented as a 2D array of integers, where each integer represents a color.
+  private int[][] canvas;
+
+  // The width of the canvas
+  private int width;
+
+  // The height of the canvas
+  private int height;
+
+  // The minimum coordinates of the canvas
+  private Vector2D minCoords;
+
+  // The maximum coordinates of the canvas
+  private Vector2D maxCoords;
+
+  // The transformation from coordinates to indices
+  private AffineTransform2D transformCoordsToIndices;
+
+  /**
+   * Constructs a ChaosCanvas object with the given
+   * width, height, minimum coordinates, and maximum coordinates.
+   *
+   * @param width     the width of the canvas
+   * @param height    the height of the canvas
+   * @param minCoords the minimum coordinates of the canvas
+   * @param maxCoords the maximum coordinates of the canvas
+   */
+  public ChaosCanvas(int width, int height, Vector2D minCoords, Vector2D maxCoords) {
+    this.width = width;
+    this.height = height;
+    this.minCoords = minCoords;
+    this.maxCoords = maxCoords;
+    this.canvas = new int[width][height];
+    setUpTransformation();
+  }
+
+  /**
+   * Sets up the transformation from coordinates to indices.
+   */
+  private void setUpTransformation() {
+    this.transformCoordsToIndices = new AffineTransform2D(
+        new Matrix2x2(0,
+            ((height - 1) / (minCoords.getX1() - maxCoords.getX1())),
+            ((width - 1) / (maxCoords.getX0() - minCoords.getX0())),
+            0),
+        new Vector2D(((height - 1) * maxCoords.getX1()) / (maxCoords.getX1() - minCoords.getX1()),
+            ((width - 1) * minCoords.getX0()) / (minCoords.getX0() - maxCoords.getX0())));
+  }
+
+  /**
+   * Gets the minimum coordinates of the canvas.
+   *
+   * @return the minimum coordinates of the canvas
+   */
+  public Vector2D getMinCoords() {
+    return minCoords;
+  }
+
+  /**
+   * Gets the maximum coordinates of the canvas.
+   *
+   * @return the maximum coordinates of the canvas
+   */
+  public Vector2D getMaxCoords() {
+    return maxCoords;
+  }
+
+  /**
+   * Gets the color of the pixel at the given point.
+   *
+   * @param point the point at which to get the pixel color
+   * @return the color of the pixel at the given point
+   */
+  public int getPixel(Vector2D point) {
+    Vector2D indices = transformCoordsToIndices.transform(point);
+    int x = (int) indices.getX0();
+    int y = (int) indices.getX1();
+    return canvas[x][y];
+  }
+
+
+  /**
+   * Puts a pixel at the given point.
+   *
+   * @param point the point at which to put the pixel
+   * @return true if the pixel was successfully put, false otherwise
+   */
+  public boolean putPixel(Vector2D point) {
+    Vector2D indices = transformCoordsToIndices.transform(point);
+    int x = (int) indices.getX0();
+    int y = (int) indices.getX1();
+    if (x >= 0 && x < width && y >= 0 && y < height) {
+      canvas[x][y] += 1;
+      return true;
+    } else {
+      return false;
     }
-
-    private void setUpTransformation() {
-     this.transformCoordsToIndices = new AffineTransform2D(
-         new Matrix2x2(0,
-             ((height - 1)/(minCoords.getX1() - maxCoords.getX1())),
-             ((width - 1)/(maxCoords.getX0() - minCoords.getX0())),
-         0),
-         new Vector2D(((height - 1) * maxCoords.getX1())/(maxCoords.getX1() - minCoords.getX1()),
-             ((width - 1) * minCoords.getX0())/(minCoords.getX0() - maxCoords.getX0())));
-    }
-
-    /**
-     * Gets the color of the pixel at the given point.
-
-     * @return the color of the pixel at the given point
-     */
-    public int getPixel(Vector2D point){
-        Vector2D indices = transformCoordsToIndices.transform(point);
-        int x = (int) indices.getX0();
-        int y = (int) indices.getX1();
-        return canvas[x][y];
-    }
-
-
-    /**
-     * Puts a pixel at the given point.
-
-     * @param point the point at which to put the pixel
-     */
-
-    public void putPixel(Vector2D point) {
-        Vector2D indices = transformCoordsToIndices.transform(point);
-        int x = (int) indices.getX0();
-        int y = (int) indices.getX1();
-        canvas[x][y] = 1;
-    }
-
-    /**
-      * Gets the canvas array.
-
-      * @return the canvas array
-      */
-   public int[][]getCanvasArray(){
-        return canvas;
-    }
-
-    /**
-     * Clears the canvas.
-     */
-    public void clear() {
-      for (int i = 0; i < canvas.length; i++) {
-        for (int j = 0; j < canvas[i].length; j++) {
-          canvas[i][j] = 0;
-        }
-      }
-    }
-
-    public int getWidth() {
-      return width;
-    }
-
-    public int getHeight() {
-      return height;
-    }
-
-    public void printCanvas() {
-      for (int i = 0; i < canvas.length; i++) {
-        for (int j = 0; j < canvas[i].length; j++) {
-          if(canvas[i][j] == 0) {
-            System.out.print(" ");
-          } else {
-            System.out.print("X");
-          }
-        }
-        System.out.println();
+  }
+
+  /**
+   * Gets the canvas array.
+   *
+   * @return the canvas array
+   */
+  public int[][] getCanvasArray() {
+    return canvas;
+  }
+
+  /**
+   * Clears the canvas.
+   */
+  public void clear() {
+    for (int i = 0; i < canvas.length; i++) {
+      for (int j = 0; j < canvas[i].length; j++) {
+        canvas[i][j] = 0;
       }
     }
+  }
 }
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGame.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGame.java
index 705aa751f92292e0d31b570287df427517780307..0df2ff54d1e6387fd12c2a21a6c6e61d258ea423 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGame.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGame.java
@@ -1,67 +1,238 @@
 package edu.ntnu.idatt2003.mappevurderingprog2.models.chaos;
 
-import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
-import edu.ntnu.idatt2003.mappevurderingprog2.models.JuliaTransform;
-import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
-import edu.ntnu.idatt2003.mappevurderingprog2.models.UserInterface;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
+import javafx.util.Pair;
 
 /**
  * The ChaosGame class represents a chaos game.
- * The class simulate a chaos game with a given description on a canvas.
+ * The class simulates a chaos game with a given description on a canvas.
  */
 public class ChaosGame {
 
+  /** The instance of the ChaosGame (singleton pattern). */
+  private static ChaosGame instance;
+
+  /** The canvas of the ChaosGame. */
   private ChaosCanvas canvas;
+
+  /** The description of the ChaosGame. */
   private ChaosGameDescription description;
+
+  /** The number of steps of the ChaosGame. */
+  private int steps;
+
+  /** The current point of the ChaosGame. */
   private Vector2D currentPoint;
-  public Random random;
 
-  public ChaosGame(ChaosGameDescription description, int width, int height) {
-    this.description = description;
-    this.canvas = new ChaosCanvas(width, height, description.getMinCoords(), description.getMaxCoords());
+  /** The random object of the ChaosGame. */
+  private Random random;
+
+  /** The number of points that are out of bounds. */
+  private int outOfBoundsCount = 0;
+
+  /** The list of observers of the ChaosGame. */
+  private List<ChaosGameObserver> observers;
+
+  /** The height of the canvas. */
+  private final int height = 200;
+
+  /** The width of the canvas. */
+  private final int width = 200;
+
+  /**
+   * Constructs a new ChaosGame instance.
+   * Initializes the current point to (0, 0), random object, and observers list.
+   */
+  private ChaosGame() {
     this.currentPoint = new Complex(0, 0);
     this.random = new Random();
+    this.observers = new ArrayList<>();
   }
 
-  public ChaosCanvas getCanvas() {
-    return canvas;
+  /**
+   * Returns the singleton instance of ChaosGame.
+   * If the instance does not exist, it creates a new one.
+   *
+   * @return the singleton instance of ChaosGame.
+   */
+  public static synchronized ChaosGame getInstance() {
+    if (instance == null) {
+      instance = new ChaosGame();
+    }
+    return instance;
+  }
+
+  /**
+   * Sets the description of the ChaosGame and resets the game.
+   *
+   * @param description the description of the ChaosGame.
+   */
+  public void setDescription(ChaosGameDescription description) {
+    resetGame();
+    this.description = description;
   }
 
-  public void runSteps(int steps) {
-    for (int i = 0; i < steps; i++) {
-      int randomIndex = random.nextInt(description.getTransforms().size());
-      Transform2D transform = description.getTransforms().get(randomIndex);
-      currentPoint = transform.transform(currentPoint);
-      canvas.putPixel(currentPoint);
+  /**
+   * Sets up the canvas for the ChaosGame using the description's min and max coordinates.
+   *
+   * @throws IllegalStateException if the description is not set.
+   */
+  public void setCanvas() {
+    if (description == null) {
+      throw new IllegalStateException("ChaosGame description is not set.");
     }
+    this.canvas = new ChaosCanvas(
+        width, height, description.getMinCoords(), description.getMaxCoords());
   }
 
-  public static void main(String[] args) throws Exception {
-    UserInterface ui = new UserInterface();
-    ui.start();
+  /**
+   * Sets the number of steps for the ChaosGame.
+   *
+   * @param steps the number of steps, must be between 0 and 1000000.
+   * @throws IllegalArgumentException if the number of steps is out of bounds.
+   */
+  public void setSteps(int steps) {
+    if (steps < 0 || steps > 1000000) {
+      throw new IllegalArgumentException("Number of steps must be between 0 and 1000000.");
+    }
+    this.steps = steps;
   }
 
-  private static ChaosGameDescription createAffineChaosGameDescription() {
-    List<Transform2D> transforms = new ArrayList<>();
-    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0, 0)));
-    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.5, 0)));
-    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.25, 0.5)));
-    ChaosGameDescription description = new ChaosGameDescription(transforms, new Vector2D(0, 0), new Vector2D(1, 1));
-    return description;
+  public void resetOutOfBoundsCount() {
+    this.outOfBoundsCount = 0;
   }
 
-  private static ChaosGameDescription createJuliaChaosGameDescription() {
-    List<Transform2D> transforms = new ArrayList<>();
-    Complex julaPoint = new Complex(-0.74543, 0.11301);
-    int sign = -1;
-    transforms.add(new JuliaTransform(julaPoint, sign));
-    ChaosGameDescription description = new ChaosGameDescription(transforms, new Vector2D(-1.6, -1), new Vector2D(1.6, 1));
+  /**
+   * Resets the ChaosGame by clearing the canvas and resetting the current point.
+   */
+  private void resetGame() {
+    if (this.canvas != null) {
+      this.canvas.clear();
+    }
+    this.currentPoint = new Complex(0, 0);
+  }
+
+  /**
+   * Adds an observer to the ChaosGame.
+   *
+   * @param observer the observer to be added.
+   * @throws IllegalArgumentException if the observer is null.
+   */
+  public void addObserver(ChaosGameObserver observer) {
+    if (observer == null) {
+      throw new IllegalArgumentException("Observer cannot be null.");
+    }
+    observers.add(observer);
+  }
+
+  /**
+   * Removes an observer from the ChaosGame.
+   *
+   * @param observer the observer to be removed.
+   * @throws IllegalArgumentException if the observer is null.
+   */
+  public void removeObserver(ChaosGameObserver observer) {
+    if (observer == null) {
+      throw new IllegalArgumentException("Observer cannot be null.");
+    }
+    observers.remove(observer);
+  }
+
+  /**
+   * Notifies all observers that the ChaosGame has been updated.
+   */
+  protected void notifyChaosGameUpdated() {
+    for (ChaosGameObserver observer : observers) {
+      observer.onChaosGameUpdated();
+    }
+  }
+
+  /**
+   * Returns the canvas of the ChaosGame.
+   *
+   * @return the canvas of the ChaosGame.
+   */
+  public ChaosCanvas getCanvas() {
+    return canvas;
+  }
+
+  /**
+   * Returns the description of the ChaosGame.
+   *
+   * @return the description of the ChaosGame.
+   */
+  public ChaosGameDescription getDescription() {
     return description;
   }
+
+  /**
+   * Returns the number of steps of the ChaosGame.
+   *
+   * @return the number of steps.
+   */
+  public int getSteps() {
+    return steps;
+  }
+
+  /**
+   * Returns the number of points that are out of bounds.
+   *
+   * @return the number of points that are out of bounds.
+   */
+  public int getOutOfBoundsCount() {
+    return outOfBoundsCount;
+  }
+
+  /**
+   * Returns the list of observers of the ChaosGame.
+   *
+   * @return the list of observers.
+   */
+  public List<ChaosGameObserver> getObservers() {
+    return observers;
+  }
+
+  /**
+   * Runs the chaos game for the set number of steps.
+   * Updates the current point based on weighted or non-weighted transformations.
+   * Notifies observers after completing the steps.
+   */
+  public void runSteps() {
+    ChaosGameDescription description = ChaosGame.getInstance().getDescription();
+    outOfBoundsCount = 0;
+
+    if (description.isWeighted()) {
+      List<Pair<Transform2D, Double>> weightedTransforms = description.getWeightedTransforms();
+      for (int i = 0; i < steps; i++) {
+        double p = random.nextDouble();
+        double cumulativeProbability = 0.0;
+        for (Pair<Transform2D, Double> weightedTransform : weightedTransforms) {
+          cumulativeProbability += weightedTransform.getValue();
+          if (p <= cumulativeProbability) {
+            currentPoint = weightedTransform.getKey().transform(currentPoint);
+            break;
+          }
+        }
+        if (!canvas.putPixel(currentPoint)) {
+          outOfBoundsCount++;
+        }
+      }
+    } else {
+      List<Transform2D> transforms = description.getTransforms();
+      for (int i = 0; i < steps; i++) {
+        int randomIndex = random.nextInt(transforms.size());
+        Transform2D transform = transforms.get(randomIndex);
+        currentPoint = transform.transform(currentPoint);
+        if (!canvas.putPixel(currentPoint)) {
+          outOfBoundsCount++;
+        }
+      }
+    }
+    notifyChaosGameUpdated();
+  }
 }
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescription.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescription.java
index f868166ec13e3b11f940763392462db0bc61a66d..967649af1eab6a60c26ba998749dc7756436f67c 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescription.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescription.java
@@ -2,7 +2,10 @@ package edu.ntnu.idatt2003.mappevurderingprog2.models.chaos;
 
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
+import javafx.util.Pair;
 
 /**
  * The ChaosGameDescription class represents a description of a chaos game.
@@ -16,8 +19,16 @@ public class ChaosGameDescription {
   // The maximum of the x and y coordinates of the game area
   private Vector2D maxCoords;
 
+  // The initial minimum and maximum coordinates
+  private Vector2D initialMinCoords;
+
+  // The initial minimum and maximum coordinates
+  private Vector2D initialMaxCoords;
+
   // The list of transforms
   private List<Transform2D> transforms;
+  private List<Pair<Transform2D, Double>> weightedTransforms;
+  private final boolean isWeighted;
 
   /**
    * Constructs a ChaosGameDescription object with the given list of transforms
@@ -27,10 +38,32 @@ public class ChaosGameDescription {
    * @param minCoords  the minimum of the x and y coordinates of the game area
    * @param maxCoords  the maximum of the x and y coordinates of the game area
    */
-  public ChaosGameDescription(List<Transform2D> transforms, Vector2D minCoords, Vector2D maxCoords){
+  public ChaosGameDescription(
+      List<Transform2D> transforms, Vector2D minCoords, Vector2D maxCoords) {
     this.minCoords = minCoords;
     this.maxCoords = maxCoords;
     this.transforms = transforms;
+    this.isWeighted = false;
+    this.initialMinCoords = new Vector2D(minCoords.getX0(), minCoords.getX1());
+    this.initialMaxCoords = new Vector2D(maxCoords.getX0(), maxCoords.getX1());
+  }
+
+  /**
+   * Constructs a ChaosGameDescription object with the given list of transforms
+   * and the minimum and maximum coordinates.
+   *
+   * @param minCoords  the minimum of the x and y coordinates of the game area
+   * @param maxCoords  the maximum of the x and y coordinates of the game area
+   */
+  public ChaosGameDescription(List<Pair<Transform2D, Double>> weightedTransforms,
+                              Vector2D minCoords, Vector2D maxCoords, boolean isWeighted) {
+    this.minCoords = minCoords;
+    this.maxCoords = maxCoords;
+    this.weightedTransforms = weightedTransforms;
+    this.transforms = weightedTransforms.stream().map(Pair::getKey).collect(Collectors.toList());
+    this.isWeighted = isWeighted;
+    this.initialMinCoords = new Vector2D(minCoords.getX0(), minCoords.getX1());
+    this.initialMaxCoords = new Vector2D(maxCoords.getX0(), maxCoords.getX1());
   }
 
   /**
@@ -42,6 +75,15 @@ public class ChaosGameDescription {
     return transforms;
   }
 
+  /**
+   * Gets the list of weighted transformations associated with the chaos game.
+   *
+   * @return the list of weighted transforms
+   */
+  public List<Pair<Transform2D, Double>> getWeightedTransforms() {
+    return Collections.unmodifiableList(weightedTransforms);
+  }
+
   /**
    * Gets the minimum of the x and y coordinates of the game area.
    *
@@ -59,4 +101,68 @@ public class ChaosGameDescription {
   public Vector2D getMaxCoords() {
     return maxCoords;
   }
+
+  /**
+   * Checks if the transforms associated with the chaos game are weighted.
+   *
+   * @return true if the transforms are weighted, false otherwise
+   */
+  public boolean isWeighted() {
+    return isWeighted;
+  }
+
+  /**
+   * Sets the minimum coordinates of the game area.
+   *
+   * @param minCoords the new minimum coordinates
+   * @throws IllegalArgumentException if the provided coordinates are invalid
+   */
+  public void setMinCoords(Vector2D minCoords) {
+    if (minCoords == null) {
+      throw new IllegalArgumentException("Maximum coordinates cannot be null.");
+    }
+    if (this.maxCoords != null && (minCoords.getX0()
+        >= this.maxCoords.getX0() || minCoords.getX1() >= this.maxCoords.getX1())) {
+      throw new IllegalArgumentException(
+          "Minimum coordinates must be less than or equal to maximum coordinates.");
+    }
+    this.minCoords = minCoords;
+  }
+
+  /**
+   * Sets the maximum coordinates of the game area.
+   *
+   * @param maxCoords the new maximum coordinates
+   * @throws IllegalArgumentException if the provided coordinates are invalid
+   */
+  public void setMaxCoords(Vector2D maxCoords) {
+    if (maxCoords == null) {
+      throw new IllegalArgumentException("Maximum coordinates cannot be null.");
+    }
+    if (this.minCoords != null && (maxCoords.getX0()
+        <= this.minCoords.getX0() || maxCoords.getX1() <= this.minCoords.getX1())) {
+      throw new IllegalArgumentException(
+          "Maximum coordinates must be greater than or equal to minimum coordinates.");
+    }
+    this.maxCoords = maxCoords;
+  }
+
+  /**
+   * Gets the initial minimum coordinates of the game area.
+   *
+   * @return the initial minimum coordinates
+   */
+  public Vector2D getInitialMinCoords() {
+    return initialMinCoords;
+  }
+
+  /**
+   * Gets the initial maximum coordinates of the game area.
+   *
+   * @return the initial maximum coordinates
+   */
+  public Vector2D getInitialMaxCoords() {
+    return initialMaxCoords;
+  }
+
 }
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescriptionFactory.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescriptionFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..78d26b14c2f9ac376ebbf266f8b698449d797612
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameDescriptionFactory.java
@@ -0,0 +1,63 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.models.chaos;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.JuliaTransform;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import java.util.ArrayList;
+import java.util.List;
+import javafx.util.Pair;
+
+/**
+ * The ChaosGameDescriptionFactory class provides static methods
+ * to create instances of ChaosGameDescription
+ * for different types of chaos games.
+ */
+public class ChaosGameDescriptionFactory {
+
+  /**
+   * Creates a ChaosGameDescription for generating a Sierpinski triangle.
+   *
+   * @return a ChaosGameDescription instance representing a Sierpinski triangle
+   */
+  public static ChaosGameDescription createSierpinskiTriangle() {
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0, 0)));
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.5, 0)));
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.25, 0.5)));
+    return new ChaosGameDescription(transforms, new Vector2D(0, 0), new Vector2D(1, 1));
+  }
+
+  /**
+   * Creates a ChaosGameDescription for generating a Barnsley Fern.
+   *
+   * @return a ChaosGameDescription instance representing a Barnsley Fern
+   */
+  public static ChaosGameDescription createBarnsleyFern() {
+    List<Pair<Transform2D, Double>> weightedTransforms = new ArrayList<>();
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(
+        0, 0, 0, 0.16), new Vector2D(0, 0)), 0.01));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(
+        0.85, 0.04, -0.04, 0.85), new Vector2D(0, 1.6)), 0.85));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(
+        0.2, -0.26, 0.23, 0.22), new Vector2D(0, 1.6)), 0.07));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(
+        -0.15, 0.28, 0.26, 0.24), new Vector2D(0, 0.44)), 0.07));
+    return new ChaosGameDescription(
+        weightedTransforms, new Vector2D(-3, 0), new Vector2D(3, 10), true);
+  }
+
+  /**
+   * Creates a ChaosGameDescription for generating a standard Julia transformation.
+   *
+   * @return a ChaosGameDescription instance representing a standard Julia transformation
+   */
+  public static ChaosGameDescription createStandardJuliaTransformation() {
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new JuliaTransform(new Complex(-0.74543, 0.11301), -1));
+    transforms.add(new JuliaTransform(new Complex(-0.74543, 0.11301), 1));
+    return new ChaosGameDescription(transforms, new Vector2D(-1.6, -1.0), new Vector2D(1.6, 1.0));
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameFileHandler.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameFileHandler.java
index ae13c64664a676d6ed477cc0fdf380363a555c57..14250ef18f6037f5f474005c4fcc8b64f22ce8d8 100644
--- a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameFileHandler.java
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameFileHandler.java
@@ -14,9 +14,24 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Scanner;
 
+/**
+ * The ChaosGameFileHandler class provides static methods
+ * for reading and writing chaos game descriptions
+ * to and from files.
+ */
 public class ChaosGameFileHandler {
-  public ChaosGameDescription readTransformationsFromFile(String path) throws FileNotFoundException {
-    File file = new File(path);
+
+  /**
+   * Reads a chaos game description from a file.
+   *
+   * @param name the name of the file containing the transformations
+   * @return a ChaosGameDescription object representing the transformations read from the file
+   * @throws FileNotFoundException if the specified file is not found
+   */
+  public static ChaosGameDescription readTransformationsFromFile(
+      String name) throws FileNotFoundException {
+    File file = new File(
+        "src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/" + name);
     Scanner scanner = new Scanner(file).useLocale(Locale.ENGLISH);
 
     String transformType = scanner.nextLine().trim();
@@ -32,7 +47,10 @@ public class ChaosGameFileHandler {
         if (transformType.equals("Affine2D")) {
           transforms.add(parseAffineTransform2D(line));
         } else if (transformType.equals("Julia")) {
-          transforms.add(parseJuliaTransform(line));
+          JuliaTransform transformation = parseJuliaTransform(line);
+          transforms.add(transformation);
+          transforms.add(new JuliaTransform(
+              transformation.getPoint(), -transformation.getSign()));
         }
       }
     }
@@ -41,10 +59,20 @@ public class ChaosGameFileHandler {
     return new ChaosGameDescription(transforms, minCoords, maxCoords);
   }
 
-  public void writeTransformationsToFile(ChaosGameDescription description, String path) throws Exception{
-    try (FileWriter writer = new FileWriter(new File(path))) {
-      if (description.getTransforms().isEmpty()) return;
-
+  /**
+   * Writes a chaos game description to a file.
+   *
+   * @param description the ChaosGameDescription object to be written to the file
+   * @param name        the name of the file to write the transformations to
+   * @throws Exception if an error occurs while writing to the file
+   */
+  public static void writeTransformationsToFile(
+      ChaosGameDescription description, String name) throws Exception {
+    try (FileWriter writer = new FileWriter(
+        "src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/" + name)) {
+      if (description.getTransforms().isEmpty()) {
+        return;
+      }
       Transform2D firstTransform = description.getTransforms().get(0);
       if (firstTransform instanceof AffineTransform2D) {
         writer.write("Affine2D\n");
@@ -58,51 +86,130 @@ public class ChaosGameFileHandler {
       for (Transform2D transform : description.getTransforms()) {
         if (transform instanceof AffineTransform2D) {
           AffineTransform2D affine = (AffineTransform2D) transform;
-          writer.write(formatMatrix2x2(affine.getMatrix()) + ", " + formatVector2D(affine.getVector()) + "\n");
+          writer.write(formatMatrix2x2(affine.getMatrix())
+              + ", " + formatVector2D(affine.getVector()) + "\n");
         } else if (transform instanceof JuliaTransform) {
           JuliaTransform julia = (JuliaTransform) transform;
-          writer.write(formatComplex(julia.getPoint()) + ", " + julia.getSign() + "\n");
+          writer.write(formatComplex(julia.getPoint())
+              + ", " + julia.getSign() + "\n");
         }
       }
     }
   }
 
-  private String formatVector2D(Vector2D vector) {
+  /**
+   * Lists the filenames of transformation files available in the resources directory.
+   *
+   * @return a list of filenames of transformation files
+   */
+  public static List<String> listTransformationFileNames() {
+    List<String> fileNames = new ArrayList<>();
+    File directory = new File(
+        "src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/");
+
+    if (directory.exists() && directory.isDirectory()) {
+      File[] files = directory.listFiles();
+      if (files != null) {
+        for (File file : files) {
+          if (file.isFile()) {
+            fileNames.add(file.getName());
+          }
+        }
+      }
+    }
+
+    return fileNames;
+  }
+
+  /**
+   * Checks if a file with the given name exists in the resources directory.
+   *
+   * @param name the name of the file to check for existence
+   * @return true if the file exists, false otherwise
+   */
+  public static boolean checkFileExists(String name) {
+    File file = new File(
+        "src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/" + name);
+    return file.exists();
+  }
+
+  /**
+   * Formats a Vector2D object as a string.
+   *
+   * @param vector the Vector2D object to format
+   * @return the formatted string representation of the Vector2D object
+   */
+  private static String formatVector2D(Vector2D vector) {
     return String.format(Locale.ENGLISH, "%s, %s", vector.getX0(), vector.getX1());
   }
 
-  private String formatMatrix2x2(Matrix2x2 matrix) {
+  /**
+   * Formats a Matrix2x2 object as a string.
+   *
+   * @param matrix the Matrix2x2 object to format
+   * @return the formatted string representation of the Matrix2x2 object
+   */
+  private static String formatMatrix2x2(Matrix2x2 matrix) {
     return String.format(Locale.ENGLISH, "%f, %f, %f, %f",
         matrix.getA00(), matrix.getA01(),
         matrix.getA10(), matrix.getA11());
   }
 
-  private String formatComplex(Complex complex) {
-    return String.format(Locale.ENGLISH, "%s, %s", complex.getRealPart(), complex.getImaginaryPart());
+  /**
+   * Formats a Complex object as a string.
+   *
+   * @param complex the Complex object to format
+   * @return the formatted string representation of the Complex object
+   */
+  private static String formatComplex(Complex complex) {
+    return String.format(Locale.ENGLISH, "%s, %s",
+        complex.getRealPart(), complex.getImaginaryPart());
   }
 
-  private Vector2D parseVector2D(String line) {
+  /**
+   * Parses a string into a Vector2D object.
+   *
+   * @param line the string to parse
+   * @return the parsed Vector2D object
+   */
+  private static Vector2D parseVector2D(String line) {
     String[] parts = line.split(",");
-    return new Vector2D(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim()));
+    return new Vector2D(Double.parseDouble(parts[0].trim()),
+        Double.parseDouble(parts[1].trim()));
   }
 
-  private AffineTransform2D parseAffineTransform2D(String line) {
+  /**
+   * Parses a string into an AffineTransform2D object.
+   *
+   * @param line the string to parse
+   * @return the parsed AffineTransform2D object
+   */
+  private static AffineTransform2D parseAffineTransform2D(String line) {
     String[] parts = line.split(",");
     double[][] matrixData = new double[2][2];
     matrixData[0][0] = Double.parseDouble(parts[0].trim());
     matrixData[0][1] = Double.parseDouble(parts[1].trim());
     matrixData[1][0] = Double.parseDouble(parts[2].trim());
     matrixData[1][1] = Double.parseDouble(parts[3].trim());
-    Matrix2x2 matrix = new Matrix2x2(matrixData[0][0], matrixData[0][1], matrixData[1][0], matrixData[1][1]);
+    Matrix2x2 matrix = new Matrix2x2(matrixData[0][0],
+        matrixData[0][1], matrixData[1][0], matrixData[1][1]);
 
-    Vector2D vector = new Vector2D(Double.parseDouble(parts[4].trim()), Double.parseDouble(parts[5].trim()));
+    Vector2D vector = new Vector2D(Double.parseDouble(parts[4].trim()),
+        Double.parseDouble(parts[5].trim()));
 
     return new AffineTransform2D(matrix, vector);
   }
 
-  private JuliaTransform parseJuliaTransform(String line) {
+  /**
+   * Parses a string into a JuliaTransform object.
+   *
+   * @param line the string to parse
+   * @return the parsed JuliaTransform object
+   */
+  private static JuliaTransform parseJuliaTransform(String line) {
     String[] parts = line.split(",");
-    Complex point = new Complex(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim()));
+    Complex point = new Complex(Double.parseDouble(parts[0].trim()),
+        Double.parseDouble(parts[1].trim()));
     int sign = Integer.parseInt(parts[2].trim());
 
     return new JuliaTransform(point, sign);
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameObserver.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..9971e5865d5f3c307c6282c60cccc61d72225186
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/models/chaos/ChaosGameObserver.java
@@ -0,0 +1,9 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.models.chaos;
+
+/**
+ * The ChaosGameObserver interface provides a method to be called
+ * when the chaos game is updated.
+ */
+public interface ChaosGameObserver {
+  void onChaosGameUpdated();
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/utils/Colorpalette.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/utils/Colorpalette.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e1207c5e9ef599b7f93bba9e1f3b000aacef255
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/utils/Colorpalette.java
@@ -0,0 +1,12 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.utils;
+
+import javafx.scene.paint.Color;
+
+/**
+ * This class represents a color palette with different colors.
+ */
+public final class Colorpalette {
+
+  // The primary color for the program
+  public static final Color Primary = Color.web("#5072A7");
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/utils/Size.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/utils/Size.java
new file mode 100644
index 0000000000000000000000000000000000000000..060bb7981eceec3998bf2f9492f1e7c6e7ee07a9
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/utils/Size.java
@@ -0,0 +1,31 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.utils;
+
+import javafx.geometry.Rectangle2D;
+import javafx.stage.Screen;
+
+/**
+ * A class that provides the screen size.
+ */
+public class Size {
+
+  /**
+   * Get the screen width.
+   *
+   * @return the screen width
+   */
+  public static double getScreenWidth() {
+    Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
+    return primaryScreenBounds.getWidth();
+  }
+
+  /**
+   * Get the screen height.
+   *
+   * @return the screen height
+   */
+  public static double getScreenHeight() {
+    Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
+    return primaryScreenBounds.getHeight();
+
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/View.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/View.java
new file mode 100644
index 0000000000000000000000000000000000000000..166d758b0832edd457c5ce827327a5efb316efa4
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/View.java
@@ -0,0 +1,116 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.CanvasController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameObserver;
+import edu.ntnu.idatt2003.mappevurderingprog2.utils.Colorpalette;
+import edu.ntnu.idatt2003.mappevurderingprog2.utils.Size;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.components.ExtraUserOptions;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.components.Menu;
+import java.io.FileNotFoundException;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.paint.Color;
+
+/**
+ * The View class represents the main view of the application.
+ * It contains the main canvas, menu, and extra user options.
+ * It also implements the ChaosGameObserver interface to listen
+ * for updates from the GameController.
+ */
+public class View extends BorderPane implements ChaosGameObserver {
+
+  // Private Canvas object.
+  private Canvas mainCanvas;
+
+  // Private GameController object.
+  private GameController gameController;
+
+  // Private CanvasController object.
+  private CanvasController canvasController;
+
+  // Private FileController object.
+  private FileController fileController;
+
+  // Private Menu object.
+  private Menu menu;
+
+  // Private ExtraUserOptions object.
+  private ExtraUserOptions extraUserOptions;
+
+  /**
+   * Constructs a new View object.
+   *
+   * @param gameController The GameController object associated with the View object.
+   * @param canvasController The CanvasController object associated with the View object.
+   * @param fileController The FileController object associated with the View object.
+   */
+  public View(GameController gameController, CanvasController canvasController,
+              FileController fileController) {
+    this.gameController = gameController;
+    this.canvasController = canvasController;
+    this.fileController = fileController;
+    this.mainCanvas = new Canvas(600, 400);
+  }
+
+  /**
+   * Retrieves the main canvas of the view.
+   *
+   * @return The main canvas.
+   */
+  public Canvas getMainCanvas() {
+    return mainCanvas;
+  }
+
+  /**
+   * Creates the scene for the view.
+   *
+   * @return The created scene.
+   * @throws FileNotFoundException If a file is not found.
+   */
+  public Scene createScene() throws FileNotFoundException {
+    this.setBackground(new Background(
+        new BackgroundFill(Colorpalette.Primary, CornerRadii.EMPTY, Insets.EMPTY)));
+    this.setCenter(mainCanvas);
+    this.menu = new Menu(this, gameController, canvasController, fileController);
+    menu.setPrefWidth(Size.getScreenWidth() * 0.15);
+    menu.setMaxWidth(Size.getScreenWidth() * 0.15);
+    initializeExtraUserOptions();
+    this.setLeft(menu);
+    return new Scene(this, Size.getScreenWidth(), Size.getScreenHeight());
+  }
+
+  /**
+   * Initializes the extra user options.
+   */
+  private void initializeExtraUserOptions() {
+    extraUserOptions = new ExtraUserOptions(this.getMainCanvas(), gameController);
+    extraUserOptions.setPrefWidth(Size.getScreenWidth() * 0.15);
+    extraUserOptions.setMaxWidth(Size.getScreenWidth() * 0.15);
+    extraUserOptions.setPrefHeight(Size.getScreenHeight());
+    extraUserOptions.setMaxHeight(Size.getScreenHeight());
+    extraUserOptions.setBackground(new Background(new BackgroundFill(
+        Color.rgb(238, 217, 196), CornerRadii.EMPTY, Insets.EMPTY)));
+    extraUserOptions.setStyle("-fx-border-color: black; -fx-border-width: 0 0 0 2px;");
+    this.setRight(extraUserOptions);
+  }
+
+  /**
+   * Callback method triggered when the chaos game is updated.
+   * This method updates the canvas display.
+   */
+  @Override
+  public void onChaosGameUpdated() {
+    Platform.runLater(() -> {
+      canvasController.calculateQuantiles();
+      mainCanvas = canvasController.updateCanvasDisplay(mainCanvas);
+    });
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/AffineDialog.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/AffineDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6409ca7241e601e2c12f1568fd4b5a34be018ff
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/AffineDialog.java
@@ -0,0 +1,335 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+
+/**
+ * The AffineDialog class represents a dialog for editing
+ * and displaying affine transformations.
+ * It allows users to input matrix and vector values for affine transformations
+ * and validate them.
+ */
+public class AffineDialog {
+
+  // The dialog for editing affine transformations
+  private Dialog<Void> dialog;
+
+  // The game controller
+  private GameController gameController;
+
+  // A boolean indicating whether the dialog is in edit mode
+  private boolean isEditMode;
+
+  // The grid pane for the dialog
+  private GridPane grid = new GridPane();
+
+  /**
+   * Constructs an AffineDialog object with the given game controller and edit mode flag.
+   *
+   * @param gameController the game controller to be used
+   * @param isEditMode     flag indicating if the dialog is in edit mode
+   */
+  public AffineDialog(GameController gameController, boolean isEditMode) {
+    this.gameController = gameController;
+    this.isEditMode = isEditMode;
+    setupDialog();
+  }
+
+  /**
+   * Sets up the dialog components and layout.
+   */
+  private void setupDialog() {
+    dialog = new Dialog<>();
+    dialog.setTitle(isEditMode ? "Edit Affine Set" : "Create Affine Set");
+
+    VBox dialogVbox = new VBox(10);
+    dialogVbox.setPadding(new Insets(10));
+
+    grid.setHgap(10);
+    grid.setVgap(10);
+    grid.setPadding(new Insets(10, 150, 10, 10));
+
+    TextField stepsField = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentSteps()) : "");
+    grid.add(new Label("Number of Steps:"), 0, 0);
+    grid.add(stepsField, 1, 0);
+
+    TextField minCoordsFieldX = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMinCoords().getX0()) : "");
+    TextField minCoordsFieldY = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMinCoords().getX1()) : "");
+    grid.add(new Label("Min Coordinates X:"), 0, 1);
+    grid.add(minCoordsFieldX, 1, 1);
+    grid.add(new Label("Min Coordinates Y:"), 2, 1);
+    grid.add(minCoordsFieldY, 3, 1);
+
+    TextField maxCoordsFieldX = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMaxCoords().getX0()) : "");
+    TextField maxCoordsFieldY = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMaxCoords().getX1()) : "");
+    grid.add(new Label("Max Coordinates X:"), 0, 2);
+    grid.add(maxCoordsFieldX, 1, 2);
+    grid.add(new Label("Max Coordinates Y:"), 2, 2);
+    grid.add(maxCoordsFieldY, 3, 2);
+
+    AtomicInteger rowIndex = new AtomicInteger(3);
+    grid.add(new Label("Matrix"), 0, rowIndex.get());
+    grid.add(new Label("Vector"), 1, rowIndex.get());
+    rowIndex.incrementAndGet();
+
+    List<Node[]> transformationFields = new ArrayList<>();
+
+    if (isEditMode) {
+      List<Transform2D> affineTransformations = gameController.getAffineTransformations();
+      for (Transform2D transform : affineTransformations) {
+        if (transform instanceof AffineTransform2D) {
+          addTransformationFields(grid, (
+              AffineTransform2D) transform, rowIndex.get(), transformationFields);
+          rowIndex.incrementAndGet();
+        }
+      }
+    } else {
+      for (int i = 0; i < 3; i++) {
+        addEmptyTransformationFields(grid, rowIndex.get(), transformationFields);
+        rowIndex.incrementAndGet();
+      }
+    }
+
+    ScrollPane scrollPane = new ScrollPane(grid);
+    scrollPane.setFitToWidth(true);
+    scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
+
+    HBox editBox = new HBox(10);
+    editBox.setAlignment(Pos.CENTER_LEFT);
+
+    Button editButton = new Button(isEditMode ? "Edit Fractal" : "Display Fractal");
+    editButton.setOnAction(event -> {
+      if (validateInputs(stepsField, minCoordsFieldX,
+          minCoordsFieldY, maxCoordsFieldX, maxCoordsFieldY, transformationFields)) {
+        displayFractal(stepsField, minCoordsFieldX,
+            minCoordsFieldY, maxCoordsFieldX, maxCoordsFieldY, transformationFields);
+      }
+    });
+
+    HBox buttonBox = new HBox(10);
+    buttonBox.setAlignment(Pos.CENTER_RIGHT);
+
+    Button addBtn = new Button("Add Transformation");
+    addBtn.setOnAction(event -> {
+      addEmptyTransformationFields(grid, rowIndex.get(), transformationFields);
+      rowIndex.incrementAndGet();
+    });
+
+    Button removeBtn = new Button("Remove Transformation");
+    removeBtn.setOnAction(event -> {
+      if (!transformationFields.isEmpty()) {
+        Node[] lastFields = transformationFields.remove(transformationFields.size() - 1);
+        grid.getChildren().removeAll(lastFields);
+        rowIndex.decrementAndGet();
+      }
+    });
+
+    editBox.getChildren().addAll(editButton);
+    buttonBox.getChildren().addAll(addBtn, removeBtn);
+
+    dialogVbox.getChildren().addAll(scrollPane, editBox, buttonBox);
+    dialog.getDialogPane().setContent(dialogVbox);
+    dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
+  }
+
+  /**
+   * Displays the fractal based on the user inputs.
+   *
+   * @param stepsField          the text field for the number of steps
+   * @param minCoordsFieldX     the text field for the minimum X coordinate
+   * @param minCoordsFieldY     the text field for the minimum Y coordinate
+   * @param maxCoordsFieldX     the text field for the maximum X coordinate
+   * @param maxCoordsFieldY     the text field for the maximum Y coordinate
+   * @param transformationFields the list of transformation fields
+   */
+  private void displayFractal(TextField stepsField, TextField minCoordsFieldX,
+                              TextField minCoordsFieldY, TextField maxCoordsFieldX,
+                              TextField maxCoordsFieldY, List<Node[]> transformationFields) {
+    int steps = Integer.parseInt(stepsField.getText());
+    double minX = Double.parseDouble(minCoordsFieldX.getText());
+    double minY = Double.parseDouble(minCoordsFieldY.getText());
+    double maxX = Double.parseDouble(maxCoordsFieldX.getText());
+    double maxY = Double.parseDouble(maxCoordsFieldY.getText());
+
+    List<Transform2D> transforms = new ArrayList<>();
+    for (Node[] fields : transformationFields) {
+      TextField matrixField = (TextField) fields[0];
+      TextField vectorField = (TextField) fields[1];
+      double[][] matrixValues = parseMatrix(matrixField.getText());
+      double[] vectorValues = parseVector(vectorField.getText());
+      Matrix2x2 matrix = new Matrix2x2(matrixValues[0][0], matrixValues[0][1],
+          matrixValues[1][0], matrixValues[1][1]);
+      Vector2D vector = new Vector2D(vectorValues[0], vectorValues[1]);
+      transforms.add(new AffineTransform2D(matrix, vector));
+    }
+    gameController.setChaosGameSteps(steps);
+    gameController.setAffineTransformation(transforms, new Vector2D(minX, minY),
+        new Vector2D(maxX, maxY));
+    gameController.setChaosCanvas();
+    gameController.runTransformation();
+    dialog.close();
+    Platform.runLater(() -> {
+      if (gameController.getOutOfBoundsCount() > 0) {
+        UserFeedback.showErrorPopup("Error",
+            gameController.getOutOfBoundsCount() + " pixels are out of bounds.");
+      }
+    });
+  }
+
+  /**
+   * Validates the user inputs.
+   *
+   * @param stepsField          the text field for the number of steps
+   * @param minCoordsFieldX     the text field for the minimum X coordinate
+   * @param minCoordsFieldY     the text field for the minimum Y coordinate
+   * @param maxCoordsFieldX     the text field for the maximum X coordinate
+   * @param maxCoordsFieldY     the text field for the maximum Y coordinate
+   * @param transformationFields the list of transformation fields
+   * @return true if the inputs are valid, false otherwise
+   */
+  private boolean validateInputs(TextField stepsField, TextField minCoordsFieldX,
+                                 TextField minCoordsFieldY, TextField maxCoordsFieldX,
+                                 TextField maxCoordsFieldY, List<Node[]> transformationFields) {
+    try {
+      // Validate step count
+      int steps = Integer.parseInt(stepsField.getText());
+      if (steps <= 0 || steps > 1_000_000) {
+        throw new IllegalArgumentException("Steps must be between 1 and 1,000,000.");
+      }
+
+      // Validate coordinate bounds
+      double minX = Double.parseDouble(minCoordsFieldX.getText());
+      double minY = Double.parseDouble(minCoordsFieldY.getText());
+      double maxX = Double.parseDouble(maxCoordsFieldX.getText());
+      double maxY = Double.parseDouble(maxCoordsFieldY.getText());
+      if (minX >= maxX || minY >= maxY) {
+        throw new IllegalArgumentException(
+            "Minimum coordinates must be less than maximum coordinates.");
+      }
+
+      for (Node[] fields : transformationFields) {
+        TextField matrixField = (TextField) fields[0];
+        TextField vectorField = (TextField) fields[1];
+        double[][] matrix = parseMatrix(matrixField.getText());
+        double[] vector = parseVector(vectorField.getText());
+        for (double[] row : matrix) {
+          for (double value : row) {
+            if (value < -2.0 || value > 2.0) {
+              throw new IllegalArgumentException("Matrix values must be between -2 and 2.");
+            }
+          }
+        }
+        for (double value : vector) {
+          if (value < -2.0 || value > 2.0) {
+            throw new IllegalArgumentException("Vector values must be between -2 and 2.");
+          }
+        }
+      }
+    } catch (NumberFormatException e) {
+      UserFeedback.showErrorPopup("Input Error", "Please enter valid numbers.");
+      return false;
+    } catch (IllegalArgumentException e) {
+      UserFeedback.showErrorPopup("Input Error", e.getMessage());
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Adds transformation fields to the grid for an existing transformation.
+   *
+   * @param grid         the grid to which the fields are added
+   * @param transform    the affine transformation
+   * @param rowIndex     the row index in the grid
+   * @param fieldList    the list of transformation fields
+   */
+  private void addTransformationFields(GridPane grid, AffineTransform2D transform,
+                                       int rowIndex, List<Node[]> fieldList) {
+    TextField matrixField = new TextField("[" + transform.getMatrix().getA00()
+        + ", " + transform.getMatrix().getA01() + "; " + transform.getMatrix().getA10()
+        + ", " + transform.getMatrix().getA11() + "]");
+    TextField vectorField = new TextField("[" + transform.getVector().getX0()
+        + ", " + transform.getVector().getX1() + "]");
+    grid.add(matrixField, 0, rowIndex);
+    grid.add(vectorField, 1, rowIndex);
+    fieldList.add(new Node[]{matrixField, vectorField});
+  }
+
+  /**
+   * Adds empty transformation fields to the grid.
+   *
+   * @param grid      the grid to which the fields are added
+   * @param rowIndex  the row index in the grid
+   * @param fieldList the list of transformation fields
+   */
+  private void addEmptyTransformationFields(
+      GridPane grid, int rowIndex, List<Node[]> fieldList) {
+    TextField matrixField = new TextField("[" + 1.0 + ", " + 0.0 + "; "
+        + 0.0 + ", " + 1.0 + "]");
+    TextField vectorField = new TextField("[" + 0.0 + ", " + 0.0 + "]");
+    grid.add(matrixField, 0, rowIndex);
+    grid.add(vectorField, 1, rowIndex);
+    fieldList.add(new Node[]{matrixField, vectorField});
+  }
+
+  /**
+   * Parses a matrix from the given text.
+   *
+   * @param text the text representing the matrix
+   * @return the parsed matrix
+   */
+  private double[][] parseMatrix(String text) {
+    text = text.replaceAll("[\\[\\]]", "");
+    String[] rows = text.split(";");
+    double[][] matrix = new double[2][2];
+    for (int i = 0; i < rows.length; i++) {
+      String[] values = rows[i].split(",");
+      matrix[i][0] = Double.parseDouble(values[0].trim());
+      matrix[i][1] = Double.parseDouble(values[1].trim());
+    }
+    return matrix;
+  }
+
+  /**
+   * Parses a vector from the given text.
+   *
+   * @param text the text representing the vector
+   * @return the parsed vector
+   */
+  private double[] parseVector(String text) {
+    text = text.replaceAll("[\\[\\]]", "");
+    String[] values = text.split(",");
+    return new double[]{Double.parseDouble(values[0].trim()), Double.parseDouble(values[1].trim())};
+  }
+
+  /**
+   * Shows the dialog.
+   */
+  public void showDialog() {
+    dialog.showAndWait();
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/CreateFractalMenu.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/CreateFractalMenu.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d21099d332c7931901428447f07a7a1bbb28037
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/CreateFractalMenu.java
@@ -0,0 +1,43 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+
+/**
+ * This class represents a menu for creating a new fractal.
+ */
+public class CreateFractalMenu extends VBox {
+
+  /**
+   * Constructor for the CreateFractalMenu class.
+   *
+   * @param gameController the game controller
+   */
+  public CreateFractalMenu(GameController gameController) {
+    Label headline = new Label("Create a new fractal");
+    headline.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
+    Button affineButton = new Button("Affine");
+    affineButton.setOnAction(event -> {
+      AffineDialog affineDialog = new AffineDialog(gameController, false);
+      affineDialog.showDialog();
+    });
+
+    Button juliaButton = new Button("Julia");
+    juliaButton.setOnAction(event -> {
+      JuliaDialog juliaDialog = new JuliaDialog(gameController, false);
+      juliaDialog.showDialog();
+    });
+
+    HBox buttonBox = new HBox(affineButton, juliaButton);
+    buttonBox.setAlignment(Pos.CENTER);
+    buttonBox.setSpacing(10);
+
+    getChildren().addAll(headline, buttonBox);
+    setAlignment(Pos.TOP_CENTER);
+    setSpacing(10);
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/CurrentFractalMenu.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/CurrentFractalMenu.java
new file mode 100644
index 0000000000000000000000000000000000000000..88ad1a0b60fd95aae6bf7773e39359c448064706
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/CurrentFractalMenu.java
@@ -0,0 +1,75 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import java.util.Optional;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextInputDialog;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+
+/**
+ * This class represents a menu for the current fractal.
+ */
+public class CurrentFractalMenu extends VBox {
+
+  /**
+   * Constructor for the CurrentFractalMenu class.
+   *
+   * @param gameController the game controller
+   * @param fileController the file controller
+   */
+  public CurrentFractalMenu(GameController gameController, FileController fileController) {
+    Label editLabel = new Label("Current fractal");
+    editLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
+
+    Button editButton = new Button("Edit");
+    editButton.setOnAction(event -> {
+      if (gameController.isChaosGameEmpty()) {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to edit");
+      } else if (gameController.isJuliaTransformation()) {
+        JuliaDialog juliaDialog = new JuliaDialog(gameController, true);
+        juliaDialog.showDialog();
+      } else if (gameController.isAffineTransformation()) {
+        AffineDialog affineDialog = new AffineDialog(gameController, true);
+        affineDialog.showDialog();
+      }
+    });
+
+    Button saveButton = new Button("Save");
+    saveButton.setOnAction(event -> {
+      if (gameController.isChaosGameEmpty()) {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to save");
+      } else {
+        TextInputDialog dialog = new TextInputDialog();
+        dialog.setTitle("Save fractal");
+        dialog.setHeaderText("Enter a name for the fractal");
+        dialog.setContentText("Name:");
+
+        Optional<String> result = dialog.showAndWait();
+        result.ifPresent(name -> {
+          if (fileController.doesFileExist(name)) {
+            UserFeedback.showErrorPopup("Error", "A fractal with that name already exists");
+          } else {
+            try {
+              fileController.saveFractalToFile(name);
+              UserFeedback.showConfirmationPopup("Success", "Fractal saved successfully");
+            } catch (Exception e) {
+              throw new RuntimeException(e);
+            }
+          }
+        });
+      }
+    });
+
+    HBox buttonBox = new HBox(editButton, saveButton);
+    buttonBox.setAlignment(Pos.CENTER);
+    buttonBox.setSpacing(10);
+
+    getChildren().addAll(editLabel, buttonBox);
+    setAlignment(Pos.TOP_CENTER);
+    setSpacing(10);
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ExistingFractalsMenu.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ExistingFractalsMenu.java
new file mode 100644
index 0000000000000000000000000000000000000000..c54acd626106856239593af995b2bafa9d3934b8
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ExistingFractalsMenu.java
@@ -0,0 +1,103 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import java.util.List;
+import javafx.collections.FXCollections;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonBar;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.VBox;
+
+/**
+ * This class represents a menu for choosing existing fractals.
+ */
+public class ExistingFractalsMenu extends VBox {
+
+  /**
+   * Constructor for the ExistingFractalsMenu class.
+   *
+   * @param gameController the game controller
+   */
+  public ExistingFractalsMenu(GameController gameController, FileController fileController) {
+    Label headline = new Label("Choose existing fractal");
+    headline.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
+    Button sierpinskiTriangleButton = new Button("Sierpinski Triangle");
+    sierpinskiTriangleButton.setOnAction(event -> {
+      gameController.createSierpinskiTriangle();
+      gameController.setChaosGameSteps(1000000);
+      gameController.setChaosCanvas();
+      gameController.runTransformation();
+    });
+    Button barnsleyFernButton = new Button("Barnsley Fern");
+    barnsleyFernButton.setOnAction(event -> {
+      gameController.createBarnsleyFern();
+      gameController.setChaosGameSteps(1000000);
+      gameController.setChaosCanvas();
+      gameController.runTransformation();
+    });
+    Button juliaButton = new Button("Julia Transformation");
+    juliaButton.setOnAction(event -> {
+      gameController.createJuliaTransformation();
+      gameController.setChaosGameSteps(1000000);
+      gameController.setChaosCanvas();
+      gameController.runTransformation();
+    });
+
+    Button savedFractalsButton = new Button("Saved Fractals");
+    savedFractalsButton.setOnAction(
+        event -> showSavedFractalsDialog(gameController, fileController));
+
+    getChildren().addAll(headline, sierpinskiTriangleButton,
+        barnsleyFernButton, juliaButton, savedFractalsButton);
+    setAlignment(Pos.TOP_CENTER);
+    setSpacing(10);
+  }
+
+  /**
+   * Shows a dialog with saved fractals.
+   *
+   * @param gameController the game controller
+   */
+  private void showSavedFractalsDialog(
+      GameController gameController, FileController fileController) {
+    if (fileController.listTransformationFileNames().isEmpty()) {
+      UserFeedback.showErrorPopup("Error", "There are no saved fractals");
+      return;
+    }
+    Dialog<String> dialog = new Dialog<>();
+    dialog.setTitle("Select a Fractal");
+    dialog.setHeaderText("Choose a fractal to display:");
+
+    ButtonType loadButtonType = new ButtonType("Display", ButtonBar.ButtonData.OK_DONE);
+    dialog.getDialogPane().getButtonTypes().addAll(loadButtonType, ButtonType.CANCEL);
+
+    ListView<String> listView = new ListView<>();
+    List<String> savedFractals = fileController.listTransformationFileNames();
+    listView.setItems(FXCollections.observableArrayList(savedFractals));
+    listView.setPrefHeight(180);
+    dialog.getDialogPane().setContent(listView);
+
+    dialog.setResultConverter(dialogButton -> {
+      if (dialogButton == loadButtonType && !listView.getSelectionModel().isEmpty()) {
+        return listView.getSelectionModel().getSelectedItem();
+      }
+      return null;
+    });
+
+    dialog.showAndWait().ifPresent(fractalName -> {
+      try {
+        fileController.readFractalFromFile(fractalName);
+        gameController.setChaosGameSteps(1000000);
+        gameController.setChaosCanvas();
+        gameController.runTransformation();
+      } catch (Exception e) {
+        UserFeedback.showErrorPopup("Error", "Failed to load fractal: " + e.getMessage());
+      }
+    });
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ExtraUserOptions.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ExtraUserOptions.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4073fbbcadbb6a2ce8cf810d2ea47061ddb9e8d
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ExtraUserOptions.java
@@ -0,0 +1,264 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.utils.Size;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.Separator;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+
+/**
+ * This class represents the extra user options for the program.
+ */
+public class ExtraUserOptions extends VBox {
+
+  // Button for resetting the view.
+  private final Button resetViewButton;
+
+  // Button for clearing the canvas.
+  private final Button clearCanvasButton;
+
+  // The node.
+  private final Node node;
+
+  // Button for rotating the view.
+  private final RotationButton rotationButton;
+
+  // Button for additional information.
+  private final InformationButton informationButton;
+
+  // Private GameController object.
+  private GameController gameController;
+
+  // Private Zoom object.
+  private Zoom zoom;
+
+  // Button for zooming in.
+  private Button zoomInButton;
+
+  // Button for zooming out.
+  private Button zoomOutButton;
+
+  // Button for moving up in the canvas.
+  private Button upButton;
+
+  // Button for moving down in the canvas.
+  private Button downButton;
+
+  // Button for moving left in the canvas.
+  private Button leftButton;
+
+  // Button for moving right in the canvas.
+  private Button rightButton;
+
+  /**
+   * Constructor for the ExtraUserOptions class.
+   *
+   * @param node the node
+   * @param gameController the game controller
+   */
+  public ExtraUserOptions(Node node, GameController gameController) {
+    this.node = node;
+    this.gameController = gameController;
+    resetViewButton = new Button("Reset View");
+    clearCanvasButton = new Button("Clear Canvas");
+    rotationButton = new RotationButton(node, gameController);
+    informationButton = new InformationButton("Additional Information");
+    this.zoom = new Zoom(gameController);
+    this.zoomOutButton = new Button("-");
+    this.zoomInButton = new Button("+");
+    this.upButton = new Button("Up");
+    this.downButton = new Button("Down");
+    this.leftButton = new Button("Left");
+    this.rightButton = new Button("Right");
+    getStylesheets().add(getClass().getResource(
+        "/edu/ntnu/idatt2003/mappevurderingprog2/styles/extraUserOptions.css").toExternalForm());
+    this.getStyleClass().add("extra-user-options");
+
+    setupZoomButtons();
+    extraUserOptionsLabel();
+
+    Label rotationLabel = new Label("Rotate the view");
+    rotationLabel.getStyleClass().add("rotation-label");
+
+    Label zoomLabel = new Label("Zoom");
+    zoomLabel.getStyleClass().add("zoom-label");
+
+    Label navigateLabel = new Label("Navigate");
+    navigateLabel.getStyleClass().add("navigate-label");
+
+    HBox zoomControls = new HBox(zoomOutButton, zoomInButton);
+    zoomControls.setAlignment(Pos.CENTER);
+    zoomControls.setSpacing(10);
+
+    HBox leftRightControls = new HBox(leftButton, rightButton);
+    leftRightControls.setAlignment(Pos.CENTER);
+    leftRightControls.setSpacing(10);
+
+    this.getChildren().addAll(
+        zoomLabel,
+        zoomControls,
+        navigateLabel,
+        upButton,
+        leftRightControls,
+        downButton,
+        rotationLabel,
+        rotationButton,
+        createSeparator(),
+        resetViewButton,
+        clearCanvasButton,
+        informationButton
+    );
+    extraUserOptionsInitialize();
+  }
+
+  /**
+   * Sets up the actions for the zoom buttons.
+   * Checks if the fractal exists before performing the zoom actions.
+   */
+  private void setupZoomButtons() {
+    zoomInButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        zoom.zoomIn();
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to zoom in on");
+      }
+    });
+    zoomOutButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        zoom.zoomOut();
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to zoom out on");
+      }
+    });
+    upButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        zoom.moveUp(0.03);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to move up");
+      }
+    });
+    downButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        zoom.moveDown(0.03);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to move down");
+      }
+    });
+    leftButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        zoom.moveLeft(0.03);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to move left");
+      }
+    });
+    rightButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        zoom.moveRight(0.03);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to move right");
+      }
+    });
+  }
+
+  /**
+   * Initializes additional settings for the extra user options.
+   * Sets sizes, styles, and actions for reset and clear buttons.
+   */
+  private void extraUserOptionsInitialize() {
+    extraUserOptionsSizeAndPositioning();
+    extraUserOptionsStyling();
+    resetZoomButton();
+    clearCanvasButton();
+  }
+
+  /**
+   * Sets up the action for the reset view button.
+   * Resets the fractal view and canvas coordinates.
+   */
+  private void resetZoomButton() {
+    resetViewButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        gameController.resetChaosCanvasCoordinates();
+        gameController.setChaosCanvas();
+        gameController.runTransformation();
+        node.getTransforms().clear();
+        node.setTranslateX(0);
+        node.setTranslateY(0);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to reset the view for");
+      }
+    });
+  }
+
+  /**
+   * This method sets the size and positioning of the extra user options.
+   */
+  private void extraUserOptionsSizeAndPositioning() {
+    double screenWidth = Size.getScreenWidth();
+    double screenHeight = Size.getScreenHeight();
+    setPrefWidth(0.2 * screenWidth);
+    setMaxWidth(0.2 * screenWidth);
+    setPrefHeight(0.3 * screenHeight);
+    setMaxHeight(0.3 * screenHeight);
+    setAlignment(Pos.CENTER);
+    setPadding(new Insets(10, 20, 10, 20));
+    setSpacing(10);
+  }
+
+  /**
+   * Applies styling properties to the extra user options panel.
+   */
+  private void extraUserOptionsStyling() {
+    this.getStyleClass().add("extra-user-options");
+  }
+
+  /**
+   * Sets up the action for the clear canvas button.
+   * Clears the fractal from the canvas and resets the node transformations.
+   */
+  private void clearCanvasButton() {
+    clearCanvasButton.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        if (node instanceof Canvas) {
+          GraphicsContext gc = ((Canvas) node).getGraphicsContext2D();
+          gc.clearRect(0, 0, ((Canvas) node).getWidth(), ((Canvas) node).getHeight());
+        }
+        gameController.emptyChaosGame();
+        node.getTransforms().clear();
+        node.setTranslateX(0);
+        node.setTranslateY(0);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to clear");
+      }
+    });
+  }
+
+  /**
+   * Adds a label for the extra user options section.
+   */
+  private void extraUserOptionsLabel() {
+    Label extraUserOptionsLabel = new Label("Extra Functions");
+    extraUserOptionsLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
+    getChildren().add(extraUserOptionsLabel);
+  }
+
+  /**
+   * Creates a separator for the extra user options panel.
+   *
+   * @return the separator
+   */
+  private Separator createSeparator() {
+    Separator separator = new Separator();
+    separator.setMaxWidth(Double.MAX_VALUE);
+    VBox.setVgrow(separator, Priority.ALWAYS);
+    return separator;
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/InformationButton.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/InformationButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..f00ff9d3cf0eb2683ce1df475afaf192098b8c0d
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/InformationButton.java
@@ -0,0 +1,48 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import javafx.scene.layout.Region;
+
+/**
+ * This class represents a button for showing additional information.
+ */
+public class InformationButton extends Button {
+
+  /**
+   * Constructor for the InformationButton class.
+   *
+   * @param label the label
+   */
+  public InformationButton(String label) {
+    super(label);
+
+    this.setOnAction(event -> {
+      Alert alert = new Alert(AlertType.INFORMATION);
+      alert.setTitle("Additional information");
+      alert.setHeaderText(null);
+
+      String contentText = "Fractals use 3 primary colors to show hit density:\n"
+          + "Red: Top 33% hit most\nBlue: Mid 33%\nGreen: Bottom 33%\n\n"
+          + "Left Side: Main menu - create, select, edit fractals\n"
+          + "Right Side: Extra user options for canvas interaction\n"
+          + "The reset view button brings the canvas back to its original zoom and rotation\n"
+          + "The clear canvas button clears the whole canvas\n\n"
+          + "Interact with canvas: use zoom buttons + to zoom in and the - to zoom out, \n "
+          + "Move around the canvas with navigate buttons.";
+      alert.setContentText(contentText);
+
+      alert.getDialogPane().setPrefWidth(400);
+      alert.getDialogPane().setPrefHeight(300);
+
+      alert.getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
+      alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
+
+      alert.getButtonTypes().setAll(ButtonType.CLOSE);
+
+      alert.showAndWait();
+    });
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/JuliaDialog.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/JuliaDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..69c53a0c86647c5cd79fea44e8315794d8aa3286
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/JuliaDialog.java
@@ -0,0 +1,205 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.GridPane;
+
+/**
+ * This class represents a dialog for creating or editing a Julia set.
+ */
+public class JuliaDialog {
+
+  // Private Dialog object.
+  private Dialog<Void> dialog;
+
+  // Private GameController object.
+  private GameController gameController;
+
+  // Private boolean for checking if the dialog is in edit mode.
+  private boolean isEditMode;
+
+  /**
+   * Constructor for the JuliaDialog class.
+   *
+   * @param gameController the game controller
+   * @param isEditMode     the boolean for checking if the dialog is in edit mode
+   */
+  public JuliaDialog(GameController gameController, boolean isEditMode) {
+    this.gameController = gameController;
+    this.isEditMode = isEditMode;
+    setupDialog();
+  }
+
+  /**
+   * Sets up the dialog window with appropriate fields and buttons.
+   */
+  private void setupDialog() {
+    dialog = new Dialog<>();
+    dialog.setTitle(isEditMode ? "Edit Julia Set" : "Create Julia Set");
+
+    GridPane grid = new GridPane();
+    grid.setHgap(10);
+    grid.setVgap(10);
+    grid.setPadding(new Insets(20, 150, 10, 10));
+
+    TextField stepsField = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentSteps()) : "");
+    grid.add(new Label("Number of Steps:"), 0, 0);
+    grid.add(stepsField, 1, 0);
+
+    TextField minCoordsFieldX = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMinCoords().getX0()) : "");
+    TextField minCoordsFieldY = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMinCoords().getX1()) : "");
+    grid.add(new Label("Min Coordinates X:"), 0, 1);
+    grid.add(minCoordsFieldX, 1, 1);
+    grid.add(new Label("Min Coordinates Y:"), 2, 1);
+    grid.add(minCoordsFieldY, 3, 1);
+
+    TextField maxCoordsFieldX = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMaxCoords().getX0()) : "");
+    TextField maxCoordsFieldY = new TextField(
+        isEditMode ? String.valueOf(gameController.getCurrentMaxCoords().getX1()) : "");
+    grid.add(new Label("Max Coordinates X:"), 0, 2);
+    grid.add(maxCoordsFieldX, 1, 2);
+    grid.add(new Label("Max Coordinates Y:"), 2, 2);
+    grid.add(maxCoordsFieldY, 3, 2);
+
+    TextField realField = new TextField(
+        isEditMode && gameController.getCurrentJuliaPoint() != null ? String.valueOf(
+            gameController.getCurrentJuliaPoint().getRealPart()) : "");
+    grid.add(new Label("Real Part:"), 0, 3);
+    grid.add(realField, 1, 3);
+
+    TextField imagField = new TextField(
+        isEditMode && gameController.getCurrentJuliaPoint() != null ? String.valueOf(
+            gameController.getCurrentJuliaPoint().getImaginaryPart()) : "");
+    grid.add(new Label("Imaginary Part:"), 0, 4);
+    grid.add(imagField, 1, 4);
+
+    Button editButton = new Button("Display Fractal");
+    editButton.setOnAction(event -> {
+      if (validateInputs(stepsField, minCoordsFieldX, minCoordsFieldY,
+          maxCoordsFieldX, maxCoordsFieldY, realField, imagField)) {
+        displayFractal(stepsField, minCoordsFieldX, minCoordsFieldY,
+            maxCoordsFieldX, maxCoordsFieldY, realField, imagField);
+      }
+    });
+    grid.add(editButton, 1, 5);
+
+    dialog.getDialogPane().setContent(grid);
+    dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
+  }
+
+  /**
+   * Method for displaying the fractal.
+   *
+   * @param stepsField      the TextField for the number of steps
+   * @param minCoordsFieldX the TextField for the minimum x-coordinate
+   * @param minCoordsFieldY the TextField for the minimum y-coordinate
+   * @param maxCoordsFieldX the TextField for the maximum x-coordinate
+   * @param maxCoordsFieldY the TextField for the maximum y-coordinate
+   * @param realField       the TextField for the real part
+   * @param imagField       the TextField for the imaginary part
+   */
+  private void displayFractal(TextField stepsField, TextField minCoordsFieldX,
+                              TextField minCoordsFieldY, TextField maxCoordsFieldX,
+                              TextField maxCoordsFieldY, TextField realField, TextField imagField) {
+    int steps = Integer.parseInt(stepsField.getText());
+    double minX = Double.parseDouble(minCoordsFieldX.getText());
+    double minY = Double.parseDouble(minCoordsFieldY.getText());
+    double maxX = Double.parseDouble(maxCoordsFieldX.getText());
+    double maxY = Double.parseDouble(maxCoordsFieldY.getText());
+    double real = Double.parseDouble(realField.getText());
+    double imag = Double.parseDouble(imagField.getText());
+
+    Complex newPoint = new Complex(real, imag);
+
+    gameController.setChaosGameSteps(steps);
+    gameController.setJuliaTransformation(newPoint, new Vector2D(minX, minY),
+        new Vector2D(maxX, maxY));
+    gameController.setChaosCanvas();
+    gameController.runTransformation();
+    dialog.close();
+    Platform.runLater(() -> {
+      if (gameController.getOutOfBoundsCount() > 0) {
+        UserFeedback.showErrorPopup("Error",
+            gameController.getOutOfBoundsCount() + " pixels are out of bounds.");
+      }
+    });
+  }
+
+  /**
+   * Validates the user inputs.
+   *
+   * @param stepsField the field for entering the number of steps
+   * @param minFieldX  the field for entering the minimum X coordinate
+   * @param minFieldY  the field for entering the minimum Y coordinate
+   * @param maxFieldX  the field for entering the maximum X coordinate
+   * @param maxFieldY  the field for entering the maximum Y coordinate
+   * @param realField  the field for entering the real part of the Julia point
+   * @param imagField  the field for entering the imaginary part of the Julia point
+   * @return true if the inputs are valid, false otherwise
+   */
+  private boolean validateInputs(TextField stepsField, TextField minFieldX,
+                                 TextField minFieldY, TextField maxFieldX,
+                                 TextField maxFieldY, TextField realField,
+                                 TextField imagField) {
+    try {
+      if (stepsField.getText().isEmpty() || minFieldX.getText().isEmpty()
+          || minFieldY.getText().isEmpty() || maxFieldX.getText().isEmpty()
+          || maxFieldY.getText().isEmpty() || realField.getText().isEmpty()
+          || imagField.getText().isEmpty()) {
+        throw new IllegalArgumentException(
+            "All fields must be filled out.");
+      }
+
+      int steps = Integer.parseInt(stepsField.getText());
+      if (steps <= 0 || steps > 1_000_000) {
+        throw new IllegalArgumentException(
+            "Steps must be between 1 and 1,000,000.");
+      }
+
+      double minX = Double.parseDouble(minFieldX.getText());
+      double minY = Double.parseDouble(minFieldY.getText());
+      double maxX = Double.parseDouble(maxFieldX.getText());
+      double maxY = Double.parseDouble(maxFieldY.getText());
+      if (minX >= maxX || minY >= maxY) {
+        throw new IllegalArgumentException(
+            "Minimum coordinates must be less than maximum coordinates.");
+      }
+
+      double real = Double.parseDouble(realField.getText());
+      double imag = Double.parseDouble(imagField.getText());
+      if (real < -2 || real > 2 || imag < -2 || imag > 2) {
+        throw new IllegalArgumentException(
+            "Real and imaginary parts must be within [-2, 2].");
+      }
+    } catch (NumberFormatException e) {
+      UserFeedback.showErrorPopup(
+          "Input Error", "Please enter valid numbers.");
+      return false;
+    } catch (IllegalArgumentException e) {
+      UserFeedback.showErrorPopup(
+          "Input Error", e.getMessage());
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Method for showing the dialog.
+   */
+  public void showDialog() {
+    dialog.showAndWait();
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/Menu.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/Menu.java
new file mode 100644
index 0000000000000000000000000000000000000000..d865f3ada87ead26d66f3400ae8b1b67b34f0da0
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/Menu.java
@@ -0,0 +1,87 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.CanvasController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.View;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.Separator;
+import javafx.scene.layout.VBox;
+
+/**
+ * The Menu class represents the main menu of the application.
+ * It provides options for managing existing fractals, creating new fractals,
+ * editing the current fractal, scaling the canvas size, and quitting the application.
+ */
+public class Menu extends VBox {
+
+  // Private ExistingFractalsMenu object.
+  private ExistingFractalsMenu existingFractalsMenu;
+
+  // Private CreateFractalMenu object.
+  private CreateFractalMenu createFractalMenu;
+
+  // Private CurrentFractalMenu object.
+  private CurrentFractalMenu currentFractalMenu;
+
+  // Private ScaleCanvasSize object.
+  private ScaleCanvasSize scaleCanvasSize;
+
+  // Private quit Button object.
+  private Button quitButton;
+
+  /**
+   * Constructor for the Menu class.
+   *
+   * @param view the view
+   * @param gameController the game controller
+   * @param canvasController the canvas controller
+   */
+  public Menu(View view, GameController gameController, CanvasController canvasController,
+              FileController fileController) {
+    existingFractalsMenu = new ExistingFractalsMenu(gameController, fileController);
+    createFractalMenu = new CreateFractalMenu(gameController);
+    currentFractalMenu = new CurrentFractalMenu(gameController, fileController);
+    scaleCanvasSize = new ScaleCanvasSize(view, gameController, canvasController);
+    quitButton = new Button("Quit application");
+    initializeMenu();
+  }
+
+  /**
+   * Initializes the menu by setting up its components and layout.
+   */
+  private void initializeMenu() {
+    getStylesheets().add(getClass().getResource(
+        "/edu/ntnu/idatt2003/mappevurderingprog2/styles/menu.css").toExternalForm());
+    Label menuLabel = new Label("Menu");
+    menuLabel.getStyleClass().add("menu-label");
+    quitButton.getStyleClass().add("quit-button");
+    quitButton.setOnAction(e -> Platform.exit());
+
+    VBox.setMargin(menuLabel, new Insets(7, 0, 7, 0));
+    VBox.setMargin(existingFractalsMenu, new Insets(7, 0, 7, 0));
+    VBox.setMargin(createFractalMenu, new Insets(2, 0, 2, 0));
+    VBox.setMargin(currentFractalMenu, new Insets(2, 0, 2, 0));
+    VBox.setMargin(quitButton, new Insets(5, 0, 50, 0));
+
+
+    getChildren().addAll(
+        new Separator(),
+        menuLabel,
+        new Separator(),
+        existingFractalsMenu,
+        createFractalMenu,
+        currentFractalMenu,
+        scaleCanvasSize,
+        new Separator(),
+        quitButton,
+        new Separator());
+    setAlignment(Pos.TOP_CENTER);
+    setSpacing(20);
+    this.getStyleClass().add("menu-box");
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/RotationButton.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/RotationButton.java
new file mode 100644
index 0000000000000000000000000000000000000000..d556302b8b7a3e8845fc1aeacc690320dd45d9f8
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/RotationButton.java
@@ -0,0 +1,142 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import javafx.geometry.Bounds;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.layout.HBox;
+import javafx.scene.transform.Rotate;
+
+/**
+ * The RotationButton class represents a set of buttons for rotating a Node.
+ * It provides clockwise and counterclockwise rotation functionality.
+ */
+public class RotationButton extends HBox {
+
+  // Private Button object for rotating clockwise.
+  private final Button rotateClockwise;
+
+  // Private Button object for rotating counterclockwise.
+  private final Button rotateCounterClockwise;
+
+  // Private Node object.
+  private final Node node;
+
+  // Private GameController object.
+  private GameController gameController;
+
+  // Private double for total rotation.
+  private double totalRotation = 0.0;
+
+  /**
+   * Constructs a new RotationButton.
+   *
+   * @param node           The Node to be rotated.
+   * @param gameController The GameController associated with the RotationButton.
+   */
+  public RotationButton(Node node, GameController gameController) {
+    this.node = node;
+    this.gameController = gameController;
+    rotateClockwise = new Button("Right");
+    rotateCounterClockwise = new Button("Left");
+
+    initializeUi();
+    attachEventHandlers();
+  }
+
+  /**
+   * Initializes the UI components of the RotationButton.
+   */
+  private void initializeUi() {
+    setSpacing(10);
+    setAlignment(Pos.CENTER);
+    getChildren().addAll(rotateCounterClockwise, rotateClockwise);
+  }
+
+  /**
+   * Attaches event handlers to the buttons.
+   */
+  private void attachEventHandlers() {
+    rotateClockwise.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        rotate(90);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to rotate");
+      }
+    });
+    rotateCounterClockwise.setOnAction(event -> {
+      if (!gameController.isChaosGameEmpty()) {
+        rotate(-90);
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to rotate");
+      }
+    });
+  }
+
+  /**
+   * Rotates the node by the specified angle.
+   *
+   * @param angle The angle by which to rotate the node.
+   */
+  private void rotate(double angle) {
+    clearTransforms();
+    double centerX = getCenterX();
+    double centerY = getCenterY();
+    double pivotX = getPivotX(centerX);
+    double pivotY = getPivotY(centerY);
+    totalRotation += angle;
+    node.getTransforms().add(new Rotate(totalRotation, pivotX, pivotY));
+  }
+
+  /**
+   * Clears all the transformations applied to the node.
+   */
+  private void clearTransforms() {
+    node.getTransforms().clear();
+  }
+
+  /**
+   * Calculates the x-coordinate of the center of the scene.
+   *
+   * @return The x-coordinate of the center of the scene.
+   */
+  private double getCenterX() {
+    return node.getScene().getWidth() / 2;
+  }
+
+  /**
+   * Calculates the y-coordinate of the center of the scene.
+   *
+   * @return The y-coordinate of the center of the scene.
+   */
+  private double getCenterY() {
+    return node.getScene().getHeight() / 2;
+  }
+
+  /**
+   * Calculates the x-coordinate of the pivot point for rotation.
+   *
+   * @param centerX The x-coordinate of the center of the scene.
+   * @return The x-coordinate of the pivot point.
+   */
+  private double getPivotX(double centerX) {
+    Bounds bounds = node.getBoundsInParent();
+    double nodeCenterX = bounds.getWidth() / 2;
+    double deltaX = centerX - node.localToScene(nodeCenterX, 0).getX();
+    return nodeCenterX + deltaX;
+  }
+
+  /**
+   * Calculates the y-coordinate of the pivot point for rotation.
+   *
+   * @param centerY The y-coordinate of the center of the scene.
+   * @return The y-coordinate of the pivot point.
+   */
+  private double getPivotY(double centerY) {
+    Bounds bounds = node.getBoundsInParent();
+    double nodeCenterY = bounds.getHeight() / 2;
+    double deltaY = centerY - node.localToScene(0, nodeCenterY).getY();
+    return nodeCenterY + deltaY;
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ScaleCanvasSize.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ScaleCanvasSize.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5e890967725a57fff17efbf3487c8cea6872453
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/ScaleCanvasSize.java
@@ -0,0 +1,113 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.CanvasController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.View;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+
+/**
+ * The ScaleCanvasSize class represents a component for scaling the canvas size.
+ * It provides options to increase or decrease the height and width of the canvas.
+ */
+public class ScaleCanvasSize extends VBox {
+
+  // Constants for minimum width.
+  private static final double MIN_WIDTH = 100;
+
+  // Constants for minimum height.
+  private static final double MIN_HEIGHT = 100;
+
+  // Constants for maximum width.
+  private static final double MAX_WIDTH = 800;
+
+  // Constants for maximum height.
+  private static final double MAX_HEIGHT = 700;
+
+  // Private GameController object.
+  private GameController gameController;
+
+  // Private CanvasController object.
+  private CanvasController canvasController;
+
+  /**
+   * Constructs a new ScaleCanvasSize component.
+   *
+   * @param view           The View object associated with the canvas.
+   * @param gameController The GameController object associated with the canvas.
+   */
+  public ScaleCanvasSize(View view, GameController gameController,
+                         CanvasController canvasController) {
+    this.gameController = gameController;
+    this.canvasController = canvasController;
+    Label scaleLabel = new Label("Scale canvas size");
+    scaleLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
+
+    Label heightLabel = new Label("Height");
+    heightLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
+    Button heightIncrease = new Button("+");
+    Button heightDecrease = new Button("-");
+    HBox heightControls = new HBox(heightDecrease, heightIncrease);
+    heightControls.setAlignment(Pos.CENTER);
+    heightControls.setSpacing(10);
+
+    heightIncrease.setOnAction(e -> {
+      if (!gameController.isChaosGameEmpty()) {
+        double currentHeight = view.getMainCanvas().getHeight();
+        if (currentHeight + 10 <= MAX_HEIGHT) {
+          canvasController.setCanvasHeight(view.getMainCanvas(), currentHeight + 10);
+        }
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to increase the height of");
+      }
+    });
+
+    heightDecrease.setOnAction(e -> {
+      if (!gameController.isChaosGameEmpty()) {
+        double currentHeight = view.getMainCanvas().getHeight();
+        if (currentHeight - 10 >= MIN_HEIGHT) {
+          canvasController.setCanvasHeight(view.getMainCanvas(), currentHeight - 10);
+        }
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to decrease the height of");
+      }
+    });
+
+    Label widthLabel = new Label("Width");
+    widthLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
+    Button widthIncrease = new Button("+");
+    Button widthDecrease = new Button("-");
+    HBox widthControls = new HBox(widthDecrease, widthIncrease);
+    widthControls.setAlignment(Pos.CENTER);
+    widthControls.setSpacing(10);
+
+    widthIncrease.setOnAction(e -> {
+      if (!gameController.isChaosGameEmpty()) {
+        double currentWidth = view.getMainCanvas().getWidth();
+        if (currentWidth + 10 <= MAX_WIDTH) {
+          canvasController.setCanvasWidth(view.getMainCanvas(), currentWidth + 10);
+        }
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to increase the width of");
+      }
+    });
+
+    widthDecrease.setOnAction(e -> {
+      if (!gameController.isChaosGameEmpty()) {
+        double currentWidth = view.getMainCanvas().getWidth();
+        if (currentWidth - 10 >= MIN_WIDTH) {
+          canvasController.setCanvasWidth(view.getMainCanvas(), currentWidth - 10);
+        }
+      } else {
+        UserFeedback.showErrorPopup("Error", "There is no fractal to decrease the width of");
+      }
+    });
+
+    getChildren().addAll(scaleLabel, heightLabel, heightControls, widthLabel, widthControls);
+    setAlignment(Pos.TOP_CENTER);
+    setSpacing(10);
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/UserFeedback.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/UserFeedback.java
new file mode 100644
index 0000000000000000000000000000000000000000..efcba9c2177f4ad86a8a207561beb6abf121ff33
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/UserFeedback.java
@@ -0,0 +1,38 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import javafx.scene.control.Alert;
+
+/**
+ * The UserFeedback class provides methods
+ * for displaying different types of popup messages.
+ */
+public class UserFeedback {
+
+  /**
+   * Shows a confirmation popup with the given title and message.
+   *
+   * @param title   The title of the popup.
+   * @param message The message of the popup.
+   */
+  public static void showConfirmationPopup(String title, String message) {
+    Alert alert = new Alert(Alert.AlertType.INFORMATION);
+    alert.setTitle(title);
+    alert.setHeaderText(null);
+    alert.setContentText(message);
+    alert.showAndWait();
+  }
+
+  /**
+   * Displays an error popup message.
+   *
+   * @param title   The title of the popup.
+   * @param message The message to be displayed.
+   */
+  public static void showErrorPopup(String title, String message) {
+    Alert alert = new Alert(Alert.AlertType.ERROR);
+    alert.setTitle(title);
+    alert.setHeaderText(null);
+    alert.setContentText(message);
+    alert.showAndWait();
+  }
+}
diff --git a/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/Zoom.java b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/Zoom.java
new file mode 100644
index 0000000000000000000000000000000000000000..233f2706ca235ffb8b72318e812025dfe58d91bd
--- /dev/null
+++ b/src/main/java/edu/ntnu/idatt2003/mappevurderingprog2/views/components/Zoom.java
@@ -0,0 +1,128 @@
+package edu.ntnu.idatt2003.mappevurderingprog2.views.components;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+
+/**
+ * The Zoom class provides methods for zooming in,
+ * zooming out, and moving the view in a canvas.
+ */
+public class Zoom {
+
+  // Private GameController object.
+  private GameController gameController;
+
+  /**
+   * Constructs a new Zoom object.
+   *
+   * @param gameController The GameController object associated with the Zoom object.
+   */
+  public Zoom(GameController gameController) {
+    this.gameController = gameController;
+  }
+
+  /**
+   * Zooms in on the canvas.
+   */
+  public void zoomIn() {
+    adjustZoom(0.9);
+  }
+
+  /**
+   * Zooms out on the canvas.
+   */
+  public void zoomOut() {
+    adjustZoom(1.1);
+  }
+
+  /**
+   * Moves the view to the left in the canvas.
+   *
+   * @param amount The amount to move the view.
+   */
+  public void moveLeft(double amount) {
+    translateView(-amount, 0);
+  }
+
+  /**
+   * Moves the view to the right in the canvas.
+   *
+   * @param amount The amount to move the view.
+   */
+  public void moveRight(double amount) {
+    translateView(amount, 0);
+  }
+
+  /**
+   * Moves the view up in the canvas.
+   *
+   * @param amount The amount to move the view.
+   */
+  public void moveUp(double amount) {
+    translateView(0, amount);
+  }
+
+  /**
+   * Moves the view down in the canvas.
+   *
+   * @param amount The amount to move the view.
+   */
+  public void moveDown(double amount) {
+    translateView(0, -amount);
+  }
+
+  /**
+   * Adjusts the zoom level of the canvas.
+   *
+   * @param adjustFactor The factor by which to adjust the zoom level.
+   */
+  private void adjustZoom(double adjustFactor) {
+    Vector2D currentMin = gameController.getCurrentMinCoords();
+    Vector2D currentMax = gameController.getCurrentMaxCoords();
+
+    double centerX = (currentMax.getX0() + currentMin.getX0()) / 2;
+    double centerY = (currentMax.getX1() + currentMin.getX1()) / 2;
+
+    double newMinX = centerX + (currentMin.getX0() - centerX) * adjustFactor;
+    double newMaxX = centerX + (currentMax.getX0() - centerX) * adjustFactor;
+    double newMinY = centerY + (currentMin.getX1() - centerY) * adjustFactor;
+    double newMaxY = centerY + (currentMax.getX1() - centerY) * adjustFactor;
+
+    updateCoordinates(newMinX, newMaxX, newMinY, newMaxY);
+  }
+
+  /**
+   * Translates the view by the specified amount.
+   *
+   * @param deltaX The amount to move along the x-axis.
+   * @param deltaY The amount to move along the y-axis.
+   */
+  private void translateView(double deltaX, double deltaY) {
+    Vector2D currentMin = gameController.getCurrentMinCoords();
+    Vector2D currentMax = gameController.getCurrentMaxCoords();
+
+    double newMinX = currentMin.getX0() + deltaX;
+    double newMaxX = currentMax.getX0() + deltaX;
+    double newMinY = currentMin.getX1() + deltaY;
+    double newMaxY = currentMax.getX1() + deltaY;
+
+    updateCoordinates(newMinX, newMaxX, newMinY, newMaxY);
+  }
+
+  /**
+   * Updates the coordinates of the canvas view.
+   *
+   * @param newMinX The new minimum x-coordinate.
+   * @param newMaxX The new maximum x-coordinate.
+   * @param newMinY The new minimum y-coordinate.
+   * @param newMaxY The new maximum y-coordinate.
+   */
+  private void updateCoordinates(double newMinX, double newMaxX, double newMinY, double newMaxY) {
+    Vector2D newMin = new Vector2D(newMinX, newMinY);
+    Vector2D newMax = new Vector2D(newMaxX, newMaxY);
+
+    gameController.updateChaosCanvasCoordinates(newMin, newMax);
+    gameController.setChaosCanvas();
+    gameController.runTransformation();
+  }
+}
diff --git a/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/styles/extraUserOptions.css b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/styles/extraUserOptions.css
new file mode 100644
index 0000000000000000000000000000000000000000..536fb54dbb16a67f63055c15ed1e0a3682068ec5
--- /dev/null
+++ b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/styles/extraUserOptions.css
@@ -0,0 +1,15 @@
+.extra-user-options {
+    -fx-background-color: #eed9c4;
+}
+
+.extra-functions-label,
+.zoom-label,
+.navigate-label,
+.rotation-label {
+    -fx-font-size: 14px;
+    -fx-font-weight: bold;
+}
+
+.extra-functions-label {
+    -fx-font-size: 16px;
+}
\ No newline at end of file
diff --git a/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/styles/menu.css b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/styles/menu.css
new file mode 100644
index 0000000000000000000000000000000000000000..e887e6023a5d62627754ee002fdc2ccd24a2f158
--- /dev/null
+++ b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/styles/menu.css
@@ -0,0 +1,18 @@
+.menu-label {
+    -fx-font-size: 20px;
+    -fx-font-weight: bold;
+    -fx-font-family: 'Times New Roman';
+}
+
+.quit-button {
+    -fx-background-color: red;
+    -fx-text-fill: black;
+    -fx-font-weight: bold;
+    -fx-border-color: black;
+}
+
+.menu-box {
+    -fx-background-color: #faf0e6;
+    -fx-border-color: black;
+    -fx-border-width: 0 2 0 0;
+}
\ No newline at end of file
diff --git a/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/OutOfBounds b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/OutOfBounds
new file mode 100644
index 0000000000000000000000000000000000000000..cec4278ba7e52186cf158a11ea86449b039ba77e
--- /dev/null
+++ b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/OutOfBounds
@@ -0,0 +1,6 @@
+Affine2D
+0.0, 0.0
+1.0, 1.0
+0.500000, 0.000000, 0.000000, 0.500000, 0.0, 0.0
+0.500000, 0.000000, 0.000000, 0.500000, 2.0, 0.0
+0.500000, 0.000000, 0.000000, 0.500000, 0.25, 0.5
diff --git a/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/Triangle b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/Triangle
new file mode 100644
index 0000000000000000000000000000000000000000..b2035133da0ad58e5a18f392020baa2088865bf0
--- /dev/null
+++ b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/Triangle
@@ -0,0 +1,6 @@
+Affine2D
+0.0, 0.0
+1.0, 1.0
+0.500000, 0.000000, 0.000000, 0.400000, 0.0, 0.0
+0.500000, 0.000000, 0.000000, 0.500000, 0.5, 0.0
+0.500000, 0.000000, 0.000000, 0.500000, 0.25, 0.5
diff --git a/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/ZoomedIn b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/ZoomedIn
new file mode 100644
index 0000000000000000000000000000000000000000..ba79156e35865d2ebebddd170df290ede570e708
--- /dev/null
+++ b/src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/ZoomedIn
@@ -0,0 +1,6 @@
+Affine2D
+0.31734899055740784, 0.43734899055740795
+0.502651009442592, 0.6226510094425921
+0.500000, 0.000000, 0.000000, 0.500000, 0.0, 0.0
+0.500000, 0.000000, 0.000000, 0.500000, 0.5, 0.0
+0.500000, 0.000000, 0.000000, 0.500000, 0.25, 0.5
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DNegativeTest.java
index 104c3f4cc26267aa20f0951fd593910605dec4b6..50f65f853a4f74a6749e40934e3f2bbcf049c143 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DNegativeTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DNegativeTest.java
@@ -5,8 +5,14 @@ import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The AffineTransform2DNegativeTest class contains negative test cases for the AffineTransform2D class.
+ */
 public class AffineTransform2DNegativeTest {
 
+  /**
+   * Tests the AffineTransform2D constructor with a null matrix argument.
+   */
   @Test
   public void affineTransform2DConstructorNegativeTest1() {
     Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -18,6 +24,9 @@ public class AffineTransform2DNegativeTest {
     }
   }
 
+  /**
+   * Tests the AffineTransform2D constructor with a null vector argument.
+   */
   @Test
   public void affineTransform2DConstructorNegativeTest2() {
     Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -29,6 +38,9 @@ public class AffineTransform2DNegativeTest {
     }
   }
 
+  /**
+   * Tests the getMatrix method with incorrect assertions.
+   */
   @Test
   public void getMatrixNegativeTest() {
     Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -40,6 +52,9 @@ public class AffineTransform2DNegativeTest {
     assert transform.getMatrix().getA11() != 2;
   }
 
+  /**
+   * Tests the getVector method with incorrect assertions.
+   */
   @Test
   public void getVectorNegativeTest() {
     Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -49,6 +64,9 @@ public class AffineTransform2DNegativeTest {
     assert transform.getVector().getX1() != 2;
   }
 
+  /**
+   * Tests the transform method with a null vector argument.
+   */
   @Test
   public void transformNegativeTest() {
     Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -60,4 +78,4 @@ public class AffineTransform2DNegativeTest {
       System.out.println(e.getMessage());
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DPositiveTest.java
index 4b67c27d1906344770251a905b46ccab51f06469..e588b4ce982885b5b1e09d27ac20036a51758877 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DPositiveTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/AffineTransform2DPositiveTest.java
@@ -7,8 +7,14 @@ import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The AffineTransform2DPositiveTest class contains positive test cases for the AffineTransform2D class.
+ */
 public class AffineTransform2DPositiveTest {
 
+    /**
+     * Tests the AffineTransform2D constructor with positive input.
+     */
     @Test
     public void affineTransform2DConstructorPositiveTest() {
         Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -18,6 +24,9 @@ public class AffineTransform2DPositiveTest {
         assertEquals(vector, transform.getVector());
     }
 
+    /**
+     * Tests the getMatrix method with positive input.
+     */
     @Test
     public void getMatrix() {
         Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -26,6 +35,9 @@ public class AffineTransform2DPositiveTest {
         assertEquals(matrix, transform.getMatrix());
     }
 
+    /**
+     * Tests the getVector method with positive input.
+     */
     @Test
     public void getVector() {
         Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
@@ -34,12 +46,17 @@ public class AffineTransform2DPositiveTest {
         assertEquals(vector, transform.getVector());
     }
 
+    /**
+     * Tests the transform method with positive input.
+     */
     @Test
     public void transform() {
         Matrix2x2 matrix = new Matrix2x2(1, 0, 0, 1);
         Vector2D vector = new Vector2D(1, 1);
         AffineTransform2D transform = new AffineTransform2D(matrix, vector);
-        Vector2D result = transform.transform(new Vector2D(1, 1));
-        assertEquals(new Vector2D(2, 2), result);
+        Vector2D point = new Vector2D(1, 1);
+        Vector2D result = transform.transform(point);
+        assertEquals(2, result.getX0());
+        assertEquals(2, result.getX1());
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosCanvasNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosCanvasNegativeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f50350b19f4dff607e79bef648da4ee9614d7db
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosCanvasNegativeTest.java
@@ -0,0 +1,85 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosCanvas;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosCanvasNegativeTest class contains negative test cases for the ChaosCanvas class.
+ */
+public class ChaosCanvasNegativeTest {
+
+  /**
+   * Tests the ChaosCanvas constructor with negative input.
+   */
+  @Test
+  public void ChaosCanvasConstructorNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assert chaosCanvas.getMinCoords().getX0() != 1;
+    assert chaosCanvas.getMinCoords().getX1() != 1;
+    assert chaosCanvas.getMaxCoords().getX1() != 0;
+    assert chaosCanvas.getMaxCoords().getX0() != 0;
+  }
+
+  /**
+   * Tests the getMinCoords method with negative input.
+   */
+  @Test
+  public void getMinCoordsNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assert chaosCanvas.getMinCoords().getX0() != 1;
+    assert chaosCanvas.getMinCoords().getX1() != 1;
+  }
+
+  /**
+   * Tests the getMaxCoords method with negative input.
+   */
+  @Test
+  public void getMaxCoordsNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assert chaosCanvas.getMaxCoords().getX0() != 0;
+    assert chaosCanvas.getMaxCoords().getX1() != 0;
+  }
+
+  /**
+   * Tests the getPixel method with negative input.
+   */
+  @Test
+  public void getPixelNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    chaosCanvas.putPixel(new Vector2D(0.5, 0.5));
+    assert chaosCanvas.getPixel(new Vector2D(0.5, 0.5)) != 0;
+  }
+
+  /**
+   * Tests the putPixel method with negative input.
+   */
+  @Test
+  public void putPixelNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assertFalse(chaosCanvas.putPixel(new Vector2D(100, 100)));
+  }
+
+  /**
+   * Tests the getCanvasArray method with negative input.
+   */
+  @Test
+  public void getCanvasArrayNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    chaosCanvas.getCanvasArray()[0][0] = 1;
+    assert chaosCanvas.getCanvasArray()[0][0] != 0;
+  }
+
+  /**
+   * Tests the clear method with negative input.
+   */
+  @Test
+  public void clearNegativeTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    chaosCanvas.putPixel(new Vector2D(0.5, 0.5));
+    chaosCanvas.clear();
+    assert chaosCanvas.getPixel(new Vector2D(0.5, 0.5)) != 1;
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosCanvasPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosCanvasPositiveTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0b3fd00d20d39922e8111594fb6e778185fd80c
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosCanvasPositiveTest.java
@@ -0,0 +1,83 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosCanvas;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosCanvasPositiveTest class contains positive test cases for the ChaosCanvas class.
+ */
+public class ChaosCanvasPositiveTest {
+
+  /**
+   * Tests the ChaosCanvas constructor with valid input.
+   */
+  @Test
+  public void chaosCanvasConstructorPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assertNotNull(chaosCanvas);
+  }
+
+  /**
+   * Tests the getMinCoords method with valid input.
+   */
+  @Test
+  public void getMinCoordsPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assertEquals(chaosCanvas.getMinCoords().getX0(), 0);
+    assertEquals(chaosCanvas.getMinCoords().getX1(), 0);
+  }
+
+  /**
+   * Tests the getMaxCoords method with valid input.
+   */
+  @Test
+  public void getMaxCoordsPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assertEquals(chaosCanvas.getMaxCoords().getX0(), 1);
+    assertEquals(chaosCanvas.getMaxCoords().getX1(), 1);
+  }
+
+  /**
+   * Tests the getPixel method with valid input.
+   */
+  @Test
+  public void getPixelPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    chaosCanvas.putPixel(new Vector2D(0.5, 0.5));
+    assertEquals(chaosCanvas.getPixel(new Vector2D(0.5, 0.5)), 1);
+  }
+
+  /**
+   * Tests the putPixel method with valid input.
+   */
+  @Test
+  public void putPixelPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    chaosCanvas.putPixel(new Vector2D(0.5, 0.5));
+    assertEquals(chaosCanvas.getPixel(new Vector2D(0.5, 0.5)), 1);
+  }
+
+  /**
+   * Tests the getCanvasArray method with valid input.
+   */
+  @Test
+  public void getCanvasArrayPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    assertNotNull(chaosCanvas.getCanvasArray());
+  }
+
+  /**
+   * Tests the clear method with valid input.
+   */
+  @Test
+  public void clearPositiveTest() {
+    ChaosCanvas chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1));
+    chaosCanvas.putPixel(new Vector2D(0.5, 0.5));
+    chaosCanvas.clear();
+    assertEquals(chaosCanvas.getPixel(new Vector2D(0.5, 0.5)), 0);
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionFactoryNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionFactoryNegativeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5a17a5cc4600dc0f8fe72bdb076fbb51cae76c8
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionFactoryNegativeTest.java
@@ -0,0 +1,41 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescriptionFactory;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGameDescriptionFactoryNegativeTest class contains negative test cases
+ * for the ChaosGameDescriptionFactory class.
+ */
+public class ChaosGameDescriptionFactoryNegativeTest {
+
+  /**
+   * Tests the createSierpinskiTriangle method of
+   * ChaosGameDescriptionFactory with negative input.
+   */
+  @Test
+  public void createSierpinskiTriangleNegativeTest() {
+    ChaosGameDescription chaosGameDescription = ChaosGameDescriptionFactory.createSierpinskiTriangle();
+    assert chaosGameDescription.getTransforms().size() != 0;
+  }
+
+  /**
+   * Tests the createBarnsleyFern method of
+   * ChaosGameDescriptionFactory with negative input.
+   */
+  @Test
+  public void createBarnsleyFernNegativeTest() {
+    ChaosGameDescription chaosGameDescription = ChaosGameDescriptionFactory.createBarnsleyFern();
+    assert chaosGameDescription.getWeightedTransforms().size() != 0;
+  }
+
+  /**
+   * Tests the createStandardJuliaTransformation method of ChaosGameDescriptionFactory with negative input.
+   */
+  @Test
+  public void createStandardJuliaTransformationNegativeTest() {
+    ChaosGameDescription chaosGameDescription = ChaosGameDescriptionFactory.createStandardJuliaTransformation();
+    assert chaosGameDescription.getTransforms().size() != 0;
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionFactoryPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionFactoryPositiveTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2edd081fb76029cb22dbbe9c4e498680b9304bd
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionFactoryPositiveTest.java
@@ -0,0 +1,43 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescriptionFactory;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGameDescriptionFactoryPositiveTest class contains positive test cases for the ChaosGameDescriptionFactory class.
+ */
+public class ChaosGameDescriptionFactoryPositiveTest {
+
+  /**
+   * Tests the createSierpinskiTriangle method of ChaosGameDescriptionFactory with positive input.
+   * Verifies that the generated chaos game description contains 3 transforms.
+   */
+  @Test
+  public void createSierpinskiTrianglePositiveTest() {
+    ChaosGameDescription chaosGameDescription = ChaosGameDescriptionFactory.createSierpinskiTriangle();
+    assertEquals(3, chaosGameDescription.getTransforms().size());
+  }
+
+  /**
+   * Tests the createBarnsleyFern method of ChaosGameDescriptionFactory with positive input.
+   * Verifies that the generated chaos game description contains 4 weighted transforms.
+   */
+  @Test
+  public void createBarnsleyFernPositiveTest() {
+    ChaosGameDescription chaosGameDescription = ChaosGameDescriptionFactory.createBarnsleyFern();
+    assertEquals(4, chaosGameDescription.getWeightedTransforms().size());
+  }
+
+  /**
+   * Tests the createStandardJuliaTransformation method of ChaosGameDescriptionFactory with positive input.
+   * Verifies that the generated chaos game description contains 2 transforms.
+   */
+  @Test
+  public void createStandardJuliaTransformationPositiveTest() {
+    ChaosGameDescription chaosGameDescription = ChaosGameDescriptionFactory.createStandardJuliaTransformation();
+    assertEquals(2, chaosGameDescription.getTransforms().size());
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionNegativeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..97aeede065491007e4382e426d884489a1cff1a2
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionNegativeTest.java
@@ -0,0 +1,209 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.util.Pair;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGameDescriptionNegativeTest class contains negative test cases for the ChaosGameDescription class.
+ */
+public class ChaosGameDescriptionNegativeTest {
+
+  /**
+   * Tests the ChaosGameDescription constructor without weighted transforms with negative input.
+   * Verifies that the created ChaosGameDescription instance does not match the expected parameters.
+   */
+  @Test
+  public void chaosGameDescriptionConstructorWithoutWeightedNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    Vector2D minCoords1 = new Vector2D(1, 1);
+    Vector2D maxCoords1 = new Vector2D(101, 101);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(1, 1, 1, 1), new Vector2D(1, 1)));
+    List<Transform2D> transforms1 = new ArrayList<>();
+    transforms1.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription
+        chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    assert (transforms1 != chaosGameDescription.getTransforms());
+    assert (minCoords1 != chaosGameDescription.getMinCoords());
+    assert (maxCoords1 != chaosGameDescription.getMaxCoords());
+  }
+
+  /**
+   * Tests the ChaosGameDescription constructor with weighted transforms with negative input.
+   * Verifies that the created ChaosGameDescription instance does not match the expected parameters.
+   */
+  @Test
+  public void chaosGameDescriptionConstructorWithWeightedNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    Vector2D minCoords1 = new Vector2D(1, 1);
+    Vector2D maxCoords1 = new Vector2D(101, 101);
+    List<Pair<Transform2D, Double>> weightedTransforms = new ArrayList<>();
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0, 0, 0, 0.16), new Vector2D(0, 0)), 0.01));
+    List<Pair<Transform2D, Double>> weightedTransforms1 = new ArrayList<>();
+    weightedTransforms1.add(new Pair<>(new AffineTransform2D(new Matrix2x2(1, 1, 1, 1), new Vector2D(1, 1)), 0.02));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(weightedTransforms, minCoords, maxCoords, true);
+    assert (weightedTransforms1 != chaosGameDescription.getWeightedTransforms());
+    assert (weightedTransforms1.stream().map(Pair::getKey).collect(java.util.stream.Collectors.toList()) != chaosGameDescription.getTransforms());
+    assert (minCoords1 != chaosGameDescription.getMinCoords());
+    assert (maxCoords1 != chaosGameDescription.getMaxCoords());
+  }
+
+  /**
+   * Tests the getTransforms() method with negative input.
+   * Verifies that the returned list of transforms does not match the expected value.
+   */
+  @Test
+  public void getTransformsNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    Vector2D minCoords1 = new Vector2D(1, 1);
+    Vector2D maxCoords1 = new Vector2D(101, 101);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    List<Transform2D> transforms1 = new ArrayList<>();
+    transforms1.add(new AffineTransform2D(new Matrix2x2(1, 1, 1, 1), new Vector2D(1, 1)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    assert (transforms1 != chaosGameDescription.getTransforms());
+  }
+
+  /**
+   * Tests the getWeightedTransforms() method with negative input.
+   * Verifies that the returned list of weighted transforms does not match the expected value.
+   */
+  @Test
+  public void getWeightedTransformsTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    Vector2D minCoords1 = new Vector2D(1, 1);
+    Vector2D maxCoords1 = new Vector2D(101, 101);
+    List<Pair<Transform2D, Double>> weightedTransforms = new ArrayList<>();
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0, 0, 0, 0.16), new Vector2D(0, 0)), 0.01));
+    List<Pair<Transform2D, Double>> weightedTransforms1 = new ArrayList<>();
+    weightedTransforms1.add(new Pair<>(new AffineTransform2D(new Matrix2x2(1, 1, 1, 1), new Vector2D(1, 1)), 0.02));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(weightedTransforms, minCoords, maxCoords, true);
+    assert (weightedTransforms1 != chaosGameDescription.getWeightedTransforms());
+  }
+
+  /**
+   * Tests the setTransforms() method with negative input.
+   * Verifies that the set transforms do not match the expected value.
+   */
+  @Test
+  public void getMinCoordsNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D minCoords1 = new Vector2D(1, 1);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), minCoords, new Vector2D(100, 100));
+    assert (minCoords1 != chaosGameDescription.getMinCoords());
+  }
+
+  /**
+   * Tests the getMaxCoords() method with negative input.
+   * Verifies that the returned maxCoords does not match the expected value.
+   */
+  @Test
+  public void getMaxCoordsNegativeTest() {
+    Vector2D maxCoords = new Vector2D(100, 100);
+    Vector2D maxCoords1 = new Vector2D(101, 101);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), maxCoords);
+    assert (maxCoords1 != chaosGameDescription.getMaxCoords());
+  }
+
+  /**
+   * Tests the isWeighted() method with negative input.
+   * Verifies that the returned value does not match the expected value.
+   */
+  @Test
+  public void isWeightedNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), minCoords, maxCoords);
+    assert (!chaosGameDescription.isWeighted());
+  }
+
+  /**
+   * Tests the setMinCoords() method with negative input.
+   * Verifies that the set minCoords do not match the expected value.
+   */
+  @Test
+  public void setMinCoordsNegativeTest1() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D minCoords1 = new Vector2D(1, 1);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), minCoords, new Vector2D(100, 100));
+    chaosGameDescription.setMinCoords(minCoords1);
+    assert (minCoords != chaosGameDescription.getMinCoords());
+  }
+
+  /**
+   * Tests the setMinCoords() method with negative input.
+   * Verifies that the set minCoords do not match the expected value.
+   */
+  @Test
+  public void setMinCoordsNegativeTest2() {
+    assertThrows(IllegalArgumentException.class, () -> {
+      ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+      chaosGameDescription.setMinCoords(null);
+    });
+  }
+
+  /**
+   * Tests the setMinCoords() method with negative input.
+   * Verifies that the set minCoords do not match the expected value.
+   */
+  @Test
+  public void setMinCoordsNegativeTest3() {
+    assertThrows(IllegalArgumentException.class, () -> {
+      ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+      chaosGameDescription.setMinCoords(new Vector2D(100, 100));
+    });
+  }
+
+  /**
+   * Tests the setMaxCoords() method with negative input.
+   * Verifies that the set maxCoords do not match the expected value.
+   */
+  @Test
+  public void setMaxCoordsNegativeTest1() {
+    Vector2D maxCoords = new Vector2D(100, 100);
+    Vector2D maxCoords1 = new Vector2D(101, 101);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), maxCoords);
+    chaosGameDescription.setMaxCoords(maxCoords1);
+    assert (maxCoords != chaosGameDescription.getMaxCoords());
+  }
+
+  /**
+   * Tests the setMaxCoords() method with negative input.
+   * Verifies that the set maxCoords do not match the expected value.
+   */
+  @Test
+  public void setMaxCoordsNegativeTest2() {
+    assertThrows(IllegalArgumentException.class, () -> {
+      ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+      chaosGameDescription.setMaxCoords(null);
+    });
+  }
+
+  /**
+   * Tests the setMaxCoords() method with negative input.
+   * Verifies that the set maxCoords do not match the expected value.
+   */
+  @Test
+  public void setMaxCoordsNegativeTest3() {
+    assertThrows(IllegalArgumentException.class, () -> {
+      ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+      chaosGameDescription.setMaxCoords(new Vector2D(0, 0));
+    });
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionPositiveTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3348da76846036cf38a631c6314e71bc692cf36
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameDescriptionPositiveTest.java
@@ -0,0 +1,116 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import java.util.ArrayList;
+import java.util.List;
+import javafx.util.Pair;
+import org.junit.jupiter.api.Test;
+
+public class ChaosGameDescriptionPositiveTest {
+
+  @Test
+  public void chaosGameDescriptionConstructorWithoutWeightedPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2( 0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription
+        chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    assertEquals(transforms, chaosGameDescription.getTransforms());
+    assertEquals(minCoords, chaosGameDescription.getMinCoords());
+    assertEquals(maxCoords, chaosGameDescription.getMaxCoords());
+  }
+
+  @Test
+  public void chaosGameDescriptionConstructorWithWeightedPositiveTest() {
+    List<Pair<Transform2D, Double>> weightedTransforms = new ArrayList<>();
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0, 0, 0, 0.16), new Vector2D(0, 0)), 0.01));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0.85, 0.04, -0.04, 0.85), new Vector2D(0, 1.6)), 0.85));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0.2, -0.26, 0.23, 0.22), new Vector2D(0, 1.6)), 0.07));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(-0.15, 0.28, 0.26, 0.24), new Vector2D(0, 0.44)), 0.07));
+    Vector2D minCoords = new Vector2D(-3, 0);
+    Vector2D maxCoords = new Vector2D(3, 10);
+    ChaosGameDescription barnsleyFern = new ChaosGameDescription(weightedTransforms, minCoords, maxCoords, true);
+    assertEquals(weightedTransforms, barnsleyFern.getWeightedTransforms());
+    assertEquals(weightedTransforms.stream().map(Pair::getKey).collect(java.util.stream.Collectors.toList()), barnsleyFern.getTransforms());
+    assertEquals(minCoords, barnsleyFern.getMinCoords());
+    assertEquals(maxCoords, barnsleyFern.getMaxCoords());
+  }
+
+  @Test
+  public void getTransformsPositiveTest() {
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, new Vector2D(0, 0), new Vector2D(100, 100));
+    assertEquals(transforms, chaosGameDescription.getTransforms());
+  }
+
+  @Test
+  public void getWeightedTransformsPositiveTest() {
+    List<Pair<Transform2D, Double>> weightedTransforms = new ArrayList<>();
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0, 0, 0, 0.16), new Vector2D(0, 0)), 0.01));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0.85, 0.04, -0.04, 0.85), new Vector2D(0, 1.6)), 0.85));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(0.2, -0.26, 0.23, 0.22), new Vector2D(0, 1.6)), 0.07));
+    weightedTransforms.add(new Pair<>(new AffineTransform2D(new Matrix2x2(-0.15, 0.28, 0.26, 0.24), new Vector2D(0, 0.44)), 0.07));
+    ChaosGameDescription barnsleyFern = new ChaosGameDescription(weightedTransforms, new Vector2D(-3, 0), new Vector2D(3, 10), true);
+    assertEquals(weightedTransforms, barnsleyFern.getWeightedTransforms());
+  }
+
+  @Test
+  public void getMinCoordsPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), minCoords, new Vector2D(100, 100));
+    assertEquals(minCoords, chaosGameDescription.getMinCoords());
+  }
+
+  @Test
+  public void getMaxCoordsPositiveTest() {
+    Vector2D maxCoords = new Vector2D(100, 100);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), maxCoords);
+    assertEquals(maxCoords, chaosGameDescription.getMaxCoords());
+  }
+
+  @Test
+  public void isWeightedPositiveTest() {
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+    assertEquals(false, chaosGameDescription.isWeighted());
+  }
+
+  @Test
+  public void setMinCoordsPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+    chaosGameDescription.setMinCoords(minCoords);
+    assertEquals(minCoords, chaosGameDescription.getMinCoords());
+  }
+
+  @Test
+  public void setMaxCoordsPositiveTest() {
+    Vector2D maxCoords = new Vector2D(100, 100);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), new Vector2D(100, 100));
+    chaosGameDescription.setMaxCoords(maxCoords);
+    assertEquals(maxCoords, chaosGameDescription.getMaxCoords());
+  }
+
+  @Test
+  public void getInitialMinCoordsPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), minCoords, new Vector2D(100, 100));
+    assertEquals(minCoords.getX0(), chaosGameDescription.getInitialMinCoords().getX0());
+    assertEquals(minCoords.getX1(), chaosGameDescription.getInitialMinCoords().getX1());
+  }
+
+  @Test
+  public void getInitialMaxCoordsPositiveTest() {
+    Vector2D maxCoords = new Vector2D(100, 100);
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(new ArrayList<>(), new Vector2D(0, 0), maxCoords);
+    assertEquals(maxCoords.getX0(), chaosGameDescription.getInitialMaxCoords().getX0());
+    assertEquals(maxCoords.getX1(), chaosGameDescription.getInitialMaxCoords().getX1());
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameFileHandlerNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameFileHandlerNegativeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..200844c5b1b4af9878148d02f333e64609403a97
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameFileHandlerNegativeTest.java
@@ -0,0 +1,46 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameFileHandler;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGameFileHandlerNegativeTest class contains negative test cases
+ * for the ChaosGameFileHandler class.
+ */
+public class ChaosGameFileHandlerNegativeTest {
+
+  /**
+   * Tests the readTransformationsFromNFile method with negative input.
+   */
+  @Test
+  public void readTransformationsFromNFileNegativeTest() {
+    String fileName = "nonExistentFile.txt";
+    assertThrows(FileNotFoundException.class, () -> {
+      ChaosGameFileHandler.readTransformationsFromFile(fileName);
+    });
+  }
+
+  /**
+   * Tests the writeTransformationsToInvalidPath method with negative input.
+   */
+  @Test
+  public void writeTransformationsToInvalidPathTest() {
+    String invalidPath = "/invalid/path/testTransformations.txt";
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.0, 0.0, 0.5), new Vector2D(1.0, 1.0)));
+
+    assertThrows(Exception.class, () -> {
+      ChaosGameFileHandler.writeTransformationsToFile(new ChaosGameDescription(transforms, new Vector2D(0, 0), new Vector2D(1, 1)), invalidPath);
+    });
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameFileHandlerPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameFileHandlerPositiveTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a187892c52d853ecf25c3e3e3bc34e6b3cfc3ef
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameFileHandlerPositiveTest.java
@@ -0,0 +1,114 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameFileHandler;
+import java.io.File;
+import java.util.List;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGameFileHandlerPositiveTest class contains positive test cases for the ChaosGameFileHandler class.
+ */
+public class ChaosGameFileHandlerPositiveTest {
+
+  /**
+   * Tests the writeTransformationsToFile method with positive input.
+   *
+   * @throws Exception if an error occurs during the test.
+   */
+  @Test
+  public void writeTransformationsFromFilePositiveTest() throws Exception {
+    String fileName = "testTransformations.txt";
+    List<Transform2D> transforms = List.of(
+        new AffineTransform2D(new Matrix2x2(0.5, 0.0, 0.0, 0.5), new Vector2D(1.0, 1.0))
+    );
+    Vector2D minCoords = new Vector2D(0.0, 0.0);
+    Vector2D maxCoords = new Vector2D(1.0, 1.0);
+    ChaosGameDescription description = new ChaosGameDescription(transforms, minCoords, maxCoords);
+
+    ChaosGameFileHandler.writeTransformationsToFile(description, fileName);
+    ChaosGameDescription actualDescription = ChaosGameFileHandler.readTransformationsFromFile(fileName);
+    assertEquals(description.getTransforms().size(), actualDescription.getTransforms().size());
+    assert (description.getTransforms().size() == actualDescription.getTransforms().size());
+    assert (description.getMinCoords().getX0() == actualDescription.getMinCoords().getX0());
+    assert (description.getMinCoords().getX1() == actualDescription.getMinCoords().getX1());
+    assert (description.getMaxCoords().getX0() == actualDescription.getMaxCoords().getX0());
+  }
+
+  /**
+   * Tests the readTransformationsFromFile method with positive input.
+   *
+   * @throws Exception if an error occurs during the test.
+   */
+  @Test
+  public void readTransformationsFromFilePositiveTest() throws Exception {
+    String fileName = "testTransformations.txt";
+    List<Transform2D> transforms = List.of(
+        new AffineTransform2D(new Matrix2x2(0.5, 0.0, 0.0, 0.5), new Vector2D(1.0, 1.0))
+    );
+    Vector2D minCoords = new Vector2D(0.0, 0.0);
+    Vector2D maxCoords = new Vector2D(1.0, 1.0);
+    ChaosGameDescription expectedDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGameFileHandler.writeTransformationsToFile(expectedDescription, fileName);
+    ChaosGameDescription actualDescription = ChaosGameFileHandler.readTransformationsFromFile(fileName);
+    assert (expectedDescription.getTransforms().size() == actualDescription.getTransforms().size());
+    assert (expectedDescription.getMinCoords().getX0() == actualDescription.getMinCoords().getX0());
+    assert (expectedDescription.getMinCoords().getX1() == actualDescription.getMinCoords().getX1());
+    assert (expectedDescription.getMaxCoords().getX0() == actualDescription.getMaxCoords().getX0());
+    assert (expectedDescription.getMaxCoords().getX1() == actualDescription.getMaxCoords().getX1());
+  }
+
+  /**
+   * Tests the listTransformationFileNames method with positive input.
+   *
+   * @throws Exception if an error occurs during the test.
+   */
+  @Test
+  public void listTransformationFileNamesPositiveTest() throws Exception {
+    String fileName = "testTransformations.txt";
+    List<Transform2D> transforms = List.of(
+        new AffineTransform2D(new Matrix2x2(0.5, 0.0, 0.0, 0.5), new Vector2D(1.0, 1.0))
+    );
+    Vector2D minCoords = new Vector2D(0.0, 0.0);
+    Vector2D maxCoords = new Vector2D(1.0, 1.0);
+    ChaosGameDescription description = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGameFileHandler.writeTransformationsToFile(description, fileName);
+    List<String> fileNames = ChaosGameFileHandler.listTransformationFileNames();
+    assertTrue(fileNames.contains(fileName));
+  }
+
+  /**
+   * Tests the checkFileExists method with positive input.
+   *
+   * @throws Exception if an error occurs during the test.
+   */
+  @Test
+  public void checkFileExistsPositiveTest() throws Exception {
+    String fileName = "testTransformations.txt";
+    List<Transform2D> transforms = List.of(
+        new AffineTransform2D(new Matrix2x2(0.5, 0.0, 0.0, 0.5), new Vector2D(1.0, 1.0))
+    );
+    Vector2D minCoords = new Vector2D(0.0, 0.0);
+    Vector2D maxCoords = new Vector2D(1.0, 1.0);
+    ChaosGameDescription description = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGameFileHandler.writeTransformationsToFile(description, fileName);
+    assertTrue(ChaosGameFileHandler.checkFileExists(fileName));
+  }
+
+  /**
+   * Cleans up any resources or files created during the test.
+   * Deletes the testTransformations.txt file created during the test execution.
+   */
+  @AfterEach
+  public void cleanup() {
+    new File("src/main/resources/edu/ntnu/idatt2003/mappevurderingprog2/transformations/testTransformations.txt").delete(); // Clean up file after test
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameNegativeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..670b8724530fdf04b4f0434c786dc6ef5ea68f63
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGameNegativeTest.java
@@ -0,0 +1,160 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.CanvasController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGame;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGameNegativeTest class contains negative test cases for the ChaosGame class.
+ */
+public class ChaosGameNegativeTest {
+
+  /**
+   * Resets the ChaosGame instance before each test.
+   */
+  @BeforeEach
+  public void tearDown() {
+    ChaosGame.getInstance().setDescription(null);
+    ChaosGame.getInstance().resetOutOfBoundsCount();
+    ChaosGame.getInstance().getObservers().clear();
+    ChaosGame.getInstance().setSteps(0);
+  }
+
+  /**
+   * Tests setting a description that is not allowed.
+   * Verifies that the description is not the same
+   * as the one that was attempted to be set.
+   */
+  @Test
+  public void setDescriptionNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    Vector2D minCoords2 = new Vector2D(0, 0);
+    Vector2D maxCoords2 = new Vector2D(100, 100);
+    List<Transform2D> transforms2 = new ArrayList<>();
+    transforms2.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription2 = new ChaosGameDescription(transforms2, minCoords2, maxCoords2);
+    ChaosGame.getInstance().setDescription(chaosGameDescription);
+    ChaosGame.getInstance().setDescription(chaosGameDescription2);
+    assert ChaosGame.getInstance().getDescription() != chaosGameDescription;
+  }
+
+  /**
+   * Tests setting a canvas that is not allowed.
+   * Verifies that an IllegalStateException is thrown.
+   */
+  @Test
+  public void setCanvasNegativeTest() {
+    ChaosGame.getInstance().setDescription(null);
+    assertThrows(IllegalStateException.class, () -> ChaosGame.getInstance().setCanvas());
+  }
+
+  /**
+   * Tests setting steps that are not allowed.
+   * Verifies that an IllegalArgumentException is thrown.
+   */
+  @Test
+  public void setStepsNegativeTest() {
+    assertThrows(IllegalArgumentException.class, () -> ChaosGame.getInstance().setSteps(-1));
+  }
+
+  /**
+   * Tests adding an observer that is not allowed.
+   * Verifies that an IllegalArgumentException is thrown.
+   */
+  @Test
+  public void addObserverNegativeTest() {
+    assertThrows(IllegalArgumentException.class, () -> ChaosGame.getInstance().addObserver(null));
+  }
+
+  /**
+   * Tests removing an observer that is not allowed.
+   * Verifies that an IllegalArgumentException is thrown.
+   */
+  @Test
+  public void removeObserverNegativeTest() {
+    assertThrows(IllegalArgumentException.class, () -> ChaosGame.getInstance().removeObserver(null));
+  }
+
+  /**
+   * Tests notifying observers that is not allowed.
+   * Verifies that an IllegalStateException is thrown.
+   */
+  @Test
+  public void getCanvasNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGame.getInstance().setDescription(new ChaosGameDescription(transforms, minCoords, maxCoords));
+    ChaosGame.getInstance().setCanvas();
+    assert ChaosGame.getInstance().getCanvas() != null;
+  }
+
+  /**
+   * Tests getting a description that is not allowed.
+   * Verifies that the description is not the same
+   * as the one that was attempted to be set.
+   */
+  @Test
+  public void getDescriptionNegativeTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGame.getInstance().setDescription(new ChaosGameDescription(transforms, minCoords, maxCoords));
+    assert ChaosGame.getInstance().getDescription() != null;
+  }
+
+  /**
+   * Tests getting steps that are not allowed.
+   * Verifies that the steps are not the same as the ones that were set.
+   */
+  @Test
+  public void getStepsNegativeTest() {
+    ChaosGame.getInstance().setSteps(100);
+    assert ChaosGame.getInstance().getSteps() != 101;
+  }
+
+  /**
+   * Tests getting out of bounds count that is not allowed.
+   * Verifies that the out of bounds count is not the same as the one that was set.
+   */
+  @Test
+  public void getOutOfBoundsCountNegativeTest() {
+    assert ChaosGame.getInstance().getOutOfBoundsCount() != 1;
+  }
+
+  /**
+   * Tests getting observers that is not allowed.
+   * Verifies that the observers are not the same as the ones that were set.
+   */
+  @Test
+  public void getObserversNegativeTest() {
+    GameController gameController = new GameController();
+    CanvasController canvasController = new CanvasController();
+    FileController fileController = new FileController();
+    View view = new View(gameController, canvasController, fileController);
+    ChaosGame.getInstance().addObserver(view);
+    System.out.println(ChaosGame.getInstance().getObservers().size() + " observers");
+    assert ChaosGame.getInstance().getObservers().size() != 0;
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGamePositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGamePositiveTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..848c7bdae7bbc980edecd732080e3fb700b5bc31
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ChaosGamePositiveTest.java
@@ -0,0 +1,187 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.CanvasController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.FileController;
+import edu.ntnu.idatt2003.mappevurderingprog2.controllers.GameController;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.AffineTransform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Transform2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGame;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.chaos.ChaosGameDescription;
+import edu.ntnu.idatt2003.mappevurderingprog2.views.View;
+
+import java.awt.Canvas;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The ChaosGamePositiveTest class contains positive test cases for the ChaosGame class.
+ */
+public class ChaosGamePositiveTest {
+
+  /**
+   * Resets the ChaosGame instance before each test.
+   */
+  @BeforeEach
+  public void tearDown() {
+    ChaosGame.getInstance().setDescription(null);
+    ChaosGame.getInstance().resetOutOfBoundsCount();
+    ChaosGame.getInstance().getObservers().clear();
+    ChaosGame.getInstance().setSteps(0);
+  }
+
+  /**
+   * Tests the getInstance method with valid input.
+   * Verifies that the ChaosGame instance is not null.
+   */
+  @Test
+  public void chaosGameGetInstancePositiveTest() {
+    ChaosGame chaosGame = ChaosGame.getInstance();
+    assertNotNull(chaosGame);
+  }
+
+  /**
+   * Tests setting a description that is allowed.
+   * Verifies that the description is the same
+   * as the one that was attempted to be set.
+   */
+  @Test
+  public void chaosGameSetDescriptionPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGame.getInstance().setDescription(chaosGameDescription);
+    assertEquals(chaosGameDescription, ChaosGame.getInstance().getDescription());
+  }
+
+  /**
+   * Tests setting a canvas that is allowed.
+   * Verifies that the canvas is not null.
+   */
+  @Test
+  public void setCanvasPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGame.getInstance().setDescription(chaosGameDescription);
+    ChaosGame.getInstance().setCanvas();
+    assertNotNull(ChaosGame.getInstance().getCanvas());
+  }
+
+  /**
+   * Tests setting steps that are allowed.
+   * Verifies that the steps are the same
+   * as the ones that were attempted to be set.
+   */
+  @Test
+  public void setStepsPositiveTest() {
+    ChaosGame.getInstance().setSteps(100);
+    assertEquals(100, ChaosGame.getInstance().getSteps());
+  }
+
+  /**
+   * Tests adding an observer that is allowed.
+   * Verifies that the observer list size is 1.
+   */
+  @Test
+  public void addObserverPositiveTest() {
+    GameController gameController = new GameController();
+    CanvasController canvasController = new CanvasController();
+    FileController fileController = new FileController();
+    View view = new View(gameController, canvasController, fileController);
+    ChaosGame.getInstance().addObserver(view);
+    assertEquals(1, ChaosGame.getInstance().getObservers().size());
+  }
+
+  /**
+   * Tests removing an observer that is allowed.
+   * Verifies that the observer list size is 0.
+   */
+  @Test
+  public void removeObserverPositiveTest() {
+    GameController gameController = new GameController();
+    CanvasController canvasController = new CanvasController();
+    FileController fileController = new FileController();
+    View view = new View(gameController, canvasController, fileController);
+    ChaosGame.getInstance().addObserver(view);
+    ChaosGame.getInstance().removeObserver(view);
+    assertEquals(0, ChaosGame.getInstance().getObservers().size());
+  }
+
+  /**
+   * Tests resetting the out of bounds count that is allowed.
+   * Verifies that the out of bounds count is 0.
+   */
+  @Test
+  public void getCanvasPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGame.getInstance().setDescription(chaosGameDescription);
+    ChaosGame.getInstance().setCanvas();
+    assertNotNull(ChaosGame.getInstance().getCanvas());
+  }
+
+  /**
+   * Tests getting a description that is allowed.
+   * Verifies that the description is the same
+   * as the one that was attempted to be set.
+   */
+  @Test
+  public void getDescriptionPositiveTest() {
+    Vector2D minCoords = new Vector2D(0, 0);
+    Vector2D maxCoords = new Vector2D(100, 100);
+    List<Transform2D> transforms = new ArrayList<>();
+    transforms.add(new AffineTransform2D(new Matrix2x2(0.5, 0.5, 0.5, 0.5), new Vector2D(0, 0)));
+    ChaosGameDescription chaosGameDescription = new ChaosGameDescription(transforms, minCoords, maxCoords);
+    ChaosGame.getInstance().setDescription(chaosGameDescription);
+    assertEquals(chaosGameDescription, ChaosGame.getInstance().getDescription());
+  }
+
+  /**
+   * Tests getting steps that are allowed.
+   * Verifies that the steps are the same as the ones that were set.
+   */
+  @Test
+  public void getStepsPositiveTest() {
+    ChaosGame.getInstance().setSteps(100);
+    assertEquals(100, ChaosGame.getInstance().getSteps());
+  }
+
+  /**
+   * Tests getting out of bounds count that is allowed.
+   * Verifies that the out of bounds count is 0.
+   */
+  @Test
+  public void getOutOfBoundsCountPositiveTest() {
+    assertEquals(0, ChaosGame.getInstance().getOutOfBoundsCount());
+  }
+
+  /**
+   * Tests getting observers that is allowed.
+   * Verifies that the observers list size is 1.
+   */
+  @Test
+  public void getObserversPositiveTest() {
+    GameController gameController = new GameController();
+    CanvasController canvasController = new CanvasController();
+    FileController fileController = new FileController();
+    View view = new View(gameController, canvasController, fileController);
+    ChaosGame.getInstance().addObserver(view);
+    assertEquals(1, ChaosGame.getInstance().getObservers().size());
+  }
+}
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexNegativeTest.java
index 4c4d9b2793d210daa01ea0cbac8b24d96b935f7d..52b89647bd40329211c3fded172de122dc548148 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexNegativeTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexNegativeTest.java
@@ -1,28 +1,106 @@
 package edu.ntnu.idatt2003.mappevurderingprog2;
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The ComplexNegativeTest class contains negative test cases for the Complex class.
+ */
 public class ComplexNegativeTest {
 
+  /**
+   * Tests the complexConstructor method of Complex with negative input.
+   * Verifies that the real part of the complex number is not 2.
+   */
   @Test
   public void complexConstructorNegativeTest() {
     Complex complex1 = new Complex(1, 1);
     assert complex1.getRealPart() != 2;
   }
 
+  /**
+   * Tests the getRealPart method of Complex with negative input.
+   * Verifies that the real part of the complex number is not 1.
+   */
   @Test
   public void getRealPartNegativeTest() {
     Complex complex1 = new Complex(2, 1);
     assert complex1.getRealPart() != 1;
   }
 
+  /**
+   * Tests the getImaginaryPart method of Complex with negative input.
+   * Verifies that the imaginary part of the complex number is not 2.
+   */
   @Test
   public void getImaginaryPartNegativeTest() {
     Complex complex1 = new Complex(1, 2);
     assert complex1.getImaginaryPart() != 1;
   }
 
+  /**
+   * Tests the add method of Complex with negative input.
+   * Verifies that the real part of the result is not 2.
+   */
+  @Test
+  public void addNegativeTest() {
+    Complex complex1 = new Complex(1, 1);
+    Complex complex2 = new Complex(1, 1);
+    Complex result = complex1.add(complex2);
+    assert result.getRealPart() != 1;
+    assert result.getImaginaryPart() != 1;
+  }
+
+  /**
+   * Tests the add method of Complex with negative input.
+   * Verifies that an IllegalArgumentException is
+   * thrown when adding a Complex number and a Vector2D.
+   */
+  @Test
+  public void addNegativeTest2() {
+    Complex complex = new Complex(1, 2);
+    Vector2D vector = new Vector2D(3, 4);
+
+    assertThrows(IllegalArgumentException.class, () -> {
+      complex.add(vector);
+    });
+  }
+
+  /**
+   * Tests the subtract method of Complex with negative input.
+   * Verifies that the real part of the result is not 1.
+   */
+  @Test
+  public void subtractNegativeTest() {
+    Complex complex1 = new Complex(1, 1);
+    Complex complex2 = new Complex(1, 1);
+    Complex result = complex1.subtract(complex2);
+    assert result.getRealPart() != 1;
+    assert result.getImaginaryPart() != 1;
+  }
+
+  /**
+   * Tests the subtract method of Complex with negative input.
+   * Verifies that an IllegalArgumentException is
+   * thrown when subtracting a Complex number and a Vector2D.
+   */
+  @Test
+  public void subtractNegativeTest2() {
+    Complex complex = new Complex(1, 2);
+    Vector2D vector = new Vector2D(3, 4);
+
+    assertThrows(IllegalArgumentException.class, () -> {
+      complex.subtract(vector);
+    });
+  }
+
+  /**
+   * Tests the multiply method of Complex with negative input.
+   * Verifies that the real part of the result is not 2.
+   */
   @Test
   public void sqrtNegativeTest() {
     Complex complex1 = new Complex(4, 4);
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexPositiveTest.java
index 8d96fb0039279bb45770d5a8d774084dd18399ca..47e6e04b62fdf87e336d23089b1b7e41e8d759af 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexPositiveTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/ComplexPositiveTest.java
@@ -1,32 +1,82 @@
 package edu.ntnu.idatt2003.mappevurderingprog2;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The ComplexPositiveTest class contains positive test cases for the Complex class.
+ */
 public class ComplexPositiveTest {
 
+  /**
+   * Tests the complexConstructor method of Complex with positive input.
+   * Verifies that the real part of the complex number is 1.
+   */
   @Test
   public void complexConstructorPositiveTest() {
     Complex complex1 = new Complex(1, 1);
-    System.out.println(complex1.getRealPart() + " " + complex1.getImaginaryPart());
+    assertEquals(1, complex1.getRealPart());
+    assertEquals(1, complex1.getImaginaryPart());
   }
 
+  /**
+   * Tests the getRealPart method of Complex with positive input.
+   * Verifies that the real part of the complex number is 2.
+   */
   @Test
   public void getRealPartPositiveTest() {
     Complex complex1 = new Complex(2, 1);
-    System.out.println(complex1.getRealPart());
+    assertEquals(2, complex1.getRealPart());
   }
 
+  /**
+   * Tests the getImaginaryPart method of Complex with positive input.
+   * Verifies that the imaginary part of the complex number is 2.
+   */
   @Test
   public void getImaginaryPartPositiveTest() {
     Complex complex1 = new Complex(1, 2);
-    System.out.println(complex1.getImaginaryPart());
+    assertEquals(2, complex1.getImaginaryPart());
+
+  }
+
+  /**
+   * Tests the add method of Complex with positive input.
+   * Verifies that the real part of the result is 3.
+   */
+  @Test
+  public void addPositiveTest() {
+    Complex complex1 = new Complex(1, 1);
+    Complex complex2 = new Complex(2, 2);
+    Complex result = complex1.add(complex2);
+    assertEquals(3, result.getRealPart());
+    assertEquals(3, result.getImaginaryPart());
+  }
+
+  /**
+   * Tests the subtract method of Complex with positive input.
+   * Verifies that the real part of the result is 1.
+   */
+  @Test
+  public void subtractPositiveTest() {
+    Complex complex1 = new Complex(2, 2);
+    Complex complex2 = new Complex(1, 1);
+    Complex result = complex1.subtract(complex2);
+    assertEquals(1, result.getRealPart());
+    assertEquals(1, result.getImaginaryPart());
   }
 
+  /**
+   * Tests the multiply method of Complex with positive input.
+   * Verifies that the real part of the result is 0.
+   */
   @Test
   public void sqrtPositiveTest() {
     Complex complex1 = new Complex(4, 4);
     Complex result = complex1.sqrt();
-    System.out.println(result.getRealPart() + " " + result.getImaginaryPart());
+    assertEquals(2.1973682269356196, result.getRealPart());
+    assertEquals(0.9101797211244547, result.getImaginaryPart());
   }
 }
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformNegativeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4b9955a6471bc452772931e26063590dfd413ca
--- /dev/null
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformNegativeTest.java
@@ -0,0 +1,123 @@
+package edu.ntnu.idatt2003.mappevurderingprog2;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.JuliaTransform;
+import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The JuliaTransformNegativeTest class contains negative test cases for the JuliaTransform class.
+ */
+public class JuliaTransformNegativeTest {
+
+  /**
+   * Tests the juliaTransformConstructor method of JuliaTransform with negative input.
+   * Verifies that an IllegalArgumentException is thrown when the point is null.
+   */
+  @Test
+  public void juliaTransformConstructorNegativeTest() {
+    int i = 1;
+    assertThrows(IllegalArgumentException.class, () -> {
+      JuliaTransform juliaTransform = new JuliaTransform(null, i);
+    });
+  }
+
+  /**
+   * Tests the juliaTransformConstructor method of JuliaTransform with negative input.
+   * Verifies that the point and sign are not null and 0 respectively.
+   */
+  @Test
+  public void juliaTransformConstructorNegativeTest2() {
+    Complex complex = new Complex(1, 1);
+    int i = 1;
+    JuliaTransform juliaTransform = new JuliaTransform(complex, i);
+    assert juliaTransform.getPoint() != null;
+    assert juliaTransform.getSign() != 0;
+  }
+
+  /**
+   * Tests the getPoint method of JuliaTransform with negative input.
+   * Verifies that the real and imaginary part of the point are not 2.
+   */
+  @Test
+  public void getPointNegativeTest() {
+    Complex complex = new Complex(1, 1);
+    int i = 1;
+    JuliaTransform juliaTransform = new JuliaTransform(complex, i);
+    assert juliaTransform.getPoint().getRealPart() != 2;
+    assert juliaTransform.getPoint().getImaginaryPart() != 2;
+  }
+
+  /**
+   * Tests the getSign method of JuliaTransform with negative input.
+   * Verifies that the sign is not 2.
+   */
+  @Test
+  public void getSignNegativeTest() {
+    Complex complex = new Complex(1, 1);
+    int i = 1;
+    JuliaTransform juliaTransform = new JuliaTransform(complex, i);
+    assert juliaTransform.getSign() != 2;
+  }
+
+  /**
+   * Tests the transform method of JuliaTransform with negative input.
+   * Verifies that an IllegalArgumentException is thrown when the complex number to transform is null.
+   */
+  @Test
+  public void transformNegativeTest1() {
+    Complex juliaPoint = new Complex(0.5, -0.5);
+    int sign = 1;
+    JuliaTransform juliaTransform = new JuliaTransform(juliaPoint, sign);
+    assertThrows(IllegalArgumentException.class, () -> {
+      juliaTransform.transform(null);
+    });
+  }
+
+  /**
+   * Tests the transform method of JuliaTransform with negative input.
+   * Verifies that an IllegalArgumentException is thrown when the complex number to transform is a Vector2D.
+   */
+  @Test
+  public void transformNegativeTest2() {
+    Vector2D complexToTransform = new Vector2D(1, 1);
+    Complex juliaPoint = new Complex(0.5, -0.5);
+    int sign = 1;
+    JuliaTransform juliaTransform = new JuliaTransform(juliaPoint, sign);
+    assertThrows(IllegalArgumentException.class, () -> {
+      juliaTransform.transform(complexToTransform);
+    });
+  }
+
+  /**
+   * Tests the transform method of JuliaTransform with negative input.
+   * Verifies that the transformed complex number is not the same as the input complex number.
+   */
+  @Test
+  public void transformNegativeTest3() {
+    Complex complexToTransform = new Complex(1, 1);
+    Complex juliaPoint = new Complex(0.5, -0.5);
+    int sign = 1;
+    JuliaTransform juliaTransform = new JuliaTransform(juliaPoint, sign);
+    Vector2D result = juliaTransform.transform(complexToTransform);
+    assert result.getX0() != 1;
+    assert result.getX1() != 1;
+  }
+
+  /**
+   * Tests the transform method of JuliaTransform with negative input.
+   * Verifies that the transformed complex number is not the same as the input complex number.
+   */
+  @Test
+  public void transformNegativeTest4() {
+    Complex complexToTransform = new Complex(1, 1);
+    Complex juliaPoint = new Complex(0.5, -0.5);
+    int sign = -1;
+    JuliaTransform juliaTransform = new JuliaTransform(juliaPoint, sign);
+    Vector2D result = juliaTransform.transform(complexToTransform);
+    assert result.getX0() != 1;
+    assert result.getX1() != 1;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformPositiveTest.java
index 268f8efc13e85f1e00bb1e2c472ec4905584a36e..07f0d63a3cb6259abb60f3d7e520a941d4018e9d 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformPositiveTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/JuliaTransformPositiveTest.java
@@ -3,11 +3,19 @@ package edu.ntnu.idatt2003.mappevurderingprog2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Complex;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.JuliaTransform;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
-import java.util.Vector;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+
+/**
+ * The JuliaTransformPositiveTest class contains positive test cases
+ * for the JuliaTransform class.
+ */
 public class JuliaTransformPositiveTest {
 
+  /**
+   * Tests the juliaTransformConstructor method of JuliaTransform with positive input.
+   * Verifies that the point and sign are not null and 0 respectively.
+   */
   @Test
   public void juliaTransformConstructorPositiveTest() {
     Complex complex = new Complex(1, 1);
@@ -16,6 +24,10 @@ public class JuliaTransformPositiveTest {
     System.out.println(juliaTransform.getPoint().getRealPart() + " " + juliaTransform.getPoint().getImaginaryPart() + " " + juliaTransform.getSign());
   }
 
+  /**
+   * Tests the getPoint method of JuliaTransform with positive input.
+   * Verifies that the real and imaginary part of the point are not 2.
+   */
   @Test
   public void getPointPositiveTest() {
     Complex complex = new Complex(1, 1);
@@ -24,6 +36,10 @@ public class JuliaTransformPositiveTest {
     System.out.println(juliaTransform.getPoint().getRealPart() + " " + juliaTransform.getPoint().getImaginaryPart());
   }
 
+  /**
+   * Tests the getSign method of JuliaTransform with positive input.
+   * Verifies that the sign is not 2.
+   */
   @Test
   public void getSignPositiveTest() {
     Complex complex = new Complex(1, 1);
@@ -32,6 +48,10 @@ public class JuliaTransformPositiveTest {
     System.out.println(juliaTransform.getSign());
   }
 
+  /**
+   * Tests the transform method of JuliaTransform with positive input.
+   * Verifies that the result is not null and an instance of Complex.
+   */
   @Test
   public void transformPositiveTest() {
     Complex complexToTransform = new Complex(1, 1);
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2NegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2NegativeTest.java
index 0fc2511683b657c19e848e9be8e56b67f8444a86..1b0e9b2750c82ca0db2474cacf89b82c5b819f23 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2NegativeTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2NegativeTest.java
@@ -4,7 +4,14 @@ import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The Matrix2x2NegativeTest class contains negative test cases for the Matrix2x2 class.
+ */
 public class Matrix2x2NegativeTest {
+  /**
+   * Tests the matrix2x2Constructor method of Matrix2x2 with negative input.
+   * Verifies that the matrix is not initialized with all 1's.
+   */
   @Test
   public void matrix2x2ConstructorNegativeTest() {
     Matrix2x2 matrix1 = new Matrix2x2(0, 0, 0, 0);
@@ -14,30 +21,50 @@ public class Matrix2x2NegativeTest {
     assert matrix1.getA11() != 1;
   }
 
+  /**
+   * Tests the matrix2x2GetA00 method of Matrix2x2 with negative input.
+   * Verifies that the value of a00 is not 0.
+   */
   @Test
   public void matrix2x2GetA00NegativeTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     assert matrix1.getA00() != 0;
   }
 
+  /**
+   * Tests the matrix2x2GetA01 method of Matrix2x2 with negative input.
+   * Verifies that the value of a01 is not 0.
+   */
   @Test
   public void matrix2x2GetA01NegativeTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     assert matrix1.getA01() != 0;
   }
 
+  /**
+   * Tests the matrix2x2GetA10 method of Matrix2x2 with negative input.
+   * Verifies that the value of a10 is not 0.
+   */
   @Test
   public void matrix2x2GetA10NegativeTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     assert matrix1.getA10() != 0;
   }
 
+  /**
+   * Tests the matrix2x2GetA11 method of Matrix2x2 with negative input.
+   * Verifies that the value of a11 is not 0.
+   */
   @Test
   public void matrix2x2GetA11NegativeTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     assert matrix1.getA11() != 0;
   }
 
+  /**
+   * Tests the matrix2x2Multiply method of Matrix2x2 with negative input.
+   * Verifies that an IllegalArgumentException is thrown when the vector to multiply is null.
+   */
   @Test
   public void matrix2x2MultiplyNegativeTest() {
     try {
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2PositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2PositiveTest.java
index 109821c733e4a286a2f5376d2ea31f8ca497f535..d863635b10539fee4f5c9cd0769c449e10d31796 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2PositiveTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Matrix2x2PositiveTest.java
@@ -4,38 +4,65 @@ import edu.ntnu.idatt2003.mappevurderingprog2.models.Matrix2x2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The Matrix2x2NegativeTest class contains negative test cases for the Matrix2x2 class.
+ */
 public class Matrix2x2PositiveTest {
 
+  /**
+   * Tests the matrix2x2Constructor method of Matrix2x2 with positive input.
+   * Verifies that the matrix is initialized with all 1's.
+   */
   @Test
   public void matrix2x2ConstructorPositiveTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     System.out.println(matrix1.getA00() + " " + matrix1.getA01() + " " + matrix1.getA10() + " " + matrix1.getA11());
   }
 
+  /**
+   * Tests the matrix2x2GetA00 method of Matrix2x2 with positive input.
+   * Verifies that the value of a00 is 1.
+   */
   @Test
   public void matrix2x2GetA00PositiveTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     System.out.println(matrix1.getA00());
   }
 
+  /**
+   * Tests the matrix2x2GetA01 method of Matrix2x2 with positive input.
+   * Verifies that the value of a01 is 1.
+   */
   @Test
   public void matrix2x2GetA01PositiveTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     System.out.println(matrix1.getA01());
   }
 
+  /**
+   * Tests the matrix2x2GetA10 method of Matrix2x2 with positive input.
+   * Verifies that the value of a10 is 1.
+   */
   @Test
   public void matrix2x2GetA10PositiveTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     System.out.println(matrix1.getA10());
   }
 
+  /**
+   * Tests the matrix2x2GetA11 method of Matrix2x2 with positive input.
+   * Verifies that the value of a11 is 1.
+   */
   @Test
   public void matrix2x2GetA11PositiveTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
     System.out.println(matrix1.getA11());
   }
 
+  /**
+   * Tests the matrix2x2Multiply method of Matrix2x2 with positive input.
+   * Verifies that the result is not null and an instance of Vector2D.
+   */
   @Test
   public void matrix2x2MultiplyPositiveTest() {
     Matrix2x2 matrix1 = new Matrix2x2(1, 1, 1, 1);
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DNegativeTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DNegativeTest.java
index dfe60b52bc7222f23fc360214f656ae6150b3ae4..45d913ab9b83127318e0b8d9a5a04ce38348650d 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DNegativeTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DNegativeTest.java
@@ -3,7 +3,15 @@ package edu.ntnu.idatt2003.mappevurderingprog2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The Vector2DNegativeTest class contains negative test cases for the Vector2D class.
+ */
 public class Vector2DNegativeTest {
+
+  /**
+   * Tests the vector2DConstructor method of Vector2D with negative input.
+   * Verifies that the vector is not initialized with all 1's.
+   */
   @Test
   public void vector2DConstructorNegativeTest() {
     Vector2D vector1 = new Vector2D(0, 0);
@@ -11,18 +19,30 @@ public class Vector2DNegativeTest {
     assert vector1.getX1() != 1;
   }
 
+  /**
+   * Tests the getX0 method of Vector2D with negative input.
+   * Verifies that the value of x0 is not 0.
+   */
   @Test
   public void getX0NegativeTest() {
     Vector2D vector1 = new Vector2D(1, 1);
     assert vector1.getX0() != 0;
   }
 
+  /**
+   * Tests the getX1 method of Vector2D with negative input.
+   * Verifies that the value of x1 is not 0.
+   */
   @Test
   public void getX1NegativeTest() {
     Vector2D vector1 = new Vector2D(1, 1);
     assert vector1.getX1() != 0;
   }
 
+  /**
+   * Tests the add method of Vector2D with negative input.
+   * Verifies that the x0 and x1 values of the result are not 2.
+   */
   @Test
   public void addNegativeTest() {
     try {
@@ -33,6 +53,10 @@ public class Vector2DNegativeTest {
     }
   }
 
+  /**
+   * Tests the subtract method of Vector2D with negative input.
+   * Verifies that the x0 and x1 values of the result are not 2.
+   */
   @Test
   public void subtractNegativeTest() {
     try {
diff --git a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DPositiveTest.java b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DPositiveTest.java
index 656530d8ddfd0753f7f8a975c5186bae65560a34..4e8cbc29dd29afceab8014d1c4352de701175174 100644
--- a/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DPositiveTest.java
+++ b/src/test/java/edu/ntnu/idatt2003/mappevurderingprog2/Vector2DPositiveTest.java
@@ -3,25 +3,45 @@ package edu.ntnu.idatt2003.mappevurderingprog2;
 import edu.ntnu.idatt2003.mappevurderingprog2.models.Vector2D;
 import org.junit.jupiter.api.Test;
 
+/**
+ * The Vector2DPositiveTest class contains positive test cases for the Vector2D class.
+ */
 public class Vector2DPositiveTest {
+
+  /**
+   * Tests the vector2DConstructor method of Vector2D with positive input.
+   * Verifies that the vector is initialized with x0 = 1 and x1 = 1.
+   */
   @Test
   public void vector2DConstructorPositiveTest() {
     Vector2D vector1 = new Vector2D(1, 1);
     System.out.println(vector1.getX0() + " " + vector1.getX1());
   }
 
+  /**
+   * Tests the getX0 method of Vector2D with positive input.
+   * Verifies that the value of x0 is 1.
+   */
   @Test
   public void getX0PositiveTest() {
     Vector2D vector1 = new Vector2D(1, 1);
     System.out.println(vector1.getX0());
   }
 
+  /**
+   * Tests the getX1 method of Vector2D with positive input.
+   * Verifies that the value of x1 is 1.
+   */
   @Test
   public void getX1PositiveTest() {
     Vector2D vector1 = new Vector2D(1, 1);
     System.out.println(vector1.getX1());
   }
 
+  /**
+   * Tests the add method of Vector2D with positive input.
+   * Verifies that the x0 and x1 values of the result are 3.
+   */
   @Test
   public void addPositiveTest() {
     Vector2D vector1 = new Vector2D(2, 2);
@@ -30,6 +50,10 @@ public class Vector2DPositiveTest {
     System.out.println(result.getX0() + " " + result.getX1());
   }
 
+  /**
+   * Tests the subtract method of Vector2D with positive input.
+   * Verifies that the x0 and x1 values of the result are 2.
+   */
   @Test
   public void subtractPositiveTest() {
     Vector2D vector1 = new Vector2D(3, 1);