Python ReportLab — Table too wide for page

Question:

I’m using the ReportLab package for python to create a table in a PDF file, but the table is too wide for the page and the first and last columns cut off. Here is a sample of the code I’m using.

    t = Table(data, style=[("INNERGRID", (0,0), (-1,-1), 0.25, colors.black),("BOX", (0,0), (-1,-1), 0.25, colors.black)])

I’ve tried using splitbyRow and similar parameters but none seem to be working. How can I easily make the table fit the page?

Asked By: user3543300

||

Answers:

you could add another setting on the TableData called colWidths=[250,250]… here is a snippet of my code where i set the width and some styles as well:

DetailedWorkData = [["Detailed Work Information",""],
                    ["Procedure: XXXXX", "Planned by: XXXXXX"]]
b = Table(DetailedWorkData, colWidths=[285,285])
b.hAlign = "LEFT"
b.setStyle(TableStyle([
                       ('BACKGROUND',(0,0),(-1,0),HexColor("#C0C0C0")),
                       ('GRID',(0,1),(-1,-1),0.01*inch,(0,0,0,)),
                       ('FONT', (0,0), (-1,0), 'Helvetica-Bold')]))
Story.append(b)

If you look at the 3rd line i have a colWidths where i specify how wide i want the columns to be then i set the style a couple of lines later.

Answered By: Nick Witkoski

you can get your page width using the frame of that page.
When you use the colWidths keyword you should know how much space you have available:

table = ar.Table(data, colWidths=columnWidths)#there is also rowHeights=
table.setStyle(tableStyle) 

As the table draws itself at build, you have to configure the Table so that it uses the available width of the frame it is going to be drawn.

Those colWidths can be computed so that they use the available space.
To show this I would have to use an extended example, I’ll try without:

First you should define your own document class, that inherits from BaseDocTemplate:

class MyDocTemplate(BaseDocTemplate):
    def __init__(self, filename):
        BaseDocTemplate.__init__(self, filename)
                                 #there are more keyword 
                                 #arguments you can pass 
                                 #pagesize=A4,
                                 #leftMargin=leftMargin,
                                 #rightMargin=rightMargin,
                                 #topMargin=topMargin,
                                 #bottomMargin=bottomMargin,
                                 #title=title,
                                 #author=author,
                                 #subject=subject,
                                 #creator=creator)            

then if you create your document:

doc = MyDocTemplate(fileName)

you can get the width of a frame by a self defined function inside of your document class:

frame = doc.getFrame('FirstL')
width = frame._aW 

this function can look like:

def getFrame(self,framename,orientation="Portrait"):
    """
    returns frame
    frame._x1,frame._y1
    frame._width,frame._height
    frame._leftPadding,frame._bottomPadding
    frame._rightPadding,frame._topPadding
    """

    f = attrgetter("id")
    frame = None

    for temp in self.pageTemplates[::-1]:
        if f(temp) == framename:
            #print( temp.id )
            for frame in temp.frames:
                print( frame.id )
                if f(frame) == orientation:

                    return frame

reportlab is quite complex, I tried explaining without giving an extensive explanation of how to register page templates as a list on your document class

pageTemplates is a list you can add page templates to:

templates.append( PageTemplate(id='FirstL',frames=frameL,onPage=onFirstPage,pagesize=pagesize) )

onFirstPage is a function, that uses the following scheme:

def drawFirstPage(canv,doc):
    """
    This is the Title Page Template (Portrait Oriented)
    """
    canv.saveState()
    #set Page Size or do some other things on the canvas
    frame = doc.getFrame('FirstP')

    canv.setPageSize( frame.pagesize )
    canv.setFont(_baseFontName,doc.fontSize)

    canv.restoreState()

When you add page templates like this, the suggested function should work:

doc.addPageTemplates(templates)
Answered By: skidzo

Table is getting cropped becuase it exeeds the page width . The solution is to create custom page size for pdf.

from reportlab.platypus import  SimpleDocTemplate ,  Table

pagesize = (20 * inch, 10 * inch)
doc = SimpleDocTemplate('sample.pdf', pagesize=pagesize)
data = [ ['sarath', 'indiana', 'usa'],
        ['jose', 'indiana', 'shhs'] ] # give data as lists with lists.
table = Table(data)
elems = []
elems.append(table)
doc.build(elems)
Answered By: Sarath Chandran K

I’ve done a function that once it creates a table it checks if the width is too large to fit in the page. If it is too wide it calculates how many times that table can fit in the page and divides the table.

def dataframe_to_table(df, lista, style, alignment = 'CENTER', padding = [72,72,100,18], page_type = A4):
    
    table = Table([[Paragraph(col, ParagraphStyle('yourtitle',alignment=1)) for col in df.columns]] + df.values.tolist(), style= style, hAlign = alignment)

    if (page_type[0]-padding[0]-padding[1])-table.minWidth() < 0:
        times_spread = int(table.minWidth()/(page_type[0]-padding[0]-padding[1]))+1
        n_columns = len(df.columns)

        number_on_columns = get_number_on_columns(n_columns,times_spread)

        dataframe_to_table(df.loc[:,df.columns[0:number_on_columns[0]]], lista, style)
        min_index = 0
        max_index = number_on_columns[0]
        for i in range(times_spread-1):
            min_index = min_index + number_on_columns[i]
            max_index = max_index + number_on_columns[i+1]
            dataframe_to_table(df.loc[:,df.columns[min_index:max_index]], lista,style)

    else: 
        lista.append(table)
        lista.append(Spacer(10,10))
    return lista

This code need the function that calculates how many tables will be on each row:

def get_number_on_columns(n_columns,times_spread):

  n_with_less = n_columns%times_spread
  n_with_more = times_spread-n_with_less
  if n_with_less != 0:
    n_on_more = int(n_columns/times_spread)+1
    n_on_less = int((n_columns - n_on_more*n_with_more)/n_with_less)
  else:
    n_on_more = int(n_columns/times_spread)
    n_on_less = 0
  number_on_columns = [n_on_more]*n_with_more+[n_on_less]*n_with_less
  
  return number_on_columns

The function dataframe_to_table will append the new tables to the lista parameter. This parameter can be the story of the document, but personally i create a new variable table_together for example:

table_together = []

So when i put it in the story i use de KeepTogether from platypus:

story.append(KeepTogether(table_together))
Answered By: Iker