Dependency Graph Visualizer Scala

👤 Sharing: AI
```scala
import scala.collection.mutable
import scala.io.Source

object DependencyGraphVisualizer {

  // Represents a node in the dependency graph
  case class Node(name: String, dependencies: List[String])

  def main(args: Array[String]): Unit = {
    // 1. Parse Dependencies from a Text File (Example)
    val filePath = "dependencies.txt" // Replace with your actual file path
    val dependencies = parseDependencies(filePath)

    // 2. Build the Dependency Graph
    val graph = buildDependencyGraph(dependencies)

    // 3. Visualize the Graph (Simple Text-Based Visualization)
    visualizeGraph(graph)
  }

  // Function to parse dependencies from a text file
  // Assumes each line is in the format:  NodeName: Dependency1, Dependency2, Dependency3
  def parseDependencies(filePath: String): List[Node] = {
    try {
      Source.fromFile(filePath).getLines().toList.map { line =>
        val parts = line.split(":").map(_.trim)  // Split by colon and trim whitespace
        if (parts.length == 2) {
          val nodeName = parts(0)
          val dependencyList = parts(1).split(",").map(_.trim).toList
          Node(nodeName, dependencyList)
        } else {
          println(s"Warning: Invalid line format: $line. Skipping.")
          Node("", List()) // Return an empty node to avoid crashing, but log a warning.  Consider throwing an exception instead, depending on how robust you need it to be.
        }
      }.filter(_.name.nonEmpty) // Filter out the empty nodes created due to invalid lines
    } catch {
      case e: java.io.FileNotFoundException =>
        println(s"Error: File not found: $filePath")
        List.empty[Node] // Return an empty list to avoid crashing
      case e: Exception =>
        println(s"Error reading file: ${e.getMessage}")
        List.empty[Node] // Return an empty list in case of other errors
    }
  }


  // Function to build the dependency graph from a list of Nodes
  def buildDependencyGraph(dependencies: List[Node]): Map[String, List[String]] = {
    val graph = mutable.Map[String, List[String]]()

    dependencies.foreach { node =>
      graph(node.name) = node.dependencies
    }

    graph.toMap // Convert to immutable Map
  }

  // Function to visualize the dependency graph (simple text-based visualization)
  def visualizeGraph(graph: Map[String, List[String]]): Unit = {
    println("Dependency Graph:")
    graph.foreach { case (nodeName, dependencies) =>
      val dependenciesString = if (dependencies.isEmpty) "None" else dependencies.mkString(", ")
      println(s"  $nodeName -> $dependenciesString")
    }
  }
}
```

**Explanation:**

1. **`Node` Case Class:**
   - Defines a simple `Node` case class to represent a node in the dependency graph.  It has a `name` (String) and a list of `dependencies` (List[String]).  Case classes are useful because they automatically provide `equals`, `hashCode`, and `toString` methods.

2. **`main` Function:**
   - The entry point of the program.
   - Calls `parseDependencies` to read dependencies from a file.
   - Calls `buildDependencyGraph` to create the graph data structure (a Map).
   - Calls `visualizeGraph` to print a text-based representation of the graph.

3. **`parseDependencies` Function:**
   - Takes a `filePath` (String) as input.
   - Reads each line from the file using `Source.fromFile`.
   - `getLines()` returns an Iterator of Strings (each line). `toList` converts it to a List.
   - `map` transforms each line into a `Node` object.
   - `split(":")` splits each line into two parts (node name and dependencies) based on the colon delimiter.
   - `trim` removes leading/trailing whitespace from the parts.
   - If the line is in the correct format (two parts), it creates a `Node` object.
     - `split(",")` splits the dependencies string into a list of individual dependencies.
   - If the line is not in the correct format, it prints a warning and creates an empty node to avoid crashes and continues the parsing. Consider throwing an exception instead if invalid lines must be handled.
   - `filter(_.name.nonEmpty)` filters out empty `Node` objects created due to invalid lines in the input file. This prevents potential errors further down the line.
   - **Error Handling:** Includes `try...catch` blocks to handle `FileNotFoundException` (if the file doesn't exist) and other potential exceptions during file reading.  This makes the code more robust.  It prints error messages and returns an empty list to avoid crashing the program.
   - Returns a `List[Node]`.

4. **`buildDependencyGraph` Function:**
   - Takes a `List[Node]` as input.
   - Creates a mutable map `graph` to store the dependencies.
   - Iterates through the list of `Node` objects.
   - For each node, it adds an entry to the `graph` map where the key is the node's name and the value is the list of its dependencies.
   - Converts the mutable map to an immutable map using `toMap` for immutability.  This is good practice to prevent accidental modification of the graph.
   - Returns the immutable `Map[String, List[String]]`.

5. **`visualizeGraph` Function:**
   - Takes the `Map[String, List[String]]` (the graph) as input.
   - Prints a simple text-based representation of the graph.
   - Iterates through the `graph` map using `foreach`.
   - For each node (key-value pair), it prints the node's name and its dependencies.
   - `dependencies.mkString(", ")` converts the list of dependencies into a comma-separated string.  If the list is empty, it prints "None."

**How to Run:**

1. **Save the Code:** Save the code as `DependencyGraphVisualizer.scala`.
2. **Create `dependencies.txt`:** Create a text file named `dependencies.txt` in the same directory as the Scala file.  The file should contain the dependencies in the following format:

   ```
   A: B, C
   B: D, E
   C: F
   D:
   E:
   F:
   ```

   Each line represents a node and its dependencies, separated by a colon. Dependencies are comma-separated. A node can have no dependencies.

3. **Compile and Run:**
   - Open a terminal or command prompt.
   - Navigate to the directory where you saved the files.
   - Compile the code: `scalac DependencyGraphVisualizer.scala`
   - Run the code: `scala DependencyGraphVisualizer`

**Output:**

The output will be a text-based representation of the dependency graph, like this:

```
Dependency Graph:
  A -> B, C
  B -> D, E
  C -> F
  D -> None
  E -> None
  F -> None
```

**Improvements and Further Development:**

* **Error Handling:** More robust error handling and validation of the input data.  Consider throwing exceptions instead of just printing warnings.
* **Graph Visualization Libraries:**  Integrate with a graph visualization library (e.g., Graphviz, JGraphT, or a JavaScript-based library for web-based visualization).  This would allow for much more visually appealing and informative graphs.
* **Circular Dependency Detection:** Implement an algorithm to detect circular dependencies in the graph (e.g., A depends on B, B depends on C, and C depends on A).
* **Topological Sorting:** Implement a topological sorting algorithm to order the nodes in a way that respects their dependencies.  This is useful for determining the order in which to build or execute the nodes.
* **More Complex Dependency Formats:** Support more complex dependency formats in the input file.
* **Web Interface:** Create a web interface for visualizing the graph.
* **Interactive Exploration:** Allow users to interact with the graph, such as zooming, panning, and filtering.
* **Color Coding:** Use color coding to highlight different types of dependencies or nodes.
* **Customizable Layout:** Allow users to customize the layout of the graph.

This example provides a basic foundation for building a dependency graph visualizer in Scala.  You can extend it to create more sophisticated and feature-rich applications. Remember to handle errors gracefully and consider using a dedicated graph visualization library for a better visual experience.
👁️ Viewed: 6

Comments