我reportlab
用来生成报告。我可以通过四个步骤定义创建过程:1) 通过 API 获取数据,2) 过滤数据,3) 生成图形matplotlib
和 4) 在 PDF 中插入信息reportlab
。
我在这个(感谢Patrick Maupin!)和这个(感谢Larry Meyn !)中找到了答案flowable
matplotlib
。ReportLab
我做了一些更改,并复制了下面感兴趣的代码部分:
import os
from matplotlib import pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
from reportlab.platypus import Paragraph, SimpleDocTemplate, Flowable
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfgen import canvas
from pdfrw import PdfReader, PdfDict
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerl
try:
from cStringIO import StringIO as BytesIO
except ImportError:
from io import BytesIO
styles = getSampleStyleSheet()
style = styles['Normal']
#Info if you want to run the script
div = ['may/18', 'jun/18', 'jul/18', 'aug/18', 'sep/18', 'oct/18', 'nov/18', \
'dec/18', 'jan/19', 'feb/19', 'mar/19', 'apr/19']
mes = [0, 15, 149, 0, 59, 0, 0, 101, 45, 25, 751.07, 5358.3]
acc = [0, 15, 164, 164, 223, 223, 223, 324, 369, 394, 1145.07, 6503.37]
class PdfImage(Flowable):
"""
Generates a reportlab image flowable for matplotlib figures. It is initialized
with either a matplotlib figure or a pointer to a list of pagexobj objects and
an index for the pagexobj to be used.
"""
def __init__(self, fig=None, width=200, height=200, cache=None, cacheindex=0):
self.img_width = width
self.img_height = height
if fig is None and cache is None:
raise ValueError("Either 'fig' or 'cache' must be provided")
if fig is not None:
imgdata = BytesIO()
fig.savefig(imgdata, format='pdf')
imgdata.seek(0)
page, = PdfReader(imgdata).pages
image = pagexobj(page)
self.img_data = image
else:
self.img_data = None
self.cache = cache
self.cacheindex = cacheindex
def wrap(self, width, height):
return self.img_width, self.img_height
def drawOn(self, canv, x, y, _sW=0):
if _sW > 0 and hasattr(self, 'hAlign'):
a = self.hAlign
if a in ('CENTER', 'CENTRE', TA_CENTER):
x += 0.5*_sW
elif a in ('RIGHT', TA_RIGHT):
x += _sW
elif a not in ('LEFT', TA_LEFT):
raise ValueError("Bad hAlign value " + str(a))
canv.saveState()
if self.img_data is not None:
img = self.img_data
else:
img = self.cache[self.cacheindex]
if isinstance(img, PdfDict):
xscale = self.img_width / img.BBox[2]
yscale = self.img_height / img.BBox[3]
canv.translate(x, y)
canv.scale(xscale, yscale)
canv.doForm(makerl(canv, img))
else:
canv.drawImage(img, x, y, self.img_width, self.img_height)
canv.restoreState()
class PdfImageCache(object):
"""
Saves matplotlib figures to a temporary multi-page PDF file using the 'savefig'
method. When closed the images are extracted and saved to the attribute 'cache'.
The temporary PDF file is then deleted. The 'savefig' returns a PdfImage object
with a pointer to the 'cache' list and an index for the figure. Use of this
cache reduces duplicated resources in the reportlab generated PDF file.
Use is similar to matplotlib's PdfPages object. When not used as a context
manager, the 'close()' method must be explictly called before the reportlab
document is built.
"""
def __init__(self):
self.pdftempfile = '_temporary_pdf_image_cache_.pdf'
self.pdf = PdfPages(self.pdftempfile)
self.cache = []
self.count = 0
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self, *args):
self.pdf.close()
pages = PdfReader(self.pdftempfile).pages
pages = [pagexobj(x) for x in pages]
self.cache.extend(pages)
os.remove(self.pdftempfile)
def savefig(self, fig, width=200, height=200):
self.pdf.savefig(fig)
index = self.count
self.count += 1
return PdfImage(width=width, height=height, cache=self.cache, cacheindex=index)
def make_report_cached_figs(outfn):
"""
Makes a dummy report with nfig matplotlib plots using PdfImageCache
to reduce PDF file size.
"""
doc = SimpleDocTemplate(outfn)
style = styles["Normal"]
story = []
with PdfImageCache() as pdfcache:
fig = plt.figure(figsize=(5, 5))
plt.bar(div, acc, width = 0.8, color = 'silver', label = 'Year')
plt.bar(div, mes, width = 0.4, color = 'k', label = 'Month', alpha = 0.5)
plt.legend()
plt.xticks(rotation=60)
plt.close()
img0 = pdfcache.savefig(fig, width=185, height=135)
img0.drawOn(doc, 0, 100)
story.append(img0)
doc.build(story)
make_report_cached_figs("Test.pdf")
我的问题在于drawOn
方法,更具体地说在于第一个论点。在上面的代码中,我收到以下错误:
AttributeError: 'SimpleDocTemplate' object has no attribute 'saveState'
如果我替换doc = SimpleDocTemplate(outfn)
为doc = canvas.Canvas(outfn)
(我知道如果我要使用canvas
我应该做一些其他的小改动)我会得到另一个错误:
img = self.cache[self.cacheindex]
IndexError: list index out of range
我在官方文档和源代码中搜索了信息,但没有成功。那么,如何设置图像的位置呢?(使用drawOn
与否)