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?
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.
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)
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))
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 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.
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)
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))