Python 正在将matplotlib对象加载到reportlab中

Python 正在将matplotlib对象加载到reportlab中,python,matplotlib,reportlab,Python,Matplotlib,Reportlab,我正在尝试将matplotlib对象加载到reportlab中。 这是我的密码: from reportlab.pdfgen import canvas from reportlab.lib.utils import ImageReader from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Image from matplotlib import pyplot as plt def __get_img_da

我正在尝试将matplotlib对象加载到reportlab中。 这是我的密码:

from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Image
from matplotlib import pyplot as plt

def __get_img_data():
    """
    returns the binary image data of the plot
    """
    img_file = NamedTemporaryFile(delete=False)
    plt.savefig(img_file.name)
    img_data = open(img_file.name + '.png', 'rb').read()
    os.remove(img_file.name)
    os.remove(img_file.name + '.png')
    return img_data

def get_plot():
    # HERE I PLOT SOME STUFF
    img_data = __get_img_data()
    plt.close()
    return img_data

class NumberedCanvas(canvas.Canvas):
    def __init__(self):
        pass

class ReportTemplate:
    def __init__(self):
        pass
    def _header_footer(self, canvas, doc):
        pass

    def get_data(self):
        elements = []
        elements.append('hello')
        ## HERE I WANT TO ADD THE IMAGE
        imgdata = get_plot()
        with open('/tmp/x.png', 'wb') as fh:
            fh.write(imgdata)
        im = Image('/tmp/x.png', width=usable_width, height=usable_width)
        elements.append(im)
        os.remove('/tmp/x.png')
        ######
        doc.build(elements, onFirstPage=self._header_footer,\
                  onLaterPages=self._header_footer,\
                  canvasmaker=NumberedCanvas)
        # blah blah
        return obj
我的目标是将绘图图像插入报告中。 这工作正常,但我不想写入临时文件。 我尝试安装PIL,因为我读过一些人使用PIL的图像库进行安装,但一旦我安装了PIL,我的另一部分代码由于枕头版本不兼容而中断。

我找到了两种解决方案:

1:使用名为pdfrw的包:

2:更简单更干净的方式:

class PdfImage(Flowable):
    def __init__(self, img_data, width=200, height=200):
        self.img_width = width
        self.img_height = height
        self.img_data = img_data

    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()
        canv.drawImage(self.img_data, x, y, self.img_width, self.img_height)
        canv.restoreState()


def make_report():
    fig = plt.figure(figsize=(4, 3))
    plt.plot([1,2,3,4],[1,4,9,26])
    plt.ylabel('some numbers')
    imgdata = cStringIO.StringIO()
    fig.savefig(imgdata, format='png')
    imgdata.seek(0)
    image = ImageReader(imgdata)

    doc = SimpleDocTemplate("hello.pdf")
    style = styles["Normal"]
    story = [Spacer(0, inch)]
    img = PdfImage(image, width=200, height=200)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    story.append(img)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    doc.build(story, onFirstPage=myFirstPage, onLaterPages=myLaterPages, canvasmaker=PageNumCanvas)
pdfrw文档很糟糕 中讨论的有点不清楚的唯一原因是文档糟糕透顶。由于这个糟糕的文档,该示例的作者@Larry Meyn使用rst2pdf的vectorpdf扩展作为起点,该扩展也没有真正的文档化,并且必须处理rst2pdf和pdfrw的怪癖(比你需要的更一般,因为它可以让rst2pdf显示一个任意的矩形,从一个已有PDF的任意页面)。令人惊讶的是,拉里成功地使它工作了,我对他敬而远之

我完全有资格这样说,因为我是pdfrw的作者,并且对rst2pdf做出了一些贡献,包括vectorpdf扩展

但您可能还是想使用pdfrw 直到一个月前,我才真正注意到stackoverflow,pdfrw本身也有几年不景气,但我现在在这里,我认为你应该再看看pdfrw,即使文档仍然很糟糕

为什么?因为如果输出到png文件,图像将被光栅化,如果使用pdfrw,图像将保持矢量格式,这意味着在任何比例下都很好看。

所以我修改了你答案的png示例 您的png示例并不是一个完整的程序——doc.build的参数没有定义,样式没有定义,缺少一些导入,等等。但是它已经足够接近于获得一些意图并使其工作

编辑——我刚刚注意到这个示例实际上是Larry示例的修改版本,因此仍然非常有价值,因为它在某些方面比这个更全面

在我修复了这些问题并获得了一些输出后,我添加了一个选项,可以使用png或pdf,以便您可以看到差异。下面的程序将创建两个不同的pdf文件,您可以自己比较结果

import cStringIO
from matplotlib import pyplot as plt
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Image, Flowable
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet

from pdfrw import PdfReader, PdfDict
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerl

styles = getSampleStyleSheet()
style = styles['Normal']

def form_xo_reader(imgdata):
    page, = PdfReader(imgdata).pages
    return pagexobj(page)


class PdfImage(Flowable):
    def __init__(self, img_data, width=200, height=200):
        self.img_width = width
        self.img_height = height
        self.img_data = img_data

    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()
        img = self.img_data
        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()

def make_report(outfn, use_pdfrw):
    fig = plt.figure(figsize=(4, 3))
    plt.plot([1,2,3,4],[1,4,9,26])
    plt.ylabel('some numbers')
    imgdata = cStringIO.StringIO()
    fig.savefig(imgdata, format='pdf' if use_pdfrw else 'png')
    imgdata.seek(0)
    reader = form_xo_reader if use_pdfrw else ImageReader
    image = reader(imgdata)

    doc = SimpleDocTemplate(outfn)
    style = styles["Normal"]
    story = [Spacer(0, inch)]
    img = PdfImage(image, width=200, height=200)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    story.append(img)

    for i in range(10):
        bogustext = ("Paragraph number %s. " % i)
        p = Paragraph(bogustext, style)
        story.append(p)
        story.append(Spacer(1,0.2*inch))

    doc.build(story)

make_report("hello_png.pdf", False)
make_report("hello_pdf.pdf", True)
这种方法的缺点是什么? 第一个明显的缺点是现在需要pdfrw,但这可以从PyPI获得

另一个缺点是,如果将大量matplotlib绘图放入文档中,我认为这种技术会复制字体等资源,因为我认为reportlab不够聪明,不会注意到重复的内容

我相信这个问题可以通过将所有绘图输出到来解决。我实际上还没有用matplotlib尝试过,但pdfrw完全可以转换


因此,如果你有很多绘图,并且它使你的最终PDF太大,你可以研究一下,或者只是尝试一个PDF优化器,看看它是否有帮助。在任何情况下,对于不同的一天,这是一个不同的问题。

这是一个非常好的观点。绘图轴标签被呈现为文本,证明你是正确的-pdfrw确实保持了e pdf中的实际信息。我对此不太确定。谢谢,pdf文档太差了。用Python创建pdf处于一种令人遗憾的状态…这很好,质量上有多大的差异。感谢分享。非常感谢Patrick的pdfrw代码和他的示例(这里和pdfrw)我注意到他关于在插入多个绘图时可能使用matplotlib PdfPages来减小PDF大小的评论,因此我在这里给出的示例的基础上添加了一个使用此想法的新答案。请参阅:感谢代码!我在尝试使用方法drawOn。我应该向它的第一个参数传递什么?