has2k1 / plotnine

A Grammar of Graphics for Python
https://plotnine.org
MIT License
4.04k stars 218 forks source link

PDF saving fails when aspect_ratio is set in theme #885

Closed ZwFink closed 1 week ago

ZwFink commented 1 week ago

When trying to save a figure to PDF format with aspect_ratio set in the theme, a TypeError is raised. The error occurs during the deepcopy operation when trying to adjust the plot's aspect ratio. The same plot saves successfully to PNG format, and saving to PDF works when aspect_ratio is not set. I am using the latest release version

Minimal Working Example:

import pandas as pd
from plotnine import ggplot, aes, geom_bar, theme_seaborn, theme, element_text, scale_y_log10
import math

data = pd.DataFrame({
    'Throughput': ['A', 'A', 'B', 'B'],
    'Type': ['X', 'Y', 'X', 'Y'],
    'Value': [100, 1000, 200, 2000]
})

p1 = (
    ggplot(data, aes(x='Throughput', y='Value', fill='Type'))
    + theme_seaborn('whitegrid')
    + geom_bar(stat='identity', position='dodge')
    + theme(axis_text=element_text(size=12))
    + scale_y_log10(labels=lambda l: [f"$10^{{{int(math.log10(x))}}}$" for x in l])
    + theme(aspect_ratio=0.5)  # This causes the issue
)

# This will raise a TypeError
p1.save('plot_with_aspect.pdf', bbox_inches='tight')

# While this works fine
p1.save('plot_with_aspect.png', bbox_inches='tight')

# And this also works (saving PDF without aspect_ratio)
p2 = (
    ggplot(data, aes(x='Throughput', y='Value', fill='Type'))
    + theme_seaborn('whitegrid')
    + geom_bar(stat='identity', position='dodge')
    + theme(axis_text=element_text(size=12))
    + scale_y_log10(labels=lambda l: [f"$10^{{{int(math.log10(x))}}}$" for x in l])
    # aspect_ratio removed
)
p2.save('plot_without_aspect.pdf', bbox_inches='tight')

Error message: TypeError: cannot pickle 'generator' object

The error traceback shows the issue occurs in the layout engine during the PDF save process.

Workaround: Instead of using theme(aspect_ratio=0.5), you can achieve the same result by setting the width and height directly in the save function:


p1.save('output.pdf', width=10, height=5, bbox_inches='tight')
has2k1 commented 1 week ago

Fixed in v0.14.1.