Add graph printing example

Shows how to print a graph to a pdf and printer

Fixes: QTBUG-124213
Change-Id: I1835b77fc5e80d065e64b69b47002932487dfe68
Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
(cherry picked from commit 91f30ef4a0)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Sakaria Pouke 2024-10-18 12:52:19 +03:00 committed by Qt Cherry-pick Bot
parent 1994506ff3
commit 1c9d3ac1c2
11 changed files with 632 additions and 1 deletions

View File

@ -12,7 +12,7 @@ project(QtGraphs
)
find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals)
find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Core Quick Gui Widgets QuickTest QuickWidgets Test Quick3D QuickShapesPrivate)
find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Core Quick Gui Widgets QuickTest QuickWidgets Test Quick3D QuickShapesPrivate PrintSupport)
macro(assertTargets)
foreach(qtTarget IN ITEMS ${ARGN})

View File

@ -11,4 +11,8 @@ if(QT_FEATURE_graphs_2d)
add_subdirectory(graphs/2d)
endif()
if(QT_FEATURE_graphs_2d AND QT_FEATURE_graphs_3d AND TARGET Qt6::PrintSupport)
add_subdirectory(graphs/graphprinting)
endif()
qt_examples_build_end()

View File

@ -0,0 +1,57 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(graphprinting LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
find_package(Qt6 COMPONENTS Core)
find_package(Qt6 COMPONENTS Gui)
find_package(Qt6 COMPONENTS Quick)
find_package(Qt6 COMPONENTS Quick3D)
find_package(Qt6 COMPONENTS Graphs)
qt_add_executable(graphprinting
main.cpp
)
set_target_properties(graphprinting PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(graphprinting PRIVATE
Qt::Core
Qt::Gui
Qt::Quick
Qt::Quick3D
Qt::Graphs
Qt::PrintSupport
)
qt6_add_qml_module(graphprinting
URI GraphPrintingExample
NO_RESOURCE_TARGET_PATH
SOURCES
graphprinter.h graphprinter.cpp
QML_FILES
qml/graphprinting/main.qml
qml/graphprinting/Graph2D.qml
qml/graphprinting/Graph3D.qml
)
install(TARGETS graphprinting
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,113 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example graphprinting
\meta tags {Graphs, GraphsView, LineSeries, Bars3D}
\examplecategory {Data Visualization}
\title Graph Printing
\ingroup qtgraphs_qmlexamples
\ingroup qtgraphs_qmlexamples_3D
\brief Printing a 2D or 3D Graph to a PDF.
The \e {Graph Printing} example demonstrates how to print or export to PDF
2D and 3D graphs.
\image graphprinting-example.png
\include examples-run.qdocinc
\section1 GraphPrinter class
The printing functionality is implemented in the \c GraphPrinter class. The
class exposes these functions:
\list
\li The \c generatePDF function, which works as follows.
\list
\li Sets up the output PDF file.
The function instantiates \l 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.
\snippet graphprinting/graphprinter.cpp 0
\li Sets up image processing.
The function creates a \l QPainter referring to the previously
created \l 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.
\snippet graphprinting/graphprinter.cpp 1
\endlist
\li The \c print function, which works like the \c generatePDF function,
but creates a \l QPainter referring a \QPrinter instance:
\snippet graphprinting/graphprinter.cpp 2
\li The \c getPrinters function returns a list of available printers.
\snippet graphprinting/graphprinter.cpp 3
\endlist
\section1 Application setup
In addition to the application setup code, the \c {main.cpp} file contains
code that creates a new instance of the \l {GraphPrinter class} and makes
it reachable from the QML code.
\snippet graphprinting/main.cpp 0
\section1 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.
\snippet graphprinting/qml/graphprinting/main.qml 0
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.
\snippet graphprinting/qml/graphprinting/main.qml 1.1
\dots
\snippet graphprinting/qml/graphprinting/main.qml 1.2
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.
\snippet graphprinting/qml/graphprinting/main.qml 2.1
\dots
\snippet graphprinting/qml/graphprinting/main.qml 2.2
The \uicontrol {Save to PDF} button and \uicontrol {Print} button in the
printing dialog run the following code:
\list
\li Capture an image using the \c grabToImage method.
The current graph is the Stacklayout's item at the current index.
\li In the \c grabToImage parameters, we specify the callback as the
\c generatePDF or \c print function in the \c 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.
\endlist
\snippet graphprinting/qml/graphprinting/main.qml 3.1
\dots
\snippet graphprinting/qml/graphprinting/main.qml 3.2
*/

View File

@ -0,0 +1,62 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "graphprinter.h"
#include <QtGui/qtransform.h>
#include <QtPrintSupport>
GraphPrinter::GraphPrinter(QObject *parent)
: QObject(parent)
{}
GraphPrinter::~GraphPrinter() {}
void GraphPrinter::generatePDF(const QUrl &path, const QImage &image)
{
//! [0]
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();
//! [0]
//! [1]
QPainter painter(&writer);
const QImage finalImage = image.scaled(painter.viewport().size(), Qt::KeepAspectRatio);
painter.setRenderHint(QPainter::LosslessImageRendering);
painter.drawImage(finalImage.rect(), finalImage);
//! [1]
qInfo("printed PDF to %ls", qUtf16Printable(file.fileName()));
}
//! [2]
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));
}
//! [2]
//! [3]
QStringList GraphPrinter::getPrinters()
{
return QPrinterInfo::availablePrinterNames();
}
//! [3]

