简体   繁体   中英

Python ReportLab -- Table too wide for page

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?

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)

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)

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.

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.

from reportlab.platypus import Table, Paragraph

def df2table(df, style, alignment = 'CENTER', padding = [72,72,100,18], page_type = A4):

    table=  Table([[Paragraph(col) 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)

        table = [df2table(df.loc[:,df.columns[0:number_on_columns[0]]],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]
            table_small = df2table(df.loc[:,df.columns[min_index:max_index]]
            table.append(table_small,style))

     return table

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 df2table will return a list of tables if the table needs to be split up, otherwise it will return jus a Table. So if you get by this manner the tables and don't know whether it is a table or many i write the tables in the document by a simple function that goes like this:

def put_tables_in_doc(tablas,story):

  if type(tablas) == list:
    for tabla in tablas:
      story.append(tabla)
      story.append(Spacer(10,10))
  else:
    story.append(tablas)

And in the main code you call all this just like this:

tabla_fallos = data_to_check.loc[:,['TIME','REBOOT_ERRORSFAILS','ELECTRIC_ERRORS']]
story.append(Paragraph('Error table:', styles['Heading2']))
tabla = df2table(tabla_fallos, style, table_alingment)
put_tables_in_doc(tabla,story)

To finalize i'll comment that it is not flawless, if the name of the title is longer than the width it will enter on a loop and raise an error. It probably has more flaws but it has worked for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM