Tree Diagrams

To generate tree diagrams that look neat and consistent every time, you can use the <pl-figure> tag along with a function in server.py to dynamically generate the figure. First, copy this code template into your question.html file:

<pl-figure file-name="tree.svg" type="dynamic" inline="true"></pl-figure>

You can modify the file-name attribute to whatever you’d like, but remember the name of the file, as we will need it for server.py. If you are adding multiple tree diagrams to one question, each <pl-figure> tag should have a different file-name.

Next, copy the following template into server.py. You can leave the generate(data) function, if present, before or after the template.

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def calculate_positions(root, x=0, y=0, x_offset=40, y_offset=60):
    positions = {}
    
    def _traverse(node, x, y):
        if not node:
            return x
        x = _traverse(node.left, x, y + y_offset)
        positions[node] = (x, y)
        x += x_offset
        x = _traverse(node.right, x, y + y_offset)
        return x

    _traverse(root, x, y)
    return positions

def generate_svg_content(root):
    """
    Generates the SVG content for the tree as a string.
    """
    positions = calculate_positions(root)
    
    # Add padding to prevent cropping
    padding = 50
    width = max(pos[0] for pos in positions.values()) + padding * 2
    height = max(pos[1] for pos in positions.values()) + padding * 2
    
    # Adjust all positions by padding to center the drawing
    adjusted_positions = {node: (pos[0] + padding, pos[1] + padding) for node, pos in positions.items()}
    
    svg_content = [
        f'<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">\n'
    ]
    
    # Draw lines connecting parent to children using adjusted positions
    for node, pos in adjusted_positions.items():
        if node.left:
            svg_content.append(f'<line x1="{pos[0]}" y1="{pos[1]}" x2="{adjusted_positions[node.left][0]}" y2="{adjusted_positions[node.left][1]}" stroke="black" stroke-width="2" />\n')
        if node.right:
            svg_content.append(f'<line x1="{pos[0]}" y1="{pos[1]}" x2="{adjusted_positions[node.right][0]}" y2="{adjusted_positions[node.right][1]}" stroke="black" stroke-width="2" />\n')
    
    # Draw the nodes as white circles with black text using adjusted positions
    for node, pos in adjusted_positions.items():
        svg_content.append(f'<circle cx="{pos[0]}" cy="{pos[1]}" r="20" fill="white" stroke="black" stroke-width="2" />\n')
        svg_content.append(f'<text x="{pos[0]}" y="{pos[1]+5}" text-anchor="middle" font-size="16" font-family="Arial">{node.value}</text>\n')
        
    svg_content.append('</svg>')
    
    return "".join(svg_content)

def file(data):
    """
    Returns the generated SVG content as a bytes-like object.
    """
    if data.get("filename") == "tree.svg":
        root = TreeNode(5)
        root.left = TreeNode(2)
        root.left.left = TreeNode(1)
        root.left.right = TreeNode(3)
        root.right = TreeNode(9)
        
        svg_string = generate_svg_content(root)
        
        return svg_string.encode('utf-8')

    return ""

To change the contents of the tree, modify the file(data) function’s if statement to build a binary tree. The if statement should check for the file name you specified in question.html, and if you have multiple <pl-figure> tags, you can add elif statements to check for the other file names.

You can access the official documentation for this question element here.