View File

@ -0,0 +1,24 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef GRAPHPRINTER_H
#define GRAPHPRINTER_H
#include <QtCore/qfile.h>
#include <QtGui>
#include <QtPrintSupport>
#include <QtQml/qqmlregistration.h>
class GraphPrinter : public QObject {
Q_OBJECT
QML_ELEMENT
public:
explicit GraphPrinter(QObject *parent = 0);
~GraphPrinter() override;
Q_INVOKABLE void generatePDF(const QUrl &path, const QImage &image);
Q_INVOKABLE void print(const QImage &image, const QString printerName);
Q_INVOKABLE QStringList getPrinters();
};
#endif // GRAPHPRINTER_H

View File

@ -0,0 +1,38 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtGui/qguiapplication.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
#include <QtQuick/qquickview.h>
#include "graphprinter.h"
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView viewer;
// The following are needed to make examples run without having to install the
// module in desktop environments.
#ifdef Q_OS_WIN
QString extraImportPath(QStringLiteral("%1/../../../../%2"));
#else
QString extraImportPath(QStringLiteral("%1/../../../%2"));
#endif
viewer.engine()->addImportPath(extraImportPath.arg(
QGuiApplication::applicationDirPath(), QString::fromLatin1("qml")));
viewer.setTitle(QStringLiteral("Graph Printing"));
//! [0]
GraphPrinter graphPrinter;
viewer.rootContext()->setContextProperty("graphPrinter", &graphPrinter);
//! [0]
viewer.setMinimumSize({1280, 720});
viewer.setSource(QUrl("qrc:/qml/graphprinting/main.qml"));
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
viewer.setColor("black");
viewer.show();
return app.exec();
}

View File

@ -0,0 +1,80 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtGraphs
Rectangle {
id: graphContainer
width: 1280
height: 720
color: "white"
GraphsView {
anchors.fill: parent
anchors.margins: 16
theme: GraphsTheme {
grid.mainColor: "darkgrey"
grid.subColor: "lightgrey"
labelTextColor: "black"
plotAreaBackgroundColor: "white"
backgroundColor: "white"
}
axisX: ValueAxis {
max: 5
tickInterval: 1
subTickCount: 9
labelDecimals: 1
}
axisY: ValueAxis {
max: 10
tickInterval: 1
subTickCount: 4
labelDecimals: 1
}
//! [linegraph]
//! [linemarker]
component Marker : Rectangle {
width: 8
height: 8
color: "#ffffff"
radius: width * 0.5
border.width: 4
border.color: "#000000"
}
//! [linemarker]
//! [lineseries1]
LineSeries {
id: lineSeries1
width: 4
pointDelegate: Marker { }
color: "black"
XYPoint { x: 0; y: 0 }
XYPoint { x: 1; y: 2.1 }
XYPoint { x: 2; y: 3.3 }
XYPoint { x: 3; y: 2.1 }
XYPoint { x: 4; y: 4.9 }
XYPoint { x: 5; y: 3.0 }
}
//! [lineseries1]
//! [lineseries2]
LineSeries {
id: lineSeries2
width: 4
pointDelegate: Marker { }
color: "black"
XYPoint { x: 0; y: 5.0 }
XYPoint { x: 1; y: 3.3 }
XYPoint { x: 2; y: 7.1 }
XYPoint { x: 3; y: 7.5 }
XYPoint { x: 4; y: 6.1 }
XYPoint { x: 5; y: 3.2 }
}
//! [lineseries2]
}
}

