Module dsa.draw

Module to access graphic drawing functions for Trees, Heaps, Tries and Graphs.

Classes

class Draw

A base class for drawing various data structures.

This class provides basic functionalities for rendering, saving, and displaying visual representations of data structures.

Initialize the Draw class with default figure size.

Expand source code
class Draw:
    """
    A base class for drawing various data structures.
    
    This class provides basic functionalities for rendering, saving, and displaying
    visual representations of data structures.
    """
    def __init__(self):
        """
        Initialize the Draw class with default figure size.
        """
        self.figsize = (5, 3)
        
    def render(self, **kwargs):
        """
        Render the visual representation of the data structure.
        
        This method should be overridden by subclasses to provide specific rendering logic.
        Args:
            **kwargs: Additional keyword arguments.
        """
        pass

    def set_figsize(self, figsize):
        """
        Set the figure size for the plot.
        
        Args:
            figsize (tuple): A tuple representing the figure size (width, height).
        """
        self.figsize = figsize

    def save(self, filename, **kwargs):
        """
        Save the rendered plot to a file.
        
        Args:
            filename (str): The name of the file to save the plot to.
            **kwargs: Additional keyword arguments.
        """
        plt = self.render(**kwargs)
        plt.axis('off')
        plt.savefig(filename)

    def draw(self, **kwargs):
        """
        Display the rendered plot.

        Args:
            **kwargs: Additional keyword arguments.
        """
        plt = self.render(**kwargs)
        plt.axis('off')
        plt.show()

Subclasses

Methods

def draw(self, **kwargs)

Display the rendered plot.

Args

**kwargs
Additional keyword arguments.
def render(self, **kwargs)

Render the visual representation of the data structure.

This method should be overridden by subclasses to provide specific rendering logic.

Args

**kwargs
Additional keyword arguments.
def save(self, filename, **kwargs)

Save the rendered plot to a file.

Args

filename : str
The name of the file to save the plot to.
**kwargs
Additional keyword arguments.
def set_figsize(self, figsize)

Set the figure size for the plot.

Args

figsize : tuple
A tuple representing the figure size (width, height).
class GraphDraw (graph, directed=False, weighted=False)

A class for drawing graphs using the NetworkX library.

This class extends the Draw class to visualize graphs. It supports both directed and undirected graphs, as well as weighted and unweighted graphs. Additionally, it provides an option to display the Minimum Spanning Tree (MST) of the graph.

Attributes

graph
The graph to be drawn
directed : bool
Specifies if the graph is directed. Defaults to False
weighted : bool
Specifies if the graph has weighted edges. Defaults to False

Usage Example: gd = GraphDraw(g) # g is a Graph type (AdjacencyMatrixGraph, AdjacencyMatrixWeightedGraph, AdjacencyListWeightedGraph, AdjacencyListGraph) gd.draw()

Initializes the GraphDraw object.

