344 lines
11 KiB
Python
344 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Report Template - Complete example of a professional multi-page report
|
|
|
|
This template demonstrates:
|
|
- Cover page
|
|
- Table of contents
|
|
- Multiple sections with headers
|
|
- Charts and graphs integration
|
|
- Tables with data
|
|
- Headers and footers
|
|
- Professional styling
|
|
"""
|
|
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.lib.units import inch
|
|
from reportlab.lib import colors
|
|
from reportlab.platypus import (
|
|
BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer,
|
|
Table, TableStyle, PageBreak, KeepTogether, TableOfContents
|
|
)
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
|
from reportlab.graphics.shapes import Drawing
|
|
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
|
from reportlab.graphics.charts.linecharts import HorizontalLineChart
|
|
from datetime import datetime
|
|
|
|
|
|
def header_footer(canvas, doc):
|
|
"""Draw header and footer on each page (except cover)"""
|
|
canvas.saveState()
|
|
|
|
# Skip header/footer on cover page (page 1)
|
|
if doc.page > 1:
|
|
# Header
|
|
canvas.setFont('Helvetica', 9)
|
|
canvas.setFillColor(colors.grey)
|
|
canvas.drawString(inch, letter[1] - 0.5*inch, "Quarterly Business Report")
|
|
canvas.line(inch, letter[1] - 0.55*inch, letter[0] - inch, letter[1] - 0.55*inch)
|
|
|
|
# Footer
|
|
canvas.drawString(inch, 0.5*inch, f"Generated: {datetime.now().strftime('%B %d, %Y')}")
|
|
canvas.drawRightString(letter[0] - inch, 0.5*inch, f"Page {doc.page - 1}")
|
|
|
|
canvas.restoreState()
|
|
|
|
|
|
def create_report(filename, report_data):
|
|
"""
|
|
Create a comprehensive business report.
|
|
|
|
Args:
|
|
filename: Output PDF filename
|
|
report_data: Dict containing report information
|
|
{
|
|
'title': 'Report Title',
|
|
'subtitle': 'Report Subtitle',
|
|
'author': 'Author Name',
|
|
'date': 'Date',
|
|
'sections': [
|
|
{
|
|
'title': 'Section Title',
|
|
'content': 'Section content...',
|
|
'subsections': [...],
|
|
'table': {...},
|
|
'chart': {...}
|
|
},
|
|
...
|
|
]
|
|
}
|
|
"""
|
|
# Create document with custom page template
|
|
doc = BaseDocTemplate(filename, pagesize=letter,
|
|
rightMargin=72, leftMargin=72,
|
|
topMargin=inch, bottomMargin=inch)
|
|
|
|
# Define frame for content
|
|
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 0.5*inch, id='normal')
|
|
|
|
# Create page template with header/footer
|
|
template = PageTemplate(id='normal', frames=[frame], onPage=header_footer)
|
|
doc.addPageTemplates([template])
|
|
|
|
# Get styles
|
|
styles = getSampleStyleSheet()
|
|
|
|
# Custom styles
|
|
title_style = ParagraphStyle(
|
|
'ReportTitle',
|
|
parent=styles['Title'],
|
|
fontSize=28,
|
|
textColor=colors.HexColor('#2C3E50'),
|
|
spaceAfter=20,
|
|
alignment=TA_CENTER,
|
|
)
|
|
|
|
subtitle_style = ParagraphStyle(
|
|
'ReportSubtitle',
|
|
parent=styles['Normal'],
|
|
fontSize=14,
|
|
textColor=colors.grey,
|
|
alignment=TA_CENTER,
|
|
spaceAfter=30,
|
|
)
|
|
|
|
heading1_style = ParagraphStyle(
|
|
'CustomHeading1',
|
|
parent=styles['Heading1'],
|
|
fontSize=18,
|
|
textColor=colors.HexColor('#2C3E50'),
|
|
spaceAfter=12,
|
|
spaceBefore=12,
|
|
)
|
|
|
|
heading2_style = ParagraphStyle(
|
|
'CustomHeading2',
|
|
parent=styles['Heading2'],
|
|
fontSize=14,
|
|
textColor=colors.HexColor('#34495E'),
|
|
spaceAfter=10,
|
|
spaceBefore=10,
|
|
)
|
|
|
|
body_style = ParagraphStyle(
|
|
'ReportBody',
|
|
parent=styles['BodyText'],
|
|
fontSize=11,
|
|
alignment=TA_JUSTIFY,
|
|
spaceAfter=12,
|
|
leading=14,
|
|
)
|
|
|
|
# Build story
|
|
story = []
|
|
|
|
# --- COVER PAGE ---
|
|
story.append(Spacer(1, 2*inch))
|
|
story.append(Paragraph(report_data['title'], title_style))
|
|
story.append(Paragraph(report_data.get('subtitle', ''), subtitle_style))
|
|
story.append(Spacer(1, inch))
|
|
|
|
# Cover info table
|
|
cover_info = [
|
|
['Prepared by:', report_data.get('author', '')],
|
|
['Date:', report_data.get('date', datetime.now().strftime('%B %d, %Y'))],
|
|
['Period:', report_data.get('period', 'Q4 2023')],
|
|
]
|
|
|
|
cover_table = Table(cover_info, colWidths=[2*inch, 4*inch])
|
|
cover_table.setStyle(TableStyle([
|
|
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
|
|
('ALIGN', (1, 0), (1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, -1), 11),
|
|
('TOPPADDING', (0, 0), (-1, -1), 6),
|
|
]))
|
|
|
|
story.append(cover_table)
|
|
story.append(PageBreak())
|
|
|
|
# --- TABLE OF CONTENTS ---
|
|
toc = TableOfContents()
|
|
toc.levelStyles = [
|
|
ParagraphStyle(name='TOCHeading1', fontSize=14, leftIndent=20, spaceBefore=10, spaceAfter=5),
|
|
ParagraphStyle(name='TOCHeading2', fontSize=12, leftIndent=40, spaceBefore=3, spaceAfter=3),
|
|
]
|
|
|
|
story.append(Paragraph("Table of Contents", heading1_style))
|
|
story.append(toc)
|
|
story.append(PageBreak())
|
|
|
|
# --- SECTIONS ---
|
|
for section in report_data.get('sections', []):
|
|
# Section heading
|
|
section_title = section['title']
|
|
story.append(Paragraph(f'<a name="{section_title}"/>{section_title}', heading1_style))
|
|
|
|
# Add to TOC
|
|
toc.addEntry(0, section_title, doc.page)
|
|
|
|
# Section content
|
|
if 'content' in section:
|
|
for para in section['content'].split('\n\n'):
|
|
if para.strip():
|
|
story.append(Paragraph(para.strip(), body_style))
|
|
|
|
story.append(Spacer(1, 0.2*inch))
|
|
|
|
# Subsections
|
|
for subsection in section.get('subsections', []):
|
|
story.append(Paragraph(subsection['title'], heading2_style))
|
|
|
|
if 'content' in subsection:
|
|
story.append(Paragraph(subsection['content'], body_style))
|
|
|
|
story.append(Spacer(1, 0.1*inch))
|
|
|
|
# Add table if provided
|
|
if 'table_data' in section:
|
|
table = create_section_table(section['table_data'])
|
|
story.append(table)
|
|
story.append(Spacer(1, 0.2*inch))
|
|
|
|
# Add chart if provided
|
|
if 'chart_data' in section:
|
|
chart = create_section_chart(section['chart_data'])
|
|
story.append(chart)
|
|
story.append(Spacer(1, 0.2*inch))
|
|
|
|
story.append(Spacer(1, 0.3*inch))
|
|
|
|
# Build PDF (twice for TOC to populate)
|
|
doc.multiBuild(story)
|
|
return filename
|
|
|
|
|
|
def create_section_table(table_data):
|
|
"""Create a styled table for report sections"""
|
|
data = table_data['data']
|
|
table = Table(data, colWidths=table_data.get('colWidths'))
|
|
|
|
table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
|
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 11),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
|
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
|
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
|
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
|
('FONTSIZE', (0, 1), (-1, -1), 10),
|
|
]))
|
|
|
|
return table
|
|
|
|
|
|
def create_section_chart(chart_data):
|
|
"""Create a chart for report sections"""
|
|
chart_type = chart_data.get('type', 'bar')
|
|
drawing = Drawing(400, 200)
|
|
|
|
if chart_type == 'bar':
|
|
chart = VerticalBarChart()
|
|
chart.x = 50
|
|
chart.y = 30
|
|
chart.width = 300
|
|
chart.height = 150
|
|
chart.data = chart_data['data']
|
|
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
|
|
chart.valueAxis.valueMin = 0
|
|
|
|
# Style bars
|
|
for i in range(len(chart_data['data'])):
|
|
chart.bars[i].fillColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
|
|
|
|
elif chart_type == 'line':
|
|
chart = HorizontalLineChart()
|
|
chart.x = 50
|
|
chart.y = 30
|
|
chart.width = 300
|
|
chart.height = 150
|
|
chart.data = chart_data['data']
|
|
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
|
|
|
|
# Style lines
|
|
for i in range(len(chart_data['data'])):
|
|
chart.lines[i].strokeColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
|
|
chart.lines[i].strokeWidth = 2
|
|
|
|
drawing.add(chart)
|
|
return drawing
|
|
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
report = {
|
|
'title': 'Quarterly Business Report',
|
|
'subtitle': 'Q4 2023 Performance Analysis',
|
|
'author': 'Analytics Team',
|
|
'date': 'January 15, 2024',
|
|
'period': 'October - December 2023',
|
|
'sections': [
|
|
{
|
|
'title': 'Executive Summary',
|
|
'content': """
|
|
This report provides a comprehensive analysis of our Q4 2023 performance.
|
|
Overall, the quarter showed strong growth across all key metrics, with
|
|
revenue increasing by 25% year-over-year and customer satisfaction
|
|
scores reaching an all-time high of 4.8/5.0.
|
|
|
|
Key highlights include the successful launch of three new products,
|
|
expansion into two new markets, and the completion of our digital
|
|
transformation initiative.
|
|
""",
|
|
'subsections': [
|
|
{
|
|
'title': 'Key Achievements',
|
|
'content': 'Successfully launched Product X with 10,000 units sold in first month.'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
'title': 'Financial Performance',
|
|
'content': """
|
|
The financial results for Q4 exceeded expectations across all categories.
|
|
Revenue growth was driven primarily by strong product sales and increased
|
|
market share in key regions.
|
|
""",
|
|
'table_data': {
|
|
'data': [
|
|
['Metric', 'Q3 2023', 'Q4 2023', 'Change'],
|
|
['Revenue', '$2.5M', '$3.1M', '+24%'],
|
|
['Profit', '$500K', '$680K', '+36%'],
|
|
['Expenses', '$2.0M', '$2.4M', '+20%'],
|
|
],
|
|
'colWidths': [2*inch, 1.5*inch, 1.5*inch, 1*inch]
|
|
},
|
|
'chart_data': {
|
|
'type': 'bar',
|
|
'data': [[2.5, 3.1], [0.5, 0.68], [2.0, 2.4]],
|
|
'categories': ['Q3', 'Q4']
|
|
}
|
|
},
|
|
{
|
|
'title': 'Market Analysis',
|
|
'content': """
|
|
Market conditions remained favorable throughout the quarter, with
|
|
strong consumer confidence and increasing demand for our products.
|
|
""",
|
|
'chart_data': {
|
|
'type': 'line',
|
|
'data': [[100, 120, 115, 140, 135, 150]],
|
|
'categories': ['Oct', 'Nov', 'Dec', 'Oct', 'Nov', 'Dec']
|
|
}
|
|
},
|
|
]
|
|
}
|
|
|
|
create_report("sample_report.pdf", report)
|
|
print("Report created: sample_report.pdf")
|