QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
224
225void QgsLayoutItemChart::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
226{
227 if ( !mLayout || !painter || !painter->device() )
228 {
229 return;
230 }
231
232 if ( !shouldDrawItem() )
233 {
234 return;
235 }
236
237 if ( !mPlot )
238 return;
239
240 QPaintDevice *paintDevice = painter->device();
241 if ( !paintDevice )
242 return;
243
244 QRectF thisPaintRect = rect();
245 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
246 return;
247
248 if ( mLayout->renderContext().isPreviewRender() )
249 {
250 if ( mNeedsGathering || mIsGathering )
251 {
252 if ( mNeedsGathering )
253 {
254 mNeedsGathering = false;
255 refreshData();
256 }
257
258 QgsScopedQPainterState painterState( painter );
259 painter->setClipRect( thisPaintRect );
260
261 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
262 painter->drawRect( thisPaintRect );
263 painter->setBrush( Qt::NoBrush );
264 QFont messageFont;
265 messageFont.setPointSize( 12 );
266 painter->setFont( messageFont );
267 painter->setPen( QColor( 255, 255, 255, 255 ) );
268 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering chart" ) );
269 return;
270 }
271 }
272 else
273 {
274 if ( mNeedsGathering )
275 {
276 mNeedsGathering = false;
277 prepareGatherer();
278 if ( mGatherer )
279 {
280 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
281 mGatherer->waitForFinished( 60000 );
282 }
283 }
284 }
285
286 const double scaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
287 const QSizeF size = mLayout->convertToLayoutUnits( sizeWithUnits() ) * scaleFactor;
288 if ( size.width() == 0 || size.height() == 0 )
289 return;
290
291 mPlot->setSize( size );
292
293 {
294 QgsScopedQPainterState painterState( painter );
295 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
296
298 renderContext.setScaleFactor( scaleFactor );
300
301 QgsPlotRenderContext plotRenderContext;
302 mPlot->render( renderContext, plotRenderContext, mPlotData );
303 }
304
305 if ( mSeriesList.isEmpty() )
306 {
307 QFont messageFont;
308 messageFont.setPointSize( 8 );
309 painter->setFont( messageFont );
310 painter->setPen( QColor( 125, 125, 125, 125 ) );
311 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Missing chart data" ) );
312 }
313}
314
316{
318 if ( mVectorLayer && !mSeriesList.isEmpty() )
319 {
320 mNeedsGathering = true;
321 }
322}
323
324void QgsLayoutItemChart::refreshData()
325{
326 mGathererTimer.start();
327}
328
329void QgsLayoutItemChart::gatherData()
330{
331 prepareGatherer();
332 if ( mGatherer )
333 {
334 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
335 }
336
337 mIsGathering = true;
338 update();
339}
340
341void QgsLayoutItemChart::prepareGatherer()
342{
343 if ( mGatherer )
344 {
345 disconnect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
346 mGatherer->cancel();
347 mGatherer.clear();
348 }
349
350 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
351 {
352 mPlotData.clearSeries();
353 mIsGathering = false;
354 update();
355 }
356
357 QgsPlotAbstractMetadata *metadata = QgsApplication::instance()->plotRegistry()->plotMetadata( mPlot->type() );
358 if ( !metadata )
359 {
360 mPlotData.clearSeries();
361 mIsGathering = false;
362 update();
363 }
364
365 if ( !metadata )
366 {
367 QgsDebugError( "Could not find plot metadata" );
368 return;
369 }
370
371 mGatherer = metadata->createPlotDataGatherer( mPlot.get() );
372 if ( !mGatherer )
373 {
374 mPlotData.clearSeries();
375 mIsGathering = false;
376 update();
377 }
378
379 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer = dynamic_cast<QgsVectorLayerXyPlotDataGatherer *>( mGatherer.data() ) )
380 {
381 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
382 for ( const SeriesDetails &series : mSeriesList )
383 {
384 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), series.xExpression(), series.yExpression(), series.filterExpression() );
385 }
386
387 QgsFeatureRequest request;
388 QStringList filterExpressions;
389 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
390 {
391 if ( !series.filterExpression().isEmpty() )
392 {
393 filterExpressions << series.filterExpression();
394 }
395 }
396 if ( !filterExpressions.isEmpty() )
397 {
398 request.setFilterExpression( u"(%1)"_s.arg( filterExpressions.join( ") OR ("_L1 ) ) );
399 }
400
401 if ( mSortFeatures && !mSortExpression.isEmpty() )
402 {
403 request.addOrderBy( mSortExpression, mSortAscending );
404 }
405
406 if ( mFilterToAtlasIntersection )
407 {
408 const QgsGeometry atlasGeometry = mLayout->reportContext().currentGeometry( mVectorLayer->crs() );
409 if ( !atlasGeometry.isNull() )
410 {
411 request.setDistanceWithin( atlasGeometry, 0.0 );
412 }
413 }
414 else if ( mMap && mFilterOnlyVisibleFeatures )
415 {
416 QgsGeometry visibleRegionGeometry = QgsGeometry::fromQPolygonF( mMap->visibleExtentPolygon() );
417 if ( mVectorLayer->crs() != mMap->crs() )
418 {
419 const QgsCoordinateTransform transform( mVectorLayer->crs(), mMap->crs(), mLayout->project() );
420 if ( visibleRegionGeometry.transform( transform ) != Qgis::GeometryOperationResult ::Success )
421 {
422 visibleRegionGeometry = QgsGeometry();
423 }
424 }
425 if ( !visibleRegionGeometry.isNull() )
426 {
427 request.setDistanceWithin( visibleRegionGeometry, 0.0 );
428 }
429 }
431
432 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
433
434 xyGatherer->setFeatureIterator( featureIterator );
436 xyGatherer->setSeriesDetails( xYSeriesList );
437 }
438
439 connect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
440}
441
442void QgsLayoutItemChart::processData()
443{
444 mPlotData = mGatherer->data();
445 mGatherer.clear();
446
447 mIsGathering = false;
448 update();
449}
450
451bool QgsLayoutItemChart::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
452{
453 if ( mPlot )
454 {
455 QDomElement plotElement = document.createElement( u"plot"_s );
456 mPlot->writeXml( plotElement, document, context );
457 element.appendChild( plotElement );
458 }
459
460 QDomElement seriesListElement = document.createElement( u"seriesList"_s );
461 for ( const SeriesDetails &series : mSeriesList )
462 {
463 QDomElement seriesElement = document.createElement( u"series"_s );
464 seriesElement.setAttribute( u"name"_s, series.name() );
465 seriesElement.setAttribute( u"xExpression"_s, series.xExpression() );
466 seriesElement.setAttribute( u"yExpression"_s, series.yExpression() );
467 seriesElement.setAttribute( u"filterExpression"_s, series.filterExpression() );
468 seriesListElement.appendChild( seriesElement );
469 }
470 element.appendChild( seriesListElement );
471
472 if ( mVectorLayer )
473 {
474 element.setAttribute( u"vectorLayer"_s, mVectorLayer.layerId );
475 element.setAttribute( u"vectorLayerName"_s, mVectorLayer.name );
476 element.setAttribute( u"vectorLayerSource"_s, mVectorLayer.source );
477 element.setAttribute( u"vectorLayerProvider"_s, mVectorLayer.provider );
478 }
479
480 element.setAttribute( u"sortFeatures"_s, mSortFeatures ? u"1"_s : u"0"_s );
481 element.setAttribute( u"sortAscending"_s, mSortAscending ? u"1"_s : u"0"_s );
482 element.setAttribute( u"sortExpression"_s, mSortExpression );
483
484 element.setAttribute( u"sortExpression"_s, mSortExpression );
485
486 element.setAttribute( u"filterOnlyVisibleFeatures"_s, mFilterOnlyVisibleFeatures );
487 element.setAttribute( u"filterToAtlasIntersection"_s, mFilterToAtlasIntersection );
488
489 if ( mMap )
490 {
491 element.setAttribute( u"mapUuid"_s, mMap->uuid() );
492 }
493
494 return true;
495}
496
497bool QgsLayoutItemChart::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
498{
499 QDomElement plotElement = element.firstChildElement( u"plot"_s );
500 if ( !plotElement.isNull() )
501 {
502 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( plotElement.attribute( u"plotType"_s ) ) ) );
503 if ( mPlot )
504 {
505 mPlot->readXml( plotElement, context );
506 }
507 }
508
509 mSeriesList.clear();
510 const QDomNodeList seriesNodeList = element.firstChildElement( u"seriesList"_s ).childNodes();
511 for ( int i = 0; i < seriesNodeList.count(); i++ )
512 {
513 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
514 SeriesDetails series( seriesElement.attribute( "name" ) );
515 series.setXExpression( seriesElement.attribute( "xExpression" ) );
516 series.setYExpression( seriesElement.attribute( "yExpression" ) );
517 series.setFilterExpression( seriesElement.attribute( "filterExpression" ) );
518 mSeriesList << series;
519 }
520
521 QString layerId = element.attribute( u"vectorLayer"_s );
522 QString layerName = element.attribute( u"vectorLayerName"_s );
523 QString layerSource = element.attribute( u"vectorLayerSource"_s );
524 QString layerProvider = element.attribute( u"vectorLayerProvider"_s );
525 mVectorLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
526 mVectorLayer.resolveWeakly( mLayout->project() );
527
528 mSortFeatures = element.attribute( u"sortFeatures"_s, u"0"_s ).toInt();
529 mSortAscending = element.attribute( u"sortAscending"_s, u"1"_s ).toInt();
530 mSortExpression = element.attribute( u"sortExpression"_s );
531
532 mFilterOnlyVisibleFeatures = element.attribute( u"filterOnlyVisibleFeatures"_s, u"1"_s ).toInt();
533 mFilterToAtlasIntersection = element.attribute( u"filterToAtlasIntersection"_s, u"0"_s ).toInt();
534
535 mMapUuid = element.attribute( u"mapUuid"_s );
536 if ( mMap )
537 {
540 mMap = nullptr;
541 }
542
543 mNeedsGathering = true;
544
545 return true;
546}
547
549{
550 if ( !mMap && !mMapUuid.isEmpty() && mLayout )
551 {
552 mMap = qobject_cast< QgsLayoutItemMap *>( mLayout->itemByUuid( mMapUuid, true ) );
553 if ( mMap )
554 {
557 }
558 }
559}
Base class for 2-dimensional plot/chart/graphs.
Definition qgsplot.h:585
Base class for 2-dimensional plot/chart/graphs with an X and Y axes.
Definition qgsplot.h:687
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:783
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:124
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:191
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:6975
#define QgsDebugError(str)
Definition qgslogger.h:59
_LayerRef< QgsVectorLayer > QgsVectorLayerRef