QGIS API Documentation 3.99.0-Master (26c88405ac0)
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
32#include "moc_qgslayoutitemchart.cpp"
33
36{
37 // default to no background
38 setBackgroundEnabled( false );
39
40 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( "line" ) ) );
41
42 mGathererTimer.setInterval( 10 );
43 mGathererTimer.setSingleShot( true );
44 connect( &mGathererTimer, &QTimer::timeout, this, &QgsLayoutItemChart::gatherData );
45}
46
51
53{
54 return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemChart.svg" ) );
55}
56
61
63{
64 Qgs2DPlot *plot2d = dynamic_cast<Qgs2DPlot *>( plot );
65 if ( !plot2d )
66 {
67 delete plot;
68 return;
69 }
70
71 // Logic to minimize plot data refresh to bare minimum
72 bool requireRefresh = !mPlot || !plot;
73 if ( mPlot && plot )
74 {
75 if ( mPlot->type() != plot->type() )
76 {
77 requireRefresh = true;
78 }
79 else
80 {
81 Qgs2DXyPlot *oldPlot2dXy = dynamic_cast<Qgs2DXyPlot *>( mPlot.get() );
82 Qgs2DXyPlot *newPlot2dXy = dynamic_cast<Qgs2DXyPlot *>( plot2d );
83 if ( oldPlot2dXy && newPlot2dXy && oldPlot2dXy->xAxis().type() == newPlot2dXy->xAxis().type() )
84 {
85 // this is a case in which we don't need to refresh the plot data.
86 requireRefresh = false;
87 }
88 else
89 {
90 requireRefresh = true;
91 }
92 }
93 }
94
95 mPlot.reset( plot2d );
96 if ( requireRefresh )
97 {
98 refresh();
99 }
100
101 emit changed();
102}
103
105{
106 if ( layer == mVectorLayer.get() )
107 {
108 return;
109 }
110
111 mVectorLayer.setLayer( layer );
112 refresh();
113
114 emit changed();
115}
116
117
119{
120 if ( mSortFeatures == sorted )
121 {
122 return;
123 }
124
125 mSortFeatures = sorted;
126 refresh();
127
128 emit changed();
129}
130
132{
133 if ( mSortAscending == ascending )
134 {
135 return;
136 }
137
138 mSortAscending = ascending;
139 refresh();
140
141 emit changed();
142}
143
144void QgsLayoutItemChart::setSortExpression( const QString &expression )
145{
146 if ( mSortExpression == expression )
147 {
148 return;
149 }
150
151 mSortExpression = expression;
152 refresh();
153
154 emit changed();
155}
156
157void QgsLayoutItemChart::setSeriesList( const QList<QgsLayoutItemChart::SeriesDetails> &seriesList )
158{
159 if ( mSeriesList == seriesList )
160 {
161 return;
162 }
163
164 mSeriesList = seriesList;
165 refresh();
166
167 emit changed();
168}
169
173
174void QgsLayoutItemChart::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
175{
176 if ( !mLayout || !painter || !painter->device() )
177 {
178 return;
179 }
180
181 if ( !shouldDrawItem() )
182 {
183 return;
184 }
185
186 if ( !mPlot )
187 return;
188
189 QPaintDevice *paintDevice = painter->device();
190 if ( !paintDevice )
191 return;
192
193 QRectF thisPaintRect = rect();
194 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
195 return;
196
197 if ( mLayout->renderContext().isPreviewRender() )
198 {
199 if ( mNeedsGathering || mIsGathering )
200 {
201 if ( mNeedsGathering )
202 {
203 mNeedsGathering = false;
204 refreshData();
205 }
206
207 QgsScopedQPainterState painterState( painter );
208 painter->setClipRect( thisPaintRect );
209
210 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
211 painter->drawRect( thisPaintRect );
212 painter->setBrush( Qt::NoBrush );
213 QFont messageFont;
214 messageFont.setPointSize( 12 );
215 painter->setFont( messageFont );
216 painter->setPen( QColor( 255, 255, 255, 255 ) );
217 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering chart" ) );
218 return;
219 }
220 }
221 else
222 {
223 if ( mNeedsGathering )
224 {
225 mNeedsGathering = false;
226 prepareGatherer();
227 if ( mGatherer )
228 {
229 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
230 mGatherer->waitForFinished( 60000 );
231 }
232 }
233 }
234
235 const double scaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
236 const QSizeF size = mLayout->convertToLayoutUnits( sizeWithUnits() ) * scaleFactor;
237 if ( size.width() == 0 || size.height() == 0 )
238 return;
239
240 mPlot->setSize( size );
241
242 {
243 QgsScopedQPainterState painterState( painter );
244 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
245
247 renderContext.setScaleFactor( scaleFactor );
249
250 QgsPlotRenderContext plotRenderContext;
251 mPlot->render( renderContext, plotRenderContext, mPlotData );
252 }
253
254 if ( mSeriesList.isEmpty() )
255 {
256 QFont messageFont;
257 messageFont.setPointSize( 8 );
258 painter->setFont( messageFont );
259 painter->setPen( QColor( 125, 125, 125, 125 ) );
260 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Missing chart data" ) );
261 }
262}
263
265{
267 if ( mVectorLayer && !mSeriesList.isEmpty() )
268 {
269 mNeedsGathering = true;
270 }
271}
272
273void QgsLayoutItemChart::refreshData()
274{
275 mGathererTimer.start();
276}
277
278void QgsLayoutItemChart::gatherData()
279{
280 prepareGatherer();
281 if ( mGatherer )
282 {
283 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
284 }
285
286 mIsGathering = true;
287 update();
288}
289
290void QgsLayoutItemChart::prepareGatherer()
291{
292 if ( mGatherer )
293 {
294 disconnect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
295 mGatherer->cancel();
296 mGatherer.clear();
297 }
298
299 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
300 {
301 mPlotData.clearSeries();
302 mIsGathering = false;
303 update();
304 }
305
306 QgsPlotAbstractMetadata *metadata = QgsApplication::instance()->plotRegistry()->plotMetadata( mPlot->type() );
307 if ( !metadata )
308 {
309 mPlotData.clearSeries();
310 mIsGathering = false;
311 update();
312 }
313
314 if ( !metadata )
315 {
316 QgsDebugError( "Could not find plot metadata" );
317 return;
318 }
319
320 mGatherer = metadata->createPlotDataGatherer( mPlot.get() );
321 if ( !mGatherer )
322 {
323 mPlotData.clearSeries();
324 mIsGathering = false;
325 update();
326 }
327
328 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer = dynamic_cast<QgsVectorLayerXyPlotDataGatherer *>( mGatherer.data() ) )
329 {
330 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
331 for ( const SeriesDetails &series : mSeriesList )
332 {
333 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.xExpression(), series.yExpression(), series.filterExpression() );
334 }
335
336 QgsFeatureRequest request;
337 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
338 {
339 if ( series.filterExpression().isEmpty() )
340 {
341 request = QgsFeatureRequest();
342 break;
343 }
344
345 request.combineFilterExpression( series.filterExpression() );
346 }
347
348 if ( mSortFeatures && !mSortExpression.isEmpty() )
349 {
350 request.addOrderBy( mSortExpression, mSortAscending );
351 }
352
353 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
354
355 xyGatherer->setFeatureIterator( featureIterator );
356 xyGatherer->setExpressionContext( createExpressionContext() );
357 xyGatherer->setSeriesDetails( xYSeriesList );
358 }
359
360 connect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
361}
362
363void QgsLayoutItemChart::processData()
364{
365 mPlotData = mGatherer->data();
366 mGatherer.clear();
367
368 mIsGathering = false;
369 update();
370}
371
372bool QgsLayoutItemChart::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
373{
374 if ( mPlot )
375 {
376 QDomElement plotElement = document.createElement( QStringLiteral( "plot" ) );
377 mPlot->writeXml( plotElement, document, context );
378 element.appendChild( plotElement );
379 }
380
381 QDomElement seriesListElement = document.createElement( QStringLiteral( "seriesList" ) );
382 for ( const SeriesDetails &series : mSeriesList )
383 {
384 QDomElement seriesElement = document.createElement( QStringLiteral( "series" ) );
385 seriesElement.setAttribute( QStringLiteral( "name" ), series.name() );
386 seriesElement.setAttribute( QStringLiteral( "xExpression" ), series.xExpression() );
387 seriesElement.setAttribute( QStringLiteral( "yExpression" ), series.yExpression() );
388 seriesElement.setAttribute( QStringLiteral( "filterExpression" ), series.filterExpression() );
389 seriesListElement.appendChild( seriesElement );
390 }
391 element.appendChild( seriesListElement );
392
393 if ( mVectorLayer )
394 {
395 element.setAttribute( QStringLiteral( "vectorLayer" ), mVectorLayer.layerId );
396 element.setAttribute( QStringLiteral( "vectorLayerName" ), mVectorLayer.name );
397 element.setAttribute( QStringLiteral( "vectorLayerSource" ), mVectorLayer.source );
398 element.setAttribute( QStringLiteral( "vectorLayerProvider" ), mVectorLayer.provider );
399 }
400
401 element.setAttribute( QStringLiteral( "sortFeatures" ), mSortFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
402 element.setAttribute( QStringLiteral( "sortAscending" ), mSortAscending ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
403 element.setAttribute( QStringLiteral( "sortExpression" ), mSortExpression );
404
405 element.setAttribute( QStringLiteral( "sortExpression" ), mSortExpression );
406
407 return true;
408}
409
410bool QgsLayoutItemChart::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
411{
412 QDomElement plotElement = element.firstChildElement( QStringLiteral( "plot" ) );
413 if ( !plotElement.isNull() )
414 {
415 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( plotElement.attribute( QStringLiteral( "plotType" ) ) ) ) );
416 if ( mPlot )
417 {
418 mPlot->readXml( plotElement, context );
419 }
420 }
421
422 mSeriesList.clear();
423 const QDomNodeList seriesNodeList = element.firstChildElement( QStringLiteral( "seriesList" ) ).childNodes();
424 for ( int i = 0; i < seriesNodeList.count(); i++ )
425 {
426 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
427 SeriesDetails series( seriesElement.attribute( "name" ) );
428 series.setXExpression( seriesElement.attribute( "xExpression" ) );
429 series.setYExpression( seriesElement.attribute( "yExpression" ) );
430 series.setFilterExpression( seriesElement.attribute( "filterExpression" ) );
431 mSeriesList << series;
432 }
433
434 QString layerId = element.attribute( QStringLiteral( "vectorLayer" ) );
435 QString layerName = element.attribute( QStringLiteral( "vectorLayerName" ) );
436 QString layerSource = element.attribute( QStringLiteral( "vectorLayerSource" ) );
437 QString layerProvider = element.attribute( QStringLiteral( "vectorLayerProvider" ) );
438 mVectorLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
439 mVectorLayer.resolveWeakly( mLayout->project() );
440
441 mSortFeatures = element.attribute( QStringLiteral( "sortFeatures" ), QStringLiteral( "0" ) ).toInt();
442 mSortAscending = element.attribute( QStringLiteral( "sortAscending" ), QStringLiteral( "1" ) ).toInt();
443 mSortExpression = element.attribute( QStringLiteral( "sortExpression" ) );
444
445 mNeedsGathering = true;
446
447 return true;
448}
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:659
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
Definition qgsplot.h:756
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 & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & addOrderBy(const QString &expression, bool ascending=true)
Adds a new OrderByClause, appending it as the least important one.
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 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 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.
QgsLayoutItemChart(QgsLayout *layout)
Constructor for QgsLayoutItemChart, with the specified parent layout.
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.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
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.
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:103
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:6607
#define QgsDebugError(str)
Definition qgslogger.h:57
_LayerRef< QgsVectorLayer > QgsVectorLayerRef