Expand source code
class GraphDraw(Draw):
    """
    A class for drawing graphs using the NetworkX library.

    This class extends the `Draw` class to visualize graphs. It supports both directed 
    and undirected graphs, as well as weighted and unweighted graphs. Additionally, 
    it provides an option to display the Minimum Spanning Tree (MST) of the graph.

    Attributes:
        graph: The graph to be drawn
        directed (bool): Specifies if the graph is directed. Defaults to False
        weighted (bool): Specifies if the graph has weighted edges. Defaults to False

    Usage Example:
        gd = GraphDraw(g) # g is a Graph type (AdjacencyMatrixGraph, AdjacencyMatrixWeightedGraph, AdjacencyListWeightedGraph, AdjacencyListGraph)
        gd.draw()
    """
    def __init__(self, graph, directed=False, weighted=False):
        """
        Initializes the GraphDraw object.
        """
        super().__init__()
        self.graph = graph
        self.directed = directed
        self.weighted = weighted
            
    def render(self, pos=None, show_mst=False, mst_only=False, **kwargs):
        """
        Renders the graph using Matplotlib. Not to be called directly. Call draw() instead.
        """
        super().render(**kwargs)
        edges = self.graph.edges()

        if self.directed:
            g = nx.DiGraph()
        else:
            g = nx.Graph()

        if self.weighted:
            g.add_weighted_edges_from(edges)
        else:
            g.add_edges_from(edges)
    
        if pos is None:
            pos = nx.shell_layout(g) 

        plt.figure(figsize=self.figsize)

        if mst_only:
            show_mst = True
            nx.draw_networkx_nodes(g, pos, node_color="tab:blue", node_size=800)
            nx.draw_networkx_labels(g, pos, font_size=10, font_color="white")
        else:
            nx.draw(g, pos, with_labels=True, node_color="tab:blue", node_size=800, font_size=10, font_color="white")

        if self.weighted:
            edge_labels = {(u, v): d["weight"] for u, v, d in g.edges(data=True)}
            nx.draw_networkx_edge_labels(g, pos, edge_labels=edge_labels, font_size=14, label_pos=0.5)

        if show_mst:
            T = nx.minimum_spanning_tree(g)
            nx.draw_networkx_edges(T, pos, edge_color="tab:red", width=2)
        return plt

Ancestors

Methods

def render(self, pos=None, show_mst=False, mst_only=False, **kwargs)

Renders the graph using Matplotlib. Not to be called directly. Call draw() instead.

Inherited members

class HeapDraw (heap: Heap, **kwargs)

A class for drawing a heap structure using the NetworkX library.

This class extends the Draw class to visualize heap structures, such as binary heaps or min-heaps.

Attributes

heap : Heap
The heap structure to be drawn.

Usage Example: h = MinHeap() # Define your heap, e.g., MinHeap or Heap hd = HeapDraw(h) hd.draw()

Initializes the HeapDraw object.

Args

heap : Heap
The heap structure to be drawn.
**kwargs
Additional keyword arguments passed to the parent Draw class.
Expand source code
class HeapDraw(Draw):
    """
    A class for drawing a heap structure using the NetworkX library.

    This class extends the `Draw` class to visualize heap structures, such as binary heaps or min-heaps.

    Attributes:
        heap (Heap): The heap structure to be drawn.

    Usage Example:
        h = MinHeap()  # Define your heap, e.g., MinHeap or Heap
        hd = HeapDraw(h)
        hd.draw()
    """

    def __init__(self, heap: Heap, **kwargs):
        """
        Initializes the HeapDraw object.

        Args:
            heap (Heap): The heap structure to be drawn.
            **kwargs: Additional keyword arguments passed to the parent `Draw` class.
        """
        super().__init__(**kwargs)
        self.heap = heap

    def array_to_node(self, index: int, array):
        """
        Converts an array-based heap into a tree node structure.

        This helper function recursively constructs a tree from the array representation
        of the heap, reflecting the binary tree structure of the heap.

        Args:
            index (int): The current index in the array representing the node.
            array (list): The array containing heap values, organized as a complete binary tree.

        Returns:
            TreeNode: The root node of the constructed subtree.
        """
        if index >= len(array):
            return None
        else:
            value = array[index]
            left_index = index * 2 + 1
            right_index = index * 2 + 2
            node = TreeNode(value)
            node.left = self.array_to_node(left_index, array)
            node.right = self.array_to_node(right_index, array)
            return node

    def render(self, **kwargs):
        """
        Renders the heap as a tree using Matplotlib. Not to be called directly. Call draw() instead.

        This method converts the heap into a tree structure and then uses the `TreeDraw` class
        to render it visually. Customization options can be provided via keyword arguments.

        Returns:
            matplotlib.pyplot: The Matplotlib plot object for further customization or display.
        """
        node = self.array_to_node(0, [node[1] for node in self.heap.enumerate()])
        tree = Tree(node)

        tree_draw = TreeDraw(tree)
        tree_draw.set_figsize(self.figsize)
        return tree_draw.render(**kwargs)

Ancestors

