Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtGraphs/private/customrenderer_p.h>
#include <QtGraphs/qcustomseries.h>
#if QT_CONFIG(graphs_2d_high_performance_backend)
#include <QtGraphs/qcustomseriescanvasrenderer.h>
#endif
#include <private/charthelpers_p.h>
#include <private/qcustomseries_p.h>
#include <private/qcustomseriesdata_p.h>
#include <private/qgraphsview_p.h>
QT_BEGIN_NAMESPACE
/*!
\class QCustomSeries
\inmodule QtGraphs
\brief The QCustomSeries class allows presenting customized graph types.
\since 6.11
Use QCustomSeries to create customized graphs. Think of it as a scatter graph
that lets you access custom data for each element.
QCustomSeries defines a delegate that is used to render every item added to
the graph. Each item includes user-defined data stored in a \l QVariantMap.
Index of the item in the graph is also automatically added to the data map.
The series passes this map to each element created based on the delegate.
The delegate determines how to use the data.
To map data to the render coordinates defined by the \c QGraphsView axes,
use the \l mapX and \l mapY functions.
\note Currently, individual elements in a custom series do not share
information with one another. For this reason, you can't implement a custom
line series.
*/
/*!
\qmltype CustomSeries
\nativetype QCustomSeries
\inqmlmodule QtGraphs
\brief The CustomSeries type allows presenting customized graph types.
\since 6.11
Use CustomSeries to create customized graphs. Think of it as a scatter graph
that lets you access custom data for each element.
CustomSeries defines a delegate that is used to render every item added to
the graph. Each item includes user-defined data stored in a \l QVariantMap.
Index of the item in the graph is also automatically added to the data map.
In the QML context the variant map is defined by a JavaScript object.
The series passes this map to each element created based on the delegate.
The delegate determines how to use the data.
To map data to the render coordinates defined by the \l GraphsView axes,
use the \l mapX and \l mapY functions.
\note Currently, individual elements in a custom series do not share
information with one another. For this reason, you can't implement a custom
line series.
\image graphs2d-custom.png {Bar graph with two gradient-filled bars}
The following code implements the depicted custom graph. In this case the
custom data contains two values: upper and lower. Those are then defined
in the delegate to affect the height and the y coordinate of the element.
\qml
import QtQuick
import QtGraphs
Window {
width: 640
height: 480
visible: true
GraphsView {
anchors.fill: parent
axisX: BarCategoryAxis {
categories: ["ABC", "DEF"]
}
axisY: ValueAxis {
max: 8
tickInterval: 1
}
CustomSeries {
id: custom
delegate: Rectangle {
property var data
gradient: Gradient {
GradientStop { position: 0.0; color: "lightsteelblue" }
GradientStop { position: 1.0; color: "blue" }
}
border.width: 1
x: custom.mapX(data.index + 0.5) - width * 0.5
y: custom.mapY((data.upper + data.lower) * 0.5) - height * 0.5
width: Math.abs(custom.mapX(1) - custom.mapX(0)) * 0.5
height: Math.abs(custom.mapY(data.upper) - custom.mapY(data.lower))
}
CustomSeriesData {
data: ({
upper: 5,
lower: 1
})
}
CustomSeriesData {
data: ({
upper: 6,
lower: 4
})
}
}
}
}
\endqml
*/
QCustomSeries::QCustomSeries(QObject *parent)
: QAbstractSeries(*(new QCustomSeriesPrivate()), parent)
{}
QCustomSeries::~QCustomSeries()
{
#if QT_CONFIG(graphs_2d_high_performance_backend)
Q_D(QCustomSeries);
delete d->m_customSeriesPainter;
#endif
}
QCustomSeries::QCustomSeries(QCustomSeriesPrivate &dd, QObject *parent)
: QAbstractSeries(dd, parent)
{}
QAbstractSeries::SeriesType QCustomSeries::type() const
{
return QAbstractSeries::SeriesType::Custom;
}
QList<QCustomSeriesData *> QCustomSeries::dataItems() const
{
Q_D(const QCustomSeries);
return d->m_dataItems;
}
/*!
\property QCustomSeries::delegate
The delegate is used to insert elements to QML corresponding to provided
custom data. Data is defined in the CustomSeries itself and given to
the delegated elements through the "data" property.
*/
/*!
\qmlproperty Component CustomSeries::delegate
The delegate is used to insert elements to QML corresponding to provided
custom data. Data is defined in the the CustomSeries itself and given to
the delegated elements through the "data" property.
\code
delegate: Rectangle {
property var data
Rectangle {
color: "tan"
border.color: "black"
border.width: 4
y: series.mapY(data.index + 1) - height / 2 + 1
x: series.mapX(data.median) - width / 2
height: 32
width: Math.abs(series.mapX(data.upperQuartile) - series.mapX(data.lowerQuartile))
}
}
}
\endcode
*/
QQmlComponent *QCustomSeries::delegate() const
{
Q_D(const QCustomSeries);
return d->m_delegate;
}
void QCustomSeries::setDelegate(QQmlComponent *newDelegate)
{
Q_D(QCustomSeries);
if (d->m_delegate == newDelegate) {
qCDebug(lcProperties2D) << "QCustomSeries::setDelegate. Point delegate is already set to:"
<< newDelegate;
return;
}
d->m_delegate = newDelegate;
emit delegateChanged();
emit update();
}
#if QT_CONFIG(graphs_2d_high_performance_backend) || defined(Q_QDOC)
/*!
Sets \a painter as the canvas painter renderer used to draw this series
when the high-performance canvas painter backend is in use.
QCustomSeries takes ownership of \a painter. It is adopted by the
CanvasPainter-backend renderer the next time this series is
synchronized, after which it is destroyed only by the renderer, on the
render thread -- never by QCustomSeries directly. If \a painter is
replaced or cleared before that first synchronization, or if this series
is destroyed before it, QCustomSeries deletes it itself.
*/
void QCustomSeries::setCustomSeriesPainter(QCustomSeriesCanvasRenderer *painter)
{
Q_D(QCustomSeries);
if (d->m_customSeriesPainter == painter)
return;
delete d->m_customSeriesPainter;
d->m_customSeriesPainter = painter;
update();
}
#endif
/*!
\qmlmethod real CustomSeries::mapX(real x)
Returns \a x axis-space coordinate converted into render-space.
*/
/*!
Returns \a x axis-space coordinate converted into render-space.
*/
qreal QCustomSeries::mapX(qreal x)
{
Q_D(const QCustomSeries);
if (d->m_graph) {
auto renderer = d->m_graph->customRenderer();
if (renderer) {
auto metaObj = renderer->metaObject();
auto propIndex = metaObj->indexOfProperty("plotArea");
if (propIndex >= 0) {
auto prop = metaObj->property(propIndex);
qmlEngine(this)->captureProperty(renderer, prop);
}
}
return d->m_graph->mapX(this, x);
}
return x;
}
/*!
\qmlmethod real CustomSeries::mapY(real y)
Returns \a y axis-space coordinate converted into render-space.
*/
/*!
Returns \a y axis-space coordinate converted into render-space.
*/
qreal QCustomSeries::mapY(qreal y)
{
Q_D(const QCustomSeries);
if (d->m_graph) {
auto renderer = d->m_graph->customRenderer();
if (renderer) {
auto metaObj = renderer->metaObject();
auto propIndex = metaObj->indexOfProperty("plotArea");
if (propIndex >= 0) {
auto prop = metaObj->property(propIndex);
qmlEngine(this)->captureProperty(renderer, prop);
}
}
return d->m_graph->mapY(this, y);
}
return y;
}
/*!
\qmlmethod void CustomSeries::append()
Appends an empty data item to the series.
*/
/*!
Appends an empty data item to the series.
*/
void QCustomSeries::append()
{
append(QVariantMap());
}
/*!
\qmlmethod void CustomSeries::append(var data)
Appends a data item defined by \a data to the series.
*/
/*!
Appends a data item defined by \a data to the series.
*/
void QCustomSeries::append(const QVariantMap &data)
{
Q_D(QCustomSeries);
QCustomSeriesData *dataItem = new QCustomSeriesData(this);
dataItem->setData(data);
d->m_dataItems.append(dataItem);
}
/*!
\qmlmethod void CustomSeries::insert(int index)
Appends an empty data item to the series at position \a index.
*/
/*!
Appends an empty data item to the series at position \a index.
*/
void QCustomSeries::insert(qsizetype index)
{
insert(index, QVariantMap());
}
/*!
\qmlmethod void CustomSeries::insert(int index, var data)
Appends a data item defined by \a data to the series at position \a index.
*/
/*!
Appends a data item defined by \a data to the series at position \a index.
*/
void QCustomSeries::insert(qsizetype index, const QVariantMap &data)
{
Q_D(QCustomSeries);
if (d->m_dataItems.size() <= index)
return;
QCustomSeriesData *dataItem = new QCustomSeriesData(this);
dataItem->setData(data);
d->m_dataItems.insert(index, dataItem);
}
/*!
\qmlmethod void CustomSeries::remove(int index)
Remove a data item at position \a index.
*/
/*!
Remove a data item at position \a index.
*/
void QCustomSeries::remove(qsizetype index)
{
Q_D(QCustomSeries);
if (d->m_dataItems.size() <= index)
return;
delete d->m_dataItems[index];
d->m_dataItems.remove(index);
}
/*!
\qmlmethod void CustomSeries::clear()
Clear all data items.
*/
/*!
Clear all data items.
*/
void QCustomSeries::clear()
{
Q_D(QCustomSeries);
qDeleteAll(d->m_dataItems);
d->m_dataItems.clear();
}
void QCustomSeries::componentComplete()
{
Q_D(QCustomSeries);
for (auto *child : children()) {
if (auto dataItem = qobject_cast<QCustomSeriesData *>(child)) {
if (!d->m_dataItems.contains(dataItem)) {
d->m_dataItems.append(dataItem);
qCDebug(lcSeries2D, "append dataItem to customSeries");
}
}
}
qCDebug(lcEvents2D, "QCustomSeries::componentComplete");
QAbstractSeries::componentComplete();
}
/*!
Called by the graph renderer to give access to the \a item
representing the data, and the \a index corresponding to the
data item.
Override to implement custom property values for QQuickItem
instance(s).
*/
void QCustomSeries::updateDelegate(QQuickItem *item, qsizetype index)
{
auto &&data = dataItems()[index]->data();
data[QStringLiteral("index")] = index;
item->setProperty("data", data);
}
bool QCustomSeries::event(QEvent *event)
{
return QObject::event(event);
}
QCustomSeriesPrivate::QCustomSeriesPrivate()
: QAbstractSeriesPrivate(QAbstractSeries::SeriesType::Custom)
{}
QT_END_NAMESPACE
#include "moc_qcustomseries.cpp"