Загрузка объекта 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_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, у меня другая часть кода ломается из-за несовместимых версий Pillow.

2 ответа

Решение

документация pdfrw отстой

Единственная причина, по которой пример pdfrw, рассмотренный в первом ответе на этот вопрос, немного клеветник, заключается в том, что документация pdfrw плохо сосет. Из-за отстойного документа автор этого примера @Larry-Meyn использовал расширение vectorpdf для rst2pdf в качестве отправной точки, и это расширение на самом деле не документировано, и имеет дело с причудами rst2pdf, а также с pdfrw (и более В общем случае, чем вам нужно, он может позволить rst2pdf отобразить произвольный прямоугольник на произвольной странице ранее существующего PDF). Удивительно, что Ларри вообще удалось заставить его работать, и моя шляпа ему снята.

Я вполне способен сказать это, потому что я являюсь автором pdfrw и внес несколько вкладов в rst2pdf, включая это расширение vectorpdf.

Но вы, вероятно, все равно хотите использовать pdfrw

Я действительно не обращал внимания на stackru до месяца назад, и сам pdfrw томился в течение нескольких лет, но я здесь и сейчас, и я думаю, что вам следует еще раз взглянуть на pdfrw, хотя документация все еще отстой,

Зачем? Потому что, если вы выводите в файл png, ваше изображение будет растеризовано, а если вы используете pdfrw, оно останется в векторном формате, что означает, что оно будет хорошо смотреться в любом масштабе.

Поэтому я изменил пример вашего ответа в png

Ваш png-пример не был достаточно полной программой - параметры doc.build не были определены, стили не были определены, в нем отсутствовали некоторые операции импорта и т. Д. Но это было достаточно близко, чтобы получить какое-то намерение и получить его. за работой.

Редактировать - я только что заметил, что этот пример на самом деле является модифицированной версией примера Ларри, так что этот пример все еще очень полезен, потому что в некоторых отношениях он немного более полнофункциональный, чем этот.

После того, как я исправил эти проблемы и получил некоторый вывод, я добавил опцию, чтобы иметь возможность использовать 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 достаточно умен, чтобы заметить дубликаты.

Я считаю, что эта проблема может быть решена путем вывода всех ваших графиков на разные страницы одного PDF. На самом деле я не пробовал это с помощью matplotlib, но pdfrw вполне способен конвертировать каждую страницу существующего pdf в отдельный поток.

Так что, если у вас много графиков и ваш окончательный PDF слишком велик, вы можете посмотреть на это или просто попробовать один из оптимизаторов PDF и посмотреть, поможет ли это. В любом случае это другая проблема для другого дня.

Я нашел 2 решения:

1: использование пакета с именем pdfrw: существует ли библиотека matplotlib для ReportLab?

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)
Другие вопросы по тегам