Methods

def array_to_node(self, index: int, array)

Converts an array-based heap into a tree node structure.

This helper function recursively constructs a tree from the array representation of the heap, reflecting the binary tree structure of the heap.

Args

index : int
The current index in the array representing the node.
array : list
The array containing heap values, organized as a complete binary tree.

Returns

TreeNode
The root node of the constructed subtree.
def render(self, **kwargs)

Renders the heap as a tree using Matplotlib. Not to be called directly. Call draw() instead.

This method converts the heap into a tree structure and then uses the TreeDraw class to render it visually. Customization options can be provided via keyword arguments.

Returns

matplotlib.pyplot
The Matplotlib plot object for further customization or display.

Inherited members

class TreeDraw (tree: Tree)

A class for drawing a tree structure using the NetworkX library.

This class extends the Draw class to visualize tree structures. It organizes the nodes in a hierarchical tree layout and provides options for customization through the render method.

Attributes

tree : Tree
The tree structure to be drawn.

Usage Example: t = Tree(root_node) # Define your tree structure with a root node td = TreeDraw(t) td.draw()

Initializes the TreeDraw object.

Args

tree : Tree
The tree structure to be drawn.
Expand source code
class TreeDraw(Draw):
    """
    A class for drawing a tree structure using the NetworkX library.

    This class extends the `Draw` class to visualize tree structures. It organizes the nodes
    in a hierarchical tree layout and provides options for customization through the `render` method.

    Attributes:
        tree (Tree): The tree structure to be drawn.

    Usage Example:
        t = Tree(root_node)  # Define your tree structure with a root node
        td = TreeDraw(t)
        td.draw()
    """

    def __init__(self, tree: Tree):
        """ 
        Initializes the TreeDraw object.

        Args:
            tree (Tree): The tree structure to be drawn.
        """
        super().__init__()
        self.tree = tree
        
    def add_edges(self, graph, node, pos=None, x: float=0, y: float=0, layer=1):
        """
        Recursively adds edges to the graph and positions the nodes in a tree layout.
    
        Args:
            graph (networkx.DiGraph): The graph object where edges are added.
            node (TreeNode): The current node in the tree.
            pos (dict, optional): A dictionary to store the positions of the nodes. Defaults to None.
            x (float): The x-coordinate for the current node. Defaults to 0.
            y (float): The y-coordinate for the current node. Defaults to 0.
            layer (int): The current layer/level of the node in the tree. Defaults to 1.

        Returns:
            dict: A dictionary containing the positions of all nodes in the tree.
        """
        if pos is None:
            pos = {}
        if node is not None:
            pos[node.value] = (x, y)
            if node.left:
                graph.add_edge(node.value, node.left.value)
                pos = self.add_edges(graph, node.left, pos=pos, x=x-1/layer, y=y-1, layer=layer+1)
            if node.right:
                graph.add_edge(node.value, node.right.value)
                pos = self.add_edges(graph, node.right, pos=pos, x=x+1/layer, y=y-1, layer=layer+1)
        return pos
    
    def render(self, **kwargs):
        """ 
        Renders the tree using Matplotlib. Not to be called directly. Call draw() instead.

        This method generates a graphical representation of the tree with nodes positioned
        in a hierarchical layout. Customization options can be provided via keyword arguments.

        Args:
            **kwargs: Additional keyword arguments for customization.

        Returns:
            matplotlib.pyplot: The Matplotlib plot object for further customization or display.
        """
        super().render(**kwargs)
        graph = nx.DiGraph()
        pos = self.add_edges(graph, self.tree.root)
        plt.figure(figsize=self.figsize)
        nx.draw(graph, pos, with_labels=True, arrows=False, node_color="tab:blue", node_size=800, font_size=12, font_color="white") 
        return plt

Ancestors

Methods

def add_edges(self, graph, node, pos=None, x: float = 0, y: float = 0, layer=1)

Recursively adds edges to the graph and positions the nodes in a tree layout.

Args