View File

@ -0,0 +1,63 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtGraphs
Item {
id: graphContainer
width: 1280
height: 720
Bars3D {
anchors.fill: parent
msaaSamples: 8
cameraPreset: Graphs3D.CameraPreset.IsometricLeftHigh
theme: GraphsTheme {
backgroundColor: "white"
plotAreaBackgroundVisible: false
grid.mainColor: "black"
labelFont.pointSize: 20
labelBackgroundVisible: false
}
Bar3DSeries {
id: series
itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel"
baseGradient: gradient
colorStyle: GraphsTheme.ColorStyle.RangeGradient
ItemModelBarDataProxy {
id: barProxy
itemModel: ListModel {
ListElement{ coords: "0,0"; data: "20.0/10.0/4.75"; }
ListElement{ coords: "1,0"; data: "21.1/10.3/3.00"; }
ListElement{ coords: "0,1"; data: "20.2/11.2/3.55"; }
ListElement{ coords: "1,1"; data: "21.3/11.5/3.03"; }
ListElement{ coords: "0,2"; data: "20.2/12.3/3.37"; }
ListElement{ coords: "1,2"; data: "21.1/12.4/2.98"; }
ListElement{ coords: "0,3"; data: "20.7/13.3/5.34"; }
ListElement{ coords: "1,3"; data: "21.5/13.2/4.54"; }
ListElement{ coords: "0,4"; data: "20.6/15.0/6.01"; }
ListElement{ coords: "1,4"; data: "21.3/14.6/5.83"; }
}
rowRole: "coords"
columnRole: "coords"
valueRole: "data"
rowRolePattern: /(\d),\d/
columnRolePattern: /(\d),(\d)/
valueRolePattern: /^([^\/]*)\/([^\/]*)\/(.*)$/
rowRoleReplace: "\\1"
columnRoleReplace: "\\2"
valueRoleReplace: "\\3"
}
Gradient {
id: gradient
GradientStop { position: 1.0; color: "#5000FF" }
GradientStop { position: 0.0; color: "#2000FF" }
}
}
}
}

View File

@ -0,0 +1,190 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Fusion
import QtQuick.Dialogs
Item {
id: mainView
width: 1280
height: 720
//! [0]
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 {}
}
//! [0]
RowLayout {
id: rowLayout
anchors.bottom: parent.bottom
anchors.left: parent.left
//! [1.2]
Button {
id: setFolderButton
onClicked: dialog.open()
text: "Set save location"
Layout.margins: 5
}
//! [1.2]
//! [3.1]
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
}
}
}
//! [3.1]
Button {
id: printButton
text: "Send to printer"
Layout.margins: 5
onPressed: printerDialog.open()
}
}
//! [1.1]
FolderDialog {
id: dialog
onAccepted: console.log("Saving to " + currentFolder)
}
//! [1.1]
//! [2.1]
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}))
}
//! [2.1]
//! [3.2]
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
}
}
//! [3.2]
Component {
id : printerDelegate
Rectangle {
width: 200; height: 40
color: "transparent"
border.color: "black"
Text {
padding: 5
text: qsTr("<b>%1</b>").arg(name)
color: "white"
}
MouseArea {
anchors.fill: parent
onClicked: printerListView.currentIndex = index
}
}
}
//! [2.2]
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"}
}
}
//! [2.2]
footer: DialogButtonBox {
Button {
text: "Print"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
standardButtons: Dialog.Cancel
}
}
ListModel {
id: printerModel
}
}