19. Dataviz Workshop: Data Reporting com HTML/PDF#

19.1. Objetivos do workshop#

  • Apresentar o módulo xhtml2pdf para conversão de código HTML para PDF;

  • Converter DataFrame para código HTML;

  • Desenvolver workflow básico para geração de relatórios automatizados utilizando templates HTML atualizáveis utilizando dados sobre crimes violentos letais intencionais em João Pessoa.

19.2. Ferramentas utilizadas#

  • Módulos Python

    • xhtml2pdf

    • re

    • pandas

19.3. Data reporting#

Existem diversas ferramentas para operacionalizar PDFs, seja para análise (parsing), extração, encriptação ou conversão de informações. Algumas delas, com suporte em Python são:

  • pdfminer.six

  • pyPDF2

  • reportlab

  • json2pdf

  • pymupdf

  • pikepdf

Entretanto a arte do data reporting, que consiste em gerar relatórios automatizados a partir de dados e exportá-los, principalmente em arquivos PDF ma forma de brochuras, portfólios, ou one-pagers, depende da um misto de habilidades e, às vezes, da integração de mais de uma API.

Cada uma das bibliotecas acima possuem forças e fraquezas, de maneira que, raramente, teremos à mão, soluções imediatas para gerarmos nossos relatórios.

A proposta deste capítulo é apresentar os módulos xhtml2pdf e reportlab como primeiras aproximações ao tema.

19.3.1. xhtml2pdf#

O módulo xhtml2pdf oferece uma maneira ágil de automatizar a geração de PDFs a partir de conteúdo HTML. Ele utiliza a a biblioteca reportlab como backbone.

Ele funciona com base em templates mínimos de layouts inspirados em estilos CSS. As partes principais são os objetos @page e @frame, os quais emolduram o conteúdo em página. Além disso, são disponíveis tags do tipo PDF-vendor, que habilitam o desenvolvedor a inserir informações nativas de arquivos PDF, tais como paginação, sumário e idioma.

Em termos de gráficos, o módulo fornece algumas representações visuais, tais como gráficos de linhas e de barras, mas é bastante limitado.

Algumas suas vantagens são:

  • flexibilidade para gerar múltiplos templates de página;

  • encriptação e assinatura;

  • superposição de marca d’água;

19.3.2. Instalação#

Recomenda-se utilizar pip com: pip install xhtml2pdf.

from xhtml2pdf import pisa
import pandas as pd, re

19.4. Workflow demonstrativo#

  • Primeiramente, geramos um conteúdo básico em HTML que, na prática, seria a composição de um briefing atualizável periodicamente e dados obtidos de um DataFrame.

# geração de HTML atualizável
def html_data(table):
    text = f'\
        <h2>Dados</h2>\
        <p>Mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare massa eget. Ultricies mi eget mauris pharetra et ultrices neque ornare aenean.\
        Sit amet cursus sit amet. Varius vel pharetra vel turpis nunc eget lorem. Enim lobortis scelerisque fermentum dui faucibus.\
        Nibh tellus molestie nunc non blandit massa enim nec dui. Non nisi est sit amet facilisis magna.\
        {table.to_html(index=False)}\
        </p>\
        <h2>Análise</h2>\
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\
        Viverra ipsum nunc aliquet bibendum enim facilisis. Dui nunc mattis enim ut tellus elementum sagittis vitae. Sed adipiscing diam donec adipiscing tristique.\
        Ut sem nulla pharetra diam sit. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Diam phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet.\
        Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Pharetra convallis posuere morbi leo. Et magnis dis parturient montes nascetur.\
        Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Amet justo donec enim diam vulputate ut pharetra sit amet. Tempor nec feugiat nisl pretium fusce id velit.\
        Mattis vulputate enim nulla aliquet porttitor lacus luctus accumsan. Mollis nunc sed id semper risus in hendrerit gravida rutrum. Ultrices vitae auctor eu augue ut.\
        Sit amet nisl suscipit adipiscing bibendum est ultricies integer. Urna condimentum mattis pellentesque id. Quisque non tellus orci ac auctor augue.\
        Dolor sed viverra ipsum nunc aliquet bibendum enim.\
        \n\
        Et malesuada fames ac turpis egestas. Pretium nibh ipsum consequat nisl vel pretium. Consectetur adipiscing elit ut aliquam purus.\
        Duis at consectetur lorem donec massa. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus. In iaculis nunc sed augue lacus viverra.\
        Malesuada fames ac turpis egestas maecenas pharetra convallis. Massa sed elementum tempus egestas. Turpis egestas maecenas pharetra convallis posuere morbi. \
        Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Tempus iaculis urna id volutpat.\
        \n'
    
    return text
  • Em seguida fazemos o carregamento dos dados que serão usados para compor o relatório. Na prática, poderia já ser um gráfico pós-processado.

