Content-Aware Auto Layout

This example demonstrates dm.auto_layout() which automatically detects and fixes text overflow by measuring actual bounding boxes and iteratively adjusting margins. This is particularly useful for figures with long labels or multi-line titles that might extend beyond figure boundaries.

import matplotlib.pyplot as plt
import numpy as np

import dartwork_mpl as dm

# Apply scientific style for consistent formatting
dm.style.use("scientific")

# Create figure with potentially problematic layout
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(dm.cm2in(16), dm.cm2in(8)))

# Generate sample data
x = np.linspace(0, 10, 100)
y1 = np.sin(x) + 0.1 * np.random.randn(100)
y2 = np.cos(x) + 0.1 * np.random.randn(100)

# Left plot with very long labels that would typically overflow
ax1.plot(x, y1, color="oc.blue5", lw=dm.lw(1))
ax1.set_title(
    "Panel A: Demonstration of\nMulti-Line Title with Potential Overflow\nThird Line for Extra Challenge",
    fontsize=dm.fs(1),
)
ax1.set_ylabel(
    "Extremely Long Y-Axis Label\nThat Spans Multiple Lines\n(Units: km/h)",
    fontsize=dm.fs(0),
)
ax1.set_xlabel("Time [seconds]", fontsize=dm.fs(0))

# Right plot with normal labels for comparison
ax2.plot(x, y2, color="oc.red5", lw=dm.lw(1))
ax2.set_title("Panel B: Normal Title", fontsize=dm.fs(1))
ax2.set_ylabel("Value", fontsize=dm.fs(0))
ax2.set_xlabel("Time [seconds]", fontsize=dm.fs(0))

# Add panel labels
dm.label_axes([ax1, ax2])

# Apply auto layout with verbose output to show the iteration process
print("Applying auto_layout to prevent text overflow...")
dm.auto_layout(fig, padding=0.05, max_iter=5, verbose=True)
Panel A: Demonstration of Multi-Line Title with Potential Overflow Third Line for Extra Challenge, Panel B: Normal Title
Applying auto_layout to prevent text overflow...
[auto_layout] iter 1: margins=(0.050, 0.050, 0.050, 0.050)  overflow=L:0.0px R:0.0px B:0.0px T:0.0px
[auto_layout] Converged in 1 iteration(s).

The auto_layout function works by:

  1. Initial Layout: Applies minimal margins as a starting point

  2. Overflow Detection: Measures actual bounding boxes of all text elements

  3. Margin Adjustment: Increases margins only where overflow is detected

  4. Iteration: Repeats until no overflow remains or max iterations reached

This ensures that all text remains within the figure bounds without unnecessarily large margins.

Comparison with simple_layout

fig2, (ax3, ax4) = plt.subplots(1, 2, figsize=(dm.cm2in(16), dm.cm2in(8)))

# Create identical plots
for ax in [ax3, ax4]:
    ax.plot(x, y1, color="oc.green5", lw=dm.lw(1))
    ax.set_title(
        "Complex Title with\nMultiple Lines\nThat Might Overflow",
        fontsize=dm.fs(1),
    )
    ax.set_ylabel(
        "Long Y-Axis Label\nWith Units\n(measurement)", fontsize=dm.fs(0)
    )
    ax.set_xlabel("X-Axis Label", fontsize=dm.fs(0))

ax3.text(
    0.5,
    1.15,
    "simple_layout()",
    transform=ax3.transAxes,
    ha="center",
    fontsize=dm.fs(2),
    weight="bold",
)
ax4.text(
    0.5,
    1.15,
    "auto_layout()",
    transform=ax4.transAxes,
    ha="center",
    fontsize=dm.fs(2),
    weight="bold",
)

# Apply auto_layout for the full figure
dm.auto_layout(fig2)
Complex Title with Multiple Lines That Might Overflow, Complex Title with Multiple Lines That Might Overflow

Advanced Usage

# Create a complex dashboard with varying label lengths
fig3 = plt.figure(figsize=(dm.cm2in(20), dm.cm2in(15)))
gs = fig3.add_gridspec(3, 3, hspace=0.3, wspace=0.3)

# Create subplots with different label complexities
axes = []
for i in range(3):
    for j in range(3):
        ax = fig3.add_subplot(gs[i, j])
        axes.append(ax)

        # Generate random data
        data = np.random.randn(50).cumsum()
        ax.plot(
            data,
            color=f"oc.{['blue', 'red', 'green'][j]}{4 + i}",
            lw=dm.lw(0.8),
        )

        # Vary label complexity
        if i == 0 and j == 0:
            ax.set_ylabel(
                "Very Long Label\nWith Multiple\nLines of Text\n(Complex Units)"
            )
            ax.set_title(
                "Panel with Extremely Long Title\nThat Would Normally Overflow"
            )
        elif i == 1 and j == 1:
            ax.set_ylabel("Medium Label\n(units)")
            ax.set_title("Moderate Title")
        else:
            ax.set_ylabel("Value")
            ax.set_title(f"Panel {i * 3 + j + 1}")

        ax.set_xlabel("Time" if i == 2 else "")

# Add panel labels
dm.label_axes(axes)

# Apply auto layout with custom parameters
dm.auto_layout(
    fig3,
    padding=0.08,  # Minimum padding around text (in inches)
    max_iter=10,  # Maximum iterations to prevent infinite loops
    verbose=False,  # Set to True to see convergence details
)

plt.suptitle("Dashboard with Auto Layout", fontsize=dm.fs(3), y=1.02)
Dashboard with Auto Layout, Panel with Extremely Long Title That Would Normally Overflow, Panel 2, Panel 3, Panel 4, Moderate Title, Panel 6, Panel 7, Panel 8, Panel 9

When to Use auto_layout

Recommended for:

  • Figures with unknown or variable label lengths (e.g., generated by AI)

  • Multi-line titles or axis labels

  • Complex dashboards with many panels

  • Publication figures where text cutoff is unacceptable

Performance Note:

auto_layout is slightly slower than simple_layout due to the iterative measurement process. For simple figures with predictable labels, simple_layout is usually sufficient and faster.

plt.show()

Total running time of the script: (0 minutes 11.660 seconds)