QGIS API Documentation 3.99.0-Master (c22de0620c0)
Loading...
Searching...
No Matches
qgslayoutitemchart.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitemchart.cpp
3 -------------------
4 begin : August 2025
5 copyright : (C) 2025 by Mathieu
6 email : mathieu at opengis dot ch
7***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgslayoutitemchart.h"
19
20#include "qgsapplication.h"
21#include "qgslayout.h"
25#include "qgslayoututils.h"
26#include "qgsplotregistry.h"
27
28#include <QDomDocument>
29#include <QDomElement>
30#include <QPainter>
31#include <QString>
32
33#include "moc_qgslayoutitemchart.cpp"
34
35using namespace Qt::StringLiterals;
36
39{
40 // default to no background
41 setBackgroundEnabled( false );
42
43 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( "line" ) ) );
44
45 mGathererTimer.setInterval( 10 );
46 mGathererTimer.setSingleShot( true );
47 connect( &mGathererTimer, &QTimer::timeout, this, &QgsLayoutItemChart::gatherData );
48}
49
54
56{
57 return QgsApplication::getThemeIcon( u"/mLayoutItemChart.svg"_s );
58}
59
64
66{
67 Qgs2DPlot *plot2d = dynamic_cast<Qgs2DPlot *>( plot );
68 if ( !plot2d )
69 {
70 delete plot;
71 return;
72 }
73
74 // Logic to minimize plot data refresh to bare minimum
75 bool requireRefresh = !mPlot || !plot;
76 if ( mPlot && plot )
77 {
78 if ( mPlot->type() != plot->type() )
79 {
80 requireRefresh = true;
81 }
82 else
83 {
84 Qgs2DXyPlot *oldPlot2dXy = dynamic_cast<Qgs2DXyPlot *>( mPlot.get() );
85 Qgs2DXyPlot *newPlot2dXy = dynamic_cast<Qgs2DXyPlot *>( plot2d );
86 if ( oldPlot2dXy && newPlot2dXy && oldPlot2dXy->xAxis().type() == newPlot2dXy->xAxis().type() )
87 {
88 // this is a case in which we don't need to refresh the plot data.
89 requireRefresh = false;
90 }
91 else
92 {
93 requireRefresh = true;
94 }
95 }
96 }
97
98 mPlot.reset( plot2d );
99 if ( requireRefresh )
100 {
101 refresh();
102 }
103
104 emit changed();
105}
106
108{
109 if ( layer == mVectorLayer.get() )
110 {
111 return;
112 }
113
114 mVectorLayer.setLayer( layer );
115 refresh();
116
117 emit changed();
118}
119
120
122{
123 if ( mSortFeatures == sorted )
124 {
125 return;
126 }
127
128 mSortFeatures = sorted;
129 refresh();
130
131 emit changed();
132}
133
135{
136 if ( mSortAscending == ascending )
137 {
138 return;
139 }
140
141 mSortAscending = ascending;
142 refresh();
143
144 emit changed();
145}
146
147void QgsLayoutItemChart::setSortExpression( const QString &expression )
148{
149 if ( mSortExpression == expression )
150 {
151 return;
152 }
153
154 mSortExpression = expression;
155 refresh();
156
157 emit changed();
158}
159
161{
162 if ( mMap == map )
163 {
164 return;
165 }
166
167 if ( mMap )
168 {
171 }
172 mMap = map;
173 if ( mMap )
174 {
177 }
178 refresh();
179
180 emit changed();
181}
182
184{
185 if ( mFilterOnlyVisibleFeatures == visibleOnly )
186 {
187 return;
188 }
189
190 mFilterOnlyVisibleFeatures = visibleOnly;
191 refresh();
192
193 emit changed();
194}
195
196void QgsLayoutItemChart::setFilterToAtlasFeature( const bool filterToAtlas )
197{
198 if ( mFilterToAtlasIntersection == filterToAtlas )
199 {
200 return;
201 }
202
203 mFilterToAtlasIntersection = filterToAtlas;
204 refresh();
205
206 emit changed();
207}
208
209void QgsLayoutItemChart::setSeriesList( const QList<QgsLayoutItemChart::SeriesDetails> &seriesList )
210{
211 if ( mSeriesList == seriesList )
212 {
213 return;
214 }
215
216 mSeriesList = seriesList;
217 refresh();
218
219 emit changed();
220}
221
225
226void QgsLayoutItemChart::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
227{
228 if ( !mLayout || !painter || !painter->device() )
229 {
230 return;
231 }
232
233 if ( !shouldDrawItem() )
234 {
235 return;
236 }
237
238 if ( !mPlot )
239 return;
240
241 QPaintDevice *paintDevice = painter->device();
242 if ( !paintDevice )
243 return;
244
245 QRectF thisPaintRect = rect();
246 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
247 return;
248
249 if ( mLayout->renderContext().isPreviewRender() )
250 {
251 if ( mNeedsGathering || mIsGathering )
252 {
253 if ( mNeedsGathering )
254 {
255 mNeedsGathering = false;
256 refreshData();
257 }
258
259 QgsScopedQPainterState painterState( painter );
260 painter->setClipRect( thisPaintRect );
261
262 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
263 painter->drawRect( thisPaintRect );
264 painter->setBrush( Qt::NoBrush );
265 QFont messageFont;
266 messageFont.setPointSize( 12 );
267 painter->setFont( messageFont );
268 painter->setPen( QColor( 255, 255, 255, 255 ) );
269 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering chart" ) );
270 return;
271 }
272 }
273 else
274 {
275 if ( mNeedsGathering )
276 {
277 mNeedsGathering = false;
278 prepareGatherer();
279 if ( mGatherer )
280 {
281 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
282 mGatherer->waitForFinished( 60000 );
283 }
284 }
285 }
286
287 const double scaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
288 const QSizeF size = mLayout->convertToLayoutUnits( sizeWithUnits() ) * scaleFactor;
289 if ( size.width() == 0 || size.height() == 0 )
290 return;
291
292 mPlot->setSize( size );
293
294 {
295 QgsScopedQPainterState painterState( painter );
296 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
297
299 renderContext.setScaleFactor( scaleFactor );
301
302 QgsPlotRenderContext plotRenderContext;
303 mPlot->render( renderContext, plotRenderContext, mPlotData );
304 }
305
306 if ( mSeriesList.isEmpty() )
307 {
308 QFont messageFont;
309 messageFont.setPointSize( 8 );
310 painter->setFont( messageFont );
311 painter->setPen( QColor( 125, 125, 125, 125 ) );
312 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Missing chart data" ) );
313 }
314}
315
317{
319 if ( mVectorLayer && !mSeriesList.isEmpty() )
320 {
321 mNeedsGathering = true;
322 }
323}
324
325void QgsLayoutItemChart::refreshData()
326{
327 mGathererTimer.start();
328}
329
330void QgsLayoutItemChart::gatherData()
331{
332 prepareGatherer();
333 if ( mGatherer )
334 {
335 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
336 }
337
338 mIsGathering = true;
339 update();
340}
341
342void QgsLayoutItemChart::prepareGatherer()
343{
344 if ( mGatherer )
345 {
346 disconnect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
347 mGatherer->cancel();
348 mGatherer.clear();
349 }
350
351 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
352 {
353 mPlotData.clearSeries();
354 mIsGathering = false;
355 update();
356 }
357
358 QgsPlotAbstractMetadata *metadata = QgsApplication::instance()->plotRegistry()->plotMetadata( mPlot->type() );
359 if ( !metadata )
360 {
361 mPlotData.clearSeries();
362 mIsGathering = false;
363 update();
364 }
365
366 if ( !metadata )
367 {
368 QgsDebugError( "Could not find plot metadata" );
369 return;
370 }
371
372 mGatherer = metadata->createPlotDataGatherer( mPlot.get() );
373 if ( !mGatherer )
374 {
375 mPlotData.clearSeries();
376 mIsGathering = false;
377 update();
378 }
379
380 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer = dynamic_cast<QgsVectorLayerXyPlotDataGatherer *>( mGatherer.data() ) )
381 {
382 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
383 for ( const SeriesDetails &series : mSeriesList )
384 {
385 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), series.xExpression(), series.yExpression(), series.filterExpression() );
386 }
387
388 QgsFeatureRequest request;
389 QStringList filterExpressions;
390 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
391 {
392 if ( !series.filterExpression().isEmpty() )
393 {
394 filterExpressions << series.filterExpression();
395 }
396 }
397 if ( !filterExpressions.isEmpty() )
398 {
399 request.setFilterExpression( u"(%1)"_s.arg( filterExpressions.join( ") OR ("_L1 ) ) );
400 }
401
402 if ( mSortFeatures && !mSortExpression.isEmpty() )
403 {
404 request.addOrderBy( mSortExpression, mSortAscending );
405 }
406
407 if ( mFilterToAtlasIntersection )
408 {
409 const QgsGeometry atlasGeometry = mLayout->reportContext().currentGeometry( mVectorLayer->crs() );
410 if ( !atlasGeometry.isNull() )
411 {
412 request.setDistanceWithin( atlasGeometry, 0.0 );
413 }
414 }
415 else if ( mMap && mFilterOnlyVisibleFeatures )
416 {
417 QgsGeometry visibleRegionGeometry = QgsGeometry::fromQPolygonF( mMap->visibleExtentPolygon() );
418 if ( mVectorLayer->crs() != mMap->crs() )
419 {
420 const QgsCoordinateTransform transform( mVectorLayer->crs(), mMap->crs(), mLayout->project() );
421 if ( visibleRegionGeometry.transform( transform ) != Qgis::GeometryOperationResult ::Success )
422 {
423 visibleRegionGeometry = QgsGeometry();
424 }
425 }
426 if ( !visibleRegionGeometry.isNull() )
427 {
428 request.setDistanceWithin( visibleRegionGeometry, 0.0 );
429 }
430 }
432
433 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
434
435 xyGatherer->setFeatureIterator( featureIterator );
437 xyGatherer->setSeriesDetails( xYSeriesList );
438 }
439
440 connect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
441}
442
443void QgsLayoutItemChart::processData()
444{
445 mPlotData = mGatherer->data();
446 mGatherer.clear();
447
448 mIsGathering = false;
449 update();
450}
451
452bool QgsLayoutItemChart::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
453{
454 if ( mPlot )
455 {
456 QDomElement plotElement = document.createElement( u"plot"_s );
457 mPlot->writeXml( plotElement, document, context );
458 element.appendChild( plotElement );
459 }
460
461 QDomElement seriesListElement = document.createElement( u"seriesList"_s );
462 for ( const SeriesDetails &series : mSeriesList )
463 {
464 QDomElement seriesElement = document.createElement( u"series"_s );
465 seriesElement.setAttribute( u"name"_s, series.name() );
466 seriesElement.setAttribute( u"xExpression"_s, series.xExpression() );
467 seriesElement.setAttribute( u"yExpression"_s, series.yExpression() );
468 seriesElement.setAttribute( u"filterExpression"_s, series.filterExpression() );
469 seriesListElement.appendChild( seriesElement );
470 }
471 element.appendChild( seriesListElement );
472
473 if ( mVectorLayer )
474 {
475 element.setAttribute( u"vectorLayer"_s, mVectorLayer.layerId );
476 element.setAttribute( u"vectorLayerName"_s, mVectorLayer.name );
477 element.setAttribute( u"vectorLayerSource"_s, mVectorLayer.source );
478 element.setAttribute( u"vectorLayerProvider"_s, mVectorLayer.provider );
479 }
480
481 element.setAttribute( u"sortFeatures"_s, mSortFeatures ? u"1"_s : u"0"_s );
482 element.setAttribute( u"sortAscending"_s, mSortAscending ? u"1"_s : u"0"_s );
483 element.setAttribute( u"sortExpression"_s, mSortExpression );
484
485 element.setAttribute( u"sortExpression"_s, mSortExpression );
486
487 element.setAttribute( u"filterOnlyVisibleFeatures"_s, mFilterOnlyVisibleFeatures );
488 element.setAttribute( u"filterToAtlasIntersection"_s, mFilterToAtlasIntersection );
489
490 if ( mMap )
491 {
492 element.setAttribute( u"mapUuid"_s, mMap->uuid() );
493 }
494
495 return true;
496}
497
498bool QgsLayoutItemChart::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
499{
500 QDomElement plotElement = element.firstChildElement( u"plot"_s );
501 if ( !plotElement.isNull() )
502 {
503 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( plotElement.attribute( u"plotType"_s ) ) ) );
504 if ( mPlot )
505 {
506 mPlot->readXml( plotElement, context );
507 }
508 }
509
510 mSeriesList.clear();
511 const QDomNodeList seriesNodeList = element.firstChildElement( u"seriesList"_s ).childNodes();
512 for ( int i = 0; i < seriesNodeList.count(); i++ )
513 {
514 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
515 SeriesDetails series( seriesElement.attribute( "name" ) );
516 series.setXExpression( seriesElement.attribute( "xExpression" ) );
517 series.setYExpression( seriesElement.attribute( "yExpression" ) );
518 series.setFilterExpression( seriesElement.attribute( "filterExpression" ) );
519 mSeriesList << series;
520 }
521
522 QString layerId = element.attribute( u"vectorLayer"_s );
523 QString layerName = element.attribute( u"vectorLayerName"_s );
524 QString layerSource = element.attribute( u"vectorLayerSource"_s );
525 QString layerProvider = element.attribute( u"vectorLayerProvider"_s );
526 mVectorLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
527 mVectorLayer.resolveWeakly( mLayout->project() );
528
529 mSortFeatures = element.attribute( u"sortFeatures"_s, u"0"_s ).toInt();
530 mSortAscending = element.attribute( u"sortAscending"_s, u"1"_s ).toInt();
531 mSortExpression = element.attribute( u"sortExpression"_s );
532
533 mFilterOnlyVisibleFeatures = element.attribute( u"filterOnlyVisibleFeatures"_s, u"1"_s ).toInt();
534 mFilterToAtlasIntersection = element.attribute( u"filterToAtlasIntersection"_s, u"0"_s ).toInt();
535
536 mMapUuid = element.attribute( u"mapUuid"_s );
537 if ( mMap )
538 {
541 mMap = nullptr;
542 }
543
544 mNeedsGathering = true;
545
546 return true;
547}
548
550{
551 if ( !mMap && !mMapUuid.isEmpty() && mLayout )
552 {
553 mMap = qobject_cast< QgsLayoutItemMap *>( mLayout->itemByUuid( mMapUuid, true ) );
554 if ( mMap )
555 {
558 }
559 }
560}
Base class for 2-dimensional plot/chart/graphs.
Definition qgsplot.h:562
Base class for 2-dimensional plot/chart/graphs with an X and Y axes.
Definition qgsplot.h:661
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:758
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
static QgsPlotRegistry * plotRegistry()
Returns the application's plot registry, used for plot types.
QgsFeatureRequest & addOrderBy(const QString &expression, bool ascending=true)
Adds a new OrderByClause, appending it as the least important one.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
QgsFeatureRequest & setDistanceWithin(const QgsGeometry &geometry, double distance)
Sets a reference geometry and a maximum distance from this geometry to retrieve features within.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
Chart series details covering all supported series types.
void setFilterExpression(const QString &filterExpression)
Sets the filter expression used to generate a series against a subset of the source layer.
void setXExpression(const QString &xExpression)
Sets the expression used to generate X-axis values.
void setYExpression(const QString &yExpression)
Sets the expression used to generate Y-axis values.
QgsPlot * plot()
Returns the plot used to render the chart.
void setSortFeatures(bool sorted)
Sets whether features should be sorted when iterating through the vector layer from which the plot da...
int type() const override
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
static QgsLayoutItemChart * create(QgsLayout *layout)
Returns a new chart item for the specified layout.
void setSortAscending(bool ascending)
Sets whether features should be sorted in an ascending order when iterating through the vector layer ...
void setFilterToAtlasFeature(bool filterToAtlas)
Sets series to only use features which intersect the current atlas feature.
void setSeriesList(const QList< QgsLayoutItemChart::SeriesDetails > &seriesList)
Sets the plot series details used to generate the plot data.
void setSourceLayer(QgsVectorLayer *layer)
Sets the source vector layer from which the plot data will be gathered from.
void setFilterOnlyVisibleFeatures(bool visibleOnly)
Sets the series to only use features which are visible in a map item.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
QgsLayoutItemChart(QgsLayout *layout)
Constructor for QgsLayoutItemChart, with the specified parent layout.
QgsLayoutItemMap * map() const
Returns the layout map to use to limit the series' use of features.
void setPlot(QgsPlot *plot)
Sets the plot used to render the chart.
QList< QgsLayoutItemChart::SeriesDetails > seriesList() const
Returns the plot series details used to generate the plot data.
void setSortExpression(const QString &expression)
Sets the expression used to sort features when iterating through the vector layer from which the plot...
QIcon icon() const override
Returns the item's icon.
void setMap(QgsLayoutItemMap *map)
Sets a layout map to use to limit the series' use of features.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
void extentChanged()
Emitted when the map's extent changes.
void mapRotationChanged(double newRotation)
Emitted when the map's rotation changes.
Contains settings and helpers relating to a render of a QgsLayoutItem.
friend class QgsLayout
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
QgsLayoutItem(QgsLayout *layout, bool manageZValue=true)
Constructor for QgsLayoutItem, with the specified parent layout.
friend class QgsLayoutItemMap
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
void refresh() override
Refreshes the item, causing a recalculation of any property overrides and recalculation of its positi...
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
static Q_DECL_DEPRECATED double scaleFactorFromItemStyle(const QStyleOptionGraphicsItem *style)
Extracts the scale factor from an item style.
virtual QgsVectorLayerAbstractPlotDataGatherer * createPlotDataGatherer(QgsPlot *plot=nullptr)=0
Creates a plot data gatherer of this class.
Qgis::PlotAxisType type() const
Returns the axis type.
Definition qgsplot.cpp:107
QgsPlotAbstractMetadata * plotMetadata(const QString &type) const
Returns the metadata for the specified plot type.
Contains information about the context of a plot rendering operation.
Definition qgsplot.h:184
Base class for plot/chart/graphs.
Definition qgsplot.h:48
A container for the context for various read/write operations on objects.
Contains information about the context of a rendering operation.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Scoped object for saving and restoring a QPainter object's state.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void taskCompleted()
Will be emitted by task to indicate its successful completion.
Represents a vector layer which manages a vector based dataset.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6950
#define QgsDebugError(str)
Definition qgslogger.h:59
_LayerRef< QgsVectorLayer > QgsVectorLayerRef