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