QGIS API Documentation 3.99.0-Master (357b655ed83)
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
160void QgsLayoutItemChart::setSeriesList( const QList<QgsLayoutItemChart::SeriesDetails> &seriesList )
161{
162 if ( mSeriesList == seriesList )
163 {
164 return;
165 }
166
167 mSeriesList = seriesList;
168 refresh();
169
170 emit changed();
171}
172
176
177void QgsLayoutItemChart::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
178{
179 if ( !mLayout || !painter || !painter->device() )
180 {
181 return;
182 }
183
184 if ( !shouldDrawItem() )
185 {
186 return;
187 }
188
189 if ( !mPlot )
190 return;
191
192 QPaintDevice *paintDevice = painter->device();
193 if ( !paintDevice )
194 return;
195
196 QRectF thisPaintRect = rect();
197 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
198 return;
199
200 if ( mLayout->renderContext().isPreviewRender() )
201 {
202 if ( mNeedsGathering || mIsGathering )
203 {
204 if ( mNeedsGathering )
205 {
206 mNeedsGathering = false;
207 refreshData();
208 }
209
210 QgsScopedQPainterState painterState( painter );
211 painter->setClipRect( thisPaintRect );
212
213 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
214 painter->drawRect( thisPaintRect );
215 painter->setBrush( Qt::NoBrush );
216 QFont messageFont;
217 messageFont.setPointSize( 12 );
218 painter->setFont( messageFont );
219 painter->setPen( QColor( 255, 255, 255, 255 ) );
220 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering chart" ) );
221 return;
222 }
223 }
224 else
225 {
226 if ( mNeedsGathering )
227 {
228 mNeedsGathering = false;
229 prepareGatherer();
230 if ( mGatherer )
231 {
232 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
233 mGatherer->waitForFinished( 60000 );
234 }
235 }
236 }
237
238 const double scaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
239 const QSizeF size = mLayout->convertToLayoutUnits( sizeWithUnits() ) * scaleFactor;
240 if ( size.width() == 0 || size.height() == 0 )
241 return;
242
243 mPlot->setSize( size );
244
245 {
246 QgsScopedQPainterState painterState( painter );
247 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
248
250 renderContext.setScaleFactor( scaleFactor );
252
253 QgsPlotRenderContext plotRenderContext;
254 mPlot->render( renderContext, plotRenderContext, mPlotData );
255 }
256
257 if ( mSeriesList.isEmpty() )
258 {
259 QFont messageFont;
260 messageFont.setPointSize( 8 );
261 painter->setFont( messageFont );
262 painter->setPen( QColor( 125, 125, 125, 125 ) );
263 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Missing chart data" ) );
264 }
265}
266
268{
270 if ( mVectorLayer && !mSeriesList.isEmpty() )
271 {
272 mNeedsGathering = true;
273 }
274}
275
276void QgsLayoutItemChart::refreshData()
277{
278 mGathererTimer.start();
279}
280
281void QgsLayoutItemChart::gatherData()
282{
283 prepareGatherer();
284 if ( mGatherer )
285 {
286 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
287 }
288
289 mIsGathering = true;
290 update();
291}
292
293void QgsLayoutItemChart::prepareGatherer()
294{
295 if ( mGatherer )
296 {
297 disconnect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
298 mGatherer->cancel();
299 mGatherer.clear();
300 }
301
302 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
303 {
304 mPlotData.clearSeries();
305 mIsGathering = false;
306 update();
307 }
308
309 QgsPlotAbstractMetadata *metadata = QgsApplication::instance()->plotRegistry()->plotMetadata( mPlot->type() );
310 if ( !metadata )
311 {
312 mPlotData.clearSeries();
313 mIsGathering = false;
314 update();
315 }
316
317 if ( !metadata )
318 {
319 QgsDebugError( "Could not find plot metadata" );
320 return;
321 }
322
323 mGatherer = metadata->createPlotDataGatherer( mPlot.get() );
324 if ( !mGatherer )
325 {
326 mPlotData.clearSeries();
327 mIsGathering = false;
328 update();
329 }
330
331 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer = dynamic_cast<QgsVectorLayerXyPlotDataGatherer *>( mGatherer.data() ) )
332 {
333 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
334 for ( const SeriesDetails &series : mSeriesList )
335 {
336 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.xExpression(), series.yExpression(), series.filterExpression() );
337 }
338
339 QgsFeatureRequest request;
340 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
341 {
342 if ( series.filterExpression().isEmpty() )
343 {
344 request = QgsFeatureRequest();
345 break;
346 }
347
348 request.combineFilterExpression( series.filterExpression() );
349 }
350
351 if ( mSortFeatures && !mSortExpression.isEmpty() )
352 {
353 request.addOrderBy( mSortExpression, mSortAscending );
354 }
355
356 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
357
358 xyGatherer->setFeatureIterator( featureIterator );
359 xyGatherer->setExpressionContext( createExpressionContext() );
360 xyGatherer->setSeriesDetails( xYSeriesList );
361 }
362
363 connect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
364}
365
366void QgsLayoutItemChart::processData()
367{
368 mPlotData = mGatherer->data();
369 mGatherer.clear();
370
371 mIsGathering = false;
372 update();
373}
374
375bool QgsLayoutItemChart::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
376{
377 if ( mPlot )
378 {
379 QDomElement plotElement = document.createElement( u"plot"_s );
380 mPlot->writeXml( plotElement, document, context );
381 element.appendChild( plotElement );
382 }
383
384 QDomElement seriesListElement = document.createElement( u"seriesList"_s );
385 for ( const SeriesDetails &series : mSeriesList )
386 {
387 QDomElement seriesElement = document.createElement( u"series"_s );
388 seriesElement.setAttribute( u"name"_s, series.name() );
389 seriesElement.setAttribute( u"xExpression"_s, series.xExpression() );
390 seriesElement.setAttribute( u"yExpression"_s, series.yExpression() );
391 seriesElement.setAttribute( u"filterExpression"_s, series.filterExpression() );
392 seriesListElement.appendChild( seriesElement );
393 }
394 element.appendChild( seriesListElement );
395
396 if ( mVectorLayer )
397 {
398 element.setAttribute( u"vectorLayer"_s, mVectorLayer.layerId );
399 element.setAttribute( u"vectorLayerName"_s, mVectorLayer.name );
400 element.setAttribute( u"vectorLayerSource"_s, mVectorLayer.source );
401 element.setAttribute( u"vectorLayerProvider"_s, mVectorLayer.provider );
402 }
403
404 element.setAttribute( u"sortFeatures"_s, mSortFeatures ? u"1"_s : u"0"_s );
405 element.setAttribute( u"sortAscending"_s, mSortAscending ? u"1"_s : u"0"_s );
406 element.setAttribute( u"sortExpression"_s, mSortExpression );
407
408 element.setAttribute( u"sortExpression"_s, mSortExpression );
409
410 return true;
411}
412
413bool QgsLayoutItemChart::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
414{
415 QDomElement plotElement = element.firstChildElement( u"plot"_s );
416 if ( !plotElement.isNull() )
417 {
418 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( plotElement.attribute( u"plotType"_s ) ) ) );
419 if ( mPlot )
420 {
421 mPlot->readXml( plotElement, context );
422 }
423 }
424
425 mSeriesList.clear();
426 const QDomNodeList seriesNodeList = element.firstChildElement( u"seriesList"_s ).childNodes();
427 for ( int i = 0; i < seriesNodeList.count(); i++ )
428 {
429 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
430 SeriesDetails series( seriesElement.attribute( "name" ) );
431 series.setXExpression( seriesElement.attribute( "xExpression" ) );
432 series.setYExpression( seriesElement.attribute( "yExpression" ) );
433 series.setFilterExpression( seriesElement.attribute( "filterExpression" ) );
434 mSeriesList << series;
435 }
436
437 QString layerId = element.attribute( u"vectorLayer"_s );
438 QString layerName = element.attribute( u"vectorLayerName"_s );
439 QString layerSource = element.attribute( u"vectorLayerSource"_s );
440 QString layerProvider = element.attribute( u"vectorLayerProvider"_s );
441 mVectorLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
442 mVectorLayer.resolveWeakly( mLayout->project() );
443
444 mSortFeatures = element.attribute( u"sortFeatures"_s, u"0"_s ).toInt();
445 mSortAscending = element.attribute( u"sortAscending"_s, u"1"_s ).toInt();
446 mSortExpression = element.attribute( u"sortExpression"_s );
447
448 mNeedsGathering = true;
449
450 return true;
451}
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: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:6935
#define QgsDebugError(str)
Definition qgslogger.h:59
_LayerRef< QgsVectorLayer > QgsVectorLayerRef