Plotly Charts in ReportLab

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?