graph : networkx.DiGraph
The graph object where edges are added.
node : TreeNode
The current node in the tree.
pos : dict, optional
A dictionary to store the positions of the nodes. Defaults to None.
x : float
The x-coordinate for the current node. Defaults to 0.
y : float
The y-coordinate for the current node. Defaults to 0.
layer : int
The current layer/level of the node in the tree. Defaults to 1.

Returns

dict
A dictionary containing the positions of all nodes in the tree.
def render(self, **kwargs)

Renders the tree using Matplotlib. Not to be called directly. Call draw() instead.

This method generates a graphical representation of the tree with nodes positioned in a hierarchical layout. Customization options can be provided via keyword arguments.

Args

**kwargs
Additional keyword arguments for customization.

Returns

matplotlib.pyplot
The Matplotlib plot object for further customization or display.

Inherited members

class TrieDraw (trie: Trie, **kwargs)

A class for drawing a Trie structure using the NetworkX library.

This class extends the Draw class to visualize Trie structures, commonly used for storing strings or prefix trees. It provides methods to convert the Trie into a networkx graph, arrange nodes hierarchically, and render the visualization using Matplotlib.

Attributes

trie : Trie
The Trie structure to be drawn.

Usage Example: trie = Trie() # Initialize your Trie and populate it with words trd = TrieDraw(trie) trd.draw()

Initializes the TrieDraw object.

Args

trie : Trie
The Trie structure to be drawn.
**kwargs
Additional keyword arguments passed to the parent Draw class.
Expand source code
class TrieDraw(Draw):
    """
    A class for drawing a Trie structure using the NetworkX library.

    This class extends the `Draw` class to visualize Trie structures, commonly used for storing strings
    or prefix trees. It provides methods to convert the Trie into a networkx graph, arrange nodes
    hierarchically, and render the visualization using Matplotlib.

    Attributes:
        trie (Trie): The Trie structure to be drawn.

    Usage Example:
        trie = Trie()  # Initialize your Trie and populate it with words
        trd = TrieDraw(trie)
        trd.draw()
    """

    def __init__(self, trie: Trie, **kwargs):
        """
        Initializes the TrieDraw object.

        Args:
            trie (Trie): The Trie structure to be drawn.
            **kwargs: Additional keyword arguments passed to the parent `Draw` class.
        """
        super().__init__(**kwargs)
        self.figsize = (10, 6)
        self.trie = trie
        
    def _add_edges(self, graph, node, current_path):
        """
        Recursively adds edges to the networkx graph based on the Trie structure.

        This helper function traverses the Trie, adding edges between nodes in the
        networkx graph and associating them with characters from the Trie.

        Args:
            graph (networkx.DiGraph): The directed graph to which edges are added.
            node (TrieNode): The current node in the Trie.
            current_path (str): The path representing the current prefix in the Trie.
        """
        if node is None:
            return
        for char, child in node.children.items():
            new_path = current_path + char
            graph.add_edge(current_path, new_path, label=char)
            self._add_edges(graph, child, new_path)
    
    def to_networkx(self) -> nx.DiGraph:
        """
        Converts the Trie into a NetworkX directed graph (DiGraph).

        This method creates a networkx graph representation of the Trie, where each node
        represents a prefix and each edge represents a character transition.

        Returns:
            networkx.DiGraph: The networkx graph representation of the Trie.
        """
        graph = nx.DiGraph()
        self._add_edges(graph, self.trie.root, "")
        return graph
    
    def hierarchical_pos(self, G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5):
        """
        Computes the hierarchical position of nodes in the graph for visualization.

        This method arranges the nodes of the Trie in a hierarchical layout, which is
        particularly useful for visualizing tree-like structures such as Tries.

        Args:
            G (networkx.Graph): The graph for which to compute positions.
            root (str, optional): The root node of the graph. Defaults to None, which means
                                  the root will be determined automatically.
            width (float): The width of the entire drawing. Defaults to 1.
            vert_gap (float): The gap between levels in the hierarchy. Defaults to 0.2.
            vert_loc (float): The vertical location of the root node. Defaults to 0.
            xcenter (float): The horizontal center of the root node. Defaults to 0.5.

        Returns:
            dict: A dictionary mapping each node to its (x, y) position in the layout.
        """
        if root is None:
            root = next(iter(nx.topological_sort(G)))
    
        def _hierarchical_pos(G, node, width: float=1., vert_gap: float=0.2, vert_loc:float=0, xcenter:float=0.5, pos=None, parent=None, parsed=[]):
            if pos is None:
                pos = {node: (xcenter, vert_loc)}
            else:
                pos[node] = (xcenter, vert_loc)
    
            children = list(G.neighbors(node))
            if not isinstance(G, nx.DiGraph) and parent is not None:
                children.remove(parent)
    
            if len(children) != 0:
                dx = width / len(children)
                nextx = xcenter - width / 2 - dx / 2
                for child in children:
                    nextx += dx
                    pos = _hierarchical_pos(G, child, width=dx, vert_gap=vert_gap, vert_loc=vert_loc - vert_gap, xcenter=nextx, pos=pos, parent=node, parsed=parsed)
            return pos
    
        return _hierarchical_pos(G, root, width, vert_gap, vert_loc, xcenter)
    
    def render(self, **kwargs):
        """
        Renders the Trie as a hierarchical graph using Matplotlib. Not to be called directly. Call draw() instead.

        This method uses the hierarchical positions of the nodes to render the Trie
        as a directed graph. Nodes are drawn as rectangles, and edges represent the transitions
        between prefixes.

        Returns:
            matplotlib.pyplot: The Matplotlib plot object for further customization or display.
        """
        super().render(**kwargs)
        trie_graph = self.to_networkx()
        
        pos = self.hierarchical_pos(trie_graph)
        
        plt.figure(figsize=self.figsize)
        nx.draw_networkx_edges(trie_graph, pos, arrows=True)
        
        ax = plt.gca()
        rect_width = 0.05
        rect_height = 0.15
        for node in pos:
            x, y = pos[node]
            rectangle = plt.Rectangle((x - (rect_width / 2), y - (rect_height / 2)), rect_width, rect_height, color="tab:blue")
            ax.add_patch(rectangle)
            plt.text(x, y, node[-1] if node else "", verticalalignment='center', horizontalalignment='center', fontsize=12, color="white")
        return plt

