Загрузка объекта 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)