df = pd.read_csv('../data/crimes-pb-2015-2018.csv')
df
Bairros Prevalência Arma de fogo Arma branca Outros Total de casos
0 Varadouro 15.05 44 8 4 56
1 Centro 12.34 27 14 4 45
2 Mandacaru 5.72 64 5 3 72
3 Grotão 5.68 31 4 0 35
4 Bairro das Indústrias 5.51 46 2 0 48
5 Costa do Sol 4.44 26 6 5 37
6 Distrito Industrial 4.24 5 3 0 8
7 Penha 3.89 3 0 1 3
8 Ilha do Bispo 3.63 26 1 2 29
9 Costa e Silva 3.53 24 3 1 29
10 Roger 3.47 26 5 5 36
11 Gramame 3.14 70 8 5 81
12 Varjão 3.18 49 2 3 54
13 Trincheiras 3.15 13 4 5 22
14 Padre Zé 2.02 20 0 1 21
15 Mangabeira 1.65 109 15 2 126
  • Fazemos o carregamento de um template HTML para o relatório. Podemos ter tantos modelos para quantas forem as necessidades de projeto.

# carregamento do modelo
html_report_template = '../dw/reporting/report_template.html'

!cat ../dw/reporting/report_template.html
<html>
<head>
    <style>
        @page {
            size: a4 portrait;
            /* Descomente para depuração */            
            background-image: url('../dw/reporting/watermark.pdf');
            @frame content_frame {
                left: 50pt; 
                width: 505pt; 
                top: 50pt; 
                height: 700pt;
                /*-pdf-frame-border: 1;*/
            }               
            @frame footer_frame {                    
                left: 50pt; 
                width: 505pt; 
                top: 772pt; 
                height: 30pt;                   
                -pdf-frame-content: footer_content; /* estático */
                -pdf-frame-border: 1;
            }
        }
    </style>
</head>
<body>
    <div id="footer_content">
        <p>Pág. <pdf:pagenumber> de <pdf:pagecount></p>
    </div>   
    <div id="content">
        <div id="header_info">
            <h1>Relatório: Crimes Violentos Letais Intencionais (CVLI)</h1>
            <p>Flatmetrics Analytics S/A<br>
            João Pessoa - PB - Brasil<br>
            CNPJ: 00.000.000/0001-99
            </p>
        </div>
        <div id="header_logo">
            <img src="../dw/reporting/flatmetrics_logo.png" width="120" height="50">
        </div>
    </div>
</body>
</html>
  • Por fim, escrevemos uma função de utilidade para converter o conteúdo de HTML para produzir o nosso PDF.

# função de utilidade para conversão
def html_to_pdf(html_in, pdf_out):
    
    # arquivo de saída
    outfile = open(pdf_out, 'w+b')

    # conversão
    c = pisa.CreatePDF(html_in, dest=outfile)

    # fecha arquivo
    outfile.close()

    # retorna True se houver erro; False, senão
    return c.err
  • A execução da função de utilidade é melhor disposta em script. Aqui para fins de demonstração, o meio é indiferente.

No código abaixo, fazemos uma leve inserção de conteúdo no template, modificando o código-fonte HTML e exportando a saída para o documento dw-report.pdf.

if __name__ == "__main__":
    pisa.showLogging()
    
    # inserção de dado no modelo
    with open(html_report_template) as h:
        source_html = h.read()
        cutoff = re.search('(</body>)',source_html).span()[0]
        head = source_html[:cutoff]    
        tail = source_html[cutoff:]
        mid = html_data(df)    
        source_html = head + mid + tail
    
        html_to_pdf(source_html,'../dw/reporting/pdf-output/dw-report.pdf')
        

Obs.: parte deste material é fictício. Qualquer semelhança com nomes, pessoas, factos ou situações da vida real terá sido mera coincidência.

#!open '../dw/reporting/pdf-output/dw-report.pdf'

19.5. Referências#