Reportlab: Tabellenumbruch

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
arren
User
Beiträge: 10
Registriert: Samstag 21. Mai 2016, 16:28

Hallo Community,

nach Stundenlangem ausprobieren und googln bin ich an die Grenzen meiner Fähigkeiten gestoßen um folgendes scheinbar schlichtes Problem zu lösen:

Ich generiere mithilfe von Reportlab eine PDF die mir u.a. eine Tabelle mit Inhalt ausgibt. Hat die Tabelle eine gewisse Anzahl an Spalten erreicht, so findet kein Umbruch statt. Die Tabelle geht "unendlich" weiter, das pdf A4 Format schneidet die Tabelle daher bis zur sichtbaren Grenze ab, der restliche Teil fehlt mir also:
Bild


Gibt es hierfür ein Befehl, mit dem ich einen neuen Tebellenumbruch im neuen Absatz starten kann? Anbei der vollständige Code:

[codebox=pys60 file=Unbenannt.txt]import time
from reportlab.platypus import Spacer, Image
from reportlab.platypus import Paragraph, Table, TableStyle, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors

doc = SimpleDocTemplate("Kenndatenauswertung.pdf", pagesize=A4,)

Daten = [['BEISPIEL', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
['Strom in mA', 100, 300, 500, 700, 900, 1100, 1300, 1500, 1700, 1900, 100, 300, 500, 700, 900, 1100, 1300, 1500, 1700, 1900],
['Spannung in V', 2, 4, 6, 8, 10, 12, 14, 16, 18,20, 2, 4, 6, 8, 10, 12, 14, 16, 18,20]]

Story = []

styles = getSampleStyleSheet()

ptext = '<font size=20>Kenndatenauswertung </font>'
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 22))

ptext = '<font size=10>%s </font>' % time.strftime("%d.%m.%Y %H:%M:%S")
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 32))

ptext = '<font size=12>Die Diodenkennlinie zeigt das Widerstandsverhalten der Diode bei unterschied-\
lichen Stroemen und Die Diodenkennlinie zeigt das Widerstandsverhalten der Diode bei unterschiedlichen Stroemen und Spannungen an. Da die Diode je nach Polung ein unterschiedliches Verhalten aufweist, besteht das \
Kennlinienfeld aus einem Durchlassbereich (Diode in Durchlassrichtung) und einen Sperrbereich \
(Diode in Sperrrichtung). Da Dioden nicht alle gleich sind, hat jede Diode eine andere Kennlinie.</font>'
Story.append(Paragraph(ptext, styles["Normal"]))
Story.append(Spacer(1, 18))

t = Table(Daten)

t.hAlign = 'LEFT'
t.spaceBefore = 10
t.spaceAfter = 10
t.setStyle(TableStyle(
[('BOX', (0,0), (-1,-1), 0.5, colors.black),
('INNERGRID', (0,0), (-1,-1), 0.5, colors.black)]))
Story.append(t)
Story.append(Spacer(1, 32))

logo = "diagramm.gif"
im = Image(logo,width=14*cm,height=14*cm,kind='proportional')
Story.append(im)
Story.append(Spacer(1, 32))



doc.build(Story)[/code]
Benutzeravatar
noisefloor
User
Beiträge: 4151
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Gibt es hierfür ein Befehl, mit dem ich einen neuen Tebellenumbruch im neuen Absatz starten kann?
Nein, gibt's nicht. Jedenfalls keinen mir bekannten. Es war wohl mal angedacht, sowas wie "splitByColumn" einzuführen, ist aber wohl nie passiert.

Reportlab kennt, wenn man mit Frames arbeitet, zwar `reportlab.platypus.flowables.KeepInFrame`, aber das funktioniert mit Tabellen auch nicht.

Abhilfe: selber den Umbruch vornehmen. Einziger kleiner Nachteil: man _muss_ dafür eine Spaltenbreite festlegen, sonst geht das nicht.

Der folgende Code bricht die Tabelle an passender Stelle um (bzw. es werden zwei Tabellen geschrieben):

Code: Alles auswählen

from math import floor, ceil
from reportlab.platypus import Table, TableStyle, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors
 
doc = SimpleDocTemplate('test.pdf', pagesize=A4,)
available_width = doc.width
col_width = 1*cm
max_number_of_cols = floor(available_width/col_width)

data = [['BEISPIEL', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20'],
        ['Strom in mA', 100, 300, 500, 700, 900, 1100, 1300, 1500, 1700, 1900, 100, 300, 500, 700, 900, 1100, 1300, 1500, 1700, 1900],
        ['Spannung in V', 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]]
 
story = []
 
styles = getSampleStyleSheet()

for i in range(0, ceil(len(data[0])/max_number_of_cols)):
    data_section = []
    for d in data:
        data_section.append(d[i*max_number_of_cols:(i+1)*max_number_of_cols])
    t = Table(data_section)
    t.hAlign = 'LEFT'
    t.spaceBefore =  10
    t.spaceAfter = 10
    t.setStyle(TableStyle(
        [('BOX', (0,0), (-1,-1), 0.5, colors.black),
         ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black)]))
    story.append(t)
