Graph Printing

Printing a 2D or 3D Graph to a PDF.

The Graph Printing example demonstrates how to print or export to PDF 2D and 3D graphs.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, see Qt Creator: Tutorial: Build and run.

GraphPrinter class

The printing functionality is implemented in the GraphPrinter class. The class exposes these functions:

  • The generatePDF function, which works as follows.
    • Sets up the output PDF file.

      The function instantiates QPdfWriter with a "graph.pdf" file pointed in the specified folder. The function also specifies the options for the exported PDF file: title, resolution, page size, and margins.

       const QFile file = QFile(path.toLocalFile() + QStringLiteral("/graph.pdf"));
      
       QPdfWriter writer(file.fileName());
       writer.setResolution(90);
       writer.setTitle("Graph");
       writer.setPageSize(QPageSize(image.size()));
       writer.setPageMargins(QMarginsF(0, 0, 0, 0));
       writer.newPage();
      
    • Sets up image processing.

      The function creates a QPainter referring to the previously created QPdfWriter.

      To ensure the graph is printed correctly, it is scaled to the painter's viewport size with the original aspect ratio.

      The painter's rendering hint is set to lossless image rendering. After this, the function draws the image to the PDF file.

       QPainter painter(&writer);
       const QImage finalImage = image.scaled(painter.viewport().size(), Qt::KeepAspectRatio);
       painter.setRenderHint(QPainter::LosslessImageRendering);
       painter.drawImage(finalImage.rect(), finalImage);
      
  • The print function, which works like the generatePDF function, but creates a QPainter referring a QPrinter instance:
     void GraphPrinter::print(const QImage &image, const QString printerName)
     {
         QPrinterInfo printInfo = QPrinterInfo::printerInfo(printerName);
         if (printInfo.isNull()) {
             qWarning("%ls is not a valid printer", qUtf16Printable(printerName));
             return;
         }
    
         QPrinter printer(printInfo, QPrinter::HighResolution);
         printer.setOutputFormat(QPrinter::NativeFormat);
    
         QPainter painter(&printer);
         const QImage finalImage = image.scaled(painter.viewport().size(), Qt::KeepAspectRatio);
         painter.setRenderHint(QPainter::LosslessImageRendering);
         painter.drawImage(finalImage.rect(), finalImage);
    
         qInfo("printed image with %ls", qUtf16Printable(printerName));
     }
    
  • The getPrinters function returns a list of available printers.
     QStringList GraphPrinter::getPrinters()
     {
         return QPrinterInfo::availablePrinterNames();
     }
    

Application setup

In addition to the application setup code, the main.cpp file contains code that creates a new instance of the GraphPrinter class and makes it reachable from the QML code.

 GraphPrinter graphPrinter;
 viewer.rootContext()->setContextProperty("graphPrinter", &graphPrinter);

Setting up the layout and image capture

The 2D and 3D graphs are laid out in a Stacklayout. Users can navigate it with a TabBar.

 TabBar {
     id: tabBar
     anchors.top: parent.top
     anchors.left: parent.left
     anchors.right: parent.right

     TabButton {
         text: "2D Graph"
         implicitHeight: 50
     }

     TabButton {
         text: "3D Graph"
         implicitHeight: 50
     }
 }

 StackLayout {
     id: stackLayout
     anchors.top: tabBar.bottom
     anchors.bottom: parent.bottom
     width: parent.width
     currentIndex: tabBar.currentIndex

     Graph2D {}

     Graph3D {}
 }

The FolderDialog component is used to select a folder for saving an exported file. This component has no visual representation in the application layout, but its API is accessible from the current QML file.

The Button invokes a folder dialog.

 FolderDialog {
     id: dialog
     onAccepted: console.log("Saving to " + currentFolder)
 }
     ...
 Button {
     id: setFolderButton
     onClicked: dialog.open()
     text: "Set save location"
     Layout.margins: 5
 }

A custom printing dialog is created for selecting a printer. The Dialog retrieves the list of available printers and displays them in a list view.

 Dialog {
     id: printerDialog
     x: parent.width * 0.5 - width * 0.5
     y: parent.height * 0.5;
     contentHeight: 200
     contentWidth: 200

     title: qsTr("Available printers")
     modal: true

     property var item: stackLayout.itemAt(stackLayout.currentIndex)

     onOpened: {
         printerModel.clear()
         var printers = graphPrinter.getPrinters()
         printers.forEach((x,i) =>
                          printerModel.append({"name": x}))
     }
     ...
 contentItem: Item {
     id: printerItem
     height: 200
     width: parent.width
     ListView {
         id: printerListView
         height: 200
         width: 200
         clip: true

         model: printerModel
         delegate: printerDelegate
         highlight: Rectangle {color: "darkgrey"}
     }
 }

The Save to PDF button and Print button in the printing dialog run the following code:

  • Capture an image using the grabToImage method. The current graph is the Stacklayout's item at the current index.
  • In the grabToImage parameters, we specify the callback as the generatePDF or print function in the GraphPrinter class.

    For the size, the code makes the image render at a 7282 by 4096 resolution. For 3D graphs, the item must also be expanded for the duration of printing.

 Button {
     id: captureButton
     text: "Save to PDF"
     Layout.margins: 5
     property var item: stackLayout.itemAt(stackLayout.currentIndex)

     onPressed: {
         if (stackLayout.currentIndex === 1) {
             item.width = 7282
             item.height = 4096
         }
         item.grabToImage(function(result) {
             graphPrinter.generatePDF(dialog.currentFolder, result.image)
         }, Qt.size(7282, 4096))
     }

     onReleased: {
         if (stackLayout.currentIndex === 1) {
             item.width = mainView.width
             item.height = mainView.height
         }
     }
 }
     ...
 onAccepted: {
     var selectedPrinter = printerModel.get(printerListView.currentIndex)
     if (stackLayout.currentIndex === 1) {
         item.width = 7282
         item.height = 4096
     }
     item.grabToImage(function(result) {
         graphPrinter.print(result.image, selectedPrinter.name)
     }, Qt.size(7282, 4096))
 }
 onClosed: {
     if (stackLayout.currentIndex === 1) {
         item.width = mainView.width
         item.height = mainView.height
     }
 }

Example project @ code.qt.io