40#include <QDomDocument>
45#include "moc_qgslayoutitemchart.cpp"
47using namespace Qt::StringLiterals;
58 mSeriesList << series;
60 mGathererTimer.setInterval( 10 );
61 mGathererTimer.setSingleShot(
true );
62 connect( &mGathererTimer, &QTimer::timeout,
this, &QgsLayoutItemChart::gatherData );
90 bool requireRefresh = !mPlot || !
plot;
93 if ( mPlot->type() !=
plot->type() )
95 requireRefresh =
true;
101 if ( oldPlot2dXy && newPlot2dXy && oldPlot2dXy->
xAxis().
type() == newPlot2dXy->
xAxis().
type() )
104 requireRefresh =
false;
108 requireRefresh =
true;
113 mPlot.reset( plot2d );
114 if ( requireRefresh )
124 if ( layer == mVectorLayer.get() )
129 mVectorLayer.setLayer( layer );
138 if ( mSortFeatures == sorted )
143 mSortFeatures = sorted;
151 if ( mSortAscending == ascending )
156 mSortAscending = ascending;
164 if ( mSortExpression == expression )
169 mSortExpression = expression;
200 if ( mFilterOnlyVisibleFeatures == visibleOnly )
205 mFilterOnlyVisibleFeatures = visibleOnly;
213 if ( mFilterToAtlasIntersection == filterToAtlas )
218 mFilterToAtlasIntersection = filterToAtlas;
246 if ( mGenerateCategoriesFromRenderer )
273 if ( !
mLayout || !painter || !painter->device() )
288 QPaintDevice *paintDevice = painter->device();
292 QRectF thisPaintRect = rect();
296 if (
mLayout->renderContext().isPreviewRender() )
298 if ( mNeedsGathering || mIsGathering )
300 if ( mNeedsGathering )
302 mNeedsGathering =
false;
307 painter->setClipRect( thisPaintRect );
309 painter->setBrush( QBrush( QColor( 125, 125, 125, 125 ) ) );
310 painter->drawRect( thisPaintRect );
311 painter->setBrush( Qt::NoBrush );
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" ) );
322 if ( mNeedsGathering )
324 mNeedsGathering =
false;
329 mGatherer->waitForFinished( 60000 );
336 if ( size.width() == 0 || size.height() == 0 )
341 std::unique_ptr< Qgs2DPlot > newPlot;
342 if ( mGenerateCategoriesFromRenderer && mVectorLayer )
345 plot = newPlot.get();
346 plot->initFromPlot( mPlot.get() );
352 if ( mApplyRendererStyle && mVectorLayer->renderer() )
354 applyRendererStyleToPlot(
plot );
358 plot->setSize( size );
361 painter->scale( 1 / scaleFactor, 1 / scaleFactor );
368 plot->render( renderContext, plotRenderContext, mPlotData );
371 if ( mSeriesList.isEmpty() || ( mSeriesList.size() == 1 && !mGenerateCategoriesFromRenderer && ( mSeriesList[0].xExpression().isEmpty() || mSeriesList[0].yExpression().isEmpty() ) ) )
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" ) );
384 if ( mVectorLayer && !mSeriesList.isEmpty() )
386 mNeedsGathering =
true;
390void QgsLayoutItemChart::refreshData()
392 mGathererTimer.start();
395void QgsLayoutItemChart::gatherData()
407void QgsLayoutItemChart::prepareGatherer()
416 if ( !mVectorLayer || !mPlot || mSeriesList.isEmpty() )
418 mPlotData.clearSeries();
419 mIsGathering =
false;
426 mPlotData.clearSeries();
427 mIsGathering =
false;
440 mPlotData.clearSeries();
441 mIsGathering =
false;
445 if ( QgsVectorLayerXyPlotDataGatherer *xyGatherer =
dynamic_cast<QgsVectorLayerXyPlotDataGatherer *
>( mGatherer.data() ) )
447 QList<QgsVectorLayerXyPlotDataGatherer::XySeriesDetails> xYSeriesList;
448 if ( mGenerateCategoriesFromRenderer && mVectorLayer->renderer() )
450 QString rendererXExpression;
451 QStringList rendererCategories;
452 createRendererSeriesDetails( rendererXExpression, rendererCategories );
455 xyGatherer->setPredefinedCategories( rendererCategories );
458 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), rendererXExpression, !series.yExpression().isEmpty() ? series.yExpression() :
"1"_L1, series.filterExpression() );
465 xYSeriesList << QgsVectorLayerXyPlotDataGatherer::XySeriesDetails( series.name(), series.xExpression(), series.yExpression(), series.filterExpression() );
468 xyGatherer->setSeriesDetails( xYSeriesList );
470 QgsFeatureRequest request;
471 QStringList filterExpressions;
472 for ( QgsLayoutItemChart::SeriesDetails &series : mSeriesList )
474 if ( !series.filterExpression().isEmpty() )
476 filterExpressions << series.filterExpression();
479 if ( !filterExpressions.isEmpty() )
484 if ( mSortFeatures && !mSortExpression.isEmpty() )
486 request.
addOrderBy( mSortExpression, mSortAscending );
489 if ( mFilterToAtlasIntersection )
491 const QgsGeometry atlasGeometry =
mLayout->reportContext().currentGeometry( mVectorLayer->crs() );
492 if ( !atlasGeometry.
isNull() )
497 else if ( mMap && mFilterOnlyVisibleFeatures )
500 if ( mVectorLayer->crs() != mMap->crs() )
502 const QgsCoordinateTransform transform( mVectorLayer->crs(), mMap->crs(),
mLayout->project() );
503 if ( visibleRegionGeometry.
transform( transform ) != Qgis::GeometryOperationResult ::Success )
505 visibleRegionGeometry = QgsGeometry();
508 if ( !visibleRegionGeometry.
isNull() )
515 QgsFeatureIterator featureIterator = mVectorLayer->getFeatures( request );
517 xyGatherer->setFeatureIterator( featureIterator );
524void QgsLayoutItemChart::processData()
526 mPlotData = mGatherer->data();
529 mIsGathering =
false;
537 QDomElement plotElement = document.createElement( u
"plot"_s );
538 mPlot->writeXml( plotElement, document, context );
539 element.appendChild( plotElement );
542 QDomElement seriesListElement = document.createElement( u
"seriesList"_s );
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 );
552 element.appendChild( seriesListElement );
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 );
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 );
566 if ( mGenerateCategoriesFromRenderer )
568 element.setAttribute( u
"generateCategoriesFromRenderer"_s, mGenerateCategoriesFromRenderer );
570 if ( mApplyRendererStyle )
572 element.setAttribute( u
"applyRendererStyle"_s, mApplyRendererStyle );
575 element.setAttribute( u
"filterOnlyVisibleFeatures"_s, mFilterOnlyVisibleFeatures );
576 element.setAttribute( u
"filterToAtlasIntersection"_s, mFilterToAtlasIntersection );
580 element.setAttribute( u
"mapUuid"_s, mMap->uuid() );
588 QDomElement plotElement = element.firstChildElement( u
"plot"_s );
589 if ( !plotElement.isNull() )
594 mPlot->readXml( plotElement, context );
599 const QDomNodeList seriesNodeList = element.firstChildElement( u
"seriesList"_s ).childNodes();
600 for (
int i = 0; i < seriesNodeList.count(); i++ )
602 const QDomElement seriesElement = seriesNodeList.at( i ).toElement();
604 series.
setXExpression( seriesElement.attribute(
"xExpression" ) );
605 series.
setYExpression( seriesElement.attribute(
"yExpression" ) );
607 mSeriesList << series;
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() );
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 );
621 mGenerateCategoriesFromRenderer = element.attribute( u
"generateCategoriesFromRenderer"_s, u
"0"_s ).toInt();
622 mApplyRendererStyle = element.attribute( u
"applyRendererStyle"_s, u
"1"_s ).toInt();
624 mFilterOnlyVisibleFeatures = element.attribute( u
"filterOnlyVisibleFeatures"_s, u
"1"_s ).toInt();
625 mFilterToAtlasIntersection = element.attribute( u
"filterToAtlasIntersection"_s, u
"0"_s ).toInt();
627 mMapUuid = element.attribute( u
"mapUuid"_s );
635 mNeedsGathering =
true;
642 if ( !mMap && !mMapUuid.isEmpty() &&
mLayout )
644 mMap = qobject_cast< QgsLayoutItemMap *>(
mLayout->itemByUuid( mMapUuid,
true ) );
653void QgsLayoutItemChart::createRendererSeriesDetails( QString &rendererXExpression, QStringList &rendererCategories )
658 renderer = embeddedRenderer;
661 QStringList expressionCases;
662 if (
const QgsCategorizedSymbolRenderer *categorizedRenderer =
dynamic_cast<const QgsCategorizedSymbolRenderer *
>( renderer ) )
666 for (
const QgsRendererCategory &category : categories )
668 rendererCategories << category.label();
672 else if (
const QgsGraduatedSymbolRenderer *graduatedRenderer =
dynamic_cast<const QgsGraduatedSymbolRenderer *
>( renderer ) )
674 const QgsRangeList ranges = graduatedRenderer->ranges();
676 for (
const QgsRendererRange &range : ranges )
678 rendererCategories << range.label();
682 else if (
const QgsRuleBasedRenderer *ruleBasedRenderer =
dynamic_cast<const QgsRuleBasedRenderer *
>( renderer ) )
685 bool hasElse =
false;
688 const QList< QgsRuleBasedRenderer::Rule * > rules =
const_cast< QgsRuleBasedRenderer *
>( ruleBasedRenderer )->rootRule()->children();
689 for (
const QgsRuleBasedRenderer::Rule *rule : rules )
691 if ( rule->hasActiveChildren() )
698 if ( rule->isElse() )
701 elseLabel = rule->label();
702 rendererCategories << rule->label();
706 rendererCategories << rule->label();
719 rendererCategories.clear();
720 expressionCases.clear();
724 rendererXExpression = !expressionCases.isEmpty() ? u
"CASE %1 END"_s.arg( expressionCases.join(
' ' ) ) : QString();
727void QgsLayoutItemChart::applyRendererStyleToPlot(
Qgs2DPlot *plot )
const
729 Q_ASSERT(
plot != mPlot.get() );
731 const QgsFeatureRenderer *renderer = mVectorLayer->renderer();
732 if (
const QgsFeatureRenderer *embeddedRenderer = renderer->
embeddedRenderer() )
734 renderer = embeddedRenderer;
737 QStringList expressionCases;
738 if (
const QgsCategorizedSymbolRenderer *categorizedRenderer =
dynamic_cast<const QgsCategorizedSymbolRenderer *
>( renderer ) )
741 for (
const QgsRendererCategory &category : categories )
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() ) )
746 .arg( color.greenF() )
747 .arg( color.blueF() )
748 .arg( color.alphaF() );
751 else if (
const QgsGraduatedSymbolRenderer *graduatedRenderer =
dynamic_cast<const QgsGraduatedSymbolRenderer *
>( renderer ) )
753 const QgsRangeList ranges = graduatedRenderer->ranges();
754 for (
const QgsRendererRange &range : ranges )
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() ) )
759 .arg( color.greenF() )
760 .arg( color.blueF() )
761 .arg( color.alphaF() );
764 else if (
const QgsRuleBasedRenderer *ruleBasedRenderer =
dynamic_cast<const QgsRuleBasedRenderer *
>( renderer ) )
767 bool hasElse =
false;
770 const QList< QgsRuleBasedRenderer::Rule * > rules =
const_cast< QgsRuleBasedRenderer *
>( ruleBasedRenderer )->rootRule()->children();
771 for ( QgsRuleBasedRenderer::Rule *rule : rules )
773 if ( rule->hasActiveChildren() )
780 if ( rule->isElse() )
783 elseColor = rule->symbol() ? rule->symbol()->color() : QColor( 90, 90, 90 );
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() ) )
790 .arg( color.greenF() )
791 .arg( color.blueF() )
792 .arg( color.alphaF() );
799 expressionCases << u
"ELSE color_rgbf(%1, %2, %3, %4)"_s.arg( elseColor.redF() ).arg( elseColor.greenF() ).arg( elseColor.blueF() ).arg( elseColor.alphaF() );
804 expressionCases.clear();
808 if ( !expressionCases.isEmpty() )
810 const QString rendererColorExpression = u
"CASE %1 END"_s.arg( expressionCases.join(
' ' ) );
811 if ( QgsBarChartPlot *barChartPlot =
dynamic_cast<QgsBarChartPlot *
>(
plot ) )
813 for (
int idx = 0; idx < barChartPlot->fillSymbolCount(); idx++ )
815 QgsFillSymbol *fillSymbol = barChartPlot->fillSymbolAt( idx );
816 for ( QgsSymbolLayer *symbolLayer : fillSymbol->
symbolLayers() )
822 else if ( QgsLineChartPlot *lineChartPlot =
dynamic_cast<QgsLineChartPlot *
>(
plot ) )
824 for (
int idx = 0; idx < lineChartPlot->markerSymbolCount(); idx++ )
826 QgsMarkerSymbol *markerSymbol = lineChartPlot->markerSymbolAt( idx );
827 for ( QgsSymbolLayer *symbolLayer : markerSymbol->
symbolLayers() )
833 else if ( QgsPieChartPlot *pieChartPlot =
dynamic_cast<QgsPieChartPlot *
>(
plot ) )
835 for (
int idx = 0; idx < pieChartPlot->fillSymbolCount(); idx++ )
837 QgsFillSymbol *fillSymbol = pieChartPlot->fillSymbolAt( idx );
838 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