Has anyone managed to include plotly charts in reportLab pdf ? I am trying to include plots but whenever I include the code, the whole pdf report does not render.
See my code below:
# Import packages
import pandas as pd
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QListWidget, QPushButton, QFormLayout, QWidget
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.platypus import Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Spacer, Paragraph
class ReportGenerator(QMainWindow):
def __init__(self, dataframe):
super().__init__()
self.dataframe = dataframe
self.init_ui()
def init_ui(self):
self.setWindowTitle("PDF Report Generator")
self.setGeometry(200, 200, 300, 250)
main_layout = QFormLayout()
widget = QWidget(self)
widget.setLayout(main_layout)
self.setCentralWidget(widget)
label1 = QLabel("Client:", self)
label1.setStyleSheet("font-weight: bold;")
self.client_input = QListWidget(self)
label2 = QLabel("Region:", self)
label2.setStyleSheet("font-weight: bold;")
self.region_input = QListWidget(self)
label3 = QLabel("Country:", self)
label3.setStyleSheet("font-weight: bold;")
self.country_input = QListWidget(self)
label4 = QLabel("City:", self)
label4.setStyleSheet("font-weight: bold;")
self.city_input = QListWidget(self)
label5 = QLabel("Ownership Type:", self)
label5.setStyleSheet("font-weight: bold;")
self.ownership_input = QListWidget(self)
main_layout.addRow(label1, self.client_input)
main_layout.addRow(label2, self.region_input)
main_layout.addRow(label3, self.country_input)
main_layout.addRow(label4, self.city_input)
main_layout.addRow(label5, self.ownership_input)
button = QPushButton("Generate Report", self)
button.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
button.clicked.connect(self.generate_report)
main_layout.addRow(button)
self.adjustSize()
self.client_input.setSelectionMode(QListWidget.ExtendedSelection)
self.region_input.setSelectionMode(QListWidget.ExtendedSelection)
self.country_input.setSelectionMode(QListWidget.ExtendedSelection)
self.city_input.setSelectionMode(QListWidget.ExtendedSelection)
self.ownership_input.setSelectionMode(QListWidget.ExtendedSelection)
self.client_input.addItems(self.dataframe['Client'].unique())
self.client_input.itemSelectionChanged.connect(self.populate_regions)
self.region_input.itemSelectionChanged.connect(self.populate_countries)
self.country_input.itemSelectionChanged.connect(self.populate_cities)
self.city_input.itemSelectionChanged.connect(self.populate_ownership_types)
def populate_regions(self):
selected_clients = [item.text() for item in self.client_input.selectedItems()]
regions = self.dataframe.loc[self.dataframe['Client'].isin(selected_clients), 'Region'].unique()
self.region_input.clear()
self.region_input.addItems(regions)
def populate_countries(self):
selected_clients = [item.text() for item in self.client_input.selectedItems()]
selected_regions = [item.text() for item in self.region_input.selectedItems()]
countries = self.dataframe.loc[
(self.dataframe['Client'].isin(selected_clients)) &
(self.dataframe['Region'].isin(selected_regions)),
'Country'
].unique()
self.country_input.clear()
self.country_input.addItems(countries)
def populate_cities(self):
selected_clients = [item.text() for item in self.client_input.selectedItems()]
selected_regions = [item.text() for item in self.region_input.selectedItems()]
selected_countries = [item.text() for item in self.country_input.selectedItems()]
cities = self.dataframe.loc[
(self.dataframe['Client'].isin(selected_clients)) &
(self.dataframe['Region'].isin(selected_regions)) &
(self.dataframe['Country'].isin(selected_countries)),
'City'
].unique()
self.city_input.clear()
self.city_input.addItems(cities)
def populate_ownership_types(self):
selected_clients = [item.text() for item in self.client_input.selectedItems()]
selected_regions = [item.text() for item in self.region_input.selectedItems()]
selected_countries = [item.text() for item in self.country_input.selectedItems()]
selected_cities = [item.text() for item in self.city_input.selectedItems()]
ownership_types = self.dataframe.loc[
(self.dataframe['Client'].isin(selected_clients)) &
(self.dataframe['Region'].isin(selected_regions)) &
(self.dataframe['Country'].isin(selected_countries)) &
(self.dataframe['City'].isin(selected_cities)),
'Ownership Type'
].unique()
self.ownership_input.clear()
self.ownership_input.addItems(ownership_types)
def generate_report(self):
selected_clients = [item.text() for item in self.client_input.selectedItems()]
selected_regions = [item.text() for item in self.region_input.selectedItems()]
selected_countries = [item.text() for item in self.country_input.selectedItems()]
selected_cities = [item.text() for item in self.city_input.selectedItems()]
selected_ownership_types = [item.text() for item in self.ownership_input.selectedItems()]
pdf_filename = "report.pdf"
c = canvas.Canvas(pdf_filename, pagesize=letter)
c.setFont("Helvetica-Bold", 12)
c.drawString(50, 700, "Report:")
c.drawString(50, 680, "This is your customized report.")
for ownership_type in selected_ownership_types:
c.showPage() # Create a new page for each ownership type
# Get the filtered dataframe based on user selections and ownership type
filtered_df = self.dataframe.loc[
(self.dataframe['Client'].isin(selected_clients)) &
(self.dataframe['Region'].isin(selected_regions)) &
(self.dataframe['Country'].isin(selected_countries)) &
(self.dataframe['City'].isin(selected_cities)) &
(self.dataframe['Ownership Type'] == ownership_type)
]
# Get the page width and height
page_width, page_height = letter
# Set the card dimensions and coordinates
card_width = page_width - 2 * 5
card_height = 60
card_x = 5
card_y = page_height - 5 - card_height # Adjust the y-coordinate to position the card closer to the top of the page
# Draw the card with the 'bldg_name_new' on the top of the page
c.setFillColorRGB(136 / 255, 137 / 255, 141 / 255) # Background color
c.rect(card_x, card_y, card_width, card_height, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica-Bold", 16)
bldg_name = filtered_df['bldg_name_new'].values[0]
bldg_name_width = c.stringWidth(bldg_name, "Helvetica-Bold", 16)
# Calculate the x-coordinate to center the text within the card
text_x = card_x + (card_width - bldg_name_width) / 2
text_y = card_y + (card_height - 16) / 2 + 8 # Adjust the y-coordinate to vertically center the text
c.drawString(text_x, text_y, bldg_name)
# Add the "Total Area (SQM)" card below the bldg_name_new card
total_area_card_height = 40 # Increased height to accommodate the break
total_area_card_y = card_y - total_area_card_height - 10 # Adjust the y-coordinate to position the card below the bldg_name_new card
total_area_card_width = 84 # Set the width of the card to 84 pixels
# Draw the card with the title "Total Area (SQM)"
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.roundRect(card_x, total_area_card_y, total_area_card_width, total_area_card_height, radius=5, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica", 10) # Smaller font size for the title
# Calculate the x-coordinate to center the text within the card
title = "Total Area (m\u00b2)"
title_width = c.stringWidth(title, "Helvetica", 10)
title_x = card_x + (total_area_card_width - title_width) / 2
title_y = total_area_card_y + total_area_card_height - 15 # Adjust the y-coordinate to position the title below the top of the card
c.drawString(title_x, title_y, title)
# Get the sum of rentable area for the particular building
total_area = filtered_df['Rentable Area'].sum()
# Draw the total area value
value = f"{total_area:,.0f}"
c.setFont("Helvetica-Bold", 16) # Larger font size and bold for the value
value_width = c.stringWidth(value, "Helvetica-Bold", 16)
value_x = card_x + (total_area_card_width - value_width) / 2
value_y = title_y - 20 # Adjust the y-coordinate to position the value below the title
c.drawString(value_x, value_y, value)
# Add the "Headcount" card adjacent to the "Total Area (SQM)" card
headcount_card_x = card_x + total_area_card_width + 20 # Adjust the x-coordinate to position the "Headcount" card next to the "Total Area (SQM)" card
# Draw the "Headcount" card
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.roundRect(headcount_card_x, total_area_card_y, total_area_card_width, total_area_card_height, radius=5, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica", 10) # Smaller font size for the title
# Calculate the x-coordinate to center the text within the card
headcount_title = "Headcount"
headcount_title_width = c.stringWidth(headcount_title, "Helvetica", 10)
headcount_title_x = headcount_card_x + (total_area_card_width - headcount_title_width) / 2
headcount_title_y = total_area_card_y + total_area_card_height - 15 # Adjust the y-coordinate to position the title below the top of the card
c.drawString(headcount_title_x, headcount_title_y, headcount_title)
# Get the sum of headcount for the particular building
headcount_sum = filtered_df['Headcount'].sum()
# Draw the headcount value
headcount_value = str(headcount_sum)
c.setFont("Helvetica-Bold", 16) # Larger font size and bold for the value
headcount_value_width = c.stringWidth(headcount_value, "Helvetica-Bold", 16)
headcount_value_x = headcount_card_x + (total_area_card_width - headcount_value_width) / 2
headcount_value_y = headcount_title_y - 20 # Adjust the y-coordinate to position the value below the title
c.drawString(headcount_value_x, headcount_value_y, headcount_value)
# Add the "Workstations" card adjacent to the "Headcount" card
workstation_card_x = headcount_card_x + total_area_card_width + 20 # Adjust the x-coordinate to position the "Workstations" card next to the "Headcount" card
# Draw the "Workstations" card
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.roundRect(workstation_card_x, total_area_card_y, total_area_card_width, total_area_card_height, radius=5, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica", 10) # Smaller font size for the title
# Calculate the x-coordinate to center the text within the card
workstation_title = "Workstations"
workstation_title_width = c.stringWidth(workstation_title, "Helvetica", 10)
workstation_title_x = workstation_card_x + (total_area_card_width - workstation_title_width) / 2
workstation_title_y = total_area_card_y + total_area_card_height - 15 # Adjust the y-coordinate to position the title below the top of the card
c.drawString(workstation_title_x, workstation_title_y, workstation_title)
# Get the sum of seatcount for the particular building
seatcount_sum = filtered_df['Seatcount'].sum()
# Draw the seatcount value
seatcount_value = str(seatcount_sum)
c.setFont("Helvetica-Bold", 16) # Larger font size and bold for the value
seatcount_value_width = c.stringWidth(seatcount_value, "Helvetica-Bold", 16)
seatcount_value_x = workstation_card_x + (total_area_card_width - seatcount_value_width) / 2
seatcount_value_y = workstation_title_y - 20 # Adjust the y-coordinate to position the value below the title
c.drawString(seatcount_value_x, seatcount_value_y, seatcount_value)
# Add the "Annual Rent ($)" card adjacent to the "Workstations" card
rent_card_x = workstation_card_x + total_area_card_width + 20 # Adjust the x-coordinate to position the "Annual Rent ($)" card next to the "Workstations" card
# Draw the "Annual Rent ($)" card
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.roundRect(rent_card_x, total_area_card_y, total_area_card_width, total_area_card_height, radius=5, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica", 10) # Smaller font size for the title
# Calculate the x-coordinate to center the text within the card
rent_title = "Annual Rent ($)"
rent_title_width = c.stringWidth(rent_title, "Helvetica", 10)
rent_title_x = rent_card_x + (total_area_card_width - rent_title_width) / 2
rent_title_y = total_area_card_y + total_area_card_height - 15 # Adjust the y-coordinate to position the title below the top of the card
c.drawString(rent_title_x, rent_title_y, rent_title)
# Get the sum of annual rent for the particular building
rent_sum = filtered_df['Annual Rent'].sum()
# Draw the annual rent value
rent_value = f"{rent_sum:,.0f}"
c.setFont("Helvetica-Bold", 16) # Larger font size and bold for the value
rent_value_width = c.stringWidth(rent_value, "Helvetica-Bold", 16)
rent_value_x = rent_card_x + (total_area_card_width - rent_value_width) / 2
rent_value_y = rent_title_y - 20 # Adjust the y-coordinate to position the value below the title
c.drawString(rent_value_x, rent_value_y, rent_value)
# Add the "Rent/WS ($)" card adjacent to the "Annual Rent ($)" card
rent_ws_card_x = rent_card_x + total_area_card_width + 20 # Adjust the x-coordinate to position the "Rent/WS ($)" card next to the "Annual Rent ($)" card
# Draw the "Rent/WS ($)" card
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.roundRect(rent_ws_card_x, total_area_card_y, total_area_card_width, total_area_card_height, radius=5, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica", 10) # Smaller font size for the title
# Calculate the x-coordinate to center the text within the card
rent_ws_title = "Rent/WS ($)"
rent_ws_title_width = c.stringWidth(rent_ws_title, "Helvetica", 10)
rent_ws_title_x = rent_ws_card_x + (total_area_card_width - rent_ws_title_width) / 2
rent_ws_title_y = total_area_card_y + total_area_card_height - 15 # Adjust the y-coordinate to position the title below the top of the card
c.drawString(rent_ws_title_x, rent_ws_title_y, rent_ws_title)
# Get the sum of annual rent/seatcount for the particular building
rent_ws_sum = filtered_df['Annual Rent'].sum() / df['Seatcount'].sum()
# Calculate the rent per workstation value
rent_ws_value = f"{rent_ws_sum:.1f}"
c.setFont("Helvetica-Bold", 16) # Larger font size and bold for the value
rent_ws_value_width = c.stringWidth(rent_ws_value, "Helvetica-Bold", 16)
rent_ws_value_x = rent_ws_card_x + (total_area_card_width - rent_ws_value_width) / 2
rent_ws_value_y = rent_ws_title_y - 20 # Adjust the y-coordinate to position the value below the title
c.drawString(rent_ws_value_x, rent_ws_value_y, rent_ws_value)
# Add the "Rent/HC ($)" card adjacent to the "Rent/WS ($)" card
rent_hc_card_x = rent_ws_card_x + total_area_card_width + 20 # Adjust the x-coordinate to position the "Rent/HC ($)" card next to the "Rent/WS ($)" card
# Draw the "Rent/HC ($)" card
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.roundRect(rent_hc_card_x, total_area_card_y, total_area_card_width, total_area_card_height, radius=5, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica", 10) # Smaller font size for the title
# Calculate the x-coordinate to center the text within the card
rent_hc_title = "Rent/HC ($)"
rent_hc_title_width = c.stringWidth(rent_hc_title, "Helvetica", 10)
rent_hc_title_x = rent_hc_card_x + (total_area_card_width - rent_hc_title_width) / 2
rent_hc_title_y = total_area_card_y + total_area_card_height - 15 # Adjust the y-coordinate to position the title below the top of the card
c.drawString(rent_hc_title_x, rent_hc_title_y, rent_hc_title)
# Get the sum of annual rent/seatcount for the particular building
rent_hc_sum = filtered_df['Annual Rent'].sum() / df['Headcount'].sum()
# Calculate the rent per headcount value
rent_hc_value = f"{rent_hc_sum:.1f}"
c.setFont("Helvetica-Bold", 16) # Larger font size and bold for the value
rent_hc_value_width = c.stringWidth(rent_hc_value, "Helvetica-Bold", 16)
rent_hc_value_x = rent_hc_card_x + (total_area_card_width - rent_hc_value_width) / 2
rent_hc_value_y = rent_hc_title_y - 20 # Adjust the y-coordinate to position the value below the title
c.drawString(rent_hc_value_x, rent_hc_value_y, rent_hc_value)
# Add the "Building Overview" card below all the other cards
building_overview_card_y = total_area_card_y - 30 # Adjust the y-coordinate to position the card below the other cards
building_overview_card_width = card_width # Set the width of the card to accommodate all 6 cards
building_overview_card_height = 20 # Set the height of the card to 30 pixels
# Draw the card with the title "Building Overview"
c.setFillColorRGB(1 / 255, 81 / 255, 81 / 255) # Background color (#015151)
c.rect(card_x, building_overview_card_y, building_overview_card_width, building_overview_card_height, fill=True)
c.setFillColor(colors.white) # Font color
c.setFont("Helvetica-Bold", 10) # Larger font size and bold for the title
# Calculate the x-coordinate to center the text within the card
building_overview_title = "BUILDING OVERVIEW"
building_overview_title_width = c.stringWidth(building_overview_title, "Helvetica-Bold", 10)
building_overview_title_x = card_x + (building_overview_card_width - building_overview_title_width) / 2
building_overview_title_y = building_overview_card_y + (building_overview_card_height - 14) / 2 + 7 # Adjust the y-coordinate to vertically center the title
c.drawString(building_overview_title_x, building_overview_title_y, building_overview_title)
# Computations table
bldg_market_rent = filtered_df['Annual Market Rent'].sum()
bldg_vacancy = filtered_df['Building Vacancy Rate'].mean() * 100
# Create a data list for the table
table_data = [
['Address', filtered_df['Address'].iloc[0]],
['Tenure', filtered_df['Tenure'].iloc[0]],
['Building Area (SQM)', f"{total_area:,.0f}"],
['Building Market Rent ($/Yr)', f"{bldg_market_rent:,.0f}"],
['Building Vacancy Rate (%)', f"{bldg_vacancy:.0f}%"],
['Building Tenants', filtered_df['Building Tenants'].iloc[0]],
['Property Type', filtered_df['Property Type'].iloc[0]],
['Green Accreditation', filtered_df['Green Accreditation'].iloc[0]],
]
# Set the table style
table_style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # Alignment
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), # First column font
('BACKGROUND', (0, 1), (-1, -1), colors.white), # Table data background color
('GRID', (0, 0), (-1, -1), 1, colors.black), # Table grid color
('FONTSIZE', (0, 1), (-1, -1), 10), # Table data font size
('LEFTPADDING', (0, 0), (-1, -1), 6), # Table data left padding
('RIGHTPADDING', (0, 0), (-1, -1), 6), # Table data right padding
])
# Create the table object and apply the style
table = Table(table_data, colWidths=[150, 200])
table.setStyle(table_style)
# Calculate the table width and height
table_width, table_height = table.wrapOn(c, 0, 0)
table_x = card_x + card_width - table_width # Align to the right side
table_y = building_overview_card_y - 10 - table_height
# Draw the table on the PDF canvas
table.drawOn(c, table_x, table_y)
# Perform visualization or further processing on the filtered dataframe
# Example: Bar chart with 'Building Name' on x-axis and 'Annual Rent' on y-axis
print(f"PDF page generated for Ownership Type: {ownership_type}")
c.save()
print(f"PDF report generated: {pdf_filename}")
if __name__ == "__main__":
# Load the initial
data = {
'Client': ['Client A', 'Client A', 'Client B', 'Client B', 'Client C', 'Client C'],
'Region': ['North', 'South', 'North', 'South', 'North', 'South'],
'Country': ['USA', 'USA', 'Canada', 'Canada', 'Mexico', 'Mexico'],
'City': ['New York', 'Los Angeles', 'Toronto', 'Vancouver', 'Mexico City', 'Cancun'],
'Ownership Type': ['Type 1', 'Type 2', 'Type 1', 'Type 2', 'Type 1', 'Type 2'],
'Building Name': ['Building 1', 'Building 2', 'Building 3', 'Building 4', 'Building 5', 'Building 6'],
'Annual Rent': [1000, 2000, 1500, 1800, 1200, 2200],
'Rentable Area': [5000, 6000, 4500, 5500, 4000, 7000],
'Latitude': [40.7128, 34.0522, 41.8781, 39.9042, 40.7128, 39.9042],
'Longitude': [-74.0060, -118.2437, -87.6298, -75.1652, -74.0060, -75.1652],
'Annual Market Rent': [1100, 2200, 1600, 2000, 1300, 2400],
'Annual Submarket Rent': [900, 1800, 1400, 1600, 1000, 2000],
'Inexpensive Submarket ERV': [950, 1900, 1300, 1500, 950, 1900],
'Expensive Submarket ERV': [1050, 2100, 1500, 1900, 1150, 2300],
'Lease Start Date': ['2021-01-01', '2021-02-01', '2021-03-01', '2021-04-01', '2021-05-01', '2021-06-01'],
'Lease Expiry Date': ['2023-12-31', '2024-01-31', '2023-12-31', '2024-02-29', '2023-12-31', '2024-03-31'],
'Headcount': [50, 60, 40, 70, 45, 55],
'Seatcount': [60, 70, 50, 80, 55, 65],
'Address': ['Address1', 'Address2', 'Address3', 'Address4', 'Address5', 'Address6'],
'Tenure': ['Tenure11', 'Tenure12', 'Tenure13', 'Tenure14', 'Tenure15', 'Tenure16'],
'Building Vacancy Rate': [0.3, 0.2, 0.6, 0.1, 0.4, 0.8],
'Building Tenants': ['Tenanats1', 'Tenanats2', 'Tenanats3', 'Tenanats4', 'Tenanats5', 'Tenanats6'],
'Property Type': ['Property', 'Property2', 'Property3', 'Property4', 'Property5', 'Property6'],
'Green Accreditation': ['Green1', 'Green2', 'Green3', 'Green4', 'Green5', 'Green6']
}
df = pd.DataFrame(data)
# Perform feature engineering on the dataframe
df['bldg_name_new'] = df['Building Name'] + " | " + df['Country'] + " | " + df['City']
df['rent_ws'] = df['Annual Rent'].sum() / df['Rentable Area'].sum()
df['rent_year'] = df['Annual Rent'].sum()
df['rent_hc'] = df['Annual Rent'].sum() / df['Headcount'].sum()
# Instantiate the ReportGenerator with the modified dataframe
app = QApplication([])
window = ReportGenerator(df)
window.setStyleSheet("background-color: #F0F0F0;")
window.showMaximized()
app.exec_()
i want to include a scattermapbox using plotly next to the left of the table that i have created at the end. whenever i try to plot, the whole report/pdf does not generate.
can anyone help with this please?