QGIS API Documentation 4.1.0-Master (01362494303)
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 "qgsbarchartplot.h"
23#include "qgsfillsymbol.h"
25#include "qgslayout.h"
29#include "qgslayoututils.h"
30#include "qgslinechartplot.h"
31#include "qgsmarkersymbol.h"
32#include "qgspiechartplot.h"
33#include "qgsplotregistry.h"
35#include "qgsrenderer.h"
37#include "qgssymbollayer.h"
38
39#include <QDomDocument>
40#include <QDomElement>
41#include <QPainter>
42#include <QString>
43
44#include "moc_qgslayoutitemchart.cpp"
45
46using namespace Qt::StringLiterals;
47
50{
51 // default to no background
52 setBackgroundEnabled( false );
53
54 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( "line" ) ) );
55
56 SeriesDetails series( tr( "Series 1" ) );
57 mSeriesList << series;
58
59 mGathererTimer.setInterval( 10 );
60 mGathererTimer.setSingleShot( true );
61 connect( &mGathererTimer, &QTimer::timeout, this, &QgsLayoutItemChart::gatherData );
62}
63
68
70{
71 return QgsApplication::getThemeIcon( u"/mLayoutItemChart.svg"_s );
72}
73
78
80{
81 Qgs2DPlot *plot2d = dynamic_cast<Qgs2DPlot *>( plot );
82 if ( !plot2d )
83 {
84 delete plot;
85 return;
86 }
87
88 // Logic to minimize plot data refresh to bare minimum
89 bool requireRefresh = !mPlot || !plot;
90 if ( mPlot && plot )
91 {
92 if ( mPlot->type() != plot->type() )
93 {
94 requireRefresh = true;
95 }
96 else
97 {
98 Qgs2DXyPlot *oldPlot2dXy = dynamic_cast<Qgs2DXyPlot *>( mPlot.get() );
99 Qgs2DXyPlot *newPlot2dXy = dynamic_cast<Qgs2DXyPlot *>( plot2d );
100 if ( oldPlot2dXy && newPlot2dXy && oldPlot2dXy->xAxis().type() == newPlot2dXy->xAxis().type() )
101 {
102 // this is a case in which we don't need to refresh the plot data.
103 requireRefresh = false;
104 }
105 else
106 {
107 requireRefresh = true;
108 }
109 }
110 }
111
112 mPlot.reset( plot2d );
113 if ( requireRefresh )
114 {
115 refresh();
116 }
117
118 emit changed();
119}
120
122{
123 if ( layer == mVectorLayer.get() )
124 {
125 return;
126 }
127
128 mVectorLayer.setLayer( layer );
129 refresh();
130
131 emit changed();
132}
133
134
136{
137 if ( mSortFeatures == sorted )
138 {
139 return;
140 }
141
142 mSortFeatures = sorted;
143 refresh();
144
145 emit changed();
146}
147
149{
150 if ( mSortAscending == ascending )
151 {
152 return;
153 }
154
155 mSortAscending = ascending;
156 refresh();
157
158 emit changed();
159}
160
161void QgsLayoutItemChart::setSortExpression( const QString &expression )
162{
163 if ( mSortExpression == expression )
164 {
165 return;
166 }
167
168 mSortExpression = expression;
169 refresh();
170
171 emit changed();
172}
173
175{
176 if ( mMap == map )
177 {
178 return;
179 }
180
181 if ( mMap )
182 {
185 }
186 mMap = map;
187 if ( mMap )
188 {
191 }
192 refresh();
193
194 emit changed();
195}
196
198{
199 if ( mFilterOnlyVisibleFeatures == visibleOnly )
200 {
201 return;
202 }
203
204 mFilterOnlyVisibleFeatures = visibleOnly;
205 refresh();
206
207 emit changed();
208}
209
210void QgsLayoutItemChart::setFilterToAtlasFeature( const bool filterToAtlas )
211{
212 if ( mFilterToAtlasIntersection == filterToAtlas )
213 {
214 return;
215 }
216
217 mFilterToAtlasIntersection = filterToAtlas;
218 refresh();
219
220 emit changed();
221}
222
224{
225 if ( mGenerateCategoriesFromRenderer == generateCategoriesFromRenderer )
226 {
227 return;
228 }
229
230 mGenerateCategoriesFromRenderer = generateCategoriesFromRenderer;
231 refresh();
232
233 emit changed();
234}
235
237{
238 if ( mApplyRendererStyle == applyRendererStyle )
239 {
240 return;
241 }
242
243 mApplyRendererStyle = applyRendererStyle;
244
245 if ( mGenerateCategoriesFromRenderer )
246 {
247 refresh();
248 }
249
250 emit changed();
251}
252
253void QgsLayoutItemChart::setSeriesList( const QList<QgsLayoutItemChart::SeriesDetails> &seriesList )
254{
255 if ( mSeriesList == seriesList )
256 {
257 return;
258 }
259
260 mSeriesList = seriesList;
261 mPlotData = QgsPlotData();
262 refresh();
263
264 emit changed();
265}
266
269
270void QgsLayoutItemChart::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
271{
272 if ( !mLayout || !painter || !painter->device() )
273 {
274 return;
275 }
276
277 if ( !shouldDrawItem() )
278 {
279 return;
280 }
281
282 if ( !mPlot )
283 {
284 return;
285 }
286
287 QPaintDevice *paintDevice = painter->device();
288 if ( !paintDevice )
289 return;
290
291 QRectF thisPaintRect = rect();
292 if ( qgsDoubleNear( thisPaintRect.width(), 0.0 ) || qgsDoubleNear( thisPaintRect.height(), 0 ) )
293 return;
294
295 if ( mLayout->renderContext().isPreviewRender() )
296 {
297 if ( mNeedsGathering || mIsGathering )
298 {
299 if ( mNeedsGathering )
300 {
301 mNeedsGathering = false;
302 refreshData();
303 }
304
305 QgsScopedQPainterState painterState( painter );
306 painter->setClipRect( thisPaintRect );
307
308 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
309 painter->drawRect( thisPaintRect );
310 painter->setBrush( Qt::NoBrush );
311 QFont messageFont;
312 messageFont.setPointSize( 12 );
313 painter->setFont( messageFont );
314 painter->setPen( QColor( 255, 255, 255, 255 ) );
315 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Rendering chart" ) );
316 return;
317 }
318 }
319 else
320 {
321 if ( mNeedsGathering )
322 {
323 mNeedsGathering = false;
324 prepareGatherer();
325 if ( mGatherer )
326 {
327 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
328 mGatherer->waitForFinished( 60000 );
329 }
330 }
331 }
332
333 const double scaleFactor = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
334 const QSizeF size = mLayout->convertToLayoutUnits( sizeWithUnits() ) * scaleFactor;
335 if ( size.width() == 0 || size.height() == 0 )
336 return;
337
338 Qgs2DPlot *plot = mPlot.get();
339
340 std::unique_ptr< Qgs2DPlot > newPlot;
341 if ( mGenerateCategoriesFromRenderer && mVectorLayer )
342 {
343 newPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::plotRegistry()->createPlot( mPlot->type() ) ) );
344 plot = newPlot.get();
345 plot->initFromPlot( mPlot.get() );
346 if ( Qgs2DXyPlot *plotXy = dynamic_cast<Qgs2DXyPlot *>( plot ) )
347 {
348 plotXy->xAxis().setType( Qgis::PlotAxisType::Categorical );
349 }
350
351 if ( mApplyRendererStyle && mVectorLayer->renderer() )
352 {
353 applyRendererStyleToPlot( plot );
354 }
355 }
356
357 plot->setSize( size );
358 {
359 QgsScopedQPainterState painterState( painter );
360 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
361
363 renderContext.setScaleFactor( scaleFactor );
365
366 QgsPlotRenderContext plotRenderContext;
367 plot->render( renderContext, plotRenderContext, mPlotData );
368 }
369
370 if ( mSeriesList.isEmpty() || ( mSeriesList.size() == 1 && !mGenerateCategoriesFromRenderer && ( mSeriesList[0].xExpression().isEmpty() || mSeriesList[0].yExpression().isEmpty() ) ) )
371 {
372 QFont messageFont;
373 messageFont.setPointSize( 8 );
374 painter->setFont( messageFont );
375 painter->setPen( QColor( 125, 125, 125, 125 ) );
376 painter->drawText( thisPaintRect, Qt::AlignCenter | Qt::AlignHCenter, tr( "Missing chart data" ) );
377 }
378}
379
381{
383 if ( mVectorLayer && !mSeriesList.isEmpty() )
384 {
385 mNeedsGathering = true;
386 }
387}
388
389void QgsLayoutItemChart::refreshData()
390{
391 mGathererTimer.start();
392}
393
394void QgsLayoutItemChart::gatherData()
395{
396 prepareGatherer();
397 if ( mGatherer )
398 {
399 QgsApplication::instance()->taskManager()->addTask( mGatherer.data() );
400 }
401
402 mIsGathering = true;
403 update();
404}
405
406void QgsLayoutItemChart::prepareGatherer()
407{
408 if ( mGatherer )
409 {
410 disconnect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
411 mGatherer->cancel();
412 mGatherer.clear();
413 }
414
415 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
416 {
417 mPlotData.clearSeries();
418 mIsGathering = false;
419 update();
420 }
421
422 QgsPlotAbstractMetadata *metadata = QgsApplication::instance()->plotRegistry()->plotMetadata( mPlot->type() );
423 if ( !metadata )
424 {
425 mPlotData.clearSeries();
426 mIsGathering = false;
427 update();
428 }
429
430 if ( !metadata )
431 {
432 QgsDebugError( "Could not find plot metadata" );
433 return;
434 }
435
436 mGatherer = metadata->createPlotDataGatherer( mPlot.get() );
437 if ( !mGatherer )
438 {
439 mPlotData.clearSeries();
440 mIsGathering = false;
441 update();
442 }
443
444 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer = dynamic_cast<QgsVectorLayerXyPlotDataGatherer *>( mGatherer.data() ) )
445 {
446 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
447 if ( mGenerateCategoriesFromRenderer && mVectorLayer->renderer() )
448 {
449 QString rendererXExpression;
450 QStringList rendererCategories;
451 createRendererSeriesDetails( rendererXExpression, rendererCategories );
452
453 xyGatherer->setXAxisType( Qgis::PlotAxisType::Categorical );
454 xyGatherer->setPredefinedCategories( rendererCategories );
455 for ( const SeriesDetails &series : mSeriesList )
456 {
457 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), rendererXExpression, !series.yExpression().isEmpty() ? series.yExpression() : "1"_L1, series.filterExpression() );
458 }
459 }
460 else
461 {
462 for ( const SeriesDetails &series : mSeriesList )
463 {
464 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), series.xExpression(), series.yExpression(), series.filterExpression() );
465 }
466 }
467 xyGatherer->setSeriesDetails( xYSeriesList );
468
469 QgsFeatureRequest request;
470 QStringList filterExpressions;
471 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
472 {
473 if ( !series.filterExpression().isEmpty() )
474 {
475 filterExpressions << series.filterExpression();
476 }
477 }
478 if ( !filterExpressions.isEmpty() )
479 {
480 request.setFilterExpression( u"(%1)"_s.arg( filterExpressions.join( ") OR ("_L1 ) ) );
481 }
482
483 if ( mSortFeatures && !mSortExpression.isEmpty() )
484 {
485 request.addOrderBy( mSortExpression, mSortAscending );
486 }
487
488 if ( mFilterToAtlasIntersection )
489 {
490 const QgsGeometry atlasGeometry = mLayout->reportContext().currentGeometry( mVectorLayer->crs() );
491 if ( !atlasGeometry.isNull() )
492 {
493 request.setDistanceWithin( atlasGeometry, 0.0 );
494 }
495 }
496 else if ( mMap && mFilterOnlyVisibleFeatures )
497 {
498 QgsGeometry visibleRegionGeometry = QgsGeometry::fromQPolygonF( mMap->visibleExtentPolygon() );
499 if ( mVectorLayer->crs() != mMap->crs() )
500 {
501 const QgsCoordinateTransform transform( mVectorLayer->crs(), mMap->crs(), mLayout->project() );
502 if ( visibleRegionGeometry.transform( transform ) != Qgis::GeometryOperationResult ::Success )
503 {
504 visibleRegionGeometry = QgsGeometry();
505 }
506 }
507 if ( !visibleRegionGeometry.isNull() )
508 {
509 request.setDistanceWithin( visibleRegionGeometry, 0.0 );
510 }
511 }
513
514 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
515
516 xyGatherer->setFeatureIterator( featureIterator );
518 }
519
520 connect( mGatherer.data(), &QgsTask::taskCompleted, this, &QgsLayoutItemChart::processData );
521}
522
523void QgsLayoutItemChart::processData()
524{
525 mPlotData = mGatherer->data();
526 mGatherer.clear();
527
528 mIsGathering = false;
529 update();
530}
531
532bool QgsLayoutItemChart::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
533{
534 if ( mPlot )
535 {
536 QDomElement plotElement = document.createElement( u"plot"_s );
537 mPlot->writeXml( plotElement, document, context );
538 element.appendChild( plotElement );
539 }
540
541 QDomElement seriesListElement = document.createElement( u"seriesList"_s );
542 for ( const SeriesDetails &series : mSeriesList )
543 {
544 QDomElement seriesElement = document.createElement( u"series"_s );
545 seriesElement.setAttribute( u"name"_s, series.name() );
546 seriesElement.setAttribute( u"xExpression"_s, series.xExpression() );
547 seriesElement.setAttribute( u"yExpression"_s, series.yExpression() );
548 seriesElement.setAttribute( u"filterExpression"_s, series.filterExpression() );
549 seriesListElement.appendChild( seriesElement );
550 }
551 element.appendChild( seriesListElement );
552
553 if ( mVectorLayer )
554 {
555 element.setAttribute( u"vectorLayer"_s, mVectorLayer.layerId );
556 element.setAttribute( u"vectorLayerName"_s, mVectorLayer.name );
557 element.setAttribute( u"vectorLayerSource"_s, mVectorLayer.source );
558 element.setAttribute( u"vectorLayerProvider"_s, mVectorLayer.provider );
559 }
560
561 element.setAttribute( u"sortFeatures"_s, mSortFeatures ? u"1"_s : u"0"_s );
562 element.setAttribute( u"sortAscending"_s, mSortAscending ? u"1"_s : u"0"_s );
563 element.setAttribute( u"sortExpression"_s, mSortExpression );
564
565 if ( mGenerateCategoriesFromRenderer )
566 {
567 element.setAttribute( u"generateCategoriesFromRenderer"_s, mGenerateCategoriesFromRenderer );
568 }
569 if ( mApplyRendererStyle )
570 {
571 element.setAttribute( u"applyRendererStyle"_s, mApplyRendererStyle );
572 }
573
574 element.setAttribute( u"filterOnlyVisibleFeatures"_s, mFilterOnlyVisibleFeatures );
575 element.setAttribute( u"filterToAtlasIntersection"_s, mFilterToAtlasIntersection );
576
577 if ( mMap )
578 {
579 element.setAttribute( u"mapUuid"_s, mMap->uuid() );
580 }
581
582 return true;
583}
584
585bool QgsLayoutItemChart::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
586{
587 QDomElement plotElement = element.firstChildElement( u"plot"_s );
588 if ( !plotElement.isNull() )
589 {
590 mPlot.reset( dynamic_cast<Qgs2DPlot *>( QgsApplication::instance()->plotRegistry()->createPlot( plotElement.attribute( u"plotType"_s ) ) ) );
591 if ( mPlot )
592 {
593 mPlot->readXml( plotElement, context );
594 }
595 }
596
597 mSeriesList.clear();
598 const QDomNodeList seriesNodeList = element.firstChildElement( u"seriesList"_s ).childNodes();
599 for ( int i = 0; i < seriesNodeList.count(); i++ )
600 {
601 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
602 SeriesDetails series( seriesElement.attribute( "name" ) );
603 series.setXExpression( seriesElement.attribute( "xExpression" ) );
604 series.setYExpression( seriesElement.attribute( "yExpression" ) );
605 series.setFilterExpression( seriesElement.attribute( "filterExpression" ) );
606 mSeriesList << series;
607 }
608
609 QString layerId = element.attribute( u"vectorLayer"_s );
610 QString layerName = element.attribute( u"vectorLayerName"_s );
611 QString layerSource = element.attribute( u"vectorLayerSource"_s );
612 QString layerProvider = element.attribute( u"vectorLayerProvider"_s );
613 mVectorLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
614 mVectorLayer.resolveWeakly( mLayout->project() );
615
616 mSortFeatures = element.attribute( u"sortFeatures"_s, u"0"_s ).toInt();
617 mSortAscending = element.attribute( u"sortAscending"_s, u"1"_s ).toInt();
618 mSortExpression = element.attribute( u"sortExpression"_s );
619
620 mGenerateCategoriesFromRenderer = element.attribute( u"generateCategoriesFromRenderer"_s, u"0"_s ).toInt();
621 mApplyRendererStyle = element.attribute( u"applyRendererStyle"_s, u"1"_s ).toInt();
622
623 mFilterOnlyVisibleFeatures = element.attribute( u"filterOnlyVisibleFeatures"_s, u"1"_s ).toInt();
624 mFilterToAtlasIntersection = element.attribute( u"filterToAtlasIntersection"_s, u"0"_s ).toInt();
625
626 mMapUuid = element.attribute( u"mapUuid"_s );
627 if ( mMap )
628 {
631 mMap = nullptr;
632 }
633
634 mNeedsGathering = true;
635
636 return true;
637}
638
640{
641 if ( !mMap && !mMapUuid.isEmpty() && mLayout )
642 {
643 mMap = qobject_cast< QgsLayoutItemMap *>( mLayout->itemByUuid( mMapUuid, true ) );
644 if ( mMap )
645 {
648 }
649 }
650}
651
652void QgsLayoutItemChart::createRendererSeriesDetails( QString &rendererXExpression, QStringList &rendererCategories )
653{
654 const QgsFeatureRenderer *renderer = mVectorLayer->renderer();
655 if ( const QgsFeatureRenderer *embeddedRenderer = renderer->embeddedRenderer() )
656 {
657 renderer = embeddedRenderer;
658 }
659
660 QStringList expressionCases;
661 if ( const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer ) )
662 {
663 const QgsCategoryList categories = categorizedRenderer->categories();
664 bool ok;
665 for ( const QgsRendererCategory &category : categories )
666 {
667 rendererCategories << category.label();
668 expressionCases << u"WHEN %1 THEN %2"_s.arg( renderer->legendKeyToExpression( category.uuid(), mVectorLayer.get(), ok ), QgsExpression::quotedString( category.label() ) );
669 }
670 }
671 else if ( const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer ) )
672 {
673 const QgsRangeList ranges = graduatedRenderer->ranges();
674 bool ok;
675 for ( const QgsRendererRange &range : ranges )
676 {
677 rendererCategories << range.label();
678 expressionCases << u"WHEN %1 THEN %2"_s.arg( renderer->legendKeyToExpression( range.uuid(), mVectorLayer.get(), ok ), QgsExpression::quotedString( range.label() ) );
679 }
680 }
681 else if ( const QgsRuleBasedRenderer *ruleBasedRenderer = dynamic_cast<const QgsRuleBasedRenderer *>( renderer ) )
682 {
683 bool proceed = true;
684 bool hasElse = false;
685 QString elseLabel;
686
687 const QList< QgsRuleBasedRenderer::Rule * > rules = const_cast< QgsRuleBasedRenderer * >( ruleBasedRenderer )->rootRule()->children();
688 for ( const QgsRuleBasedRenderer::Rule *rule : rules )
689 {
690 if ( rule->hasActiveChildren() )
691 {
692 // We do not support multi-level rules configuration
693 proceed = false;
694 break;
695 }
696
697 if ( rule->isElse() )
698 {
699 hasElse = true;
700 elseLabel = rule->label();
701 rendererCategories << rule->label();
702 continue;
703 }
704
705 rendererCategories << rule->label();
706 expressionCases << u"WHEN %1 THEN %2"_s.arg( rule->filterExpression(), QgsExpression::quotedString( rule->label() ) );
707 }
708
709 if ( proceed )
710 {
711 if ( hasElse )
712 {
713 expressionCases << u"ELSE %1"_s.arg( QgsExpression::quotedString( elseLabel ) );
714 }
715 }
716 else
717 {
718 rendererCategories.clear();
719 expressionCases.clear();
720 }
721 }
722
723 rendererXExpression = !expressionCases.isEmpty() ? u"CASE %1 END"_s.arg( expressionCases.join( ' ' ) ) : QString();
724}
725
726void QgsLayoutItemChart::applyRendererStyleToPlot( Qgs2DPlot *plot ) const
727{
728 Q_ASSERT( plot != mPlot.get() );
729
730 const QgsFeatureRenderer *renderer = mVectorLayer->renderer();
731 if ( const QgsFeatureRenderer *embeddedRenderer = renderer->embeddedRenderer() )
732 {
733 renderer = embeddedRenderer;
734 }
735
736 QStringList expressionCases;
737 if ( const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer ) )
738 {
739 const QgsCategoryList categories = categorizedRenderer->categories();
740 for ( const QgsRendererCategory &category : categories )
741 {
742 const QColor color = category.symbol() ? category.symbol()->color() : QColor( 90, 90, 90 );
743 expressionCases << u"WHEN @chart_category = %1 THEN color_rgbf(%2, %3, %4, %5)"_s.arg( QgsExpression::quotedString( category.label() ) )
744 .arg( color.redF() )
745 .arg( color.greenF() )
746 .arg( color.blueF() )
747 .arg( color.alphaF() );
748 }
749 }
750 else if ( const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer ) )
751 {
752 const QgsRangeList ranges = graduatedRenderer->ranges();
753 for ( const QgsRendererRange &range : ranges )
754 {
755 const QColor color = range.symbol() ? range.symbol()->color() : QColor( 90, 90, 90 );
756 expressionCases << u"WHEN @chart_category = %1 THEN color_rgbf(%2, %3, %4, %5)"_s.arg( QgsExpression::quotedString( range.label() ) )
757 .arg( color.redF() )
758 .arg( color.greenF() )
759 .arg( color.blueF() )
760 .arg( color.alphaF() );
761 }
762 }
763 else if ( const QgsRuleBasedRenderer *ruleBasedRenderer = dynamic_cast<const QgsRuleBasedRenderer *>( renderer ) )
764 {
765 bool proceed = true;
766 bool hasElse = false;
767 QColor elseColor;
768
769 const QList< QgsRuleBasedRenderer::Rule * > rules = const_cast< QgsRuleBasedRenderer * >( ruleBasedRenderer )->rootRule()->children();
770 for ( QgsRuleBasedRenderer::Rule *rule : rules )
771 {
772 if ( rule->hasActiveChildren() )
773 {
774 // We do not support multi-level rules configuration
775 proceed = false;
776 break;
777 }
778
779 if ( rule->isElse() )
780 {
781 hasElse = true;
782 elseColor = rule->symbol() ? rule->symbol()->color() : QColor( 90, 90, 90 );
783 continue;
784 }
785
786 const QColor color = rule->symbol() ? rule->symbol()->color() : QColor( 90, 90, 90 );
787 expressionCases << u"WHEN @chart_category = %1 THEN color_rgbf(%2, %3, %4, %5)"_s.arg( QgsExpression::quotedString( rule->label() ) )
788 .arg( color.redF() )
789 .arg( color.greenF() )
790 .arg( color.blueF() )
791 .arg( color.alphaF() );
792 }
793
794 if ( proceed )
795 {
796 if ( hasElse )
797 {
798 expressionCases << u"ELSE color_rgbf(%1, %2, %3, %4)"_s.arg( elseColor.redF() ).arg( elseColor.greenF() ).arg( elseColor.blueF() ).arg( elseColor.alphaF() );
799 }
800 }
801 else
802 {
803 expressionCases.clear();
804 }
805 }
806
807 if ( !expressionCases.isEmpty() )
808 {
809 const QString rendererColorExpression = u"CASE %1 END"_s.arg( expressionCases.join( ' ' ) );
810 if ( QgsBarChartPlot *barChartPlot = dynamic_cast<QgsBarChartPlot *>( plot ) )
811 {
812 for ( int idx = 0; idx < barChartPlot->fillSymbolCount(); idx++ )
813 {
814 QgsFillSymbol *fillSymbol = barChartPlot->fillSymbolAt( idx );
815 for ( QgsSymbolLayer *symbolLayer : fillSymbol->symbolLayers() )
816 {
817 symbolLayer->setDataDefinedProperty( QgsSymbolLayer::Property::FillColor, QgsProperty::fromExpression( rendererColorExpression, true ) );
818 }
819 }
820 }
821 else if ( QgsLineChartPlot *lineChartPlot = dynamic_cast<QgsLineChartPlot *>( plot ) )
822 {
823 for ( int idx = 0; idx < lineChartPlot->markerSymbolCount(); idx++ )
824 {
825 QgsMarkerSymbol *markerSymbol = lineChartPlot->markerSymbolAt( idx );
826 for ( QgsSymbolLayer *symbolLayer : markerSymbol->symbolLayers() )
827 {
828 symbolLayer->setDataDefinedProperty( QgsSymbolLayer::Property::FillColor, QgsProperty::fromExpression( rendererColorExpression, true ) );
829 }
830 }
831 }
832 else if ( QgsPieChartPlot *pieChartPlot = dynamic_cast<QgsPieChartPlot *>( plot ) )
833 {
834 for ( int idx = 0; idx < pieChartPlot->fillSymbolCount(); idx++ )
835 {
836 QgsFillSymbol *fillSymbol = pieChartPlot->fillSymbolAt( idx );
837 for ( QgsSymbolLayer *symbolLayer : fillSymbol->symbolLayers() )
838 {
839 symbolLayer->setDataDefinedProperty( QgsSymbolLayer::Property::FillColor, QgsProperty::fromExpression( rendererColorExpression, true ) );
840 }
841 }
842 }
843 }
844}
@ Categorical
The axis represents categories.
Definition qgis.h:3517
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.
static QString quotedString(QString text)
Returns a quoted version of a string (in single quotes).
Abstract base class for all 2D vector feature renderers.
virtual QString legendKeyToExpression(const QString &key, QgsVectorLayer *layer, bool &ok) const
Attempts to convert the specified legend rule key to a QGIS expression matching the features displaye...
virtual const QgsFeatureRenderer * embeddedRenderer() const
Returns the current embedded renderer (subrenderer) for this feature renderer.
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.
bool applyRendererStyle() const
Returns true if chart symbols will adopt the source layer's symbology renderer (e....
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 setGenerateCategoriesFromRenderer(bool generateCategoriesFromRenderer)
Sets whether series' X axis will adopt categories generated from the source layer's symbology rendere...
bool generateCategoriesFromRenderer() const
Returns true if series' X axis will adopt categories generated from the source layer's symbology rend...
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 setApplyRendererStyle(bool applyRendererStyle)
Sets whether chart symbols will adopt the source layer's symbology renderer (e.g.
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
Encapsulates one or more plot series.
Definition qgsplot.h:303
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
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
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.
QgsSymbolLayerList symbolLayers() const
Returns the list of symbol layers contained in the symbol.
Definition qgssymbol.h:306
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.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7122
QList< QgsRendererCategory > QgsCategoryList
#define QgsDebugError(str)
Definition qgslogger.h:59
QList< QgsRendererRange > QgsRangeList
_LayerRef< QgsVectorLayer > QgsVectorLayerRef