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 <private/qgraphsglobal_p.h>
#if QT_CONFIG(graphs_2d_high_performance_backend)
#include <QtCanvasPainter/QCanvasPainter>
#include <QtGraphs/qcustomseriescanvasrenderer.h>
#endif
#include <QtGraphs/qcustomseries.h>
#include <private/qcustomseries_p.h>
#include <private/axisrenderer_p.h>
#include <private/charthelpers_p.h>
#include <private/customrenderer_p.h>
#include <private/qabstractseries_p.h>
#include <private/qcustomseriesdata_p.h>
#include <private/qgraphsview_p.h>
#include <QQmlIncubator>
#include <qtgraphs_tracepoints_p.h>
QT_BEGIN_NAMESPACE
Q_TRACE_PREFIX(qtgraphs,
"QT_BEGIN_NAMESPACE" \
"class CustomRenderer;" \
"QT_END_NAMESPACE"
)
Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererHandlePolish_entry);
Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererHandlePolish_exit);
Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererAfterPolish_entry, int cleanupSeriesCount);
Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererAfterPolish_exit);
class DataItemIncubator : public QQmlIncubator
{
public:
DataItemIncubator(CustomRenderer *renderer) : m_renderer(renderer) {};
private:
void setInitialState(QObject *object) override
{
auto item = qobject_cast<QQuickItem *>(object);
item->setParent(m_renderer);
item->setParentItem(m_renderer);
recursivelySetParents(item);
}
void recursivelySetParents(QQuickItem *item)
{
for (const auto &child : item->children()) {
auto childItem = qobject_cast<QQuickItem *>(child);
if (childItem) {
childItem->setParentItem(item);
recursivelySetParents(childItem);
}
}
}
CustomRenderer *m_renderer = nullptr;
};
CustomRenderer::CustomRenderer(QGraphsView *graph, bool clipPlotArea)
: QQuickItem(graph)
, m_graph(graph)
{
setFlag(QQuickItem::ItemHasContents);
setClip(clipPlotArea);
}
CustomRenderer::~CustomRenderer()
{
qDeleteAll(m_groups);
}
#if QT_CONFIG(graphs_2d_high_performance_backend)
CustomRenderer::PaintSnapshot CustomRenderer::paintSnapshot() const
{
return m_paintSnapshot;
}
void CustomRenderer::paintSnapshot(const PaintSnapshot &snapshot, QCanvasPainter *p)
{
for (auto csp : snapshot) {
csp->canvasPrePaint(p);
csp->canvasPaint(p);
}
}
void CustomRenderer::synchronizeData(QCanvasPainterItem *item, QHash<QCustomSeries *, QCustomSeriesCanvasRenderer *> &painters)
{
for (auto series : std::as_const(m_removedCustomSeries))
delete painters.take(series);
m_removedCustomSeries.clear();
m_paintSnapshot.clear();
m_paintSnapshot.reserve(m_groups.size());
for (const auto &group : std::as_const(m_groups)) {
auto *d = QCustomSeriesPrivate::get(group->series);
if (d->m_customSeriesPainter) {
// Adopt: the renderer now owns this instance exclusively; the
// series has already relinquished it (see setCustomSeriesPainter()).
// Delete any previously-adopted instance being replaced.
delete painters.take(group->series);
painters.insert(group->series, d->m_customSeriesPainter);
d->m_customSeriesPainter = nullptr;
}
auto it = painters.find(group->series);
if (it == painters.end())
continue;
(*it)->synchronizeData(group->series, item);
m_paintSnapshot.append(*it);
}
}
#endif
qreal CustomRenderer::mapX(AxisRenderer *axisRenderer, QCustomSeries *series, qreal x)
{
auto &axisX = axisRenderer->getAxisX(series);
if (axisX.isLogarithmic) {
float logBase = log(axisX.logBase);
x = log(x) / logBase;
}
auto flipX = axisX.maxValue < axisX.minValue ? -1 : 1;
return m_areaWidth * flipX * x * m_maxHorizontal - m_horizontalOffset;
}
qreal CustomRenderer::mapY(AxisRenderer *axisRenderer, QCustomSeries *series, qreal y)
{
auto &axisY = axisRenderer->getAxisY(series);
if (axisY.isLogarithmic) {
float logBase = log(axisY.logBase);
y = log(y) / logBase;
}
if (m_graph->orientation() != Qt::Vertical)
y = axisY.maxValue - y;
auto flipY = axisY.maxValue < axisY.minValue ? -1 : 1;
return m_areaHeight - m_areaHeight * flipY * y * m_maxVertical + m_verticalOffset;
}
void CustomRenderer::hideDelegates(QCustomSeries *series)
{
auto group = m_groups.value(series);
if (group && group->currentDataItem) {
for (int i = 0; i < group->dataItems.size(); ++i) {
auto *dataItem = group->dataItems[i];
dataItem->setVisible(false);
}
}
}
void CustomRenderer::handlePolish(QCustomSeries *series)
{
Q_TRACE(QGraphs2DCustomRendererHandlePolish_entry);
auto theme = m_graph->theme();
if (!theme) {
qCCritical(lcCritical2D, "Theme not found");
return;
}
if (!m_graph->m_axisRenderer) {
qCCritical(lcCritical2D, "Axis renderer not found.");
return;
}
if (series->dataItems().isEmpty()) {
auto group = m_groups.value(series);
if (group) {
for (auto m : std::as_const(group->dataItems))
m->deleteLater();
group->dataItems.clear();
}
return;
}
if (width() <= 0 || height() <= 0)
return;
m_areaWidth = width();
m_areaHeight = height();
auto &axisX = m_graph->m_axisRenderer->getAxisX(series);
auto &axisY = m_graph->m_axisRenderer->getAxisY(series);
m_maxVertical = axisY.valueRange > 0 ? 1.0 / axisY.valueRange : 100.0;
m_maxHorizontal = axisX.valueRange > 0 ? 1.0 / axisX.valueRange : 100.0;
auto vmin = axisY.minValue > axisY.maxValue ? std::abs(axisY.minValue) : axisY.minValue;
m_verticalOffset = (vmin / axisY.valueRange) * m_areaHeight;
auto hmin = axisX.minValue > axisX.maxValue ? std::abs(axisX.minValue) : axisX.minValue;
m_horizontalOffset = (hmin / axisX.valueRange) * m_areaWidth;
if (!m_groups.contains(series)) {
DataGroup *group = new DataGroup();
group->series = series;
m_groups.insert(series, group);
}
auto group = m_groups.value(series);
qsizetype dataItemCount = series->dataItems().size();
if (series->delegate())
group->currentDataItem = series->delegate();
if (group->currentDataItem != group->previousDataItem) {
for (auto &&dataItem : group->dataItems)
dataItem->deleteLater();
group->dataItems.clear();
}
group->previousDataItem = group->currentDataItem;
if (group->currentDataItem) {
qsizetype dataItemVisualCount = group->dataItems.size();
if (dataItemVisualCount < dataItemCount) {
for (qsizetype i = dataItemVisualCount; i < dataItemCount; ++i) {
DataItemIncubator incubator(this);
group->currentDataItem->create(
incubator, group->currentDataItem->creationContext());
incubator.forceCompletion();
group->dataItems << qobject_cast<QQuickItem *>(incubator.object());
}
} else if (dataItemVisualCount > dataItemCount) {
for (qsizetype i = dataItemCount; i < dataItemVisualCount; ++i)
group->dataItems[i]->deleteLater();
group->dataItems.resize(dataItemCount);
}
} else if (group->dataItems.size() > 0) {
for (auto &&dataItem : group->dataItems)
dataItem->deleteLater();
group->dataItems.clear();
}
if (series->isVisible()) {
for (int i = 0; i < group->dataItems.size(); ++i) {
auto item = group->dataItems.at(i);
series->updateDelegate(item, i);
item->setZ(group->series->zValue());
}
} else {
hideDelegates(series);
}
if (m_plotArea.width() != m_areaWidth || m_plotArea.height() != m_areaHeight) {
m_plotArea.setWidth(m_areaWidth);
m_plotArea.setHeight(m_areaHeight);
emit plotAreaChanged();
}
Q_TRACE(QGraphs2DCustomRendererHandlePolish_exit);
}
void CustomRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
{
Q_TRACE(QGraphs2DCustomRendererAfterPolish_entry, static_cast<int>(cleanupSeries.count()));
Q_UNUSED(cleanupSeries);
Q_TRACE(QGraphs2DCustomRendererAfterPolish_exit);
}
void CustomRenderer::updateSeries(QCustomSeries *series)
{
Q_UNUSED(series);
}
void CustomRenderer::seriesAboutToBeRemoved(QAbstractSeries *series)
{
if (auto *customSeries = qobject_cast<QCustomSeries *>(series)) {
auto iter = m_groups.find(customSeries);
if (iter != m_groups.end()) {
auto group = *iter;
for (const auto &dataItem : std::as_const(group->dataItems))
dataItem->deleteLater();
#if QT_CONFIG(graphs_2d_high_performance_backend)
m_removedCustomSeries.insert(customSeries);
#endif
delete group;
m_groups.erase(iter);
}
}
}
void CustomRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
{
Q_UNUSED(cleanupSeries);
}
QRectF CustomRenderer::plotArea() const
{
return m_plotArea;
}
QT_END_NAMESPACE
#include "moc_customrenderer_p.cpp"