- Qt 6.12
- Qt Graphs
- Graph Printing
- Qt Graphs | Commercial or GPLv3

# Graph Printing

Printing a 2D or 3D graph.

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

#### Running the Example

You can 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.

- Qt Extension for Visual Studio Code

  Run the Qt: Open Qt examples command from the Command Palette, and select the example from the list. For more information, see Qt Extension for Visual Studio Code: 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.

         QFile file = QFile(path.toLocalFile());

         {
             QPdfWriter writer(file.fileName());
             writer.setResolution(90);
             writer.setTitle(u"Graph"_s);
             writer.setPageSize(QPageSize(image.size()));
             writer.setPageMargins(QMarginsF(0, 0, 0, 0));
             writer.newPage();
             paintImage(image, &writer);
         }

  - Sets up image processing.

    The static helper `paintImage` function draws the image captured from QML onto an instance of the QPaintDevice class:

    The function creates a QPainter referring to the device.

    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.

         static void paintImage(const QImage &image, QPaintDevice *paintDevice)
         {
             QPainter painter(paintDevice);
             const QImage finalImage = image.scaled(painter.viewport().size(), Qt::KeepAspectRatio);
             painter.setRenderHint(QPainter::LosslessImageRendering);
             painter.drawImage(finalImage.rect(), finalImage);
         }

    Since QPdfWriter and QPrinter inherit QPaintDevice, the helper function can be called from the `generatePDF` function and the `print` function explained below.

  The function returns a status message to be displayed in the application's message dialog, including the full path to the exported file.

- The `getPrinters` function returns a list of available printers.

       QStringList GraphPrinter::getPrinters()
       {
           return QPrinterInfo::availablePrinterNames();
       }

- The `print` function, which works like the `generatePDF` function, but creates a QPainter referring a QPrinter instance:

       QString GraphPrinter::print(const QImage &image, const QString &printerName)
       {
           QPrinterInfo printInfo = QPrinterInfo::printerInfo(printerName);
           if (printInfo.isNull())
               return tr("%1 is not a valid printer").arg(printerName);

           QPrinter printer(printInfo, QPrinter::HighResolution);
           printer.setOutputFormat(QPrinter::NativeFormat);

           paintImage(image, &printer);

           return tr("Printed to %1").arg(printerName);
       }

  The function returns a status message to be displayed in the application's message dialog.

#### 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(u"graphPrinter"_s, &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.left: parent.left
         anchors.right: parent.right

         TabButton {
             text: "2D Graph"
             implicitHeight: 48
             icon.source: checked ? "flatten_square_fill.svg" : "flatten.svg"
             icon.height: 36
             icon.width: 36
         }

         TabButton {
             text: "3D Graph"
             implicitHeight: 48
             icon.source: checked ? "box_left_fill.svg" : "box_left.svg"
             icon.height: 36
             icon.width: 36
         }
     }
     Frame {
         id: tabFrame
         anchors.left: parent.left
         anchors.right: parent.right
         anchors.top: tabBar.bottom
         anchors.bottom: parent.bottom

         StackLayout {
             id: stackLayout

             anchors.fill: parent
             currentIndex: tabBar.currentIndex

             Graph2D {
                 id: linegraph
             }

             Graph3D {
                 id: bargraph
             }
         }
     }

The FileDialog component is used to choose the PDF file to be written. This component has no visual representation in the application layout, but its API is accessible from the current QML file.

     FileDialog {
         id: dialog
         currentFolder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
         nameFilters: ["PDF files (*.pdf)"]
         defaultSuffix: "pdf"

         fileMode: FileDialog.SaveFile

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

     Dialog {
         id: printerDialog
         anchors.centerIn: parent
         contentHeight: printerListView.height
         contentWidth: printerListView.width

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

         onOpened: {
             printerModel.clear()
             var printers = graphPrinter.getPrinters()
             printers.forEach((x, i) => printerModel.append({
                                                                "name": x
                                                            }))
         }
     ...
     contentItem: Rectangle {
         id: printerItem
         height: printerListView.height
         width: printerListView.width
         color: mainView.item.theme.plotAreaBackgroundColor

         ListView {
             id: printerListView
             height: 100
             width: 200
             clip: true

             model: printerModel
             delegate: printerDelegate
             highlight: Rectangle {
                 color: mainView.item.theme.grid.subColor
             }
         }
     }

The button triggers the PDF export.

When either PDF export or printing is triggered, the following code is executed:

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

  PDF export:

       onAccepted: {
           mainView.prepareForPrint()

           mainView.item.grabToImage(function (result) {
               message.title = "Save PDF"
               message.text = "PDF saved to " + graphPrinter.generatePDF(
                           dialog.selectedFile, result.image)
               message.open()
           }, mainView.outputsize)
       }

  Print:

       onAccepted: {
           var selectedPrinter = printerModel.get(printerListView.currentIndex)
           mainView.prepareForPrint()
           mainView.item.grabToImage(function (result) {
               message.title = "Print"
               message.text = graphPrinter.print(result.image,
                                                 selectedPrinter.name)
               message.open()
           }, mainView.outputsize)
       }

       onClosed: {
           mainView.cleanAfterPrint()
       }

  For the size, the code makes the image render at 4x the actual resolution. For 3D graphs, the item must also be expanded for the duration of printing:

       function prepareForPrint() {
           if (stackLayout.currentIndex === 1) {
               var newsize = Qt.size(bargraph.width * 4, bargraph.height * 4)

               // check that we do not exceed maximum texture size
               if (newsize.width * Screen.devicePixelRatio > graphPrinter.maxTextureSize() ) {
                   // scale to 25% under max texture size to be on the safe side; some GPUs seem
                   // to glitch when using the abosulute max
                   var ratio = (newsize.width * Screen.devicePixelRatio * 1.25)
                           / graphPrinter.maxTextureSize()
                   newsize.width /= ratio
                   newsize.height /= ratio
               }
               outputsize.width = Math.round(newsize.width)
               outputsize.height = Math.round(newsize.height)

               // resize the bar graph to match the PDF output size
               item.width = outputsize.width
               item.height = outputsize.height
           } else {
               outputsize = Qt.size(linegraph.width * 4, linegraph.height * 4)
           }
       }

       function cleanAfterPrint() {
           if (stackLayout.currentIndex === 1) {
               // resize the bar graph back to the actual visual size
               item.width = stackLayout.width
               item.height = stackLayout.height
           }
       }

Example project @ code.qt.io