Hintergrund
Mein Arbeitgeber lebt zu 90% von unterschiedlichsten Berichten. Diese werden bis dato in Word geschrieben, ggf. mit Seriendruckfeldern aufgefüllt und separat Diagramme in Excel erstellt. Fast alle Kollegen sind tagelang damit beschäftigt, Berichte zu erstellen, zu formatieren und zu kontrollieren. Gerade bei größeren Berichten ist die Arbeit stupide, denn Werte werden kopiert, Diagramme erstellt, Diagramme im Bericht eingefügt, ggf. wird festgestellt, dass beim Kopieren ein Fehler unterlaufen ist und der Spaß beginnt von vorne.
Beim aktuellen Bericht sollten Standortprofile für die einzelnen Bezirke einer Stadt erstellt werden: pro Bezirk sollen zwei Seiten mit Tabellen, Kommentaren und 16 Diagrammen erstellt werden. Bei 81 Bezirken müssen also knapp 1.300 Diagramme erstellt werden. Das händische Erstellen der Diagramme per MS Office hätte bestimmt Wochen beansprucht. Die automatische Berichtsgenerierung mit PHP, MySQL, Highcharts, Javascript, Ajax und PrinceXML dauert jetzt 7 Minuten.
Technische Herausforderungen
PrinceXML nimmt ein HTML-Dokument und erstellt daraus ein druckbares PDF. Mit PrinceXML kann man über HTML und CSS Seitenumbrüche, Seitenzahlen, Seitenabstände, Fußnoten, Inhaltsverzeichnisse und andere druckrelevante Features steuern. So wird bspw. aus dieser HTML-Seite diese PDF-Datei.
Eine weitere Herausforderung ist die Verwendung von Highcharts mit PrinceXML. Warum? Highcharts wird per Javascript im Browser des Besuchers gerendert. PrinceXML kann zwar auch Javascript interpretieren, aber für Highcharts reicht die Unterstützung bislang nicht aus (erst in Version 8.2 soll Highcharts unterstützt werden). Es gibt verschiedene Ansätze dieses Problem zu lösen, denn HighCharts erzeugt eine SVG, welche durch PrinceXML problemlos dargestellt werden kann: Serverseitiges Ausführen von Highcharts mit Node.JS oder PhantomJS oder indem man CutyCapt als serverseitigen Browser verwendet, der das erzeugte SVG dann an das Skript zur weiteren Verarbeitung übergibt. Mich überzeugte keines der Ansätze, denn
- es ist zusätzliche Software notwendig, in dessen Abhängigkeit man sich begibt
- in meinem Anwendungsfall ist es problemlos möglich den Bericht im Browser anzeigen zu lassen, dort die Diagramme generieren zu lassen und die SVG-Daten in einer Datenbank zu speichern, um sie später im Bericht für PrinceXML zu verwenden.
Aufbau
Die einzelnen Schritte sind:
- Rufe alle Daten zur Erstellung der Diagramme des Berichts ab
- Erstelle eine Berichtsversion, in der Highcharts über Javascript generiert wird
- Im Bericht werden nach Erstellung der Diagramme die SVG-Daten per Ajax abgesetzt
- und durch die svgsaver.php-Datei in der Datenbank gespeichert
- Zur Erstellung des Berichts mit den SVG-Daten, werden diese von der DB abgefragt
- Die SVG-Berichtsversion enthält kein Javascript und kein Highcharts-Code mehr, sondern nur noch die SVG-Daten
- Die SVG-Berichtsversion wird von PrinceXML zur…
- … Erstellung des PDFs verwendet
svgsaver.php
Der SVG-Saver sichert lediglich die per Ajax erhaltenen Daten mit folgenden Feldern: berichts_id, container (Name des Highchartscontainers), svg (die eigentlichen SVG-Daten) und eingefuegt.
[code lang=’php‘]
$stmt = $mysqli->prepare(„INSERT INTO highcharts_svg SET berichts_id=?, container=?, svg=?, eingefuegt=NOW()“);
if (isset($_POST[„berichts_id“]) AND isset($_POST[„container“]) AND isset($_POST[„svg“])){
$berichts_id = intval($_POST[„berichts_id“]);
$svg = $_POST[„svg“];
$stmt->bind_param(‚iss‘, $berichts_id, $_POST[„container“], $svg);
$stmt->execute();
}
$stmt->close();[/code]
bericht.php
Die bericht.php muss je nachdem welche Berichtsversion angezeigt werden soll, einen Platzhalter (div-Container) für das Highcharts-Diagramm anzeigen oder die SVG-Daten aus der Datenbank. Das übernimmt die Funktion container, die dies abhängig von der Variable $show_svg realisiert:
[code lang=’php‘]function container($name, $height=270, $width=470){
global $show_svg, $berichts_id, $mysqli;
if ($show_svg){
$stmt = $mysqli->prepare(„SELECT svg FROM highcharts_svg WHERE berichts_id=? AND container=? ORDER BY eingefuegt DESC LIMIT 1“);
$stmt->bind_param(‚is‘, $berichts_id, $name);
$stmt->execute();
$stmt->bind_result($svg);
$stmt->fetch();
echo $svg;
} else {
echo ‚
‚;
}
}[/code]
Im Headbereich der HTML-Seite wird über folgenden JS-Code das SVG der generierten Diagramme per Ajax an die vorher beschriebene svgsaver.php gesendet:
[code lang=’js‘]$(window).load(function() {
for (var i = 0; i < charts.length; ++i){
$.ajax({
type: 'POST',
url: 'http://yourdomain.com/svgsaver.php',
data: {
berichts_id: 1,
container: charts[i].options.chart.renderTo,
svg: charts[i].getSVG()
},
statusCode: {
404: function() {
alert("Highcharts-Diagramme konnten in der Datenbank nicht gespeichert werden.");
}
}
});
}
});[/code]
Alle Highcharts-Diagramme müssen folglich in dem Array charts enthalten sein, weswegen das Highcharts.Chart-Objekt in dem charts-Array gespeichert wird:
[code lang=’js‘]var charts = Array();
$(function () {
$(document).ready(function() {
var i = 0;
charts[i++] = new Highcharts.Chart({
chart: {
renderTo: ‚container_1‘,
type: ‚line‘,
…[/code]
Das sollte bei der Lösung der größten Hürden helfen, denn noch detaillierter möchte ich hier nicht werden.
Nachteile
Dieser Lösungsansatz zur Verwendung von Highcharts mit PrinceXML ist sicherlich nicht der Weisheit letzter Schluss:
- Es sind immer zwei Schritte nötig, um das PDF zu erzeugen: 1. Aufrufen der Highcharts-Berichtsversion, um die SVG zu erzeugen und 2. PrinceXML mit der SVG-Version aufrufen.
- Die Diagramme werden im Browser und nicht auf dem Server generiert. Bei sehr vielen Diagrammen wird der Client daher stark belastet. (Für die Generierung der 1.300 Diagramme braucht der Browser auf unseren Workstations ~ 7 Minuten).
aber es funktioniert! Und wie sagte George S. Patton?
Ein guter Plan heute ist besser als ein perfekter Plan morgen.