Ancestors

Methods

def hierarchical_pos(self, G, root=None, width=1.0, vert_gap=0.2, vert_loc=0, xcenter=0.5)

Computes the hierarchical position of nodes in the graph for visualization.

This method arranges the nodes of the Trie in a hierarchical layout, which is particularly useful for visualizing tree-like structures such as Tries.

Args

G : networkx.Graph
The graph for which to compute positions.
root : str, optional
The root node of the graph. Defaults to None, which means the root will be determined automatically.
width : float
The width of the entire drawing. Defaults to 1.
vert_gap : float
The gap between levels in the hierarchy. Defaults to 0.2.
vert_loc : float
The vertical location of the root node. Defaults to 0.
xcenter : float
The horizontal center of the root node. Defaults to 0.5.

Returns

dict
A dictionary mapping each node to its (x, y) position in the layout.
def render(self, **kwargs)

Renders the Trie as a hierarchical graph using Matplotlib. Not to be called directly. Call draw() instead.

This method uses the hierarchical positions of the nodes to render the Trie as a directed graph. Nodes are drawn as rectangles, and edges represent the transitions between prefixes.

Returns

matplotlib.pyplot
The Matplotlib plot object for further customization or display.
def to_networkx(self) ‑> networkx.classes.digraph.DiGraph

Converts the Trie into a NetworkX directed graph (DiGraph).

This method creates a networkx graph representation of the Trie, where each node represents a prefix and each edge represents a character transition.

Returns

networkx.DiGraph
The networkx graph representation of the Trie.

Inherited members