39#include <QDomDocument>
44#include "moc_qgslayoutitemchart.cpp"
46using namespace Qt::StringLiterals;
57 mSeriesList << series;
59 mGathererTimer.setInterval( 10 );
60 mGathererTimer.setSingleShot(
true );
61 connect( &mGathererTimer, &QTimer::timeout,
this, &QgsLayoutItemChart::gatherData );
89 bool requireRefresh = !mPlot || !
plot;
92 if ( mPlot->type() !=
plot->type() )
94 requireRefresh =
true;
100 if ( oldPlot2dXy && newPlot2dXy && oldPlot2dXy->
xAxis().
type() == newPlot2dXy->
xAxis().
type() )
103 requireRefresh =
false;
107 requireRefresh =
true;
112 mPlot.reset( plot2d );
113 if ( requireRefresh )
123 if ( layer == mVectorLayer.get() )
128 mVectorLayer.setLayer( layer );
137 if ( mSortFeatures == sorted )
142 mSortFeatures = sorted;
150 if ( mSortAscending == ascending )
155 mSortAscending = ascending;
163 if ( mSortExpression == expression )
168 mSortExpression = expression;
199 if ( mFilterOnlyVisibleFeatures == visibleOnly )
204 mFilterOnlyVisibleFeatures = visibleOnly;
212 if ( mFilterToAtlasIntersection == filterToAtlas )
217 mFilterToAtlasIntersection = filterToAtlas;
245 if ( mGenerateCategoriesFromRenderer )
272 if ( !
mLayout || !painter || !painter->device() )
287 QPaintDevice *paintDevice = painter->device();
291 QRectF thisPaintRect = rect();
295 if (
mLayout->renderContext().isPreviewRender() )
297 if ( mNeedsGathering || mIsGathering )
299 if ( mNeedsGathering )
301 mNeedsGathering =
false;
306 painter->setClipRect( thisPaintRect );
308 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
309 painter->drawRect( thisPaintRect );
310 painter->setBrush( Qt::NoBrush );
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" ) );
321 if ( mNeedsGathering )
323 mNeedsGathering =
false;
328 mGatherer->waitForFinished( 60000 );
335 if ( size.width() == 0 || size.height() == 0 )
340 std::unique_ptr< Qgs2DPlot > newPlot;
341 if ( mGenerateCategoriesFromRenderer && mVectorLayer )
344 plot = newPlot.get();
345 plot->initFromPlot( mPlot.get() );
351 if ( mApplyRendererStyle && mVectorLayer->renderer() )
353 applyRendererStyleToPlot(
plot );
357 plot->setSize( size );
360 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
367 plot->render( renderContext, plotRenderContext, mPlotData );
370 if ( mSeriesList.isEmpty() || ( mSeriesList.size() == 1 && !mGenerateCategoriesFromRenderer && ( mSeriesList[0].xExpression().isEmpty() || mSeriesList[0].yExpression().isEmpty() ) ) )
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" ) );
383 if ( mVectorLayer && !mSeriesList.isEmpty() )
385 mNeedsGathering =
true;
389void QgsLayoutItemChart::refreshData()
391 mGathererTimer.start();
394void QgsLayoutItemChart::gatherData()
406void QgsLayoutItemChart::prepareGatherer()
415 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
417 mPlotData.clearSeries();
418 mIsGathering =
false;
425 mPlotData.clearSeries();
426 mIsGathering =
false;
439 mPlotData.clearSeries();
440 mIsGathering =
false;
444 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer =
dynamic_cast<QgsVectorLayerXyPlotDataGatherer *
>( mGatherer.data() ) )
446 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
447 if ( mGenerateCategoriesFromRenderer && mVectorLayer->renderer() )
449 QString rendererXExpression;
450 QStringList rendererCategories;
451 createRendererSeriesDetails( rendererXExpression, rendererCategories );
454 xyGatherer->setPredefinedCategories( rendererCategories );
457 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), rendererXExpression, !series.yExpression().isEmpty() ? series.yExpression() :
"1"_L1, series.filterExpression() );
464 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), series.xExpression(), series.yExpression(), series.filterExpression() );
467 xyGatherer->setSeriesDetails( xYSeriesList );
469 QgsFeatureRequest request;
470 QStringList filterExpressions;
471 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
473 if ( !series.filterExpression().isEmpty() )
475 filterExpressions << series.filterExpression();
478 if ( !filterExpressions.isEmpty() )
483 if ( mSortFeatures && !mSortExpression.isEmpty() )
485 request.
addOrderBy( mSortExpression, mSortAscending );
488 if ( mFilterToAtlasIntersection )
490 const QgsGeometry atlasGeometry =
mLayout->reportContext().currentGeometry( mVectorLayer->crs() );
491 if ( !atlasGeometry.
isNull() )
496 else if ( mMap && mFilterOnlyVisibleFeatures )
499 if ( mVectorLayer->crs() != mMap->crs() )
501 const QgsCoordinateTransform transform( mVectorLayer->crs(), mMap->crs(),
mLayout->project() );
502 if ( visibleRegionGeometry.
transform( transform ) != Qgis::GeometryOperationResult ::Success )
504 visibleRegionGeometry = QgsGeometry();
507 if ( !visibleRegionGeometry.
isNull() )
514 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
516 xyGatherer->setFeatureIterator( featureIterator );
523void QgsLayoutItemChart::processData()
525 mPlotData = mGatherer->data();
528 mIsGathering =
false;
536 QDomElement plotElement = document.createElement( u
"plot"_s );
537 mPlot->writeXml( plotElement, document, context );
538 element.appendChild( plotElement );
541 QDomElement seriesListElement = document.createElement( u
"seriesList"_s );
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 );
551 element.appendChild( seriesListElement );
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 );
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 );
565 if ( mGenerateCategoriesFromRenderer )
567 element.setAttribute( u
"generateCategoriesFromRenderer"_s, mGenerateCategoriesFromRenderer );
569 if ( mApplyRendererStyle )
571 element.setAttribute( u
"applyRendererStyle"_s, mApplyRendererStyle );
574 element.setAttribute( u
"filterOnlyVisibleFeatures"_s, mFilterOnlyVisibleFeatures );
575 element.setAttribute( u
"filterToAtlasIntersection"_s, mFilterToAtlasIntersection );
579 element.setAttribute( u
"mapUuid"_s, mMap->uuid() );
587 QDomElement plotElement = element.firstChildElement( u
"plot"_s );
588 if ( !plotElement.isNull() )
593 mPlot->readXml( plotElement, context );
598 const QDomNodeList seriesNodeList = element.firstChildElement( u
"seriesList"_s ).childNodes();
599 for (
int i = 0; i < seriesNodeList.count(); i++ )
601 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
603 series.
setXExpression( seriesElement.attribute(
"xExpression" ) );
604 series.
setYExpression( seriesElement.attribute(
"yExpression" ) );
606 mSeriesList << series;
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() );
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 );
620 mGenerateCategoriesFromRenderer = element.attribute( u
"generateCategoriesFromRenderer"_s, u
"0"_s ).toInt();
621 mApplyRendererStyle = element.attribute( u
"applyRendererStyle"_s, u
"1"_s ).toInt();
623 mFilterOnlyVisibleFeatures = element.attribute( u
"filterOnlyVisibleFeatures"_s, u
"1"_s ).toInt();
624 mFilterToAtlasIntersection = element.attribute( u
"filterToAtlasIntersection"_s, u
"0"_s ).toInt();
626 mMapUuid = element.attribute( u
"mapUuid"_s );
634 mNeedsGathering =
true;
641 if ( !mMap && !mMapUuid.isEmpty() &&
mLayout )
643 mMap = qobject_cast< QgsLayoutItemMap *>(
mLayout->itemByUuid( mMapUuid,
true ) );
652void QgsLayoutItemChart::createRendererSeriesDetails( QString &rendererXExpression, QStringList &rendererCategories )
657 renderer = embeddedRenderer;
660 QStringList expressionCases;
661 if (
const QgsCategorizedSymbolRenderer *categorizedRenderer =
dynamic_cast<const QgsCategorizedSymbolRenderer *
>( renderer ) )
665 for (
const QgsRendererCategory &category : categories )
667 rendererCategories << category.label();
671 else if (
const QgsGraduatedSymbolRenderer *graduatedRenderer =
dynamic_cast<const QgsGraduatedSymbolRenderer *
>( renderer ) )
673 const QgsRangeList ranges = graduatedRenderer->ranges();
675 for (
const QgsRendererRange &range : ranges )
677 rendererCategories << range.label();
681 else if (
const QgsRuleBasedRenderer *ruleBasedRenderer =
dynamic_cast<const QgsRuleBasedRenderer *
>( renderer ) )
684 bool hasElse =
false;
687 const QList< QgsRuleBasedRenderer::Rule * > rules =
const_cast< QgsRuleBasedRenderer *
>( ruleBasedRenderer )->rootRule()->children();
688 for (
const QgsRuleBasedRenderer::Rule *rule : rules )
690 if ( rule->hasActiveChildren() )
697 if ( rule->isElse() )
700 elseLabel = rule->label();
701 rendererCategories << rule->label();
705 rendererCategories << rule->label();
718 rendererCategories.clear();
719 expressionCases.clear();
723 rendererXExpression = !expressionCases.isEmpty() ? u
"CASE %1 END"_s.arg( expressionCases.join(
' ' ) ) : QString();
726void QgsLayoutItemChart::applyRendererStyleToPlot(
Qgs2DPlot *plot )
const
728 Q_ASSERT(
plot != mPlot.get() );
730 const QgsFeatureRenderer *renderer = mVectorLayer->renderer();
731 if (
const QgsFeatureRenderer *embeddedRenderer = renderer->
embeddedRenderer() )
733 renderer = embeddedRenderer;
736 QStringList expressionCases;
737 if (
const QgsCategorizedSymbolRenderer *categorizedRenderer =
dynamic_cast<const QgsCategorizedSymbolRenderer *
>( renderer ) )
740 for (
const QgsRendererCategory &category : categories )
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() ) )
745 .arg( color.greenF() )
746 .arg( color.blueF() )
747 .arg( color.alphaF() );
750 else if (
const QgsGraduatedSymbolRenderer *graduatedRenderer =
dynamic_cast<const QgsGraduatedSymbolRenderer *
>( renderer ) )
752 const QgsRangeList ranges = graduatedRenderer->ranges();
753 for (
const QgsRendererRange &range : ranges )
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() ) )
758 .arg( color.greenF() )
759 .arg( color.blueF() )
760 .arg( color.alphaF() );
763 else if (
const QgsRuleBasedRenderer *ruleBasedRenderer =
dynamic_cast<const QgsRuleBasedRenderer *
>( renderer ) )
766 bool hasElse =
false;
769 const QList< QgsRuleBasedRenderer::Rule * > rules =
const_cast< QgsRuleBasedRenderer *
>( ruleBasedRenderer )->rootRule()->children();
770 for ( QgsRuleBasedRenderer::Rule *rule : rules )
772 if ( rule->hasActiveChildren() )
779 if ( rule->isElse() )
782 elseColor = rule->symbol() ? rule->symbol()->color() : QColor( 90, 90, 90 );
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() ) )
789 .arg( color.greenF() )
790 .arg( color.blueF() )
791 .arg( color.alphaF() );
798 expressionCases << u
"ELSE color_rgbf(%1, %2, %3, %4)"_s.arg( elseColor.redF() ).arg( elseColor.greenF() ).arg( elseColor.blueF() ).arg( elseColor.alphaF() );
803 expressionCases.clear();
807 if ( !expressionCases.isEmpty() )
809 const QString rendererColorExpression = u
"CASE %1 END"_s.arg( expressionCases.join(
' ' ) );
810 if ( QgsBarChartPlot *barChartPlot =
dynamic_cast<QgsBarChartPlot *
>(
plot ) )
812 for (
int idx = 0; idx < barChartPlot->fillSymbolCount(); idx++ )
814 QgsFillSymbol *fillSymbol = barChartPlot->fillSymbolAt( idx );
815 for ( QgsSymbolLayer *symbolLayer : fillSymbol->
symbolLayers() )
821 else if ( QgsLineChartPlot *lineChartPlot =
dynamic_cast<QgsLineChartPlot *
>(
plot ) )
823 for (
int idx = 0; idx < lineChartPlot->markerSymbolCount(); idx++ )
825 QgsMarkerSymbol *markerSymbol = lineChartPlot->markerSymbolAt( idx );
826 for ( QgsSymbolLayer *symbolLayer : markerSymbol->
symbolLayers() )
832 else if ( QgsPieChartPlot *pieChartPlot =
dynamic_cast<QgsPieChartPlot *
>(
plot ) )
834 for (
int idx = 0; idx < pieChartPlot->fillSymbolCount(); idx++ )
836 QgsFillSymbol *fillSymbol = pieChartPlot->fillSymbolAt( idx );
837 for ( QgsSymbolLayer *symbolLayer : fillSymbol->
symbolLayers() )
@ Categorical
The axis represents categories.
Base class for 2-dimensional plot/chart/graphs.
Base class for 2-dimensional plot/chart/graphs with an X and Y axes.
QgsPlotAxis & xAxis()
Returns a reference to the plot's x axis.
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.
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.
Qgis::PlotAxisType type() const
Returns the axis type.
Encapsulates one or more plot series.
QgsPlotAbstractMetadata * plotMetadata(const QString &type) const
Returns the metadata for the specified plot type.
Contains information about the context of a plot rendering operation.
Base class for plot/chart/graphs.
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.
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).
QList< QgsRendererCategory > QgsCategoryList
#define QgsDebugError(str)
QList< QgsRendererRange > QgsRangeList
_LayerRef< QgsVectorLayer > QgsVectorLayerRef