doc.build(story)
Übrigens: AFAIK brechen auch Word. LibreOffice Writer & Co. lange Tabellen nicht automatisch um.

Gruß, noisefloor
arren
User
Beiträge: 10
Registriert: Samstag 21. Mai 2016, 16:28

Hi noisefloor,

vielen Dank für deinen Lösungsansatz. Diese Weise es zu lösen gefällt mir sehr gut.

Ich steh gerade noch mit der folgenden Fehlermeldung, die kommt wenn ich deinen Code probiere, auf dem Schlauch:

Code: Alles auswählen

Traceback (most recent call last):
  File "test.py", line 23, in <module>
    for i in range(0, ceil(len(data[0]) / max_number_of_cols)):
TypeError: range() integer end argument expected, got float.
Benutzeravatar
noisefloor
User
Beiträge: 4151
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

also bei mir funktioniert der Code genau so, mit Python 3.4. Benutzt du Python 2.7?

Gruß, noisefloor
arren
User
Beiträge: 10
Registriert: Samstag 21. Mai 2016, 16:28

Benutze Python 2.7, das wäre auch schon der Grund hierfür.
arren
User
Beiträge: 10
Registriert: Samstag 21. Mai 2016, 16:28

Gibt es denn an dieser Stelle eine Möglichkeit "i" float-fähig zu machen und das ganze somit für 2.7 zu ermöglichen...?!
Benutzeravatar
noisefloor
User
Beiträge: 4151
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

nee, das macht auch keinen Sinn. Du brauchst ja i auch für's Slicing und dann musst du ja auch einen Integer-Wert haben.

Du musst für Python 2.7 eine Zeile wie folgt anpassen:

Code: Alles auswählen

data_section.append(d[int(i*max_number_of_cols):int((i+1)*max_number_of_cols)])
Gruß, noisefloor
arren
User
Beiträge: 10
Registriert: Samstag 21. Mai 2016, 16:28

Danke für deine weitere Behilflichkeit, noisefloor!

Die angepasste Zeile ändert soweit an der Fehlermeldung nichts. Hmm
Benutzeravatar
noisefloor
User
Beiträge: 4151
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

dann weiß ich nicht, was du machst. Weil wenn ich diese Zeile ändere, läuft das bei mir unter Python 2.7.

Gruß, noisefloor
arren
User
Beiträge: 10
Registriert: Samstag 21. Mai 2016, 16:28

So sieht der Code letztlich bei mir aus, der die besagte Fehlermeldung rausgiebt.
Ich poste ihn mal hier, vielleicht fällt ja was auf, ansonsten vielen Dank für die geleistete Hilfestellung!!

Code: Alles auswählen

from math import floor, ceil
from reportlab.platypus import Table, TableStyle, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors

doc = SimpleDocTemplate('test.pdf', pagesize=A4, )
available_width = doc.width
col_width = 1 * cm
max_number_of_cols = floor(available_width / col_width)

data = [['BEISPIEL', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18',
         '19', '20'],
        ['Strom in mA', 100, 300, 500, 700, 900, 1100, 1300, 1500, 1700, 1900, 100, 300, 500, 700, 900, 1100, 1300,
         1500, 1700, 1900],
        ['Spannung in V', 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]]

story = []

styles = getSampleStyleSheet()

for i in range(0, ceil(len(data[0]) / max_number_of_cols)):
    data_section = []
    for d in data:
        data_section.append(d[int(i * max_number_of_cols):int((i + 1) * max_number_of_cols)])
    t = Table(data_section)
    t.hAlign = 'LEFT'
    t.spaceBefore = 10
    t.spaceAfter = 10
    t.setStyle(TableStyle(
        [('BOX', (0, 0), (-1, -1), 0.5, colors.black),
         ('INNERGRID', (0, 0), (-1, -1), 0.5, colors.black)]))
    story.append(t)
doc.build(story)
Benutzeravatar
noisefloor
User
Beiträge: 4151
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ok - habe noch eine Zeile geändert, aber hier nicht gepostet, sorry:

Code: Alles auswählen

for i in range(0, int(ceil(len(data[0])/max_number_of_cols))):
Gruß, noisefloor
Sirius3
User
Beiträge: 18220
Registriert: Sonntag 21. Oktober 2012, 17:20

Eigentlich sollte man gleich max_number_of_cols in ein int umwandeln:

Code: Alles auswählen

max_number_of_cols = int(available_width / col_width)
und mit groupby kommt man ohne dieses umständliche slicing aus:

Code: Alles auswählen

from itertools import groupby, izip, count
for _, data_section in groupby(izip(count(), *data), lambda entry: entry[0] // max_number_of_cols):
    t = Table(zip(*data_section)[1:])
    [...]
Antworten