Outputs text containing the diagram.

        Args:
            block_span_x: The width of the diagram in blocks. Set to None to
                default to using the smallest width that would include all
                accessed blocks and columns with a specified minimum width.
            block_span_y: The height of the diagram in blocks. Set to None to
                default to using the smallest height that would include all
                accessed blocks and rows with a specified minimum height.
            min_block_width: A global minimum width for all blocks.
            min_block_height: A global minimum height for all blocks.

        Returns:
            The diagram as a string.

def render(self,
               *,
               block_span_x: Optional[int] = None,
               block_span_y: Optional[int] = None,
               min_block_width: int = 0,
               min_block_height: int = 0) -> str:
        """Outputs text containing the diagram.

        Args:
            block_span_x: The width of the diagram in blocks. Set to None to
                default to using the smallest width that would include all
                accessed blocks and columns with a specified minimum width.
            block_span_y: The height of the diagram in blocks. Set to None to
                default to using the smallest height that would include all
                accessed blocks and rows with a specified minimum height.
            min_block_width: A global minimum width for all blocks.
            min_block_height: A global minimum height for all blocks.

        Returns:
            The diagram as a string.
        """

        # Determine desired size of diagram in blocks.
        if block_span_x is None:
            block_span_x = 1 + max(
                max(x for x, _ in self._blocks.keys()),
                max(self._min_widths.keys()),
            )
        if block_span_y is None:
            block_span_y = 1 + max(
                max(y for _, y in self._blocks.keys()),
                max(self._min_heights.keys()),
            )

        # Method for accessing blocks without creating new entries.
        empty = Block()
        def block(x: int, y: int) -> Block:
            return self._blocks.get((x, y), empty)

        # Determine the width of every column and the height of every row.
        widths = {
            x: max(
                max(block(x, y).min_width() for y in range(block_span_y)),
                self._min_widths.get(x, 0),
                min_block_width,
            )
            for x in range(block_span_x)
        }
        heights = {
            y: max(
                max(block(x, y).min_height() for x in range(block_span_x)),
                self._min_heights.get(y, 0),
                min_block_height,
            )
            for y in range(block_span_y)
        }

        # Get the individually rendered blocks.
        block_renders = {
            (x, y): block(x, y).render(widths[x], heights[y])
            for x in range(block_span_x)
            for y in range(block_span_y)
        }

        # Paste together all of the rows of rendered block content.
        out_lines = []  # type: List[str]
        for y in range(block_span_y):
            for by in range(heights[y]):
                out_line_chunks = []  # type: List[str]
                for x in range(block_span_x):
                    out_line_chunks.extend(block_renders[x, y][by])
                out_lines.append(''.join(out_line_chunks).rstrip())

        # Then paste together the rows.
        return '\n'.join(out_lines)