如何将for循环生成的Plotly Express图表保存到单个PDF?
如何将Plotly循环生成的图表保存到单个PDF文件?
问题背景
我希望将通过for循环生成的所有图表保存到单个PDF文件中。
示例数据
import pandas as pd import numpy as np import plotly.io as pio import plotly.express as px import plotly.graph_objects as go np.random.seed(0) df = pd.DataFrame({'State' : np.repeat(['NY', 'TX', 'FL', 'PA'], 12), 'Month' : np.tile(pd.date_range('2023-09-01', '2024-08-01', freq = 'MS'), 4), 'Actual' : np.random.randint(1000, 1500, size = 48), 'Forecast' : np.random.randint(1000, 1500, size = 48)}) df['Month'] = pd.to_datetime(df['Month']) df.set_index('Month', inplace = True)
当前已实现的图表生成代码
我已能在Jupyter Notebook中通过以下代码生成图表:
for s in df['State'].unique(): d = df.loc[df['State'] == s, ['Actual', 'Forecast']] fig = px.line(d, x = d.index, y = d.columns) fig.update_layout(title = 'Actuals vs Forecast for ' + s, template = 'plotly_dark', xaxis_title = 'Month') fig.update_xaxes(tickformat = '%Y-%B', dtick = 'M1') fig.show()
尝试过使用fig_trace和make_subplots结合Graph Object go.Scatter替代px.line的方法,但无法生效。
解决方案
方法1:将图表转为图片后合并为PDF
Plotly本身不支持直接导出多页PDF,可先将每个图表导出为图片,再用PDF库合并成多页PDF。这里用plotly.io.to_image生成图片,结合reportlab创建PDF。
安装依赖库
pip install reportlab pillow plotly pandas numpy
完整代码
import pandas as pd import numpy as np import plotly.io as pio import plotly.express as px from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas from PIL import Image import io # 生成示例数据 np.random.seed(0) df = pd.DataFrame({'State' : np.repeat(['NY', 'TX', 'FL', 'PA'], 12), 'Month' : np.tile(pd.date_range('2023-09-01', '2024-08-01', freq = 'MS'), 4), 'Actual' : np.random.randint(1000, 1500, size = 48), 'Forecast' : np.random.randint(1000, 1500, size = 48)}) df['Month'] = pd.to_datetime(df['Month']) df.set_index('Month', inplace = True) # 初始化PDF画布 pdf_filename = 'state_forecasts.pdf' c = canvas.Canvas(pdf_filename, pagesize=letter) page_width, page_height = letter # 循环生成图表并添加到PDF for idx, s in enumerate(df['State'].unique()): d = df.loc[df['State'] == s, ['Actual', 'Forecast']] fig = px.line(d, x=d.index, y=d.columns) fig.update_layout(title='Actuals vs Forecast for ' + s, template='plotly_dark', xaxis_title='Month') fig.update_xaxes(tickformat='%Y-%B', dtick='M1') # 将Plotly图表转为PNG图片(内存中) img_bytes = pio.to_image(fig, format='png') img = Image.open(io.BytesIO(img_bytes)) # 调整图片大小以适应PDF页面(保持比例) img_width, img_height = img.size scale = min(page_width / img_width, page_height / img_height) new_width = img_width * scale new_height = img_height * scale # 计算居中位置 x = (page_width - new_width) / 2 y = (page_height - new_height) / 2 # 绘制图片到PDF c.drawImage(io.BytesIO(img_bytes), x, y, width=new_width, height=new_height) # 最后一页不添加新页面 if idx != len(df['State'].unique()) - 1: c.showPage() # 保存PDF c.save() print(f"PDF文件已保存为: {pdf_filename}")
方法2:用HTML嵌入图表后导出为PDF
将所有图表嵌入单个HTML页面,再用pdfkit将HTML转为多页PDF。该方法需要安装wkhtmltopdf工具。
安装依赖
- 安装Python库:
pip install pdfkit plotly pandas numpy
- 安装
wkhtmltopdf:- Windows:从官网下载安装包
- Linux:执行
sudo apt-get install wkhtmltopdf - macOS:执行
brew install wkhtmltopdf
完整代码
import pandas as pd import numpy as np import plotly.express as px import plotly.io as pio import pdfkit # 生成示例数据 np.random.seed(0) df = pd.DataFrame({'State' : np.repeat(['NY', 'TX', 'FL', 'PA'], 12), 'Month' : np.tile(pd.date_range('2023-09-01', '2024-08-01', freq = 'MS'), 4), 'Actual' : np.random.randint(1000, 1500, size = 48), 'Forecast' : np.random.randint(1000, 1500, size = 48)}) df['Month'] = pd.to_datetime(df['Month']) df.set_index('Month', inplace = True) # 构建HTML内容 html_content = "<html><body>" for s in df['State'].unique(): d = df.loc[df['State'] == s, ['Actual', 'Forecast']] fig = px.line(d, x=d.index, y=d.columns) fig.update_layout(title='Actuals vs Forecast for ' + s, template='plotly_dark', xaxis_title='Month') fig.update_xaxes(tickformat='%Y-%B', dtick='M1') # 将图表转为HTML片段 html_content += pio.to_html(fig, full_html=False) # 添加分页符 html_content += "<div style='page-break-after: always;'></div>" html_content += "</body></html>" # 导出为PDF pdf_filename = 'state_forecasts_html.pdf' pdfkit.from_string(html_content, pdf_filename) print(f"PDF文件已保存为: {pdf_filename}")
内容的提问来源于stack exchange,提问作者Karthik S
相关产品推荐
相关产品推荐

